Implemented signed OpenID token with default algorithm RSA256.
Change-Id: I5bd5bed5c556e550244e06299bc7e54f53226401
diff --git a/core/Changes b/core/Changes
index 3c77c97..687adb1 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,3 +1,8 @@
+version 0.60.4
+25/06/2018
+ - added the redirect URI property in KustvaktException (margaretha)
+ - added openid related status codes (margaretha)
+
version 0.60.3
30/05/2018
- added parameter checker for collection (margaretha)
diff --git a/core/pom.xml b/core/pom.xml
index 91bb414..de109cc 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.3</version>
+ <version>0.60.4</version>
<properties>
<java.version>1.8</java.version>
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
index 5c98520..1d1c1ec 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
@@ -4,7 +4,7 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
+import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -40,7 +40,6 @@
private List<String> queryLanguages;
private String serverHost;
- private URL issuer;
private int maxhits;
private int returnhits;
@@ -83,7 +82,7 @@
// deprec?!
private final BACKENDS DEFAULT_ENGINE = BACKENDS.LUCENE;
- public KustvaktConfiguration (Properties properties) throws IOException {
+ public KustvaktConfiguration (Properties properties) throws IOException, URISyntaxException {
load(properties);
}
@@ -93,10 +92,11 @@
* @param properties
* @return
* @throws IOException
+ * @throws URISyntaxException
* @throws KustvaktException
*/
protected void load (Properties properties)
- throws IOException {
+ throws IOException, URISyntaxException {
baseURL = properties.getProperty("kustvakt.base.url", "/api/*");
maxhits = new Integer(properties.getProperty("maxhits", "50000"));
returnhits = new Integer(properties.getProperty("returnhits", "50000"));
@@ -110,11 +110,6 @@
queryLanguages = new ArrayList<>();
for (String querylang : qls)
queryLanguages.add(querylang.trim().toUpperCase());
- String is = properties.getProperty("security.jwt.issuer", "");
-
- if (!is.startsWith("http"))
- is = "http://" + is;
- issuer = new URL(is);
default_const = properties
.getProperty("default.layer.constituent", "mate");
@@ -173,8 +168,9 @@
* properties can be overloaded after spring init
*
* @param stream
+ * @throws URISyntaxException
*/
- public void setPropertiesAsStream (InputStream stream) {
+ public void setPropertiesAsStream (InputStream stream) throws URISyntaxException {
try {
Properties p = new Properties();
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 ed1a11f..655a98a 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
@@ -131,6 +131,9 @@
public static final int UNSUPPORTED_GRANT_TYPE = 1810;
public static final int UNSUPPORTED_AUTHENTICATION_METHOD = 1811;
+ public static final int ID_TOKEN_CLAIM_ERROR = 1812;
+ public static final int ID_TOKEN_SIGNING_FAILED = 1813;
+
/**
* 1900 User account and logins
diff --git a/full/Changes b/full/Changes
index 8a401af..b46a483 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,5 @@
version 0.60.4
-19/06/2018
+25/06/2018
- implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
- enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
- implemented response handler for OpenID authentication errors in authorization requests (margaretha)
@@ -7,6 +7,7 @@
- implemented OAuth2 authorization error response via redirect URI instead of JSON (margaretha)
- added state to OAuth2 authorization error response (margaretha)
- implemented OpenID token service for authorization code flow (margaretha)
+ - implemented signed OpenID token with default algorithm RSA256 (margaretha)
version 0.60.3
06/06/2018
diff --git a/full/pom.xml b/full/pom.xml
index 3bdf5d4..9ae96d9 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -32,14 +32,15 @@
<testResources>
<testResource>
<directory>src/test/resources</directory>
- <filtering>true</filtering>
+ <!-- <filtering>true</filtering>
<includes>
+ <include>**/*.key</include>
<include>**/*.token</include>
<include>**/*.xml</include>
<include>**/*.conf</include>
<include>**/*.info</include>
<include>**/*.properties</include>
- </includes>
+ </includes> -->
</testResource>
<testResource>
<directory>src/main/resources</directory>
@@ -156,7 +157,7 @@
<dependency>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-core</artifactId>
- <version>0.60.3</version>
+ <version>0.60.4</version>
</dependency>
<!-- LDAP -->
<dependency>
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 be9d330..e984c11 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
@@ -6,8 +6,8 @@
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.SignedJWT;
+import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.config.JWTSigner;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
import de.ids_mannheim.korap.constant.TokenType;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -30,7 +30,7 @@
//private Cache id_tokens = CacheManager.getInstance().getCache("id_tokens");
- public APIAuthentication (KustvaktConfiguration config) throws JOSEException {
+ public APIAuthentication (FullConfiguration config) throws JOSEException {
this.signedToken = new JWTSigner(config.getSharedSecret(),
config.getIssuer(), config.getTokenTTL());
}
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 8258dcf..b76d113 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,15 +26,23 @@
package de.ids_mannheim.korap.authentication;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Properties;
+
import com.nimbusds.jose.JOSEException;
-import com.unboundid.ldap.sdk.*;
+import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.LDAPSearchException;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.constant.TokenType;
-import java.io.*;
-import java.util.*;
-
/**
* LDAP Login Tests
@@ -73,7 +81,7 @@
public static final int LDAP_AUTH_RLOCKED = 3;
public static final int LDAP_AUTH_RNOTREG = 4;
- public LdapAuth3 (KustvaktConfiguration config) throws JOSEException {
+ public LdapAuth3 (FullConfiguration 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 635b2ca..549b4c7 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,9 +1,14 @@
package de.ids_mannheim.korap.authentication;
+import java.text.ParseException;
+import java.util.Map;
+
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.SignedJWT;
+
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.config.JWTSigner;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
import de.ids_mannheim.korap.constant.TokenType;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
@@ -11,17 +16,9 @@
import de.ids_mannheim.korap.interfaces.AuthenticationIface;
import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.utils.NamingUtils;
-import de.ids_mannheim.korap.utils.StringUtils;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
-
-import java.text.ParseException;
-import java.util.Map;
/**
* @author hanl
@@ -30,10 +27,10 @@
public class OpenIDconnectAuthentication implements AuthenticationIface {
private OAuthDb database;
- private KustvaktConfiguration config;
+ private FullConfiguration config;
- public OpenIDconnectAuthentication (KustvaktConfiguration config,
+ public OpenIDconnectAuthentication (FullConfiguration config,
PersistenceClient client) {
this.database = new OAuthDb(client);
this.config = config;
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index d237494..6c215e0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -1,6 +1,19 @@
package de.ids_mannheim.korap.config;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -9,6 +22,8 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import org.apache.commons.codec.binary.Base64;
+
import de.ids_mannheim.korap.constant.AuthenticationMethod;
import de.ids_mannheim.korap.interfaces.EncryptionIface;
@@ -58,12 +73,45 @@
private Set<String> clientCredentialsScopes;
private int maxAuthenticationAttempts;
- public FullConfiguration (Properties properties) throws IOException {
+ private URL issuer;
+ private URI issuerURI;
+ private RSAPrivateKey rsaPrivateKey;
+
+ public FullConfiguration (Properties properties)
+ throws IOException, URISyntaxException, InvalidKeySpecException,
+ NoSuchAlgorithmException {
super(properties);
+ setRSAPrivateKey();
+ }
+
+ public void setRSAPrivateKey () throws IOException, InvalidKeySpecException,
+ NoSuchAlgorithmException {
+ InputStream is = getClass().getClassLoader()
+ .getResourceAsStream("kustvakt-private.key");
+
+ if (is == null){
+ this.rsaPrivateKey = null;
+ return;
+ }
+
+ String privateKey = null;
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(is));) {
+ privateKey = reader.readLine();
+ }
+ byte[] decodedKey = Base64.decodeBase64(privateKey);
+ KeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
+ this.rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
+ .generatePrivate(keySpec);
+ }
+
+ public RSAPrivateKey getRSAPrivateKey () {
+ return this.rsaPrivateKey;
}
@Override
- public void load (Properties properties) throws IOException {
+ public void load (Properties properties)
+ throws IOException, URISyntaxException {
super.load(properties);
// EM: regex used for storing vc
@@ -79,6 +127,18 @@
properties.getProperty("security.encryption", "BCRYPT")));
setOAuth2Configuration(properties);
+ setOpenIdConfiguration(properties);
+ }
+
+ private void setOpenIdConfiguration (Properties properties)
+ throws URISyntaxException, MalformedURLException {
+ String issuerStr = properties.getProperty("security.jwt.issuer", "");
+
+ if (!issuerStr.startsWith("http")) {
+ issuerStr = "http://" + issuerStr;
+ }
+ setIssuer(new URL(issuerStr));
+ setIssuerURI(issuer.toURI());
}
private void setOAuth2Configuration (Properties properties) {
@@ -375,4 +435,20 @@
this.clientCredentialsScopes = clientCredentialsScopes;
}
+ public URL getIssuer () {
+ return issuer;
+ }
+
+ public void setIssuer (URL issuer) {
+ this.issuer = issuer;
+ }
+
+ public URI getIssuerURI () {
+ return issuerURI;
+ }
+
+ public void setIssuerURI (URI issuerURI) {
+ this.issuerURI = issuerURI;
+ }
+
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
index 8ce9b16..59f7e67 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
@@ -1,25 +1,40 @@
package de.ids_mannheim.korap.oauth2.openid.service;
import java.net.URI;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Date;
import java.util.Set;
import org.springframework.stereotype.Service;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSSigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.AccessTokenResponse;
import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
import com.nimbusds.oauth2.sdk.AuthorizationGrant;
import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.TokenRequest;
import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;
import com.nimbusds.oauth2.sdk.token.Tokens;
import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -28,7 +43,23 @@
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
+import de.ids_mannheim.korap.utils.TimeUtils;
+/**
+ * ID Tokens MUST be signed using JWS and optionally both signed and
+ * then encrypted using JWS [JWS] and JWE [JWE] respectively.
+ *
+ * ID Tokens MUST NOT use none as the alg value unless the Response
+ * Type used returns no ID Token from the Authorization Endpoint (such
+ * as when using the Authorization Code Flow) and the Client
+ * explicitly requested the use of none at Registration time.
+ *
+ * ID Tokens SHOULD NOT use the JWS or JWE x5u, x5c, jku, or jwk
+ * Header Parameter fields.
+ *
+ * @author margaretha
+ *
+ */
@Service
public class OpenIdTokenService extends OAuth2TokenService {
@@ -72,7 +103,7 @@
private AccessTokenResponse createsAccessTokenResponse (
- Authorization authorization) {
+ Authorization authorization) throws KustvaktException {
Set<AccessScope> scopes = authorization.getScopes();
String[] scopeArray = scopes.stream().map(scope -> scope.toString())
.toArray(String[]::new);
@@ -82,9 +113,12 @@
RefreshToken refreshToken = new RefreshToken();
if (scope.contains("openid")) {
- // id token should be encrypted according to keys and
- // algorithms the client specified during registration
- String idToken = "thisIsIdToken";
+ JWTClaimsSet claims = createIdTokenClaims(
+ authorization.getClientId(), authorization.getUserId());
+ SignedJWT idToken = signIdToken(claims,
+ // default
+ new JWSHeader(JWSAlgorithm.RS256),
+ config.getRSAPrivateKey());
OIDCTokens tokens =
new OIDCTokens(idToken, accessToken, refreshToken);
return new OIDCTokenResponse(tokens);
@@ -122,4 +156,71 @@
}
return new String[] { clientId, clientSecret };
}
+
+ private JWTClaimsSet createIdTokenClaims (String client_id, String username)
+ throws KustvaktException {
+ // A locally unique and never reassigned identifier within the
+ // Issuer for the End-User
+ Subject sub = new Subject(username);
+ Issuer iss = new Issuer(config.getIssuerURI());
+ Audience aud = new Audience(client_id);
+ ArrayList<Audience> audList = new ArrayList<Audience>(1);
+ audList.add(aud);
+ Date iat = TimeUtils.getNow().toDate();
+ Date exp =
+ TimeUtils.getNow().plusSeconds(config.getTokenTTL()).toDate();
+
+ IDTokenClaimsSet claims =
+ new IDTokenClaimsSet(iss, sub, audList, exp, iat);
+ try {
+ return claims.toJWTClaimsSet();
+ }
+ catch (ParseException e) {
+ throw new KustvaktException(StatusCodes.ID_TOKEN_CLAIM_ERROR,
+ e.getMessage());
+ }
+ }
+
+ /**
+ * id token should be signed and additionally encrypted
+ * according to keys and algorithms the client specified
+ * during registration
+ *
+ * Currently supporting only:
+ * default algorithm = RSA SHA-256 (RS256)
+ *
+ * @param jwtClaimsSet
+ * id token claim set
+ * @param jwsHeader
+ * jws header
+ * @param privateKey
+ *
+ * @return
+ * @throws KustvaktException
+ */
+ private SignedJWT signIdToken (JWTClaimsSet jwtClaimsSet,
+ JWSHeader jwsHeader, PrivateKey privateKey)
+ throws KustvaktException {
+
+ SignedJWT idToken = new SignedJWT(jwsHeader, jwtClaimsSet);
+ JWSSigner signer = null;
+ if (jwsHeader.getAlgorithm().equals(JWSAlgorithm.RS256)) {
+ signer = new RSASSASigner(privateKey);
+ }
+ else {
+ throw new KustvaktException(StatusCodes.ID_TOKEN_SIGNING_FAILED,
+ "Unsupported algorithm "
+ + jwsHeader.getAlgorithm().getName());
+ }
+
+ try {
+ idToken.sign(signer);
+ }
+ catch (JOSEException e) {
+ throw new KustvaktException(StatusCodes.ID_TOKEN_SIGNING_FAILED,
+ e.getMessage());
+ }
+
+ return idToken;
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
index b103e33..f1131b0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AuthorizationService.java
@@ -66,13 +66,8 @@
throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
"response_type is missing.", OAuth2Error.INVALID_REQUEST);
}
- else if (responseType.equals("token")) {
- throw new KustvaktException(StatusCodes.NOT_SUPPORTED,
- "response_type token is not supported.",
- OAuth2Error.INVALID_REQUEST);
- }
else if (!responseType.equals("code")) {
- throw new KustvaktException(StatusCodes.INVALID_ARGUMENT,
+ throw new KustvaktException(StatusCodes.NOT_SUPPORTED,
"unsupported response_type: " + responseType,
OAuth2Error.INVALID_REQUEST);
}
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 82bab07..962a14f 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
@@ -12,7 +12,7 @@
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.FullConfiguration;
import de.ids_mannheim.korap.config.SpringJerseyTest;
import de.ids_mannheim.korap.constant.TokenType;
import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -23,7 +23,7 @@
public class APIAuthenticationTest extends SpringJerseyTest {
@Autowired
- private KustvaktConfiguration config;
+ private FullConfiguration config;
@Test
public void testCreateGetTokenContext () throws KustvaktException,
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
index ac3a131..ed263c0 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
@@ -6,6 +6,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.net.URISyntaxException;
import java.util.Map;
import java.util.Properties;
@@ -59,7 +60,7 @@
@Test(expected = KustvaktException.class)
@Ignore
- public void testBeanOverrideInjection () throws KustvaktException {
+ public void testBeanOverrideInjection () throws KustvaktException, URISyntaxException {
helper().getContext()
.getConfiguration()
.setPropertiesAsStream(
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
index c4156d6..6bd081a 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
@@ -2,14 +2,29 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.URI;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.text.ParseException;
+import java.util.Date;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
+import org.apache.commons.codec.binary.Base64;
import org.apache.http.entity.ContentType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
@@ -17,6 +32,10 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
@@ -24,6 +43,7 @@
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.config.SpringJerseyTest;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
@@ -33,24 +53,40 @@
@Autowired
private HttpAuthorizationHandler handler;
+ @Autowired
+ private FullConfiguration config;
private String redirectUri =
"https://korap.ids-mannheim.de/confidential/redirect";
+ private String username = "dory";
+
+ private static String publicKey;
+
+ @BeforeClass
+ public static void init () throws IOException {
+ InputStream is = OAuth2OpenIdControllerTest.class.getClassLoader()
+ .getResourceAsStream("kustvakt-public.key");
+
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(is));) {
+ publicKey = reader.readLine();
+ }
+ }
private ClientResponse sendAuthorizationRequest (
MultivaluedMap<String, String> form) throws KustvaktException {
return resource().path("oauth2").path("openid").path("authorize")
.header(Attributes.AUTHORIZATION,
- handler.createBasicAuthorizationHeaderValue("dory",
+ handler.createBasicAuthorizationHeaderValue(username,
"password"))
.header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
.header(HttpHeaders.CONTENT_TYPE,
ContentType.APPLICATION_FORM_URLENCODED)
.entity(form).post(ClientResponse.class);
}
-
- private ClientResponse sendTokenRequest (MultivaluedMap<String, String> form)
- throws KustvaktException {
+
+ private ClientResponse sendTokenRequest (
+ MultivaluedMap<String, String> form) throws KustvaktException {
return resource().path("oauth2").path("openid").path("token")
.header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
.header(HttpHeaders.CONTENT_TYPE,
@@ -165,33 +201,86 @@
node.at("/error_description").asText());
}
- @Test
- public void testRequestAuthorizationCodeUnsupportedResponseType ()
+ private void testRequestAuthorizationCodeUnsupportedResponseType (
+ MultivaluedMap<String, String> form, String type)
throws KustvaktException {
- MultivaluedMap<String, String> form = new MultivaluedMapImpl();
- form.add("scope", "openid");
- form.add("redirect_uri", redirectUri);
- // we don't support implicit grant
- form.add("response_type", "id_token token");
- form.add("client_id", "fCBbQkAyYzI4NzUxMg");
- form.add("nonce", "nonce");
ClientResponse response = sendAuthorizationRequest(form);
+ System.out.println(response.getEntity(String.class));
URI location = response.getLocation();
- assertEquals(MediaType.APPLICATION_FORM_URLENCODED, response.getType().toString());
+ assertEquals(MediaType.APPLICATION_FORM_URLENCODED,
+ response.getType().toString());
MultiValueMap<String, String> params =
UriComponentsBuilder.fromUri(location).build().getQueryParams();
assertEquals("invalid_request", params.getFirst("error"));
- assertEquals("unsupported+response_type%3A+id_token",
+ assertEquals("unsupported+response_type%3A+" + type,
params.getFirst("error_description"));
}
-
+
+ /**
+ * We don't support implicit grant. Implicit grant allows
+ * response_type:
+ * <ul>
+ * <li>id_token</li>
+ * <li>id_token token</li>
+ * </ul>
+ *
+ * @throws KustvaktException
+ */
@Test
- public void testRequestAccessToken () throws KustvaktException {
+ public void testRequestAuthorizationCodeUnsupportedImplicitFlow ()
+ throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("scope", "openid");
+ form.add("redirect_uri", redirectUri);
+ form.add("response_type", "id_token");
+ form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.add("nonce", "nonce");
+
+ testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+
+ form.remove("response_type");
+ form.add("response_type", "id_token token");
+ testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+ }
+
+ /**
+ * Hybrid flow is not supported. Hybrid flow allows
+ * response_type:
+ * <ul>
+ * <li>code id_token</li>
+ * <li>code token</li>
+ * <li>code id_token token</li>
+ * </ul>
+ *
+ * @throws KustvaktException
+ */
+
+ @Test
+ public void testRequestAuthorizationCodeUnsupportedHybridFlow ()
+ throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("scope", "openid");
+ form.add("redirect_uri", redirectUri);
+ form.add("response_type", "code id_token");
+ form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.add("nonce", "nonce");
+ testRequestAuthorizationCodeUnsupportedResponseType(form, "id_token");
+
+ form.remove("response_type");
+ form.add("response_type", "code token");
+ testRequestAuthorizationCodeUnsupportedResponseType(form, "token");
+ }
+
+ @Test
+ public void testRequestAccessToken ()
+ throws KustvaktException, ParseException, InvalidKeySpecException,
+ NoSuchAlgorithmException, JOSEException {
+ String client_id = "fCBbQkAyYzI4NzUxMg";
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("response_type", "code");
- form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.add("client_id", client_id);
form.add("redirect_uri", redirectUri);
form.add("scope", "openid");
form.add("state", "thisIsMyState");
@@ -201,17 +290,17 @@
MultiValueMap<String, String> params =
UriComponentsBuilder.fromUri(location).build().getQueryParams();
String code = params.getFirst("code");
-
+
MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
tokenForm.add("grant_type", "authorization_code");
tokenForm.add("redirect_uri", redirectUri);
- tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ tokenForm.add("client_id", client_id);
tokenForm.add("client_secret", "secret");
tokenForm.add("code", code);
-
+
ClientResponse tokenResponse = sendTokenRequest(tokenForm);
String entity = tokenResponse.getEntity(String.class);
-// System.out.println(entity);
+ // System.out.println(entity);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
@@ -219,7 +308,30 @@
assertEquals(TokenType.BEARER.toString(),
node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
- assertNotNull(node.at("/id_token").asText());
+ String id_token = node.at("/id_token").asText();
+ assertNotNull(id_token);
+ verifyingIdToken(id_token, username, client_id);
+ }
+
+ private void verifyingIdToken (String id_token, String username,
+ String client_id) throws ParseException, InvalidKeySpecException,
+ NoSuchAlgorithmException, JOSEException {
+ byte[] decodedPuk = Base64.decodeBase64(publicKey);
+ KeySpec keySpec = new X509EncodedKeySpec(decodedPuk);
+ RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
+ .generatePublic(keySpec);
+
+ SignedJWT signedJWT = SignedJWT.parse(id_token);
+ JWSVerifier verifier = new RSASSAVerifier(publicKey);
+ assertTrue(signedJWT.verify(verifier));
+
+ assertEquals(client_id,
+ signedJWT.getJWTClaimsSet().getAudience().get(0));
+ assertEquals(username, signedJWT.getJWTClaimsSet().getSubject());
+ assertEquals(config.getIssuerURI().toString(),
+ signedJWT.getJWTClaimsSet().getIssuer());
+ assertTrue(new Date()
+ .before(signedJWT.getJWTClaimsSet().getExpirationTime()));
}
}
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 68f1b55..f791244 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
@@ -29,6 +29,7 @@
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.BeansFactory;
+import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.config.JWTSigner;
import de.ids_mannheim.korap.config.TestHelper;
import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -49,7 +50,8 @@
@Autowired
HttpAuthorizationHandler handler;
-
+ @Autowired
+ FullConfiguration config;
private static String[] credentials;
@Override
@@ -201,8 +203,8 @@
String token = node.path("token").asText();
JWTSigner sign = new JWTSigner(BeansFactory.getKustvaktContext().getConfiguration().getSharedSecret(),
- BeansFactory.getKustvaktContext().getConfiguration().getIssuer(), -1);
-
+ config.getIssuer(), -1);
+ //BeansFactory.getKustvaktContext().getConfiguration().getIssuer(), -1);
SignedJWT jwt = sign.verifyToken(token);
while (true) {
diff --git a/full/src/test/resources/kustvakt-private.key b/full/src/test/resources/kustvakt-private.key
new file mode 100644
index 0000000..d419b89
--- /dev/null
+++ b/full/src/test/resources/kustvakt-private.key
@@ -0,0 +1 @@
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAIlVfcPe+PXGph6BX1zU9HQ1kSt0lz2LIGAB+krHcj5oaWeS/4xicvmmGRE5MeJQEMIcijl3OXjdZR7lK1dxn1UUHuZa3ijMnMgDcQz9BuGg+49R5KdSkkMwlVW5Bdt08TmU9teFdQpg+7bsVGKpSuW6yE6wkgo+Wwufw23ULNkjAgMBAAECgYBVq8o3zTm7gH+SmhwWOhaBBAWaeTH7x3WbzsAHtCG1gsb2QMJAHg4hZJdQokBXMKEzpkAoFxL4Lgxt2IJQG2ZL778uiQiy+xHI8VTXBNXmdo+F3hlNzEmJySSSCxYefSSv+DN/yBrOx0heGXR3vbefXey4a6q8RhthCuRfpHmqmQJBALyFdf4Oj4rozi/KI8yiD71+NNR7hHMtepn3YyY0zBXxk2YEwpcPkzBhdDiL6fYJjjoGFnqKLNqlgO8gHx+ET70CQQC6faQiLjUp50wbEAZqLY7Q353k2qTdAX8W9L2lF/79GEA+EJumQ2iWOu9qYqQuSMSKwheY6mdOVWj8yOMiu2pfAkEAll0cr3aNpw3o5tUjmKPqSgnPuWqLShKMJyHaQy75WMdF+ajyS+pwS7ZvLGrsQQF+H2mbpEFxZTN8kz3blRfDQQJBAKADPdm2HBegRkTSMy7XeDrwI+JBWEPpDMr9o9sMA9XWAQk/5s15+Tstxk9Z49VyynDkqKqkNY+Y6UQ8eedLN7ECQQCDiAsbwOe79EpsHdQBOZeNvpWu1x1TxieN0nCAa/zQz8qupHkL/u8VI8csz+s3qOcgxpJqsn58G8eb9Jmk9fGY
\ No newline at end of file
diff --git a/full/src/test/resources/kustvakt-public.key b/full/src/test/resources/kustvakt-public.key
new file mode 100644
index 0000000..087d6f7
--- /dev/null
+++ b/full/src/test/resources/kustvakt-public.key
@@ -0,0 +1 @@
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJVX3D3vj1xqYegV9c1PR0NZErdJc9iyBgAfpKx3I+aGlnkv+MYnL5phkROTHiUBDCHIo5dzl43WUe5StXcZ9VFB7mWt4ozJzIA3EM/QbhoPuPUeSnUpJDMJVVuQXbdPE5lPbXhXUKYPu27FRiqUrlushOsJIKPlsLn8Nt1CzZIwIDAQAB
\ No newline at end of file
diff --git a/lite/pom.xml b/lite/pom.xml
index 9636eba..a1e95b9 100644
--- a/lite/pom.xml
+++ b/lite/pom.xml
@@ -147,7 +147,7 @@
<dependency>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-core</artifactId>
- <version>0.60.3</version>
+ <version>0.60.4</version>
</dependency>
<!-- Spring -->