Updated JWT lib & added MySQL database tables.
Change-Id: I5860e0484ef03f473a863001f44128e8274a14d3
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
index b90889b..be9d330 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/APIAuthentication.java
@@ -30,7 +30,7 @@
//private Cache id_tokens = CacheManager.getInstance().getCache("id_tokens");
- public APIAuthentication (KustvaktConfiguration config) {
+ public APIAuthentication (KustvaktConfiguration config) throws JOSEException {
this.signedToken = new JWTSigner(config.getSharedSecret(),
config.getIssuer(), config.getTokenTTL());
}
@@ -67,7 +67,7 @@
c.setUsername(user.getUsername());
SignedJWT jwt = signedToken.createJWT(user, attr);
try {
- c.setExpirationTime(jwt.getJWTClaimsSet().getExpirationTimeClaim());
+ c.setExpirationTime(jwt.getJWTClaimsSet().getExpirationTime().getTime());
}
catch (ParseException e) {
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
index 0cdd3d3..8258dcf 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
@@ -26,6 +26,7 @@
package de.ids_mannheim.korap.authentication;
+import com.nimbusds.jose.JOSEException;
import com.unboundid.ldap.sdk.*;
import de.ids_mannheim.korap.config.KustvaktConfiguration;
@@ -72,7 +73,7 @@
public static final int LDAP_AUTH_RLOCKED = 3;
public static final int LDAP_AUTH_RNOTREG = 4;
- public LdapAuth3 (KustvaktConfiguration config) {
+ public LdapAuth3 (KustvaktConfiguration config) throws JOSEException {
super(config);
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
index 1cc6150..635b2ca 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/OpenIDconnectAuthentication.java
@@ -1,5 +1,6 @@
package de.ids_mannheim.korap.authentication;
+import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.SignedJWT;
import de.ids_mannheim.korap.config.JWTSigner;
import de.ids_mannheim.korap.config.KustvaktConfiguration;
@@ -40,26 +41,35 @@
@Override
- public TokenContext getTokenContext(String authToken)
+ public TokenContext getTokenContext (String authToken)
throws KustvaktException {
return this.database.getContext(authToken);
}
@Override
- public TokenContext createTokenContext(User user, Map<String, Object> attr)
+ public TokenContext createTokenContext (User user, Map<String, Object> attr)
throws KustvaktException {
String cl_secret = (String) attr.get(Attributes.CLIENT_SECRET);
if (cl_secret == null)
throw new KustvaktException(StatusCodes.REQUEST_INVALID);
attr.remove(cl_secret);
- JWTSigner signer = new JWTSigner(cl_secret.getBytes(),
- config.getIssuer(), config.getTokenTTL());
+ JWTSigner signer;
+ try {
+ signer = new JWTSigner(cl_secret.getBytes(), config.getIssuer(),
+ config.getTokenTTL());
+ }
+ catch (JOSEException e1) {
+ // e1.printStackTrace();
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+ "Failed creating JWT.", e1);
+ }
TokenContext c = new TokenContext();
c.setUsername(user.getUsername());
SignedJWT jwt = signer.createJWT(user, attr);
try {
- c.setExpirationTime(jwt.getJWTClaimsSet().getExpirationTimeClaim());
+ c.setExpirationTime(
+ jwt.getJWTClaimsSet().getExpirationTime().getTime());
}
catch (ParseException e) {
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
@@ -79,13 +89,14 @@
@Override
- public TokenContext refresh (TokenContext context) throws KustvaktException {
+ public TokenContext refresh (TokenContext context)
+ throws KustvaktException {
throw new UnsupportedOperationException("method not supported");
}
@Override
- public TokenType getTokenType() {
+ public TokenType getTokenType () {
return TokenType.ID_TOKEN;
}
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
index 426a873..659ce4a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
@@ -4,6 +4,7 @@
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -17,6 +18,8 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -31,7 +34,8 @@
private final int defaultttl;
- public JWTSigner (final byte[] secret, URL issuer, final int defaulttl) {
+ public JWTSigner (final byte[] secret, URL issuer, final int defaulttl)
+ throws JOSEException {
this.issuer = issuer;
this.signer = new MACSigner(secret);
this.verifier = new MACVerifier(secret);
@@ -40,7 +44,7 @@
public JWTSigner (final byte[] secret, String issuer)
- throws MalformedURLException {
+ throws MalformedURLException, JOSEException {
this(secret, new URL(issuer), 72 * 60 * 60);
}
@@ -50,32 +54,35 @@
}
- public SignedJWT signContent (User user, Map<String, Object> attr, int ttl) {
+ public SignedJWT signContent (User user, Map<String, Object> attr,
+ int ttl) {
String scopes;
- JWTClaimsSet cs = new JWTClaimsSet();
- cs.setIssuerClaim(this.issuer.toString());
+ Builder csBuilder = new JWTClaimsSet.Builder();
+ csBuilder.issuer(this.issuer.toString());
if ((scopes = (String) attr.get(Attributes.SCOPES)) != null) {
Userdata data = new GenericUserData();
data.readQuietly(attr, false);
Scopes claims = Scopes.mapScopes(scopes, data);
- cs.setCustomClaims(claims.toMap());
+ Map<String, Object> map = claims.toMap();
+ for (String key : map.keySet()) {
+ csBuilder.claim(key, map.get(key));
+ }
}
- cs.setSubjectClaim(user.getUsername());
- if (attr.get(Attributes.CLIENT_ID) != null)
- cs.setAudienceClaim(new String[] { (String) attr
- .get(Attributes.CLIENT_ID) });
- cs.setExpirationTimeClaim(TimeUtils.getNow().plusSeconds(ttl)
- .getMillis());
+ csBuilder.subject(user.getUsername());
+ if (attr.get(Attributes.CLIENT_ID) != null) {
+ csBuilder.audience((String) attr.get(Attributes.CLIENT_ID));
+ }
+ csBuilder.expirationTime(TimeUtils.getNow().plusSeconds(ttl).toDate());
SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),
- cs);
+ csBuilder.build());
try {
signedJWT.sign(signer);
}
catch (JOSEException e) {
- return null;
+ e.printStackTrace();
}
return signedJWT;
}
@@ -88,19 +95,16 @@
*/
public SignedJWT signContent (String username, String userclient,
String json, int ttl) {
- JWTClaimsSet cs = new JWTClaimsSet();
- cs.setSubjectClaim(username);
- if (!json.isEmpty())
- cs.setCustomClaim("data", json);
- cs.setExpirationTimeClaim(TimeUtils.getNow().plusSeconds(ttl)
- .getMillis());
- cs.setIssuerClaim(this.issuer.toString());
+ Builder cs = new JWTClaimsSet.Builder();
+ cs.subject(username);
+ if (!json.isEmpty()) cs.claim("data", json);
+ cs.expirationTime(TimeUtils.getNow().plusSeconds(ttl).toDate());
+ cs.issuer(this.issuer.toString());
- if (!userclient.isEmpty())
- cs.setCustomClaim("userip", userclient);
+ if (!userclient.isEmpty()) cs.claim("userip", userclient);
- SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),
- cs);
+ SignedJWT signedJWT =
+ new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), cs.build());
try {
signedJWT.sign(signer);
}
@@ -133,15 +137,16 @@
try {
client = SignedJWT.parse(token);
if (!client.verify(verifier))
- throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+ throw new KustvaktException(StatusCodes.INVALID_ACCESS_TOKEN,
+ "Json Web Signature (JWS) object verification failed.");
- if (!new DateTime(client.getJWTClaimsSet().getExpirationTimeClaim())
+ if (!new DateTime(client.getJWTClaimsSet().getExpirationTime())
.isAfterNow())
throw new KustvaktException(StatusCodes.EXPIRED,
"Authentication token is expired", token);
}
catch (ParseException | JOSEException e) {
- //todo: message or entity, how to treat??!
+ // todo: message or entity, how to treat??!
throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
"Token could not be verified", token);
}
@@ -158,7 +163,7 @@
if (!jwt.verify(verifier))
throw new KustvaktException(StatusCodes.REQUEST_INVALID,
"token invalid", signedContent);
- return (String) jwt.getJWTClaimsSet().getCustomClaim("data");
+ return jwt.getJWTClaimsSet().getStringClaim("data");
}
catch (ParseException | JOSEException e) {
return null;
@@ -166,19 +171,20 @@
}
- public TokenContext getTokenContext (String idtoken) throws ParseException,
- JOSEException, KustvaktException {
+ public TokenContext getTokenContext (String idtoken)
+ throws ParseException, JOSEException, KustvaktException {
SignedJWT signedJWT = verifyToken(idtoken);
TokenContext c = new TokenContext();
- c.setUsername(signedJWT.getJWTClaimsSet().getSubjectClaim());
- if (signedJWT.getJWTClaimsSet().getAudienceClaim() != null)
- c.addContextParameter(Attributes.CLIENT_ID, signedJWT
- .getJWTClaimsSet().getAudienceClaim()[0]);
- c.setExpirationTime(signedJWT.getJWTClaimsSet()
- .getExpirationTimeClaim());
+ c.setUsername(signedJWT.getJWTClaimsSet().getSubject());
+ List<String> audienceList = signedJWT.getJWTClaimsSet().getAudience();
+ if (audienceList != null && !audienceList.isEmpty())
+ c.addContextParameter(Attributes.CLIENT_ID,
+ signedJWT.getJWTClaimsSet().getAudience().get(0));
+ c.setExpirationTime(
+ signedJWT.getJWTClaimsSet().getExpirationTime().getTime());
c.setToken(idtoken);
- c.addParams(signedJWT.getJWTClaimsSet().getCustomClaims());
+ c.addParams(signedJWT.getJWTClaimsSet().getClaims());
return c;
}
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 7bd4d72..ae53294 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
@@ -68,8 +68,8 @@
*/
@POST
@Path("authorize")
- @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public Response requestAuthorizationCode (
@Context HttpServletRequest request,
@@ -182,4 +182,14 @@
throw responseHandler.throwit(e);
}
}
+
+// @POST
+// @Path("revoke")
+// @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+// @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+// public Response revokeAccessToken (@Context HttpServletRequest request,
+// @FormParam("grant_type") String grantType,
+// MultivaluedMap<String, String> form) {
+// return null;
+// }
}
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 a24ffc9..9ef6de4 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
@@ -71,6 +71,7 @@
//todo: only allow oauth2 access_token requests GET methods?
//todo: allow refresh tokens
//@Path("/oauth2")
+@Deprecated
public class OAuthController {
@Autowired
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 81b0d92..fae3fac 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
@@ -2,16 +2,64 @@
-- oauth2 db tables
CREATE TABLE IF NOT EXISTS oauth2_client (
- id VARCHAR(100) UNIQUE PRIMARY KEY,
+ id VARCHAR(100) PRIMARY KEY NOT NULL,
name VARCHAR(200) NOT NULL,
secret VARCHAR(200),
type VARCHAR(200) NOT NULL,
native BOOLEAN DEFAULT FALSE,
url TEXT NOT NULL,
- url_hashcode UNIQUE INTEGER NOT NULL,
+ url_hashcode INTEGER NOT NULL,
redirect_uri TEXT NOT NULL,
registered_by VARCHAR(100) NOT NULL,
- description VARCHAR(250) NOT NULL
+ description VARCHAR(250) NOT NULL,
+ UNIQUE INDEX unique_url(url_hashcode)
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ code VARCHAR(255) NOT NULL,
+ client_id VARCHAR(100) NOT NULL,
+ user_id VARCHAR(100) NOT NULL,
+ redirect_uri TEXT DEFAULT NULL,
+ created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ is_revoked BOOLEAN DEFAULT 0,
+ total_attempts INTEGER DEFAULT 0,
+ FOREIGN KEY (client_id)
+ REFERENCES oauth2_client(id),
+ UNIQUE INDEX authorization_index(code, client_id)
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_access_scope (
+ id VARCHAR(255) PRIMARY KEY NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_authorization_scope (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ authorization_id INTEGER NOT NULL,
+ scope_id VARCHAR(100) NOT NULL,
+ FOREIGN KEY (authorization_id)
+ REFERENCES oauth2_authorization(id),
+ FOREIGN KEY (scope_id)
+ REFERENCES oauth2_access_scope(id),
+ UNIQUE INDEX authorization_scope_index(authorization_id, scope_id)
+);
+
+CREATE TABLE IF NOT EXISTS oauth2_access_token (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ token VARCHAR(255) NOT NULL,
+ authorization_id INTEGER DEFAULT NULL,
+ user_id VARCHAR(100) DEFAULT NULL,
+ created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ is_revoked BOOLEAN DEFAULT 0,
+ total_attempts INTEGER DEFAULT 0,
+ FOREIGN KEY (authorization_id)
+ REFERENCES oauth2_authorization(id)
+);
+
+CREATE TABLE oauth2_access_token_scope (
+ token_id INTEGER NOT NULL,
+ scope_id VARCHAR(100) NOT NULL,
+ CONSTRAINT primary_key PRIMARY KEY (token_id, scope_id)
);
--
diff --git a/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java b/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
index 0fca0b8..82bab07 100644
--- a/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/authentication/APIAuthenticationTest.java
@@ -9,6 +9,8 @@
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import com.nimbusds.jose.JOSEException;
+
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.KustvaktConfiguration;
import de.ids_mannheim.korap.config.SpringJerseyTest;
@@ -24,8 +26,8 @@
private KustvaktConfiguration config;
@Test
- public void testCreateGetTokenContext ()
- throws KustvaktException, IOException, InterruptedException {
+ public void testCreateGetTokenContext () throws KustvaktException,
+ IOException, InterruptedException, JOSEException {
User user = new KorAPUser();
user.setUsername("testUser");
@@ -38,6 +40,7 @@
// get token context
String authToken = context.getToken();
+// System.out.println(authToken);
context = auth.getTokenContext(authToken);
TokenType tokenType = context.getTokenType();
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
index 4ea6807..68f1b55 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserControllerTest.java
@@ -21,6 +21,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.JsonNode;
+import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.SignedJWT;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.core.util.MultivaluedMapImpl;
@@ -185,7 +186,7 @@
// EM: cannot do test with LDAP
@Test
@Ignore
- public void loginJWTExpired() throws InterruptedException, KustvaktException, ParseException {
+ public void loginJWTExpired() throws InterruptedException, KustvaktException, ParseException, JOSEException {
assertTrue(BeansFactory.getKustvaktContext().getConfiguration().getTokenTTL() < 10);
@@ -205,7 +206,7 @@
SignedJWT jwt = sign.verifyToken(token);
while (true) {
- if (TimeUtils.isExpired(jwt.getJWTClaimsSet().getExpirationTimeClaim()))
+ if (TimeUtils.isExpired(jwt.getJWTClaimsSet().getExpirationTime().getTime()))
break;
}
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 42f36ae..9ff8db4 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
@@ -368,7 +368,7 @@
node = testCheckHiddenGroup(groupId);
assertEquals(StatusCodes.GROUP_NOT_FOUND,
node.at("/errors/0/0").asInt());
- assertEquals("Group with id 5 is not found",
+ assertEquals("Group with id "+groupId+" is not found",
node.at("/errors/0/1").asText());
}
@@ -387,13 +387,13 @@
}
@Test
- public void testCreateVCWithExpiredToken ()
+ public void testCreateVCWithInvalidToken ()
throws IOException, KustvaktException {
String json = "{\"name\": \"new vc\",\"type\": \"PRIVATE\","
+ "\"corpusQuery\": \"corpusSigle=GOE\"}";
InputStream is = getClass().getClassLoader()
- .getResourceAsStream("test-user.token");
+ .getResourceAsStream("test-invalid-signature.token");
String authToken;
try (BufferedReader reader =
@@ -413,6 +413,40 @@
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN, node.at("/errors/0/0").asInt());
+ assertEquals("Json Web Signature (JWS) object verification failed.",
+ node.at("/errors/0/1").asText());
+
+ checkWWWAuthenticateHeader(response);
+ }
+
+ @Test
+ public void testCreateVCWithExpiredToken ()
+ throws IOException, KustvaktException {
+ String json = "{\"name\": \"new vc\",\"type\": \"PRIVATE\","
+ + "\"corpusQuery\": \"corpusSigle=GOE\"}";
+
+ InputStream is = getClass().getClassLoader()
+ .getResourceAsStream("test-expired.token");
+
+ String authToken;
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(is));) {
+ authToken = reader.readLine();
+ }
+
+ ClientResponse response = resource().path("vc").path("create")
+ .header(Attributes.AUTHORIZATION,
+ AuthenticationScheme.API.displayName() + " "
+ + authToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .entity(json).post(ClientResponse.class);
+
+ String entity = response.getEntity(String.class);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(entity);
assertEquals(StatusCodes.EXPIRED, node.at("/errors/0/0").asInt());
assertEquals("Authentication token is expired",
node.at("/errors/0/1").asText());
diff --git a/full/src/test/resources/test-expired.token b/full/src/test/resources/test-expired.token
new file mode 100644
index 0000000..5d49da3
--- /dev/null
+++ b/full/src/test/resources/test-expired.token
@@ -0,0 +1 @@
+eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0VXNlciIsImlzcyI6Imh0dHA6XC9cL2tvcmFwLmlkcy1tYW5uaGVpbS5kZSIsImV4cCI6MTUyODMwMzE5OX0.rmEFpdm8-_iyHGb2tEaJbKBoceiZwnyodixWhyLrU9w
\ No newline at end of file
diff --git a/full/src/test/resources/test-user.token b/full/src/test/resources/test-invalid-signature.token
similarity index 100%
rename from full/src/test/resources/test-user.token
rename to full/src/test/resources/test-invalid-signature.token