Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Timezone to Cert #1292

Merged
merged 17 commits into from
Sep 16, 2024
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
134 changes: 95 additions & 39 deletions src/qz/ui/component/CertificateTable.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,70 @@
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.awt.event.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
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 (Exception ignore) {}
}
// fallthrough
default:
return certFieldValue;
}
return value.toString();
}

@Override
Expand All @@ -65,16 +79,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 +161,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 +170,22 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole

// First Column
if (value instanceof CertificateField) {
label = stylizeLabel(STATUS_NORMAL, label, isSelected);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vzor- do you mind explaining these changes? At a glance I would not expect the first column logic to change as a result of this PR. Perhaps it was due to some unreverted changes, but I would expect this logic to be largely untouched.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the timezone added, the warning tag was getting cut off. I decided to move it to the left for clarity, and share the formatting color of the value, with the key.

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 +212,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);
}
}

}

}
Loading