diff --git a/src/qz/auth/Certificate.java b/src/qz/auth/Certificate.java index 69106333f..be58e9bf3 100644 --- a/src/qz/auth/Certificate.java +++ b/src/qz/auth/Certificate.java @@ -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; @@ -323,8 +322,8 @@ public static Certificate loadCertificate(HashMap 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; @@ -420,7 +419,7 @@ 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"; } @@ -428,7 +427,7 @@ public String getValidFrom() { 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"; } diff --git a/src/qz/ui/component/CertificateTable.java b/src/qz/ui/component/CertificateTable.java index 117496bbb..b5b24b864 100644 --- a/src/qz/ui/component/CertificateTable.java +++ b/src/qz/ui/component/CertificateTable.java @@ -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 getter; + TimeZone timeZone = DEFAULT_TIME_ZONE; // Date fields only - CertificateField(String description, String callBack) { + CertificateField(String description, Function getter) { this.description = description; - this.callBack = callBack; + this.getter = getter; } - /** - * Returns the String 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 @@ -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) { @@ -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 { @@ -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)); } @@ -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); } } - } - }