Skip to content

Commit

Permalink
Add Timezone to Cert (#1292)
Browse files Browse the repository at this point in the history
Always show timezone in SiteManager dialog
Add toggle for current timezone

---------

Co-authored-by: Tres Finocchiaro <[email protected]>
  • Loading branch information
Vzor- and tresf authored Sep 16, 2024
1 parent 7330141 commit de82740
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 47 deletions.
13 changes: 6 additions & 7 deletions src/qz/auth/Certificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ public enum Algorithm {
// Valid date range allows UI to only show "Expired" text for valid certificates
private static final Instant UNKNOWN_MIN = LocalDateTime.MIN.toInstant(ZoneOffset.UTC);
private static final Instant UNKNOWN_MAX = LocalDateTime.MAX.toInstant(ZoneOffset.UTC);

private static DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static DateTimeFormatter dateParse = DateTimeFormatter.ofPattern("uuuu-MM-dd['T'][ ]HH:mm:ss[.n]['Z']"); //allow parsing of both ISO and custom formatted dates
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_PARSE = DateTimeFormatter.ofPattern("uuuu-MM-dd['T'][ ]HH:mm:ss[.n]['Z']"); //allow parsing of both ISO and custom formatted dates

private X509Certificate theCertificate;
private boolean sponsored;
Expand Down Expand Up @@ -323,8 +322,8 @@ public static Certificate loadCertificate(HashMap<String,String> data) {
cert.organization = data.get("organization");

try {
cert.validFrom = Instant.from(LocalDateTime.from(dateParse.parse(data.get("validFrom"))).atZone(ZoneOffset.UTC));
cert.validTo = Instant.from(LocalDateTime.from(dateParse.parse(data.get("validTo"))).atZone(ZoneOffset.UTC));
cert.validFrom = Instant.from(LocalDateTime.from(DATE_PARSE.parse(data.get("validFrom"))).atZone(ZoneOffset.UTC));
cert.validTo = Instant.from(LocalDateTime.from(DATE_PARSE.parse(data.get("validTo"))).atZone(ZoneOffset.UTC));
}
catch(DateTimeException e) {
cert.validFrom = UNKNOWN_MIN;
Expand Down Expand Up @@ -420,15 +419,15 @@ public String getOrganization() {

public String getValidFrom() {
if (validFrom.isAfter(UNKNOWN_MIN)) {
return dateFormat.format(validFrom.atZone(ZoneOffset.UTC));
return DATE_FORMAT.format(validFrom.atZone(ZoneOffset.UTC));
} else {
return "Not Provided";
}
}

public String getValidTo() {
if (validTo.isBefore(UNKNOWN_MAX)) {
return dateFormat.format(validTo.atZone(ZoneOffset.UTC));
return DATE_FORMAT.format(validTo.atZone(ZoneOffset.UTC));
} else {
return "Not Provided";
}
Expand Down
133 changes: 93 additions & 40 deletions src/qz/ui/component/CertificateTable.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,67 @@
package qz.ui.component;

import org.joor.Reflect;
import qz.auth.Certificate;
import qz.common.Constants;
import qz.ui.Themeable;

import javax.swing.*;
import java.awt.*;
import java.time.Instant;
import java.awt.event.*;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.TimeZone;
import java.util.function.Function;

import static qz.auth.Certificate.*;

/**
* Created by Tres on 2/22/2015.
* Displays Certificate information in a JTable
*/
public class CertificateTable extends DisplayTable implements Themeable {
private Certificate cert;

private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("UTC");
private static final TimeZone ALTERNATE_TIME_ZONE = TimeZone.getDefault();
private Instant warn;
private Instant now;

/**
* Certificate fields to be displayed (and the corresponding function to Reflect upon)
*/
enum CertificateField {
ORGANIZATION("Organization", "getOrganization"),
COMMON_NAME("Common Name", "getCommonName"),
TRUSTED("Trusted", "isTrusted"),
VALID_FROM("Valid From", "getValidFrom"),
VALID_TO("Valid To", "getValidTo"),
FINGERPRINT("Fingerprint", "getFingerprint");
ORGANIZATION("Organization", (Certificate cert) -> cert.getOrganization()),
COMMON_NAME("Common Name", (Certificate cert) -> cert.getCommonName()),
TRUSTED("Trusted", (Certificate cert) -> cert.isTrusted()),
VALID_FROM("Valid From", (Certificate cert) -> cert.getValidFrom()),
VALID_TO("Valid To", (Certificate cert) -> cert.getValidTo()),
FINGERPRINT("Fingerprint", (Certificate cert) -> cert.getFingerprint());

String description;
String callBack;
Function<Certificate, Object> getter;
TimeZone timeZone = DEFAULT_TIME_ZONE; // Date fields only

CertificateField(String description, String callBack) {
CertificateField(String description, Function<Certificate, Object> getter) {
this.description = description;
this.callBack = callBack;
this.getter = getter;
}

/**
* Returns the <code>String</code> value associated with this certificate field
*
* @return Certificate field such as "commonName"
*/
public String getValue(Certificate cert) {
if (cert == null) {
return "";
}

Reflect reflect = Reflect.on(cert).call(callBack);
Object value = reflect == null? null:reflect.get();
if (value == null) {
return "";
String certFieldValue = getter.apply(cert).toString();
switch(this) {
case VALID_FROM:
case VALID_TO:
if (!certFieldValue.equals("Not Provided")) {
try {
// Parse the date string as UTC (Z/GMT)
ZonedDateTime utcTime = LocalDateTime.from(DATE_PARSE.parse(certFieldValue)).atZone(ZoneOffset.UTC);
// Shift to the new timezone
ZonedDateTime zonedTime = Instant.from(utcTime).atZone(timeZone.toZoneId());
// Append a short timezone name e.g. "EST"
return DATE_PARSE.format(zonedTime) + " " + timeZone.getDisplayName(false, TimeZone.SHORT);
} catch (DateTimeException ignore) {}
}
// fallthrough
default:
return certFieldValue;
}
return value.toString();
}

@Override
Expand All @@ -65,16 +76,47 @@ public String getDescription() {
public static int size() {
return values().length;
}
}

private Certificate cert;

private Instant warn;
private Instant now;
public void toggleTimeZone() {
switch(this) {
case VALID_TO:
case VALID_FROM:
this.timeZone = (timeZone == DEFAULT_TIME_ZONE? ALTERNATE_TIME_ZONE:DEFAULT_TIME_ZONE);
break;
default:
throw new UnsupportedOperationException("TimeZone is only supported for date fields");
}
}
}

public CertificateTable(IconCache iconCache) {
super(iconCache);
setDefaultRenderer(Object.class, new CertificateTableCellRenderer());
addMouseListener(new MouseAdapter() {
Point loc = new Point(-1, -1);

@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
JTable target = (JTable)e.getSource();
int x = target.getSelectedColumn();
int y = target.getSelectedRow();
// Only trigger after the cell is click AND highlighted.
if (loc.distance(x, y) == 0) {
CertificateField rowKey = (CertificateField)target.getValueAt(y, 0);
switch(rowKey) {
case VALID_FROM:
case VALID_TO:
rowKey.toggleTimeZone();
refreshComponents();
changeSelection(y, x, false, false);
break;
}
}
loc.setLocation(x, y);
}
});

}

public void setCertificate(Certificate cert) {
Expand Down Expand Up @@ -116,7 +158,6 @@ public void autoSize() {
super.autoSize(CertificateField.size(), 2);
}


/** Custom cell renderer for JTable to allow colors and styles not directly available in a JTable */
private class CertificateTableCellRenderer extends StyledTableCellRenderer {

Expand All @@ -126,7 +167,22 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole

// First Column
if (value instanceof CertificateField) {
label = stylizeLabel(STATUS_NORMAL, label, isSelected);
switch((CertificateField)value) {
case VALID_FROM:
boolean futureExpiration = cert.getValidFromDate().isAfter(now);
label = stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected, "future inception");
break;
case VALID_TO:
boolean expiresSoon = cert.getValidToDate().isBefore(warn);
boolean expired = cert.getValidToDate().isBefore(now);
String reason = expired? "expired":(expiresSoon? "expires soon":null);

label = stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected, reason);
break;
default:
label = stylizeLabel(STATUS_NORMAL, label, isSelected);
break;
}
if (iconCache != null) {
label.setIcon(iconCache.getIcon(IconCache.Icon.FIELD_ICON));
}
Expand All @@ -153,17 +209,14 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole
return stylizeLabel(!cert.isValid()? STATUS_WARNING:STATUS_TRUSTED, label, isSelected);
case VALID_FROM:
boolean futureExpiration = cert.getValidFromDate().isAfter(now);
return stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected, "future inception");
return stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected);
case VALID_TO:
boolean expiresSoon = cert.getValidToDate().isBefore(warn);
boolean expired = cert.getValidToDate().isBefore(now);
String reason = expired? "expired":(expiresSoon? "expires soon":null);
return stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected, reason);
return stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected);
default:
return stylizeLabel(STATUS_NORMAL, label, isSelected);
}
}

}

}

0 comments on commit de82740

Please sign in to comment.