Implemented confidential client deregistration task and added tests.

Change-Id: I78118166d0660b0816f6da496cead7d46b60b756
diff --git a/core/Changes b/core/Changes
index 00b1efe..592fa84 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,3 +1,9 @@
+version 0.60.2
+10/04/2018
+	- rearranged and cleaned up codes (margaretha)
+	- generalized some KustvaktException methods (margaretha)
+	- added status codes (margaretha)
+	
 version 0.60.1
 14/03/2018
 	- removed AdminHandlerIface (margaretha)
diff --git a/core/pom.xml b/core/pom.xml
index 9f94268..cc807bb 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>de.ids_mannheim.korap</groupId>
 	<artifactId>Kustvakt-core</artifactId>
-	<version>0.60.1</version>
+	<version>0.60.2</version>
 
 	<properties>
 		<java.version>1.8</java.version>
diff --git a/full/Changes b/full/Changes
index 16df079..31f8127 100644
--- a/full/Changes
+++ b/full/Changes
@@ -3,7 +3,9 @@
 	- implemented OAuth2 client registration (margaretha)
 	- implemented OAuth2 client authentication (margaretha)
 	- changed virtual corpus search to retrieval (margaretha)
-	- implemented deregister public client task (margaretha)
+	- implemented public client deregistration task (margaretha)
+	- added client registration and deregistration tests (margaretha)
+	- implemented confidential client deregistration task (margaretha)
 	
 version 0.60.1
 28/03/2018
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 75d09fb..c6c732b 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
@@ -50,6 +50,16 @@
 
         String secret = null;
         if (clientJson.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+            // RFC 6749:
+            // The authorization server MUST NOT issue client passwords or other
+            // client credentials to native application (clients installed and 
+            // executed on the device used by the resource owner e.g. desktop  
+            // application, native mobile application) or user-agent-based
+            // application clients for client authentication.  The authorization
+            // server MAY issue a client password or other credentials
+            // for a specific installation of a native application client on a
+            // specific device.
+
             secret = encryption.createToken();
         }
 
@@ -78,7 +88,7 @@
     }
 
 
-    public void deregisterClient (String clientId, String username)
+    public void deregisterPublicClient (String clientId, String username)
             throws KustvaktException {
 
         OAuth2Client client = clientDao.retrieveClientById(clientId);
@@ -88,7 +98,9 @@
         else if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
             throw new KustvaktException(
                     StatusCodes.CLIENT_DEREGISTRATION_FAILED,
-                    "Service is limited to public clients.");
+                    "Service is limited to public clients. To deregister "
+                            + "confidential clients, use service at path: "
+                            + "oauth2/client/deregister/confidential.");
         }
         else if (client.getRegisteredBy().equals(username)) {
             clientDao.deregisterClient(client);
@@ -100,6 +112,12 @@
     }
 
 
+    public void deregisterConfidentialClient (String authorization,
+            String clientId) throws KustvaktException {
+        OAuth2Client client = authenticateClient(authorization, null, clientId);
+        clientDao.deregisterClient(client);
+    }
+
     public TokenContext requestAccessTokenByClientCredentials (
             String authorization, String grantType) throws KustvaktException {
 
@@ -120,19 +138,19 @@
      * 
      * @param authorization
      * @param grantType
-     * @param client_id
+     * @param clientId
      * @return
      * @throws KustvaktException
      */
     public OAuth2Client authenticateClient (String authorization,
-            GrantType grantType, String client_id) throws KustvaktException {
+            GrantType grantType, String clientId) throws KustvaktException {
 
-        OAuth2Client client = clientDao.retrieveClientById(client_id);
+        OAuth2Client client = clientDao.retrieveClientById(clientId);
 
         if (authorization == null || authorization.isEmpty()) {
             if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)
                     || grantType.equals(GrantType.CLIENT_CREDENTIALS)) {
-                new KustvaktException(StatusCodes.CLIENT_AUTHENTICATION_FAILED,
+                throw new KustvaktException(StatusCodes.AUTHENTICATION_FAILED,
                         "Authorization header is not found.");
             }
             // OAuth2 does not require client authentication
@@ -142,9 +160,11 @@
                     .parseAuthorizationHeaderValue(authorization);
             if (authData.getAuthenticationScheme()
                     .equals(AuthenticationScheme.BASIC)) {
-                if (!client.getSecret().equals(authData.getPassword())) {
-                    new KustvaktException(
-                            StatusCodes.CLIENT_AUTHENTICATION_FAILED,
+                authorizationHandler.parseBasicToken(authData);
+                if (!client.getId().equals(clientId)
+                        || !client.getSecret().equals(authData.getPassword())) {
+                    throw new KustvaktException(
+                            StatusCodes.AUTHENTICATION_FAILED,
                             "Client credentials are incorrect.");
                 }
             }
@@ -158,4 +178,5 @@
         }
         return client;
     }
+
 }
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 a770e0f..eba6325 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
@@ -3,6 +3,7 @@
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
@@ -78,13 +79,13 @@
     @Path("deregister")
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
-    public Response deregisterClient (
+    public Response deregisterPublicClient (
             @Context SecurityContext securityContext,
             @FormParam("client_id") String clientId) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
-            clientService.deregisterClient(clientId,
+            clientService.deregisterPublicClient(clientId,
                     context.getUsername());
             return Response.ok().build();
         }
@@ -92,19 +93,21 @@
             throw responseHandler.throwit(e);
         }
     }
-    
 
-//    @POST
-//    @Path("deregister")
-//    public OAuth2ClientDto deregisterClient (
-//            @Context SecurityContext securityContext) {
-//        TokenContext context =
-//                (TokenContext) securityContext.getUserPrincipal();
-//        try {
-//            return clientService.deregisterClient();
-//        }
-//        catch (KustvaktException e) {
-//            throw responseHandler.throwit(e);
-//        }
-//    }
+
+    @DELETE
+    @Path("deregister/confidential")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response deregisterConfidentialClient (
+            @Context SecurityContext securityContext,
+            @HeaderParam("Authorization") String authorization,
+            @FormParam("client_id") String clientId) {
+        try {
+            clientService.deregisterConfidentialClient(authorization, clientId);
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
index 47e5968..3a91014 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
@@ -43,17 +43,14 @@
 
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.AuthCodeInfo;
-import de.ids_mannheim.korap.config.BeansFactory;
 import de.ids_mannheim.korap.config.ClientInfo;
 import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import de.ids_mannheim.korap.config.Scopes;
 import de.ids_mannheim.korap.constant.AuthenticationMethod;
 import de.ids_mannheim.korap.constant.TokenType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.handlers.OAuth2Handler;
 import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
-import de.ids_mannheim.korap.interfaces.EncryptionIface;
 import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.user.User;
 import de.ids_mannheim.korap.user.UserDetails;
@@ -97,25 +94,25 @@
     }
 
 
-    @POST
-    @Path("unregister")
-    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
-    public Response unregisterClient (@Context SecurityContext context,
-            @HeaderParam("Host") String host,
-            @QueryParam("client_secret") String secret,
-            @QueryParam("client_id") String client_id) {
-        ClientInfo info = new ClientInfo(client_id, secret);
-        info.setUrl(host);
-        TokenContext ctx = (TokenContext) context.getUserPrincipal();
-        try {
-            this.handler.getPersistenceHandler().removeClient(info,
-                    this.controller.getUser(ctx.getUsername()));
-        }
-        catch (KustvaktException e) {
-            throw kustvaktResponseHandler.throwit(e);
-        }
-        return Response.ok().build();
-    }
+//    @POST
+//    @Path("unregister")
+//    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+//    public Response unregisterClient (@Context SecurityContext context,
+//            @HeaderParam("Host") String host,
+//            @QueryParam("client_secret") String secret,
+//            @QueryParam("client_id") String client_id) {
+//        ClientInfo info = new ClientInfo(client_id, secret);
+//        info.setUrl(host);
+//        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+//        try {
+//            this.handler.getPersistenceHandler().removeClient(info,
+//                    this.controller.getUser(ctx.getUsername()));
+//        }
+//        catch (KustvaktException e) {
+//            throw kustvaktResponseHandler.throwit(e);
+//        }
+//        return Response.ok().build();
+//    }
 
 
 //    @POST
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 dc052ec..e54f354 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
@@ -57,14 +57,19 @@
         String entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         JsonNode node = JsonUtils.readTree(entity);
-        assertNotNull(node.at("/client_id").asText());
-        assertNotNull(node.at("/client_secret").asText());
+        String clientId = node.at("/client_id").asText();
+        String clientSecret = node.at("/client_secret").asText();
+        assertNotNull(clientId);
+        assertNotNull(clientSecret);
 
         response = testRegisterConfidentialClient();
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
         node = JsonUtils.readTree(response.getEntity(String.class));
         assertEquals(StatusCodes.CLIENT_REGISTRATION_FAILED,
                 node.at("/errors/0/0").asInt());
+
+        testDeregisterClientIncorrectCredentials(clientId);
+        testDeregisterConfidentialClient(clientId, clientSecret);
     }
 
     @Test
@@ -114,4 +119,49 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
+
+    private void testDeregisterConfidentialClient (String clientId,
+            String clientSecret) throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("deregister").path("confidential")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(clientId,
+                                clientSecret))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).delete(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    private void testDeregisterClientIncorrectCredentials (String clientId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("deregister").path("confidential")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(clientId,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).delete(ClientResponse.class);
+
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        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());
+    }
 }