Allowed OAuth2 clients to use localhost as redirect URIs.

Change-Id: Ia57668ab77dcdd68220d998c89bbcf366699409c
diff --git a/full/Changes b/full/Changes
index 4dac86d..8147bcc 100644
--- a/full/Changes
+++ b/full/Changes
@@ -7,6 +7,8 @@
   client authentication)
 2023-01-30
 - Made scope param required in authorization request (solved #508)
+2023-01-31
+- Allowed OAuth2 clients to use localhost as redirect URIs.
 
 
 # version 0.69.1
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index 86f686e..53c0536 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -209,10 +209,14 @@
 	<!-- URLValidator -->
 	<bean id="redirectURIValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https" index="0" />
-		<constructor-arg index="1" type="long">
+		<constructor-arg index="1" type="long" 
+		value="#{T(org.apache.commons.validator.routines.UrlValidator).ALLOW_LOCAL_URLS + 
+		T(org.apache.commons.validator.routines.UrlValidator).NO_FRAGMENTS}"/>
+		
+		<!-- <constructor-arg index="1" type="long">
 			<util:constant
 				static-field="org.apache.commons.validator.routines.UrlValidator.NO_FRAGMENTS" />
-		</constructor-arg>
+		</constructor-arg> -->
 	</bean>
 	<bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https" />
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index a0655c6..880d8f2 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -12,22 +12,20 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Form;
-import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.http.entity.ContentType;
 import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.glassfish.jersey.server.ContainerRequest;
 import org.junit.Test;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
-import javax.ws.rs.ProcessingException;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.client.Entity;
-
-import org.glassfish.jersey.server.ContainerRequest;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
@@ -223,11 +221,11 @@
                 response.getStatus());
 
         // localhost is not allowed
-        redirectUri = "http://localhost:1410";
-        clientJson.setRedirectURI(redirectUri);
-        response = registerClient(username, clientJson);
-        testInvalidRedirectUri(response.readEntity(String.class), false,
-                response.getStatus());
+//        redirectUri = "http://localhost:1410";
+//        clientJson.setRedirectURI(redirectUri);
+//        response = registerClient(username, clientJson);
+//        testInvalidRedirectUri(response.readEntity(String.class), false,
+//                response.getStatus());
         
         // fragment is not allowed
         redirectUri = "https://public.client.com/redirect.html#bar";
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index 1a6d89f..f31596f 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -182,11 +182,18 @@
     @Test
     public void testAuthorizeWithRedirectUriLocalhost ()
             throws KustvaktException {
-        Response response =
-                requestAuthorizationCode("code", publicClientId2,
-                        "http://localhost:1410", "search", state, userAuthHeader);
-        testInvalidRedirectUri(response.readEntity(String.class), true,
-                response.getStatus());    }
+        Response response = requestAuthorizationCode("code", publicClientId2,
+                "http://localhost:1410", "search", state, userAuthHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        URI redirectUri = response.getLocation();
+        MultivaluedMap<String, String> params =
+                getQueryParamsFromURI(redirectUri);
+        assertNotNull(params.getFirst("code"));
+        assertEquals(state, params.getFirst("state"));
+        assertEquals("search", params.getFirst("scope"));
+    }
 
     @Test
     public void testAuthorizeWithRedirectUriFragment ()
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java
new file mode 100644
index 0000000..358cf18
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2RClientTest.java
@@ -0,0 +1,107 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URI;
+
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.junit.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+public class OAuth2RClientTest extends OAuth2TestBase {
+
+    private String username = "OAuth2ClientControllerTest";
+    private String userAuthHeader;
+
+    public OAuth2RClientTest () throws KustvaktException {
+        userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("R-user", "password");
+    }
+
+    public OAuth2ClientJson createOAuth2RClient () {
+        OAuth2ClientJson client = new OAuth2ClientJson();
+        client.setName("R client");
+        client.setType(OAuth2ClientType.PUBLIC);
+        client.setDescription("An R client with httr web server.");
+        client.setRedirectURI("http://localhost:1410");
+        return client;
+    }
+
+    @Test
+    public void testRClientWithLocalhost ()
+            throws ProcessingException, KustvaktException, IOException {
+        // Register client
+        OAuth2ClientJson clientJson = createOAuth2RClient();
+        Response response = registerClient(username, clientJson);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+        String clientId = node.at("/client_id").asText();
+
+        // send authorization
+        String code = testAuthorize(clientId);
+
+        // send token request
+        response =
+                requestTokenWithAuthorizationCodeAndForm(clientId, null, code);
+        
+        assertEquals(Status.OK.getStatusCode(),
+                response.getStatus());
+        
+        String entity = response.readEntity(String.class);
+        node = JsonUtils.readTree(entity);
+
+        // testing
+        String accessToken = node.at("/access_token").asText();
+        testSearchWithOAuth2Token(accessToken);
+
+        // cleaning up
+        deregisterClient(username, clientId);
+
+        testSearchWithRevokedAccessToken(accessToken);
+    }
+
+    private String testAuthorize (String clientId) throws KustvaktException {
+
+        Response response = requestAuthorizationCode("code", clientId, "",
+                "search", "", userAuthHeader);
+
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+
+        URI redirectUri = response.getLocation();
+
+        assertEquals("http", redirectUri.getScheme());
+        assertEquals("localhost", redirectUri.getHost());
+        assertEquals(1410, redirectUri.getPort());
+
+        MultiValueMap<String, String> params = UriComponentsBuilder
+                .fromUri(redirectUri).build().getQueryParams();
+        String code = params.getFirst("code");
+        assertNotNull(code);
+        assertEquals("search", params.getFirst("scope"));
+        return code;
+    }
+
+}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index a84f8f7..48d888c 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -8,6 +8,8 @@
 import java.net.URI;
 
 import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Form;
@@ -18,6 +20,8 @@
 import org.apache.http.entity.ContentType;
 import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
 import org.glassfish.jersey.uri.UriComponent;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.MultiValueMap;
@@ -82,9 +86,13 @@
             String clientId, String redirectUri, String scope, String state,
             String authHeader) throws KustvaktException {
 
-        WebTarget request =
-                target().path(API_VERSION).path("oauth2").path("authorize");
+        ClientConfig clientConfig = new ClientConfig();
+        clientConfig.property(ClientProperties.FOLLOW_REDIRECTS, false);
+        Client client = ClientBuilder.newClient(clientConfig);
         
+        WebTarget request = client.target(getBaseUri()).path(API_VERSION)
+                .path("oauth2").path("authorize");
+
         if (!responseType.isEmpty()) {
             request = request.queryParam("response_type", responseType);
         }
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index dbcd8b9..aaea39c 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -191,10 +191,9 @@
 	<!-- URLValidator -->
 	<bean id="redirectURIValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https" index="0" />
-		<constructor-arg index="1" type="long">
-			<util:constant
-				static-field="org.apache.commons.validator.routines.UrlValidator.NO_FRAGMENTS" />
-		</constructor-arg>
+		<constructor-arg index="1" type="long" 
+		value="#{T(org.apache.commons.validator.routines.UrlValidator).ALLOW_LOCAL_URLS + 
+		T(org.apache.commons.validator.routines.UrlValidator).NO_FRAGMENTS}"/>
 	</bean>
 	<bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https" />