Implemented handler for OpenID authentication error response.

Change-Id: Icc7150e4f880ab38b1ea7be42afe04dd0814af10
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 eda15fc..bd66b74 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
@@ -2,7 +2,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import java.net.URI;
 
@@ -14,6 +13,8 @@
 import org.apache.oltu.oauth2.common.message.types.TokenType;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
@@ -55,13 +56,17 @@
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
         form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("state", "thisIsMyState");
 
         ClientResponse response = requestAuthorizationConfidentialClient(form);
 
         assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
                 response.getStatus());
         URI redirectUri = response.getLocation();
-        assertTrue(redirectUri.getQuery().startsWith("code"));
+        MultiValueMap<String, String> params = UriComponentsBuilder
+                .fromUri(redirectUri).build().getQueryParams();
+        assertNotNull(params.getFirst("code"));
+        assertEquals("thisIsMyState", params.getFirst("state"));
     }
 
     @Test
@@ -80,7 +85,7 @@
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(OAuthError.CodeResponse.INVALID_REQUEST,
                 node.at("/error").asText());
-        assertEquals(redirectUri + " is unknown",
+        assertEquals("Invalid redirect URI",
                 node.at("/error_description").asText());
     }
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
index da84a06..fc35235 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
@@ -1,7 +1,7 @@
 package de.ids_mannheim.korap.web.controller;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
 
 import java.net.URI;
 
@@ -10,7 +10,10 @@
 import org.apache.http.entity.ContentType;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
@@ -21,26 +24,20 @@
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.utils.JsonUtils;
 
 public class OAuth2OpenIdControllerTest extends SpringJerseyTest {
 
     @Autowired
     private HttpAuthorizationHandler handler;
 
-    @Test
-    public void testAuthorize () throws UniformInterfaceException,
-            ClientHandlerException, KustvaktException {
+    private String redirectUri =
+            "https://korap.ids-mannheim.de/confidential/redirect";
 
-        String redirectUri =
-                "https://korap.ids-mannheim.de/confidential/redirect";
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("response_type", "code");
-        form.add("scope", "openid");
-        form.add("redirect_uri", redirectUri);
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
-
-        ClientResponse response = resource().path("oauth2").path("openid")
-                .path("authorize")
+    private ClientResponse sendAuthorizationRequest (
+            MultivaluedMap<String, String> form) throws KustvaktException {
+        return resource().path("oauth2").path("openid").path("authorize")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue("dory",
                                 "password"))
@@ -48,12 +45,134 @@
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
-
-        URI location = response.getLocation();
-
-        assertEquals(redirectUri, location.getScheme() + "://"
-                + location.getHost() + location.getPath());
-        assertTrue(location.getQuery().startsWith("code"));
     }
 
+    @Test
+    public void testRequestAuthorizationCode ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+
+        testRequestAuthorizationCodeWithoutOpenID(form, redirectUri);
+        form.add("scope", "openid");
+
+        testRequestAuthorizationCodeMissingRedirectUri(form);
+        testRequestAuthorizationCodeInvalidRedirectUri(form);
+        form.add("redirect_uri", redirectUri);
+
+        form.add("state", "thisIsMyState");
+
+        ClientResponse response = sendAuthorizationRequest(form);
+        URI location = response.getLocation();
+        assertEquals(redirectUri, location.getScheme() + "://"
+                + location.getHost() + location.getPath());
+
+        MultiValueMap<String, String> params =
+                UriComponentsBuilder.fromUri(location).build().getQueryParams();
+        assertNotNull(params.getFirst("code"));
+        assertEquals("thisIsMyState", params.getFirst("state"));
+    }
+
+    private void testRequestAuthorizationCodeWithoutOpenID (
+            MultivaluedMap<String, String> form, String redirectUri)
+            throws KustvaktException {
+        ClientResponse response = sendAuthorizationRequest(form);
+        URI location = response.getLocation();
+        // System.out.println(location.toString());
+        assertEquals(redirectUri, location.getScheme() + "://"
+                + location.getHost() + location.getPath());
+    }
+
+    private void testRequestAuthorizationCodeMissingRedirectUri (
+            MultivaluedMap<String, String> form) throws KustvaktException {
+        ClientResponse response = sendAuthorizationRequest(form);
+        String entity = response.getEntity(String.class);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        assertEquals("redirect_uri is required",
+                node.at("/error_description").asText());
+    }
+
+    private void testRequestAuthorizationCodeInvalidRedirectUri (
+            MultivaluedMap<String, String> form) throws KustvaktException {
+        form.add("redirect_uri", "blah");
+        ClientResponse response = sendAuthorizationRequest(form);
+        String entity = response.getEntity(String.class);
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        assertEquals("Invalid redirect URI",
+                node.at("/error_description").asText());
+
+        form.remove("redirect_uri");
+    }
+
+    @Test
+    public void testRequestAuthorizationCodeMissingClientID ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("scope", "openid");
+        form.add("redirect_uri", redirectUri);
+
+        // error response is represented in JSON because redirect URI
+        // cannot be verified without client id
+        // Besides client_id is a mandatory parameter in a normal
+        // OAuth2 authorization request, thus it is checked first,
+        // before redirect_uri. see
+        // com.nimbusds.oauth2.sdk.AuthorizationRequest
+
+        ClientResponse response = sendAuthorizationRequest(form);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        assertEquals("Invalid request: Missing \"client_id\" parameter",
+                node.at("/error_description").asText());
+
+    }
+
+    @Test
+    public void testRequestAuthorizationCodeMissingResponseType ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("scope", "openid");
+        form.add("redirect_uri", redirectUri);
+        form.add("client_id", "blah");
+
+        // client_id has not been verified yet
+        // MUST NOT automatically redirect the user-agent to the
+        // invalid redirection URI.
+
+        ClientResponse response = sendAuthorizationRequest(form);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        assertEquals("Invalid request: Missing \"response_type\" parameter",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testRequestAuthorizationCodeUnsupportedResponseType ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("scope", "openid");
+        form.add("redirect_uri", redirectUri);
+        // we don't support implicit grant
+        form.add("response_type", "id_token token");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("nonce", "nonce");
+
+        ClientResponse response = sendAuthorizationRequest(form);
+        URI location = response.getLocation();
+        // System.out.println(location);
+
+        MultiValueMap<String, String> params =
+                UriComponentsBuilder.fromUri(location).build().getQueryParams();
+        assertEquals("invalid_request", params.getFirst("error"));
+        assertEquals("unsupported+response_type%3A+id_token",
+                params.getFirst("error_description"));
+    }
 }
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index 75edbc4..6094435 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -193,7 +193,7 @@
 		<constructor-arg ref="kustvakt_db" />
 	</bean>
 
-	<bean id="kustvaktExceptionHandler" class="de.ids_mannheim.korap.web.KustvaktExceptionHandler">
+	<bean id="kustvaktResponseHandler" class="de.ids_mannheim.korap.web.KustvaktResponseHandler">
 		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
 	</bean>