From df8a84cbc9f24f3b75342d5b19db97f8f52395d8 Mon Sep 17 00:00:00 2001
From: jgiwinski <juliaiwinski@gmail.com>
Date: Mon, 5 Feb 2024 12:07:36 -0700
Subject: [PATCH] add queries for display name and entity logo

---
 src/App.js                     | 116 +++++++++++++++++++++++++++++----
 src/assets/stylesheets/App.css |  18 +++++
 2 files changed, 123 insertions(+), 11 deletions(-)

diff --git a/src/App.js b/src/App.js
index 8506281..e29d757 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,6 +6,8 @@ function App() {
 
   const [entityID, setEntityID] = useState('');
   const [returnUrl, setReturnUrl] = useState('');
+  const [displayName, setDisplayName] = useState('');  
+  const [entityLogo, setEntityLogo] = useState('');  
   const [error, setError] = useState('');
 
   useEffect(() => {
@@ -24,12 +26,97 @@ function App() {
     const entityIdParam = queryParams.entityID || '';
     const returnUrlParam = queryParams.return || '';
 
+    // Encode entityID from URL
+    const URLencodedEntityID = encodeURIComponent(entityIdParam)
+
     if (!entityIdParam || !returnUrlParam) {
       setError('Both Entity ID and return URL are required.');
     } else {
       setEntityID(entityIdParam);
       setReturnUrl(returnUrlParam);
     }
+
+    // Fetch metadata for display name and logo
+    fetch(`https://mdq.incommon.org/entities/${URLencodedEntityID}`)
+    .then(response => {
+      if (!response.ok) {
+        throw new Error(`HTTP error! Status: ${response.status}`);
+      }
+      return response.text();
+    })
+    .then(metadata => {
+      const parser = new DOMParser();
+      const xmlDoc = parser.parseFromString(metadata, "application/xml");
+
+      const displayNameNSxPath = "//*[namespace-uri()='urn:oasis:names:tc:SAML:2.0:metadata' and local-name()='EntityDescriptor']//*[namespace-uri()='urn:oasis:names:tc:SAML:metadata:ui' and local-name()='UIInfo'][1]//*[namespace-uri()='urn:oasis:names:tc:SAML:metadata:ui' and local-name()='DisplayName'][1]";
+      const logoNSxPath = "//*[namespace-uri()='urn:oasis:names:tc:SAML:2.0:metadata' and local-name()='EntityDescriptor']//*[namespace-uri()='urn:oasis:names:tc:SAML:metadata:ui' and local-name()='UIInfo'][1]//*[namespace-uri()='urn:oasis:names:tc:SAML:metadata:ui' and local-name()='Logo'][1]";
+
+      let displayNameResult = xmlDoc.evaluate(displayNameNSxPath, xmlDoc, null, XPathResult.ANY_TYPE, null);
+      let logoResult = xmlDoc.evaluate(logoNSxPath, xmlDoc, null, XPathResult.ANY_TYPE, null);
+
+      let extractedDisplayName;
+      let extractedLogo;
+
+      // Check display name
+      switch (displayNameResult.resultType) {
+          case XPathResult.STRING_TYPE:
+            extractedDisplayName = displayNameResult.stringValue;
+              break;
+          case XPathResult.NUMBER_TYPE:
+            extractedDisplayName = displayNameResult.numberValue;
+              break;
+          case XPathResult.BOOLEAN_TYPE:
+            extractedDisplayName = displayNameResult.booleanValue;
+              break;
+          case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
+              const firstNode = displayNameResult.iterateNext();
+              if (firstNode) {
+                extractedDisplayName = firstNode.textContent;
+              }
+              break;
+          case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
+              const node = displayNameResult.iterateNext();
+              if (node) {
+                extractedDisplayName = node.textContent;
+              }
+              break;
+          default:
+              break;
+      }
+
+      // Check logo 
+      switch (logoResult.resultType) {
+        case XPathResult.STRING_TYPE:
+          extractedLogo = logoResult.stringValue;
+            break;
+        case XPathResult.NUMBER_TYPE:
+          extractedLogo = logoResult.numberValue;
+            break;
+        case XPathResult.BOOLEAN_TYPE:
+          extractedLogo = logoResult.booleanValue;
+            break;
+        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
+            const firstNode = logoResult.iterateNext();
+            if (firstNode) {
+              extractedLogo = firstNode.textContent;
+            }
+            break;
+        case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
+            const node = logoResult.iterateNext();
+            if (node) {
+              extractedLogo = node.textContent;
+            }
+            break;
+        default:
+            break;
+    }
+
+      setDisplayName(extractedDisplayName)
+      setEntityLogo(extractedLogo) 
+    })
+    .catch(error => {
+      console.error("Error fetching metadata:", error);
+    });
   }, []);
 
   return (
@@ -43,16 +130,23 @@ function App() {
                 <p><a href="https://incommon.org/help/" target="_blank">Click Here</a> for more information.</p>
               </>
           ) : (
-              <>
-                <a href={`https://service.seamlessaccess.org/ds/?entityID=${entityID}&return=${returnUrl}`} className="d-flex sa-button">
-                  <div className="sa-button-logo-wrap">
-                    <img src="https://service.seamlessaccess.org/sa-white.svg" alt="Seamless Access Logo" className="sa-button-logo"/>
-                  </div>
-                  <div className="d-flex justify-content-center align-items-center sa-button-text text-truncate">
-                    <div className="sa-button-text-primary text-truncate">Access through your institution</div>
-                  </div>
-                </a>
-              </>
+              <div id="login" className="d-flex column align-items-center">
+                <div>
+                  <p>You are being asked to login to the following service: {displayName}</p>
+                  <img src={entityLogo} className="entity-logo" alt={`${displayName} Logo`} />
+                </div>
+                <div className="d-flex column align-items-center">
+                  <p>Please select your home organization using the button below.</p>
+                  <a href={`https://service.seamlessaccess.org/ds/?entityID=${entityID}&return=${returnUrl}`} className="d-flex sa-button">
+                    <div className="sa-button-logo-wrap">
+                      <img src="https://service.seamlessaccess.org/sa-white.svg" alt="Seamless Access Logo" className="sa-button-logo"/>
+                    </div>
+                    <div className="d-flex justify-content-center align-items-center sa-button-text text-truncate">
+                      <div className="sa-button-text-primary text-truncate">Access through your institution</div>
+                    </div>
+                  </a>
+                </div>
+              </div>
           )}
         </div>
         <div>
@@ -61,7 +155,7 @@ function App() {
             <a href="https://internet2.edu/community/about-us/policies/privacy/" className="last" target="_blank">Data Privacy</a>
             <a href="https://incommon.org/help/" className="last" target="_blank">Help</a>
           </div>
-          <p>© Copyright 2020, InCommon, LLC | incommon.org | InCommon: Identity and Access for Research and Education</p>
+          <p className="copyright">© Copyright 2020, InCommon, LLC | incommon.org | InCommon: Identity and Access for Research and Education</p>
         </div>
       </div>
     </div>
diff --git a/src/assets/stylesheets/App.css b/src/assets/stylesheets/App.css
index a6d966a..c71ada9 100644
--- a/src/assets/stylesheets/App.css
+++ b/src/assets/stylesheets/App.css
@@ -24,6 +24,12 @@ html, body {
   margin: 100px 0; 
 }
 
+.entity-logo {
+  height: 20px;
+  margin-top: 20px; 
+  margin-bottom: 50px;
+}
+
 .container {
   width: 75%;
   height: 100%;
@@ -32,6 +38,10 @@ html, body {
   padding: 0 40px;
 }
 
+.copyright {
+  font-size: small;
+}
+
 /* Flexbox properties */
 .d-flex {
   display: flex;
@@ -287,6 +297,10 @@ header {
   text-align: center;
 }
 
+#login p {
+  margin-bottom: 0; 
+}
+
 footer {
   width: 100%;
   display: flex;
@@ -404,4 +418,8 @@ footer {
     float: left;
     list-style: none;
   }
+  .logo {
+    height: 35px;
+    margin: 50px 0;
+  }
 }