Implemented OAuth2 authorization error response using redirect URI.

Change-Id: Id79f3eec0beacaca792fb3130e3e41f5d9bbc7ad
diff --git a/full/Changes b/full/Changes
index ccb3096..4c99586 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,9 +1,10 @@
 version 0.60.4
-12/06/2018
+18/06/2018
     - implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
     - enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
     - implemented response handler for OpenID authentication errors in authorization requests (margaretha)
     - added tests regarding OpenID authentication in authorization requests (margaretha)
+    - implemented OAuth2 authorization error response via redirect URI instead of JSON (margaretha)
     
 version 0.60.3
 06/06/2018
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
index c0353be..77f8e17 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuAuthorizationService.java
@@ -1,5 +1,8 @@
 package de.ids_mannheim.korap.oauth2.oltu.service;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
@@ -13,6 +16,8 @@
 import com.sun.jersey.api.client.ClientResponse.Status;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 import de.ids_mannheim.korap.oauth2.service.OAuth2AuthorizationService;
 
@@ -35,29 +40,60 @@
      * @param request
      * @param authzRequest
      * @param username
-     * @return
+     * @return redirect URI containing authorization code if
+     *         successful.
+     * 
      * @throws KustvaktException
      * @throws OAuthSystemException
      */
     public String requestAuthorizationCode (HttpServletRequest request,
             OAuthAuthzRequest authzRequest, String username)
-            throws KustvaktException, OAuthSystemException {
+            throws OAuthSystemException, KustvaktException {
 
         String code = oauthIssuer.authorizationCode();
         checkResponseType(authzRequest.getResponseType());
 
         String clientId = authzRequest.getClientId();
         OAuth2Client client = clientService.authenticateClientId(clientId);
-        
+
         String redirectUri = authzRequest.getRedirectURI();
         String verifiedRedirectUri = verifyRedirectUri(client, redirectUri);
-        String scope = createAuthorization(username, authzRequest.getClientId(),
-                redirectUri, authzRequest.getScopes(), code);
 
-        OAuthResponse oAuthResponse = OAuthASResponse
-                .authorizationResponse(request, Status.FOUND.getStatusCode())
-                .setCode(code).setScope(scope)
-                .location(verifiedRedirectUri).buildQueryMessage();
+        URI redirectURI;
+        try {
+            redirectURI = new URI(verifiedRedirectUri);
+        }
+        catch (URISyntaxException e) {
+            throw new KustvaktException(StatusCodes.INVALID_REDIRECT_URI,
+                    "Invalid redirect URI", OAuth2Error.INVALID_REQUEST);
+        }
+
+        String scope;
+        try {
+            scope = createAuthorization(username, authzRequest.getClientId(),
+                    redirectUri, authzRequest.getScopes(), code);
+        }
+        catch (KustvaktException e) {
+            e.setRedirectUri(redirectURI);
+            throw e;
+        }
+
+        OAuthResponse oAuthResponse;
+        try {
+            oAuthResponse = OAuthASResponse
+                    .authorizationResponse(request,
+                            Status.FOUND.getStatusCode())
+                    .setCode(code).setScope(scope).location(verifiedRedirectUri)
+                    .buildQueryMessage();
+        }
+        catch (OAuthSystemException e) {
+            // Should not happen
+            KustvaktException ke =
+                    new KustvaktException(StatusCodes.OAUTH2_SYSTEM_ERROR,
+                            e.getMessage(), OAuth2Error.SERVER_ERROR);
+            ke.setRedirectUri(redirectURI);
+            throw ke;
+        }
         return oAuthResponse.getLocationUri();
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
index f6a223c..d244e90 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OAuth2ResponseHandler.java
@@ -4,6 +4,7 @@
 import java.net.URISyntaxException;
 
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
@@ -13,6 +14,7 @@
 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.OAuthResponse.OAuthErrorResponseBuilder;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -105,10 +107,37 @@
             int statusCode) throws OAuthSystemException {
         OAuthProblemException oAuthProblemException = OAuthProblemException
                 .error(e.getEntity()).description(e.getMessage());
-        return OAuthResponse.errorResponse(statusCode)
-                .error(oAuthProblemException).buildJSONMessage();
+
+        OAuthErrorResponseBuilder responseBuilder = OAuthResponse
+                .errorResponse(statusCode).error(oAuthProblemException);
+        URI redirectUri = e.getRedirectUri();
+        if (redirectUri != null) {
+            responseBuilder.location(redirectUri.toString());
+            return responseBuilder.buildQueryMessage();
+        }
+
+        return responseBuilder.buildJSONMessage();
     }
 
+    /**
+     * RFC 6749 regarding authorization error response:
+     * 
+     * If the request fails due to a missing, invalid, or mismatching
+     * redirection URI, or if the client identifier is missing or
+     * invalid, the authorization server SHOULD inform the resource
+     * owner of the error and MUST NOT automatically redirect the
+     * user-agent to the invalid redirection URI.
+     * 
+     * If the resource owner denies the access request or if the
+     * request fails for reasons other than a missing or invalid
+     * redirection URI, the authorization server informs the client by
+     * adding the following parameters to the query component of the
+     * redirection URI using the "application/x-www-form-urlencoded"
+     * format.
+     * 
+     * @param oAuthResponse
+     * @return
+     */
     public Response createResponse (OAuthResponse oAuthResponse) {
         ResponseBuilder builder =
                 Response.status(oAuthResponse.getResponseStatus());
@@ -121,6 +150,17 @@
             builder.header(HttpHeaders.WWW_AUTHENTICATE,
                     "Basic realm=\"Kustvakt\"");
         }
+        String uri = oAuthResponse.getLocationUri();
+        if (uri != null && !uri.isEmpty()) {
+            try {
+                builder.location(new URI(uri));
+                builder.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
+            }
+            catch (URISyntaxException e) {
+                e.printStackTrace();
+            }
+        }
+
         return builder.build();
     }
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
index 4c50d75..21cecdb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/OpenIdResponseHandler.java
@@ -4,6 +4,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
@@ -104,7 +105,8 @@
                     state, responseMode).toURI();
         }
 
-        ResponseBuilder builder = Response.temporaryRedirect(uri);
+        ResponseBuilder builder = Response.temporaryRedirect(uri)
+                .type(MediaType.APPLICATION_FORM_URLENCODED);
         return builder.build();
     }
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index 4b2e19e..6614f11 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -79,9 +79,9 @@
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
 
+        HttpServletRequest requestWithForm =
+                new FormRequestWrapper(request, form);
         try {
-            HttpServletRequest requestWithForm =
-                    new FormRequestWrapper(request, form);
             OAuth2AuthorizationRequest authzRequest =
                     new OAuth2AuthorizationRequest(requestWithForm);
             String uri = authorizationService.requestAuthorizationCode(
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
index d3605de..82affd6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2WithOpenIdController.java
@@ -5,7 +5,6 @@
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
-import javax.ws.rs.FormParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
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 bd66b74..f7aa89a 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
@@ -140,11 +140,11 @@
         ClientResponse response = requestAuthorizationConfidentialClient(form);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
-        String entity = response.getEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
-        assertEquals("read_address is an invalid scope",
-                node.at("/error_description").asText());
+        URI location = response.getLocation();
+        MultiValueMap<String, String> params =
+                UriComponentsBuilder.fromUri(location).build().getQueryParams();
+        assertEquals(OAuth2Error.INVALID_SCOPE, params.getFirst("error"));
+        assertEquals("read_address+is+an+invalid+scope", params.getFirst("error_description"));
     }
 
     private ClientResponse requestToken (MultivaluedMap<String, String> form)
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 fc35235..d4a337d 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
@@ -5,6 +5,7 @@
 
 import java.net.URI;
 
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.apache.http.entity.ContentType;
@@ -167,7 +168,7 @@
 
         ClientResponse response = sendAuthorizationRequest(form);
         URI location = response.getLocation();
-        // System.out.println(location);
+        assertEquals(MediaType.APPLICATION_FORM_URLENCODED, response.getType().toString());
 
         MultiValueMap<String, String> params =
                 UriComponentsBuilder.fromUri(location).build().getQueryParams();