Implemented OAuth2 client registration.
Change-Id: I0c51bb1ee031e2a6ff9d0181de7dcb1da53d1d07
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/ClientType.java b/full/src/main/java/de/ids_mannheim/korap/constant/ClientType.java
new file mode 100644
index 0000000..eb78b40
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/constant/ClientType.java
@@ -0,0 +1,19 @@
+package de.ids_mannheim.korap.constant;
+
+public enum ClientType {
+
+ // EM: from RFC 6749
+
+// Clients capable of maintaining the confidentiality of their
+// credentials (e.g., client implemented on a secure server with
+// restricted access to the client credentials), or capable of secure
+// client authentication using other means.
+ CONFIDENTIAL,
+
+// Clients incapable of maintaining the confidentiality of their
+// 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.
+ 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
new file mode 100644
index 0000000..09d801a
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/OAuth2ClientDao.java
@@ -0,0 +1,43 @@
+package de.ids_mannheim.korap.dao;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.criteria.CriteriaBuilder;
+
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.constant.ClientType;
+import de.ids_mannheim.korap.entity.OAuth2Client;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.ParameterChecker;
+
+@Transactional
+@Repository
+public class OAuth2ClientDao {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ public void registerClient (String id, String secret, String name,
+ ClientType type, String url, String redirectURI)
+ throws KustvaktException {
+ ParameterChecker.checkStringValue(id, "client id");
+ ParameterChecker.checkStringValue(name, "client name");
+ ParameterChecker.checkObjectValue(type, "client type");
+ ParameterChecker.checkStringValue(url, "client url");
+ ParameterChecker.checkStringValue(redirectURI, "client redirect uri");
+
+ OAuth2Client client = new OAuth2Client();
+ client.setId(id);
+ client.setName(name);
+ client.setSecret(secret);
+ client.setType(type);
+ client.setUrl(url);
+ client.setRedirectURI(redirectURI);
+
+ entityManager.persist(client);
+ }
+
+
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 1d48b02..74d5b1a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -1,6 +1,5 @@
package de.ids_mannheim.korap.dao;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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
new file mode 100644
index 0000000..c299a22
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/OAuth2Client.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import de.ids_mannheim.korap.constant.ClientType;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author margaretha
+ *
+ */
+@Getter
+@Setter
+@Entity
+@Table(name = "oauth2_client")
+public class OAuth2Client {
+
+ @Id
+ private String id;
+ private String secret;
+ @Enumerated(EnumType.STRING)
+ private ClientType type;
+ @Column(name = "redirect_uri")
+ private String redirectURI;
+ private String url;
+ private String name;
+
+ @Override
+ public String toString () {
+ return "id=" + id + ", secret=" + secret + ", type=" + type + ", name="
+ + name + ", url=" + url + ", redirectURI=" + redirectURI;
+ }
+}
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
new file mode 100644
index 0000000..dbfdc2e
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/OAuth2ClientService.java
@@ -0,0 +1,48 @@
+package de.ids_mannheim.korap.service;
+
+import org.apache.commons.validator.routines.UrlValidator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.ids_mannheim.korap.constant.ClientType;
+import de.ids_mannheim.korap.dao.OAuth2ClientDao;
+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.web.input.OAuth2ClientJson;
+
+@Service
+public class OAuth2ClientService {
+
+ @Autowired
+ private OAuth2ClientDao clientDao;
+ @Autowired
+ private UrlValidator urlValidator;
+ @Autowired
+ private EncryptionIface encryption;
+
+
+ public void registerClient (OAuth2ClientJson clientJson)
+ throws KustvaktException {
+ if (!urlValidator.isValid(clientJson.getUrl())) {
+ 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.",
+ clientJson.getRedirectURI());
+ }
+
+ String secret = null;
+ if (clientJson.getType().equals(ClientType.CONFIDENTIAL)) {
+ secret = encryption.createToken();
+ }
+
+ String id = encryption.createRandomNumber();
+
+ clientDao.registerClient(id, secret, clientJson.getName(),
+ clientJson.getType(), clientJson.getUrl(),
+ clientJson.getRedirectURI());
+ }
+}
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
new file mode 100644
index 0000000..b0b88e4
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -0,0 +1,59 @@
+package de.ids_mannheim.korap.web.controller;
+
+import javax.ws.rs.Consumes;
+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;
+import org.springframework.stereotype.Controller;
+
+import com.sun.jersey.spi.container.ResourceFilters;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.service.OAuth2ClientService;
+import de.ids_mannheim.korap.web.FullResponseHandler;
+import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
+import de.ids_mannheim.korap.web.filter.BlockingFilter;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+
+@Controller
+@Path("/client")
+//@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+//@ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+public class OAuthClientController {
+
+ @Autowired
+ private OAuth2ClientService clientService;
+ @Autowired
+ private FullResponseHandler responseHandler;
+
+ /** EM: who can register a client?
+ *
+ * The authorization server SHOULD document the size of any identifier
+ * it issues.
+ *
+ * @param context
+ * @param clientJson
+ * @return
+ */
+ @POST
+ @Path("register")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response registerClient (@Context SecurityContext context,
+ OAuth2ClientJson clientJson) {
+ try {
+ clientService.registerClient(clientJson);
+ }
+ catch (KustvaktException e) {
+ responseHandler.throwit(e);
+ }
+ return Response.ok().build();
+ }
+
+}
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 b43893b..fdc2046 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
@@ -54,7 +54,6 @@
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.server.KustvaktServer;
import de.ids_mannheim.korap.user.TokenContext;
import de.ids_mannheim.korap.user.User;
import de.ids_mannheim.korap.user.UserDetails;
@@ -74,7 +73,7 @@
*/
//todo: only allow oauth2 access_token requests GET methods?
//todo: allow refresh tokens
-@Path(KustvaktServer.API_VERSION + "/oauth2")
+@Path("/oauth2")
public class OAuthController {
@Autowired
@@ -83,17 +82,18 @@
private OAuth2Handler handler;
@Autowired
private AuthenticationManagerIface controller;
- private EncryptionIface crypto;
+
+// private EncryptionIface crypto;
+ @Autowired
private KustvaktConfiguration config;
public OAuthController () {
- this.handler = new OAuth2Handler(BeansFactory.getKustvaktContext()
- .getPersistenceClient());
+// this.handler = new OAuth2Handler(BeansFactory.getKustvaktContext()
+// .getPersistenceClient());
// this.controller = BeansFactory.getKustvaktContext()
// .getAuthenticationManager();
- this.crypto = BeansFactory.getKustvaktContext().getEncryption();
- this.config = BeansFactory.getKustvaktContext().getConfiguration();
+// this.crypto = BeansFactory.getKustvaktContext().getEncryption();
}
@@ -118,31 +118,31 @@
}
- @POST
- @Path("register")
- @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
- public Response registerClient (@Context SecurityContext context,
- @HeaderParam("Host") String host,
- @QueryParam("redirect_url") String rurl) {
- ClientInfo info = new ClientInfo(crypto.createRandomNumber(),
- crypto.createToken());
- info.setUrl(host);
- if (rurl == null)
- throw kustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
- "Missing parameter!", "redirect_url");
- info.setRedirect_uri(rurl);
- TokenContext ctx = (TokenContext) context.getUserPrincipal();
- String json = "";
- try {
- User user = this.controller.getUser(ctx.getUsername());
- this.handler.getPersistenceHandler().registerClient(info, user);
- json = info.toJSON();
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- return Response.ok(json).build();
- }
+// @POST
+// @Path("register")
+// @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+// public Response registerClient (@Context SecurityContext context,
+// @HeaderParam("Host") String host,
+// @QueryParam("redirect_url") String rurl) {
+// ClientInfo info = new ClientInfo(crypto.createRandomNumber(),
+// crypto.createToken());
+// info.setUrl(host);
+// if (rurl == null)
+// throw kustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
+// "Missing parameter!", "redirect_url");
+// info.setRedirect_uri(rurl);
+// TokenContext ctx = (TokenContext) context.getUserPrincipal();
+// String json = "";
+// try {
+// User user = this.controller.getUser(ctx.getUsername());
+// this.handler.getPersistenceHandler().registerClient(info, user);
+// json = info.toJSON();
+// }
+// catch (KustvaktException e) {
+// throw kustvaktResponseHandler.throwit(e);
+// }
+// return Response.ok(json).build();
+// }
@GET
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index ecc00bb..e96ec0a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -215,6 +215,8 @@
}
/** Invites group members to join a user-group specified in the JSON object.
+ * Only user-group admins and system admins are allowed.
+ *
* @param securityContext
* @param group UserGroupJson containing groupId and usernames to be invited
* as members
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
new file mode 100644
index 0000000..28a2a6a
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/OAuth2ClientJson.java
@@ -0,0 +1,16 @@
+package de.ids_mannheim.korap.web.input;
+
+import de.ids_mannheim.korap.constant.ClientType;
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class OAuth2ClientJson {
+
+ // all required for registration
+ private String name;
+ private ClientType type;
+ private String url;
+ private String redirectURI;
+}
diff --git a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
index 1fe0f26..3b8022b 100644
--- a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
@@ -61,7 +61,7 @@
created_by varchar(100) NOT NULL,
description varchar(255) DEFAULT NULL,
status varchar(100) DEFAULT NULL,
- corpus_query varchar(2000) NOT NULL,
+ corpus_query TEXT NOT NULL,
definition varchar(255) DEFAULT NULL,
INDEX owner_index (created_by),
INDEX type_index (type)
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
new file mode 100644
index 0000000..e36b0f9
--- /dev/null
+++ b/full/src/main/resources/db/new-mysql/V1.4__oauth2_tables.sql
@@ -0,0 +1,50 @@
+-- EM: modified from Michael Hanl version
+
+-- oauth2 db tables
+create table if not exists oauth2_client (
+ id VARCHAR(100) UNIQUE PRIMARY KEY,
+ secret VARCHAR(200),
+ type VARCHAR(200) NOT NULL,
+ redirect_uri TEXT NOT NULL,
+--is_confidential BOOLEAN DEFAULT FALSE,
+ url TEXT UNIQUE NOT NULL,
+ name VARCHAR(200) 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
diff --git a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
index d1bbcb7..d2702f7 100644
--- a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
@@ -71,7 +71,7 @@
created_by varchar(100) NOT NULL,
description varchar(255) DEFAULT NULL,
status varchar(100) DEFAULT NULL,
- corpus_query varchar(2000) NOT NULL,
+ corpus_query TEXT NOT NULL,
definition varchar(255) DEFAULT NULL
);
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
new file mode 100644
index 0000000..ecf7a24
--- /dev/null
+++ b/full/src/main/resources/db/new-sqlite/V1.4__oauth2_tables.sql
@@ -0,0 +1,11 @@
+-- EM: modified from Michael Hanl version
+
+-- oauth2 db tables
+create table IF NOT EXISTS oauth2_client (
+ id VARCHAR(100) NOT NULL,
+ secret VARCHAR(200) NOT NULL,
+ type VARCHAR(200) NOT NULL,
+ redirect_uri TEXT NOT NULL,
+ url TEXT UNIQUE NOT NULL,
+ name VARCHAR(200) NOT NULL
+);
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index e901b2a..2844538 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -169,7 +169,12 @@
<constructor-arg value="${krill.indexDir}"/>
</bean>
-
+ <!-- URLValidator -->
+ <bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https"/>
+ </bean>
+
+
<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
<constructor-arg ref="kustvakt_config" />
</bean>
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java b/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
index 930e454..9173038 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
@@ -20,7 +20,7 @@
public abstract class SpringJerseyTest extends JerseyTest {
private static String[] classPackages =
- new String[] { "de.ids_mannheim.korap.web.service.full",
+ new String[] { "de.ids_mannheim.korap.web.controller",
"de.ids_mannheim.korap.web.filter",
"de.ids_mannheim.korap.web.utils" };
diff --git a/full/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java b/full/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java
index 11f326f..40d4abd 100644
--- a/full/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/dao/UserGroupDaoTest.java
@@ -2,15 +2,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import javax.validation.constraints.AssertTrue;
-
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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
new file mode 100644
index 0000000..26a11bc
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -0,0 +1,34 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.http.entity.ContentType;
+import org.junit.Test;
+
+import com.google.common.net.HttpHeaders;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.ClientResponse.Status;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.ClientType;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
+
+public class OAuth2ClientControllerTest extends SpringJerseyTest {
+
+ @Test
+ public void testRegisterClient () throws KustvaktException {
+
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("OAuth2ClientControllerTest");
+ json.setType(ClientType.CONFIDENTIAL);
+ json.setUrl("http://example.client.com");
+ json.setRedirectURI("http://example.client.com/redirect");
+
+ ClientResponse response = resource().path("client").path("register")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .entity(json).post(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+}
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index 83291a9..ffd8c9e 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -165,6 +165,10 @@
<constructor-arg value="${krill.indexDir}" />
</bean>
+ <!-- URLValidator -->
+ <bean id="urlValidator" class="org.apache.commons.validator.routines.UrlValidator">
+ <constructor-arg value="http,https"/>
+ </bean>
<bean id="kustvakt_rewrite" class="de.ids_mannheim.korap.rewrite.FullRewriteHandler">
<constructor-arg ref="kustvakt_config" />