From a249addd5a19040f748b13aba8c4e8608b7dc8ec Mon Sep 17 00:00:00 2001
From: Dominik Frantisek Bucik <bucik@ics.muni.cz>
Date: Wed, 3 Apr 2024 20:17:34 +0200
Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Pretty=20print=20GA4GH?=
 =?UTF-8?q?=20in=20consent?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

pretty print GA4GH Visas in consent screen
---
 .../WEB-INF/tags/common/attributesConsent.tag |  13 +-
 .../oidc/server/ga4gh/Ga4ghConsentUtils.java  | 143 ++++++++++++++++++
 2 files changed, 154 insertions(+), 2 deletions(-)
 create mode 100644 perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/ga4gh/Ga4ghConsentUtils.java

diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag
index 3f3824d61..1fe2a7738 100644
--- a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag
@@ -1,4 +1,5 @@
-<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true"
+        import="cz.muni.ics.oidc.server.ga4gh.Ga4ghConsentUtils" %>
 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
 <%@ taglib prefix="o" tagdir="/WEB-INF/tags" %>
@@ -50,7 +51,15 @@
                                         </c:when>
                                         <c:when test="${claim.value.getClass().name eq 'java.util.ArrayList'}">
                                             <c:forEach var="subValue" items="${claim.value}">
-                                                <li>${subValue}</li>
+                                                <c:choose>
+                                                    <c:when test="${claim.key=='ga4gh_passport_v1'}">
+                                                        <li><%= Ga4ghConsentUtils.parseAndVerifyVisa(
+                                                                (String) jspContext.findAttribute("subValue")).getPrettyString() %></li>
+                                                    </c:when>
+                                                    <c:otherwise>
+                                                        <li>${subValue}</li>
+                                                    </c:otherwise>
+                                                </c:choose>
                                             </c:forEach>
                                         </c:when>
                                         <c:otherwise>
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/ga4gh/Ga4ghConsentUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/ga4gh/Ga4ghConsentUtils.java
new file mode 100644
index 000000000..65a060fda
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/ga4gh/Ga4ghConsentUtils.java
@@ -0,0 +1,143 @@
+package cz.muni.ics.oidc.server.ga4gh;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.jwt.SignedJWT;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Slf4j
+public class Ga4ghConsentUtils {
+
+    public static class PassportVisa {
+        private final String jwt;
+        private String prettyPayload;
+        private String sub;
+        private String iss;
+        private String type;
+        private String value;
+
+        public PassportVisa(String jwt) {
+            this.jwt = jwt;
+        }
+
+        public String getJwt() {
+            return jwt;
+        }
+
+        void setPrettyPayload(String prettyPayload) {
+            this.prettyPayload = prettyPayload;
+        }
+
+        public String getPrettyString() {
+            return prettyPayload;
+        }
+
+        @Override
+        public String toString() {
+            return "PassportVisa{" +
+                    "  type=" + type +
+                    ", sub=" + sub +
+                    ", iss=" + iss +
+                    ", value=" + value +
+                    '}';
+        }
+
+        public void setSub(String sub) {
+            this.sub = sub;
+        }
+
+        public String getSub() {
+            return sub;
+        }
+
+        public void setIss(String iss) {
+            this.iss = iss;
+        }
+
+        public String getIss() {
+            return iss;
+        }
+
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String getType() {
+            return type;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    public static PassportVisa parseAndVerifyVisa(String jwtString) {
+        PassportVisa visa = new PassportVisa(jwtString);
+        try {
+            SignedJWT signedJWT = (SignedJWT) JWTParser.parse(jwtString);
+            processPayload(visa, signedJWT.getPayload());
+        } catch (Exception ex) {
+            log.error("visa " + jwtString + " cannot be parsed and verified", ex);
+        }
+        return visa;
+    }
+
+    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+    private static void processPayload(PassportVisa visa, Payload payload) throws IOException {
+        JsonNode doc = JSON_MAPPER.readValue(payload.toString(), JsonNode.class);
+        checkVisaKey(visa, doc, "sub");
+        checkVisaKey(visa, doc, "exp");
+        checkVisaKey(visa, doc, "iss");
+        JsonNode visaV1 = doc.path("ga4gh_visa_v1");
+        checkVisaKey(visa, visaV1, "type");
+        checkVisaKey(visa, visaV1, "asserted");
+        checkVisaKey(visa, visaV1, "value");
+        checkVisaKey(visa, visaV1, "source");
+        checkVisaKey(visa, visaV1, "by");
+        long exp = doc.get("exp").asLong();
+        if (exp < Instant.now().getEpochSecond()) {
+            return;
+        }
+        visa.setPrettyPayload(
+                visaV1.get("type").asText() + ":  \"" + visaV1.get("value").asText() + "\" asserted " + isoDate(visaV1.get("asserted").asLong())
+        );
+    }
+
+    private static void checkVisaKey(PassportVisa visa, JsonNode jsonNode, String key) {
+        if (!jsonNode.path(key).isMissingNode()) {
+            switch (key) {
+                case "sub":
+                    visa.setSub(jsonNode.path(key).asText());
+                    break;
+                case "iss":
+                    visa.setIss(jsonNode.path(key).asText());
+                    break;
+                case "type":
+                    visa.setType(jsonNode.path(key).asText());
+                    break;
+                case "value":
+                    visa.setValue(jsonNode.path(key).asText());
+                    break;
+                default:
+            }
+        }
+    }
+
+    private static String isoDate(long linuxTime) {
+        return DateTimeFormatter.ISO_LOCAL_DATE.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()));
+    }
+
+}
-- 
GitLab