Implemented deregister public client task.
Change-Id: I6ad6c54ff1c44d5313b8cf23bdddf42230c213cd
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
index 52dc54a..b16c8ff 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
@@ -80,13 +80,13 @@
this.entity = entity;
}
- public KustvaktException (int status, String message, String entity, Exception e) {
+ public KustvaktException (int status, String message, String entity, Throwable e) {
super(message, e);
this.statusCode = status;
this.entity = entity;
}
- public KustvaktException (int status, String message, Exception e) {
+ public KustvaktException (int status, String message, Throwable e) {
super(message, e);
this.statusCode = status;
}
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 605607b..c7a17b3 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
@@ -116,7 +116,7 @@
*/
public static final int CLIENT_REGISTRATION_FAILED = 1800;
- public static final int CLIENT_REMOVAL_FAILURE = 1801;
+ public static final int CLIENT_DEREGISTRATION_FAILED = 1801;
public static final int CLIENT_AUTHENTICATION_FAILED = 1802;
public static final int CLIENT_NOT_FOUND = 1803;
diff --git a/full/Changes b/full/Changes
index 93157b5..16df079 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,7 +1,9 @@
version 0.60.2
-09/04/2018
+10/04/2018
- implemented OAuth2 client registration (margaretha)
+ - implemented OAuth2 client authentication (margaretha)
- changed virtual corpus search to retrieval (margaretha)
+ - implemented deregister public client task (margaretha)
version 0.60.1
28/03/2018
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 94ad319..21b3e2a 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
@@ -27,8 +27,8 @@
private EntityManager entityManager;
public void registerClient (String id, String secret, String name,
- OAuth2ClientType type, String url, String redirectURI,
- String registeredBy) throws KustvaktException {
+ OAuth2ClientType type, String url, int urlHashCode,
+ String redirectURI, String registeredBy) throws KustvaktException {
ParameterChecker.checkStringValue(id, "client id");
ParameterChecker.checkStringValue(name, "client name");
ParameterChecker.checkObjectValue(type, "client type");
@@ -42,9 +42,9 @@
client.setSecret(secret);
client.setType(type);
client.setUrl(url);
+ client.setUrlHashCode(urlHashCode);
client.setRedirectURI(redirectURI);
client.setRegisteredBy(registeredBy);
-
entityManager.persist(client);
}
@@ -69,5 +69,11 @@
}
}
+ public void deregisterClient (OAuth2Client client) {
+ if (!entityManager.contains(client)) {
+ client = entityManager.merge(client);
+ }
+ entityManager.remove(client);
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
index 9d91d93..c6d179f 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
@@ -28,6 +28,8 @@
@Enumerated(EnumType.STRING)
private OAuth2ClientType type;
private String url;
+ @Column(name = "url_hashcode")
+ private int urlHashCode;
@Column(name = "redirect_uri")
private String redirectURI;
private String registeredBy;
diff --git a/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java b/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
index 828d61e..04736b0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
+++ b/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
@@ -249,7 +249,7 @@
jlog.error("removing client '{}' failed", info.getClient_id());
throw new DatabaseException(new KustvaktException(user.getId(),
StatusCodes.ILLEGAL_ARGUMENT, "arguments given not valid",
- info.toJSON()), StatusCodes.CLIENT_REMOVAL_FAILURE,
+ info.toJSON()), StatusCodes.CLIENT_DEREGISTRATION_FAILED,
info.toJSON());
}
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 ec146fd..75d09fb 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
@@ -1,5 +1,7 @@
package de.ids_mannheim.korap.service;
+import java.sql.SQLException;
+
import org.apache.commons.validator.routines.UrlValidator;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,6 +11,7 @@
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.constant.AuthenticationScheme;
import de.ids_mannheim.korap.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.dao.AdminDao;
import de.ids_mannheim.korap.dao.OAuth2ClientDao;
import de.ids_mannheim.korap.dto.OAuth2ClientDto;
import de.ids_mannheim.korap.entity.OAuth2Client;
@@ -24,12 +27,15 @@
@Autowired
private OAuth2ClientDao clientDao;
@Autowired
+ private AdminDao adminDao;
+ @Autowired
private UrlValidator urlValidator;
@Autowired
private EncryptionIface encryption;
@Autowired
private HttpAuthorizationHandler authorizationHandler;
+
public OAuth2ClientDto registerClient (OAuth2ClientJson clientJson,
String registeredBy) throws KustvaktException {
if (!urlValidator.isValid(clientJson.getUrl())) {
@@ -48,19 +54,49 @@
}
String id = encryption.createRandomNumber();
-
- clientDao.registerClient(id, secret, clientJson.getName(),
- clientJson.getType(), clientJson.getUrl(),
- clientJson.getRedirectURI(), registeredBy);
+ try {
+ clientDao.registerClient(id, secret, clientJson.getName(),
+ clientJson.getType(), clientJson.getUrl(),
+ clientJson.getUrl().hashCode(), clientJson.getRedirectURI(),
+ registeredBy);
+ }
+ catch (Exception e) {
+ Throwable cause = e;
+ Throwable lastCause = null;
+ while ((cause = cause.getCause()) != null
+ && !cause.equals(lastCause)) {
+ if (cause instanceof SQLException) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_REGISTRATION_FAILED,
+ cause.getMessage(), cause);
+ }
+ lastCause = cause;
+ }
+ }
return new OAuth2ClientDto(id, secret);
}
- public OAuth2ClientDto deregisterClient (String clientId, String username) {
+ public void deregisterClient (String clientId, String username)
+ throws KustvaktException {
-
- return null;
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+ if (adminDao.isAdmin(username)) {
+ clientDao.deregisterClient(client);
+ }
+ else if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_DEREGISTRATION_FAILED,
+ "Service is limited to public clients.");
+ }
+ else if (client.getRegisteredBy().equals(username)) {
+ clientDao.deregisterClient(client);
+ }
+ else {
+ throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+ "Unauthorized operation for user: " + username, username);
+ }
}
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 d766537..a770e0f 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
@@ -1,11 +1,14 @@
package de.ids_mannheim.korap.web.controller;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
@@ -64,45 +67,44 @@
}
- // /** Deregisters a client via owner authentication.
- // *
- // * EM: who can deregister clients? The user registered the clients or the client itself?
- // *
- // * @param securityContext
- // * @param clientId
- // * @return HTTP Response OK if successful.
- // */
- // @POST
- // @Path("deregister")
- // @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- // @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
- // public OAuth2ClientDto deregisterClient (
- // @Context SecurityContext securityContext,
- // @FormParam("client_id") String clientId) {
- // TokenContext context =
- // (TokenContext) securityContext.getUserPrincipal();
- // try {
- // return clientService.deregisterClient(clientId,
- // context.getUsername());
- // }
- // catch (KustvaktException e) {
- // throw responseHandler.throwit(e);
- // }
- // }
- //
- // @POST
- // @Path("deregister")
- // @ResourceFilters({ OAuth2ClientAuthenticationFilter.class,
- // BlockingFilter.class })
- // public OAuth2ClientDto deregisterClient (
- // @Context SecurityContext securityContext) {
- // TokenContext context =
- // (TokenContext) securityContext.getUserPrincipal();
- // try {
- // return clientService.deregisterClient();
- // }
- // catch (KustvaktException e) {
- // throw responseHandler.throwit(e);
- // }
- // }
+ /** Deregisters a public client via owner authentication.
+ *
+ *
+ * @param securityContext
+ * @param clientId the client id
+ * @return HTTP Response OK if successful.
+ */
+ @DELETE
+ @Path("deregister")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+ public Response deregisterClient (
+ @Context SecurityContext securityContext,
+ @FormParam("client_id") String clientId) {
+ TokenContext context =
+ (TokenContext) securityContext.getUserPrincipal();
+ try {
+ clientService.deregisterClient(clientId,
+ context.getUsername());
+ return Response.ok().build();
+ }
+ catch (KustvaktException e) {
+ 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);
+// }
+// }
}
diff --git a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
index c228c08..f616782 100644
--- a/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
@@ -1,51 +1,51 @@
-- EM: modified from Michael Hanl version
-- oauth2 db tables
-create table if not exists oauth2_client (
+CREATE TABLE IF NOT EXISTS oauth2_client (
id VARCHAR(100) UNIQUE PRIMARY KEY,
name VARCHAR(200) NOT NULL,
secret VARCHAR(200),
type VARCHAR(200) NOT NULL,
- url TEXT UNIQUE NOT NULL,
+ url TEXT NOT NULL,
+ url_hashcode UNIQUE INTEGER NOT NULL,
redirect_uri TEXT NOT NULL,
---is_confidential BOOLEAN DEFAULT FALSE,
registeredBy VARCHAR(100) NOT NULL
);
-
--- status 1 = valid, 0 = revoked, -1 = disabled
-create table if not exists oauth2_access_token (
-id INTEGER PRIMARY KEY AUTO_INCREMENT,
-access_token VARCHAR(300),
-auth_code VARCHAR(250),
-client_id VARCHAR(100),
-user_id INTEGER,
--- make boolean --
-status INTEGER DEFAULT 1,
--- in case of code authorization, should match auth code scopes!
--- use scopes for levelaccess descriptor level[rw],level[r]
-scopes VARCHAR(350),
-expiration TIMESTAMP,
-FOREIGN KEY (user_id)
-REFERENCES korap_users(id)
-ON DELETE CASCADE,
-FOREIGN KEY (client_id)
-REFERENCES oauth2_client(client_id)
-ON DELETE CASCADE
-);
-
-
--- also scopes?
-create table if not exists oauth2_refresh_token (
-id INTEGER PRIMARY KEY AUTO_INCREMENT,
-client_id VARCHAR(100),
-user_id INTEGER,
-expiration TIMESTAMP,
-scopes VARCHAR(350),
-FOREIGN KEY (user_id)
-REFERENCES korap_users(id)
-ON DELETE CASCADE,
-FOREIGN KEY (client_id)
-REFERENCES oauth2_client(client_id)
-ON DELETE CASCADE
-);
\ No newline at end of file
+--
+---- status 1 = valid, 0 = revoked, -1 = disabled
+--create table if not exists oauth2_access_token (
+--id INTEGER PRIMARY KEY AUTO_INCREMENT,
+--access_token VARCHAR(300),
+--auth_code VARCHAR(250),
+--client_id VARCHAR(100),
+--user_id INTEGER,
+---- make boolean --
+--status INTEGER DEFAULT 1,
+---- in case of code authorization, should match auth code scopes!
+---- use scopes for levelaccess descriptor level[rw],level[r]
+--scopes VARCHAR(350),
+--expiration TIMESTAMP,
+--FOREIGN KEY (user_id)
+--REFERENCES korap_users(id)
+--ON DELETE CASCADE,
+--FOREIGN KEY (client_id)
+--REFERENCES oauth2_client(client_id)
+--ON DELETE CASCADE
+--);
+--
+--
+---- also scopes?
+--create table if not exists oauth2_refresh_token (
+--id INTEGER PRIMARY KEY AUTO_INCREMENT,
+--client_id VARCHAR(100),
+--user_id INTEGER,
+--expiration TIMESTAMP,
+--scopes VARCHAR(350),
+--FOREIGN KEY (user_id)
+--REFERENCES korap_users(id)
+--ON DELETE CASCADE,
+--FOREIGN KEY (client_id)
+--REFERENCES oauth2_client(client_id)
+--ON DELETE CASCADE
+--);
\ No newline at end of file
diff --git a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
index fe0391f..4b42442 100644
--- a/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -1,12 +1,16 @@
-- EM: modified from Michael Hanl version
-- oauth2 db tables
-create table IF NOT EXISTS oauth2_client (
- id VARCHAR(100) NOT NULL,
+CREATE TABLE IF NOT EXISTS oauth2_client (
+ id VARCHAR(100) PRIMARY KEY NOT NULL,
name VARCHAR(200) NOT NULL,
- secret VARCHAR(200) NOT NULL,
+ secret VARCHAR(200),
type VARCHAR(200) NOT NULL,
- url TEXT UNIQUE NOT NULL,
+ url TEXT NOT NULL,
+ url_hashcode INTEGER NOT NULL,
redirect_uri TEXT NOT NULL,
registeredBy VARCHAR(100) NOT NULL
);
+
+CREATE UNIQUE INDEX client_id_index on oauth2_client(id);
+CREATE UNIQUE INDEX client_url_index on oauth2_client(url_hashcode);
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 97cce38..dc052ec 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
@@ -2,6 +2,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.ws.rs.core.MultivaluedMap;
import org.apache.http.entity.ContentType;
import org.junit.Test;
@@ -9,14 +12,18 @@
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;
import com.sun.jersey.api.client.ClientResponse.Status;
+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.constant.OAuth2ClientType;
import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.utils.JsonUtils;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
@@ -25,9 +32,9 @@
@Autowired
private HttpAuthorizationHandler handler;
private String username = "OAuth2ClientControllerTest";
-
- @Test
- public void testRegisterClient () throws KustvaktException {
+
+ private ClientResponse testRegisterConfidentialClient ()
+ throws KustvaktException {
OAuth2ClientJson json = new OAuth2ClientJson();
json.setName("OAuth2ClientTest");
@@ -35,6 +42,40 @@
json.setUrl("http://example.client.com");
json.setRedirectURI("http://example.client.com/redirect");
+ return resource().path("oauth2").path("client").path("register")
+ .header(Attributes.AUTHORIZATION,
+ handler.createBasicAuthorizationHeaderValue(username,
+ "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .entity(json).post(ClientResponse.class);
+ }
+
+ @Test
+ public void testRegisterClientNonUniqueURL () throws KustvaktException {
+ ClientResponse response = testRegisterConfidentialClient();
+ 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());
+
+ 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());
+ }
+
+ @Test
+ public void testRegisterPublicClient () throws UniformInterfaceException,
+ ClientHandlerException, KustvaktException {
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("OAuth2PublicClient");
+ json.setType(OAuth2ClientType.PUBLIC);
+ json.setUrl("http://public.client.com");
+ json.setRedirectURI("http://public.client.com/redirect");
+
ClientResponse response = resource().path("oauth2").path("client")
.path("register")
.header(Attributes.AUTHORIZATION,
@@ -46,9 +87,31 @@
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();
+ assertNotNull(clientId);
+ assertTrue(node.at("/client_secret").isMissingNode());
+
+ testDeregisterPublicClient(clientId);
+ }
+
+ private void testDeregisterPublicClient (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")
+ .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).delete(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
}
}