From 59ad16913e913b0957350a1df88212b035436aab Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Fri, 12 Nov 2021 17:07:21 +0100
Subject: [PATCH] Added availability and capabilities controllers

---
 .../it/inaf/ia2/gms/authn/GmsLoginFilter.java |  2 +-
 .../ia2/gms/controller/DALIController.java    | 71 +++++++++++++++++
 gms/src/main/resources/availability.xml       |  5 ++
 gms/src/main/resources/capabilities.xml       | 26 +++++++
 .../gms/controller/DALIControllerTest.java    | 77 +++++++++++++++++++
 5 files changed, 180 insertions(+), 1 deletion(-)
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/controller/DALIController.java
 create mode 100644 gms/src/main/resources/availability.xml
 create mode 100644 gms/src/main/resources/capabilities.xml
 create mode 100644 gms/src/test/java/it/inaf/ia2/gms/controller/DALIControllerTest.java

diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java b/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
index 575cadf..fa02aaf 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/GmsLoginFilter.java
@@ -44,7 +44,7 @@ public class GmsLoginFilter extends LoginFilter {
         AntPathMatcher pathMatcher = new AntPathMatcher();
 
         // Authentication is ignored for these endpoints:
-        return Arrays.asList("/ws/jwt/**", "/error", "/logout", "/invited-registration", "/help/**")
+        return Arrays.asList("/ws/jwt/**", "/error", "/logout", "/invited-registration", "/help/**", "/vo/capabilities", "/vo/availability")
                 .stream()
                 .anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/DALIController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/DALIController.java
new file mode 100644
index 0000000..eb5de8c
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/DALIController.java
@@ -0,0 +1,71 @@
+/*
+ * This file is part of gms
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.ia2.gms.controller;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Controller for IVOA Data Access Layer Interface (VOSI-capabilities and
+ * VOSI-availability endpoints).
+ */
+@RestController
+public class DALIController {
+
+    @Autowired
+    private HttpServletRequest request;
+
+    @GetMapping(value = "/vo/capabilities", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
+    public String getCapabilities() throws IOException {
+        String xml = loadXmlFile("capabilities.xml");
+        return xml.replace("{{ base_url }}", getBaseUrl());
+    }
+
+    @GetMapping(value = "/vo/availability", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
+    public String getAvailability() throws IOException {
+        String xml = loadXmlFile("availability.xml");
+        return xml.replace("{{ base_url }}", getBaseUrl());
+    }
+
+    private String loadXmlFile(String fileName) throws IOException {
+        try ( InputStream in = DALIController.class.getClassLoader().getResourceAsStream(fileName)) {
+            Scanner s = new Scanner(in).useDelimiter("\\A");
+            return s.hasNext() ? s.next() : "";
+        }
+    }
+
+    /**
+     * Generate base URL considering also proxied requests.
+     */
+    private String getBaseUrl() {
+
+        String forwaredProtocol = request.getHeader("X-Forwarded-Proto");
+        String scheme = forwaredProtocol != null ? forwaredProtocol : request.getScheme();
+
+        String forwardedHost = request.getHeader("X-Forwarded-Host");
+        if (forwardedHost != null && forwardedHost.contains(",")) {
+            // X-Forwarded-Host can be a list of comma separated values
+            forwardedHost = forwardedHost.split(",")[0];
+        }
+        String host = forwardedHost != null ? forwardedHost : request.getServerName();
+
+        UriComponentsBuilder builder = UriComponentsBuilder.newInstance()
+                .scheme(scheme).host(host).path(request.getContextPath());
+
+        if (forwardedHost == null) {
+            builder.port(request.getServerPort());
+        }
+
+        return builder.toUriString();
+    }
+}
diff --git a/gms/src/main/resources/availability.xml b/gms/src/main/resources/availability.xml
new file mode 100644
index 0000000..30a7f0a
--- /dev/null
+++ b/gms/src/main/resources/availability.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<vosi:availability xmlns:vosi="http://www.ivoa.net/xml/VOSIAvailability/v1.0">
+    <vosi:available>true</vosi:available>
+    <vosi:note>IA2 GMS is available</vosi:note>
+</vosi:availability>
\ No newline at end of file
diff --git a/gms/src/main/resources/capabilities.xml b/gms/src/main/resources/capabilities.xml
new file mode 100644
index 0000000..7163198
--- /dev/null
+++ b/gms/src/main/resources/capabilities.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<vosi:capabilities
+    xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0"
+    xmlns:vs="http://www.ivoa.net/xml/VODataService/v1.1"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+    <capability standardID="ivo://ivoa.net/std/VOSI#capabilities">
+        <interface xsi:type="vs:ParamHTTP" role="std">
+            <accessURL use="full">{{ base_url }}/vo/capabilities</accessURL>
+        </interface>
+    </capability>
+
+    <capability standardID="ivo://ivoa.net/std/VOSI#availability">
+        <interface xsi:type="vs:ParamHTTP" role="std">
+            <accessURL use="full">{{ base_url }}/vo/availability</accessURL>
+        </interface>
+    </capability>
+
+    <capability standardID="ivo://ivoa.net/std/gms#search-1.0">
+        <interface xsi:type="vs:ParamHTTP" role="std" version="1.0">
+            <accessURL use="base">{{ base_url }}/vo/search</accessURL>
+            <securityMethod standardID="ivo://ivoa.net/sso#token" />
+        </interface>
+    </capability>
+
+</vosi:capabilities>
\ No newline at end of file
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/DALIControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/DALIControllerTest.java
new file mode 100644
index 0000000..05f5499
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/DALIControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * This file is part of gms
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.ia2.gms.controller;
+
+import javax.servlet.http.HttpServletRequest;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.when;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+@ExtendWith(MockitoExtension.class)
+public class DALIControllerTest {
+
+    private MockMvc mockMvc;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @InjectMocks
+    private DALIController controller;
+
+    @BeforeEach
+    public void init() {
+        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
+    }
+
+    @Test
+    public void testGetCapabilitiesBase() throws Exception {
+
+        when(request.getServerName()).thenReturn("ia2.inaf.it");
+        when(request.getServerPort()).thenReturn(8080);
+        when(request.getContextPath()).thenReturn("/gms");
+        when(request.getScheme()).thenReturn("http");
+
+        String xml = mockMvc.perform(get("/vo/capabilities"))
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+
+        assertTrue(xml.contains("http://ia2.inaf.it:8080/gms/vo/search"));
+    }
+
+    @Test
+    public void testGetCapabilitiesProxied() throws Exception {
+
+        when(request.getHeader("X-Forwarded-Proto")).thenReturn("https");
+        when(request.getHeader("X-Forwarded-Host")).thenReturn("ia2.inaf.it,server2.ia2.inaf.it");
+
+        when(request.getContextPath()).thenReturn("/gms");
+
+        String xml = mockMvc.perform(get("/vo/capabilities"))
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+
+        assertTrue(xml.contains("https://ia2.inaf.it/gms/vo/search"));
+    }
+
+    @Test
+    public void testGetAvailability() throws Exception {
+
+        String xml = mockMvc.perform(get("/vo/availability"))
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+
+        assertTrue(xml.contains("<vosi:available>true</vosi:available>"));
+    }
+}
-- 
GitLab