Updated OAuth2 client authentication.

Change-Id: Ic13a38afd2d405fa2b450d80c4737261a4ab1edc
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index c7a17b3..1e04eb0 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -23,6 +23,7 @@
     public static final int INVALID_ARGUMENT = 107;
     public static final int NOT_SUPPORTED = 108;
     public static final int NOT_ALLOWED = 109;
+    public static final int HTTPS_REQUIRED = 110;
 
     /**
      * 300 status codes for query language and serialization
@@ -115,10 +116,12 @@
      * 1800 Oauth2
      */
 
-    public static final int CLIENT_REGISTRATION_FAILED = 1800;
-    public static final int CLIENT_DEREGISTRATION_FAILED = 1801;
-    public static final int CLIENT_AUTHENTICATION_FAILED = 1802;
-    public static final int CLIENT_NOT_FOUND = 1803;
+    public static final int OAUTH2_SYSTEM_ERROR = 1800;
+    
+    public static final int CLIENT_REGISTRATION_FAILED = 1801;
+    public static final int CLIENT_DEREGISTRATION_FAILED = 1802;
+    public static final int CLIENT_AUTHENTICATION_FAILED = 1803;
+    public static final int CLIENT_NOT_FOUND = 1804;
     
     public static final int UNSUPPORTED_GRANT_TYPE = 1810;
     
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java b/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
index 1584aef..3c4c57d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
+++ b/full/src/main/java/de/ids_mannheim/korap/constant/OAuth2ClientType.java
@@ -14,6 +14,7 @@
 //    credentials (e.g., clients executing on the device used by the
 //    resource owner, such as an installed native application or a web
 //    browser-based application), and incapable of secure client
-//    authentication via any other means.
+//    authentication via any other means. Mobile and Javascript apps 
+//    are considered public clients. 
     PUBLIC;
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
index 5ab083f..8d678b8 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
@@ -49,8 +49,10 @@
     }
 
     public OAuth2Client retrieveClientById (String clientId)
-            throws KustvaktException {
+            throws Exception {
 
+        ParameterChecker.checkStringValue(clientId, "client_id");
+        
         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
         CriteriaQuery<OAuth2Client> query =
                 builder.createQuery(OAuth2Client.class);
@@ -60,13 +62,13 @@
         query.where(builder.equal(root.get(OAuth2Client_.id), clientId));
 
         Query q = entityManager.createQuery(query);
-        try {
+//        try {
             return (OAuth2Client) q.getSingleResult();
-        }
-        catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
-                    "Client with id " + clientId + "is not found");
-        }
+//        }
+//        catch (NoResultException e) {
+//            throw new KustvaktException(StatusCodes.CLIENT_NOT_FOUND,
+//                    "Client with id " + clientId + " is not found");
+//        }
     }
 
     public void deregisterClient (OAuth2Client client) {
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
index fe27503..3a44e51 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
@@ -2,8 +2,11 @@
 
 import java.sql.SQLException;
 
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.commons.validator.routines.UrlValidator;
-import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -19,9 +22,23 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
-import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
+/** According to RFC 6749, an authorization server MUST: 
+ * <ul>
+ * <li>
+ * require client authentication for confidential clients or for any
+ * client that was issued client credentials (or with other authentication 
+ * requirements),
+ * </li>
+ * 
+ * <li>authenticate the client if client authentication is included
+ * </li>
+ * </ul>
+ * 
+ * @author margaretha
+ *
+ */
 @Service
 public class OAuth2ClientService {
 
@@ -32,6 +49,8 @@
     @Autowired
     private UrlValidator urlValidator;
     @Autowired
+    private UrlValidator httpsValidator;
+    @Autowired
     private EncryptionIface encryption;
     @Autowired
     private HttpAuthorizationHandler authorizationHandler;
@@ -45,9 +64,10 @@
             throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
                     clientJson.getUrl() + " is invalid.", clientJson.getUrl());
         }
-        if (!urlValidator.isValid(clientJson.getRedirectURI())) {
-            throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
-                    clientJson.getRedirectURI() + " is invalid.",
+        if (!httpsValidator.isValid(clientJson.getRedirectURI())) {
+            throw new KustvaktException(StatusCodes.HTTPS_REQUIRED,
+                    clientJson.getRedirectURI()
+                            + " is invalid. RedirectURI requires https.",
                     clientJson.getRedirectURI());
         }
 
@@ -95,9 +115,9 @@
 
 
     public void deregisterPublicClient (String clientId, String username)
-            throws KustvaktException {
+            throws KustvaktException, OAuthProblemException {
 
-        OAuth2Client client = clientDao.retrieveClientById(clientId);
+        OAuth2Client client = retrieveClientById(clientId);
         if (adminDao.isAdmin(username)) {
             clientDao.deregisterClient(client);
         }
@@ -119,47 +139,34 @@
 
 
     public void deregisterConfidentialClient (String authorization,
-            String clientId) throws KustvaktException {
-        OAuth2Client client = authenticateClient(authorization, null, clientId);
+            String clientId) throws KustvaktException, OAuthProblemException {
+        OAuth2Client client =
+                authenticateClientByBasicAuthorization(authorization, clientId);
         clientDao.deregisterClient(client);
     }
 
-    public TokenContext requestAccessTokenByClientCredentials (
-            String authorization, String grantType) throws KustvaktException {
-
-        return null;
+    public OAuth2Client authenticateClientById (String clientId)
+            throws OAuthProblemException {
+        if (clientId == null || clientId.isEmpty()) {
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_REQUEST)
+                    .description("client_id is missing.")
+                    .responseStatus(HttpServletResponse.SC_BAD_REQUEST);
+        }
+        else {
+            return retrieveClientById(clientId);
+        }
     }
 
-    /** According to RFC 6749, an authorization server MUST: 
-     * <ul>
-     * <li>
-     * require client authentication for confidential clients or for any
-     * client that was issued client credentials (or with other authentication 
-     * requirements),
-     * </li>
-     * 
-     * <li>authenticate the client if client authentication is included
-     * </li>
-     * </ul>
-     * 
-     * @param authorization
-     * @param grantType
-     * @param clientId
-     * @return
-     * @throws KustvaktException
-     */
-    public OAuth2Client authenticateClient (String authorization,
-            GrantType grantType, String clientId) throws KustvaktException {
-
-        OAuth2Client client = clientDao.retrieveClientById(clientId);
+    public OAuth2Client authenticateClientByBasicAuthorization (
+            String authorization, String clientId)
+            throws KustvaktException, OAuthProblemException {
 
         if (authorization == null || authorization.isEmpty()) {
-            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)
-                    || grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
-                throw new KustvaktException(StatusCodes.AUTHENTICATION_FAILED,
-                        "Authorization header is not found.");
-            }
-            // OAuth2 does not require client authentication
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_REQUEST)
+                    .description("Authorization header is not found.")
+                    .responseStatus(HttpServletResponse.SC_BAD_REQUEST);
         }
         else {
             AuthorizationData authData = authorizationHandler
@@ -167,24 +174,64 @@
             if (authData.getAuthenticationScheme()
                     .equals(AuthenticationScheme.BASIC)) {
                 authorizationHandler.parseBasicToken(authData);
-                if (!client.getId().equals(clientId)
-                        || !encryption.checkHash(authData.getPassword(),
-                                client.getSecret(),
-                                config.getPasscodeSaltField())) {
-                    throw new KustvaktException(
-                            StatusCodes.AUTHENTICATION_FAILED,
-                            "Client credentials are incorrect.");
-                }
+                return verifyClientCredentials(clientId, authData);
             }
             else {
-                throw new KustvaktException(
-                        StatusCodes.UNSUPPORTED_AUTHENTICATION_SCHEME,
-                        authData.getAuthenticationScheme().displayName()
-                                + "is unsupported for client authentication.",
-                        authData.getAuthenticationScheme().displayName());
+                throw OAuthProblemException
+                        .error(OAuthError.TokenResponse.INVALID_CLIENT)
+                        .description(
+                                "Client authentication with "
+                                        + authData.getAuthenticationScheme()
+                                                .displayName()
+                                        + "is not supported")
+                        .responseStatus(HttpServletResponse.SC_BAD_REQUEST);
             }
         }
-        return client;
     }
 
+    private OAuth2Client verifyClientCredentials (String clientId,
+            AuthorizationData authData) throws OAuthProblemException {
+
+        try {
+            OAuth2Client client = retrieveClientById(authData.getUsername());
+            // EM: not sure if this is necessary
+            if (clientId != null && !clientId.isEmpty()) {
+                if (!client.getId().equals(clientId)) {
+                    throw new KustvaktException(
+                            StatusCodes.CLIENT_AUTHENTICATION_FAILED);
+                }
+            }
+            if (!encryption.checkHash(authData.getPassword(),
+                    client.getSecret(), config.getPasscodeSaltField())) {
+                throw new KustvaktException(
+                        StatusCodes.CLIENT_AUTHENTICATION_FAILED);
+            }
+            return client;
+        }
+        catch (Exception e) {
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_CLIENT)
+                    .description("Invalid client credentials.")
+                    .responseStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
+
+    public OAuth2Client retrieveClientById (String clientId)
+            throws OAuthProblemException {
+        try {
+            return clientDao.retrieveClientById(clientId);
+        }
+        catch (KustvaktException e) {
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_REQUEST)
+                    .description(e.getMessage() + "is missing.")
+                    .responseStatus(HttpServletResponse.SC_BAD_REQUEST);
+        }
+        catch (Exception e) {
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_CLIENT)
+                    .description("Invalid client credentials.")
+                    .responseStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
index aee6b13..b69b248 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2Service.java
@@ -1,57 +1,194 @@
 package de.ids_mannheim.korap.service;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.oltu.oauth2.as.issuer.MD5Generator;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
+import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.error.OAuthError;
+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.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.entity.OAuth2Client;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
 
 @Service
 public class OAuth2Service {
 
     @Autowired
     private OAuth2ClientService clientService;
+    @Autowired
+    private FullConfiguration config;
+
 
     /** 
      *  RFC 6749:
      *  
      *  If the client type is confidential or the client was issued client
      *  credentials, the client MUST authenticate with the authorization server.
+     * @param request 
      *  
      * @param authorization
      * @param grantType
      * @param scope 
      * @param password 
      * @param username 
-     * @param client_id 
+     * @param clientId required for authorization_code grant, otherwise optional
      * @param redirectURI 
      * @param authorizationCode 
+     * @return 
      * @throws KustvaktException
+     * @throws OAuthProblemException 
+     * @throws OAuthSystemException 
      */
-    public void requestAccessToken (String authorization, GrantType grantType,
-            String authorizationCode, String redirectURI, String client_id,
-            String username, String password, String scope)
-            throws KustvaktException {
-
-        OAuth2Client client = clientService.authenticateClient(authorization,
-                grantType, client_id);
+    public OAuthResponse requestAccessToken (HttpServletRequest request,
+            String authorization, GrantType grantType, String authorizationCode,
+            String redirectURI, String clientId, String username,
+            String password, String scope)
+            throws KustvaktException, OAuthProblemException {
 
         if (grantType.equals(GrantType.AUTHORIZATION_CODE)) {
-
+            return requestAccessTokenWithAuthorizationCode(authorization,
+                    authorizationCode, redirectURI, clientId);
         }
         else if (grantType.equals(GrantType.PASSWORD)) {
-
+            return requestAccessTokenWithPassword(authorization, username,
+                    password, scope);
         }
         else if (grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
-
+            return requestAccessTokenWithClientCredentials(authorization,
+                    scope);
         }
         else {
-            throw new KustvaktException(StatusCodes.UNSUPPORTED_GRANT_TYPE,
-                    "Grant type " + grantType.name() + " is unsupported.",
-                    grantType.name());
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.UNSUPPORTED_GRANT_TYPE)
+                    .description(grantType.name() + "is not supported.")
+                    .responseStatus(HttpServletResponse.SC_BAD_REQUEST);
+
         }
 
     }
+
+    /** Confidential clients must authenticate
+     * 
+     * @param authorization
+     * @param authorizationCode
+     * @param redirectURI
+     * @param clientId required if there is no authorization header
+     * @return
+     * @throws OAuthSystemException
+     * @throws KustvaktException
+     * @throws OAuthProblemException 
+     */
+    private OAuthResponse requestAccessTokenWithAuthorizationCode (
+            String authorization, String authorizationCode, String redirectURI,
+            String clientId) throws KustvaktException, OAuthProblemException {
+        OAuth2Client client;
+        if (authorization == null || authorization.isEmpty()) {
+            client = clientService.authenticateClientById(clientId);
+            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+                throw OAuthProblemException
+                        .error(OAuthError.TokenResponse.INVALID_CLIENT)
+                        .description("Client authentication using "
+                                + "authorization header is required.")
+                        .responseStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            }
+        }
+        else {
+            client = clientService.authenticateClientByBasicAuthorization(
+                    authorization, clientId);
+        }
+
+        // TODO
+        return null;
+    }
+
+    /** Confidential clients must authenticate
+     * 
+     * @param authorization
+     * @param username
+     * @param password
+     * @param scope
+     * @return
+     */
+    private OAuthResponse requestAccessTokenWithPassword (String authorization,
+            String username, String password, String scope) {
+
+
+
+        return null;
+    }
+
+    /** Clients must authenticate
+     * 
+     * @param authorization
+     * @param scope
+     * @return
+     * @throws OAuthProblemException 
+     * @throws KustvaktException 
+     */
+    private OAuthResponse requestAccessTokenWithClientCredentials (
+            String authorization, String scope)
+            throws OAuthProblemException, KustvaktException {
+
+        if (authorization == null || authorization.isEmpty()) {
+            throw OAuthProblemException
+                    .error(OAuthError.TokenResponse.INVALID_CLIENT)
+                    .description("Client authentication using "
+                            + "authorization header is required.")
+                    .responseStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        }
+        else {
+            OAuth2Client client =
+                    clientService.authenticateClientByBasicAuthorization(
+                            authorization, null);
+            //TODO
+        }
+        return null;
+    }
+
+
+    /**
+     * @param request  
+     * @return 
+     * @throws OAuthSystemException 
+     * 
+     */
+    private OAuthResponse createsAccessTokenResponse (
+            HttpServletRequest request) throws OAuthSystemException {
+        OAuthTokenRequest oauthRequest = null;
+        OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
+        OAuthResponse r = null;
+        try {
+            oauthRequest = new OAuthTokenRequest(request);
+            String authorizationCode = oauthRequest.getCode();
+
+            String accessToken = oauthIssuerImpl.accessToken();
+            String refreshToken = oauthIssuerImpl.refreshToken();
+
+            r = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
+                    .setAccessToken(accessToken)
+                    .setTokenType(TokenType.BEARER.name())
+                    .setExpiresIn(String.valueOf(config.getLongTokenTTL()))
+                    .setRefreshToken(refreshToken).buildJSONMessage();
+            // scope
+
+        }
+        catch (OAuthProblemException e) {
+            r = OAuthResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
+                    .error(e).buildJSONMessage();
+        }
+
+        return r;
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/FullResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/FullResponseHandler.java
index c2d8df5..d836ead 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/FullResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/FullResponseHandler.java
@@ -3,6 +3,9 @@
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Response;
 
+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.springframework.beans.factory.annotation.Autowired;
 
 import de.ids_mannheim.korap.authentication.http.HttpUnauthorizedHandler;
@@ -45,6 +48,24 @@
         return new WebApplicationException(r);
     }
 
+    public WebApplicationException throwit (OAuthProblemException e) {
+        OAuthResponse or = null;
+        try {
+            or = OAuthResponse.errorResponse(e.getResponseStatus()).error(e)
+                    .buildJSONMessage();
+        }
+        catch (OAuthSystemException e1) {
+            //            return throwit(new KustvaktException(
+            //                    StatusCodes.OAUTH2_SYSTEM_ERROR, e1.getMessage(), e1));
+            return throwit(StatusCodes.OAUTH2_SYSTEM_ERROR, e1.getMessage());
+        }
+
+        Response r = Response.status(or.getResponseStatus())
+                .entity(or.getBody()).build();
+        return new WebApplicationException(r);
+
+    }
+
     //    public WebApplicationException throwAuthenticationException (
     //            String message) {
     //        String notification =
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 7cd3311..08d2a0a 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
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.web.controller;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
 import javax.ws.rs.HeaderParam;
@@ -9,8 +10,13 @@
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.SecurityContext;
 
+import org.apache.http.HttpHeaders;
+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.types.GrantType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -28,11 +34,28 @@
     @Autowired
     private OAuth2Service oauth2Service;
 
+    /** Grants a client an access token, namely a string used in authenticated 
+     *  requests representing user authorization for the client to access user 
+     *  resources. 
+     * 
+     *  EM: should we allow client_secret in the request body?
+     * 
+     * @param securityContext
+     * @param authorization
+     * @param grantType
+     * @param authorizationCode
+     * @param redirectURI
+     * @param client_id a client id required for authorization_code grant, otherwise optional
+     * @param username
+     * @param password
+     * @param scope
+     * @return
+     */
     @POST
     @Path("token")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
-    public Response requestAccessToken (
+    public Response requestAccessToken (@Context HttpServletRequest request,
             @Context SecurityContext securityContext,
             @HeaderParam("Authorization") String authorization,
             // required for all grants
@@ -41,21 +64,29 @@
             @FormParam("code") String authorizationCode,
             @FormParam("redirect_uri") String redirectURI,
             @FormParam("client_id") String client_id,
-            // required for Resource Owner Password Credentials Grant
+            // required for Resource Owner Password Grant
             @FormParam("username") String username,
             @FormParam("password") String password,
             // optional for Resource Owner Password and Client Credentials Grants
             @FormParam("scope") String scope) {
 
         try {
-            oauth2Service.requestAccessToken(authorization, grantType,
-                    authorizationCode, redirectURI, client_id, username,
-                    password, scope);
+            OAuthResponse oauth2Response = oauth2Service.requestAccessToken(request,
+                    authorization, grantType, authorizationCode, redirectURI,
+                    client_id, username, password, scope);
 
-            return Response.ok().build();
+            ResponseBuilder builder =
+                    Response.status(oauth2Response.getResponseStatus());
+            builder.entity(oauth2Response.getBody());
+            builder.header(HttpHeaders.CACHE_CONTROL, "no-store");
+            builder.header(HttpHeaders.PRAGMA, "no-store");
+            return builder.build();
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index eba6325..d7eb33e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -12,6 +12,7 @@
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
@@ -27,6 +28,12 @@
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
 
+/** Defines controllers for OAuth2 clients, namely applications attempting
+ * to access users' resources. 
+ * 
+ * @author margaretha
+ *
+ */
 @Controller
 @Path("/oauth2/client")
 public class OAuthClientController {
@@ -38,7 +45,10 @@
 
     /** Registers a client application. Before starting an OAuth process, 
      * client applications have to be registered first. Only registered
-     * users are allowed to register client applications.
+     * users are allowed to register client applications. After registration,
+     * the client will receive a client_id and a client_secret, if the client 
+     * is confidential (capable of storing the client_secret), that are needed 
+     * in the authorization process.
      * 
      * From RFC 6749:
      * The authorization server SHOULD document the size of any identifier 
@@ -46,7 +56,9 @@
      * 
      * @param context
      * @param clientJson a JSON object describing the client
-     * @return client id and secret if the client type is confidential
+     * @return client_id and client_secret if the client type is confidential
+     * 
+     * @see OAuth2ClientJson
      */
     @POST
     @Path("register")
@@ -92,6 +104,9 @@
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
     }
 
 
@@ -109,5 +124,8 @@
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
     }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
index 80c5fba..2dc4034 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
@@ -4,13 +4,20 @@
 import lombok.Getter;
 import lombok.Setter;
 
+/** Defines required attributes to register an OAuth2 client. 
+ * 
+ * @author margaretha
+ *
+ */
 @Setter
 @Getter
 public class OAuth2ClientJson {
-    
+
     // all required for registration
     private String name;
     private OAuth2ClientType type;
     private String url;
+    // redirect URI determines where the OAuth 2.0 service will return the user to 
+    // after they have authorized a client. It must be https.
     private String redirectURI;
 }
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index 76917d4..95b2365 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -169,7 +169,9 @@
 	<bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https" />
 	</bean>
-
+	<bean id="httpsValidator" class="org.apache.commons.validator.routines.UrlValidator">
+		<constructor-arg value="https"/>
+	</bean>
 
 	<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
 		<constructor-arg ref="kustvakt_config" />
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 e54f354..6251147 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
@@ -7,6 +7,7 @@
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.apache.http.entity.ContentType;
+import org.apache.oltu.oauth2.common.error.OAuthError;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -27,6 +28,10 @@
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
+/**
+ * @author margaretha
+ *
+ */
 public class OAuth2ClientControllerTest extends SpringJerseyTest {
 
     @Autowired
@@ -40,7 +45,7 @@
         json.setName("OAuth2ClientTest");
         json.setType(OAuth2ClientType.CONFIDENTIAL);
         json.setUrl("http://example.client.com");
-        json.setRedirectURI("http://example.client.com/redirect");
+        json.setRedirectURI("https://example.client.com/redirect");
 
         return resource().path("oauth2").path("client").path("register")
                 .header(Attributes.AUTHORIZATION,
@@ -79,7 +84,7 @@
         json.setName("OAuth2PublicClient");
         json.setType(OAuth2ClientType.PUBLIC);
         json.setUrl("http://public.client.com");
-        json.setRedirectURI("http://public.client.com/redirect");
+        json.setRedirectURI("https://public.client.com/redirect");
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("register")
@@ -155,13 +160,14 @@
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).delete(ClientResponse.class);
 
-        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-
         String entity = response.getEntity(String.class);
+//        System.out.println(entity);
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(StatusCodes.AUTHENTICATION_FAILED,
-                node.at("/errors/0/0").asInt());
-        assertEquals("Client credentials are incorrect.",
-                node.at("/errors/0/1").asText());
+        assertEquals(OAuthError.TokenResponse.INVALID_CLIENT,
+                node.at("/error").asText());
+        assertEquals("Invalid client credentials.",
+                node.at("/error_description").asText());
     }
 }
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
new file mode 100644
index 0000000..f52c883
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -0,0 +1,53 @@
+package de.ids_mannheim.korap.web.controller;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.http.entity.ContentType;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.google.common.net.HttpHeaders;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+/**
+ * @author margaretha
+ *
+ */
+public class OAuth2ControllerTest extends SpringJerseyTest {
+
+    @Autowired
+    private HttpAuthorizationHandler handler;
+    private String username = "OAuth2ControllerTest";
+
+    @Test
+    public void testRequestTokenUnsupportedGrant ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+//        form.add("grant_type", "blahblah");
+        form.add("grant_type", GrantType.REFRESH_TOKEN.name());
+        
+        ClientResponse response = resource().path("oauth2").path("token")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        System.out.println(response.getStatus());
+        System.out.println(response.getEntity(String.class));
+    }
+
+}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
index 0653e69..af9f4ff 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerAdminTest.java
@@ -26,6 +26,10 @@
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.input.UserGroupJson;
 
+/**
+ * @author margaretha
+ *
+ */
 public class UserGroupControllerAdminTest extends SpringJerseyTest {
     @Autowired
     private HttpAuthorizationHandler handler;
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
index 373d194..5da4c40 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
@@ -26,6 +26,10 @@
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.input.UserGroupJson;
 
+/**
+ * @author margaretha
+ *
+ */
 public class UserGroupControllerTest extends SpringJerseyTest {
 
     @Autowired
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java
index 2b98f0d..28ed689 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerAdminTest.java
@@ -24,6 +24,10 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
+/**
+ * @author margaretha
+ *
+ */
 public class VirtualCorpusControllerAdminTest extends SpringJerseyTest {
 
     @Autowired
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
index b01a164..71b1162 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -37,6 +37,10 @@
 import de.ids_mannheim.korap.service.VirtualCorpusServiceTest;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
+/**
+ * @author margaretha
+ *
+ */
 public class VirtualCorpusControllerTest extends SpringJerseyTest {
 
     @Autowired
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index 562c3bb..1757481 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -169,6 +169,9 @@
 	<bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
 		<constructor-arg value="http,https"/>
 	</bean>
+	<bean id="httpsValidator" class="org.apache.commons.validator.routines.UrlValidator">
+		<constructor-arg value="https"/>
+	</bean>
 
 	<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
 		<constructor-arg ref="kustvakt_config" />