Merge remote-tracking branch 'upstream/1.3.x' into 1.3.x

pull/1601/head
Gaurav Katiyar 2018-05-28 11:51:20 +01:00
commit 1cc3b8f287
25 changed files with 773 additions and 123 deletions

View File

@ -1,22 +1,30 @@
Unreleased:
- Added changelog
- Set default redirect URI resolver strict matching to true
- Fixed XSS vulnerability on redirect URI display on approval page
*1.3.2:
- Added changelog
- Set default redirect URI resolver strict matching to true
- Fixed XSS vulnerability on redirect URI display on approval page
- Removed MITRE from copyright
- Disallow unsigned JWTs on client authentication
- Upgraded Nimbus revision
- Added French translation
- Added hooks for custom JWT claims
- Removed "Not Yet Implemented" tag from post-logout redirect URI
*1.3.1*:
- Added End Session endpoint
- Fixed discovery endpoint
- Downgrade MySQL connector dependency version from developer preview to GA release
- Added End Session endpoint
- Fixed discovery endpoint
- Downgrade MySQL connector dependency version from developer preview to GA release
*1.3.0*:
- Added device flow support
- Added PKCE support
- Modularized UI to allow better overlay and extensions
- Modularized data import/export API
- Added software statements to dynamic client registration
- Added assertion processing framework
- Removed ID tokens from storage
- Removed structured scopes
- Added device flow support
- Added PKCE support
- Modularized UI to allow better overlay and extensions
- Modularized data import/export API
- Added software statements to dynamic client registration
- Added assertion processing framework
- Removed ID tokens from storage
- Removed structured scopes
*1.2.6*:
- Added string HEART compliance mode
- Added strict HEART compliance mode

Binary file not shown.

Binary file not shown.

View File

@ -22,7 +22,7 @@
<parent>
<artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>openid-connect-client</artifactId>

View File

@ -92,7 +92,7 @@ public class TestSignedAuthRequestUrlBuilder {
@Before
public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException {
RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null);
RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null, null);
Map<String, JWK> keys = Maps.newHashMap();
keys.put("client", key);

View File

@ -22,7 +22,7 @@
<parent>
<artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>openid-connect-common</artifactId>

View File

@ -99,8 +99,10 @@ public class SymmetricKeyJWTValidatorCacheService {
try {
String id = "SYMMETRIC-KEY";
JWK jwk = new OctetSequenceKey(Base64URL.encode(key), KeyUse.SIGNATURE, null, null, id, null, null, null, null);
JWK jwk = new OctetSequenceKey.Builder(Base64URL.encode(key))
.keyUse(KeyUse.SIGNATURE)
.keyID(id)
.build();
Map<String, JWK> keys = ImmutableMap.of(id, jwk);
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keys);

View File

@ -61,7 +61,7 @@ public class TestJWKSetKeyStore {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null);
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null);
private String RSAkid_rsa2 = "rsa_2";
private JWK RSAjwk_rsa2 = new RSAKey(
@ -78,7 +78,7 @@ public class TestJWKSetKeyStore {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_rsa2, null, null, null, null);
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_rsa2, null, null, null, null, null);
List<JWK> keys_list = new LinkedList<>();

View File

@ -106,7 +106,7 @@ public class TestDefaultJWTEncryptionAndDecryptionService {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null);
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null);
private String RSAkid_2 = "rsa3210";
private JWK RSAjwk_2 = new RSAKey(
@ -123,12 +123,12 @@ public class TestDefaultJWTEncryptionAndDecryptionService {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_2, null, null, null, null);
KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_2, null, null, null, null, null);
private String AESkid = "aes123";
private JWK AESjwk = new OctetSequenceKey(new Base64URL("GawgguFyGrWKav7AX4VKUg"),
KeyUse.ENCRYPTION, null, JWEAlgorithm.A128KW,
AESkid, null, null, null, null);
AESkid, null, null, null, null, null);
private Map<String, JWK> keys = new ImmutableMap.Builder<String, JWK>()

View File

@ -21,7 +21,7 @@
<parent>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
</parent>
<artifactId>openid-connect-server-webapp</artifactId>
<packaging>war</packaging>

View File

@ -3,7 +3,7 @@
--
CREATE TABLE IF NOT EXISTS access_token (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
token_value VARCHAR(4096),
expiration TIMESTAMP,
token_type VARCHAR(256),
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS access_token_permissions (
);
CREATE TABLE IF NOT EXISTS address (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
formatted VARCHAR(256),
street_address VARCHAR(256),
locality VARCHAR(256),
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS address (
);
CREATE TABLE IF NOT EXISTS approved_site (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(256),
client_id VARCHAR(256),
creation_date TIMESTAMP,
@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS approved_site_scope (
);
CREATE TABLE IF NOT EXISTS authentication_holder (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
user_auth_id BIGINT,
approved BOOLEAN,
redirect_uri VARCHAR(2048),
@ -85,7 +85,7 @@ CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter (
);
CREATE TABLE IF NOT EXISTS saved_user_auth (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024),
authenticated BOOLEAN,
source_class VARCHAR(2048)
@ -102,7 +102,7 @@ CREATE TABLE IF NOT EXISTS client_authority (
);
CREATE TABLE IF NOT EXISTS authorization_code (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
code VARCHAR(256),
auth_holder_id BIGINT,
expiration TIMESTAMP
@ -119,12 +119,12 @@ CREATE TABLE IF NOT EXISTS client_response_type (
);
CREATE TABLE IF NOT EXISTS blacklisted_site (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
uri VARCHAR(2048)
);
CREATE TABLE IF NOT EXISTS client_details (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
client_description VARCHAR(1024),
reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL,
@ -210,7 +210,7 @@ CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
);
CREATE TABLE IF NOT EXISTS refresh_token (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
token_value VARCHAR(4096),
expiration TIMESTAMP,
auth_holder_id BIGINT,
@ -233,7 +233,7 @@ CREATE TABLE IF NOT EXISTS token_scope (
);
CREATE TABLE IF NOT EXISTS system_scope (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
scope VARCHAR(256) NOT NULL,
description VARCHAR(4096),
icon VARCHAR(256),
@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS system_scope (
);
CREATE TABLE IF NOT EXISTS user_info (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
sub VARCHAR(256),
preferred_username VARCHAR(256),
name VARCHAR(256),
@ -270,7 +270,7 @@ CREATE TABLE IF NOT EXISTS user_info (
);
CREATE TABLE IF NOT EXISTS whitelisted_site (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
creator_user_id VARCHAR(256),
client_id VARCHAR(256)
);
@ -281,14 +281,14 @@ CREATE TABLE IF NOT EXISTS whitelisted_site_scope (
);
CREATE TABLE IF NOT EXISTS pairwise_identifier (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
identifier VARCHAR(256),
sub VARCHAR(256),
sector_identifier VARCHAR(2048)
);
CREATE TABLE IF NOT EXISTS resource_set (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024) NOT NULL,
uri VARCHAR(1024),
icon_uri VARCHAR(1024),
@ -303,14 +303,14 @@ CREATE TABLE IF NOT EXISTS resource_set_scope (
);
CREATE TABLE IF NOT EXISTS permission_ticket (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
ticket VARCHAR(256) NOT NULL,
permission_id BIGINT NOT NULL,
expiration TIMESTAMP
);
CREATE TABLE IF NOT EXISTS permission (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
resource_set_id BIGINT
);
@ -320,7 +320,7 @@ CREATE TABLE IF NOT EXISTS permission_scope (
);
CREATE TABLE IF NOT EXISTS claim (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
name VARCHAR(256),
friendly_name VARCHAR(1024),
claim_type VARCHAR(1024),
@ -338,7 +338,7 @@ CREATE TABLE IF NOT EXISTS claim_to_permission_ticket (
);
CREATE TABLE IF NOT EXISTS policy (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024),
resource_set_id BIGINT
);
@ -359,13 +359,13 @@ CREATE TABLE IF NOT EXISTS claim_issuer (
);
CREATE TABLE IF NOT EXISTS saved_registered_client (
id SERIAL PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
issuer VARCHAR(1024),
registered_client VARCHAR(8192)
);
CREATE TABLE IF NOT EXISTS device_code (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
id BIGSERIAL PRIMARY KEY,
device_code VARCHAR(1024),
user_code VARCHAR(1024),
expiration TIMESTAMP NULL,

View File

@ -65,6 +65,9 @@
$(document).ready(function() {
$('#stats').hide();
var base = $('base').attr('href');
if (base.substr(-1) !== '/') {
base += '/';
}
$.getJSON(base + 'api/stats/summary', function(data) {
var stats = data;

View File

@ -548,6 +548,9 @@ $(function() {
});
var base = $('base').attr('href');
if (base.substr(-1) !== '/') {
base += '/';
}
$.getJSON(base + '.well-known/openid-configuration', function(data) {
app.serverConfiguration = data;
var baseUrl = $.url(app.serverConfiguration.issuer);

View File

@ -211,7 +211,7 @@
"no-clients": "There are no registered clients on this server.",
"no-matches": "There are no clients that match your search criteria.",
"no-redirect": "NO REDIRECT URI",
"registered": "Registrered",
"registered": "Registered",
"search": "Search...",
"whitelist": "Whitelist",
"unknown": "at an unknown time"
@ -325,7 +325,7 @@
"confirm": "Are you sure sure you would like to delete this scope? Clients that have this scope will still be able to ask for it.",
"new": "New Scope",
"text": "There are no system scopes defined. Clients may still have custom scopes.",
"tooltip-restricted": "This scope can be used only by adminisrtators. It is not available for dynamic registration.",
"tooltip-restricted": "This scope can be used only by administrators. It is not available for dynamic registration.",
"tooltip-default": "This scope is automatically assigned to newly registered clients."
}
},
@ -337,7 +337,7 @@
"associated-refresh": "This access token was issued with an associated refresh token.",
"click-to-display": "Click to display full token value",
"confirm": "Are you sure sure you would like to revoke this token?",
"confirm-refresh": "Are you sure sure you would like to revoke this refresh token and its associated access tokens?",
"confirm-refresh": "Are you sure you would like to revoke this refresh token and its associated access tokens?",
"expires": "Expires",
"no-access": "There are no active access tokens.",
"no-refresh": "There are no active refresh tokens.",
@ -522,7 +522,7 @@
"title": "Logout requested",
"header": "Logout Requested",
"requested": "Logout has been requested by ",
"explanation": "Do you wan to log out of the identity provider? This will not affect your session at any other systems.",
"explanation": "Do you want to log out of the identity provider? This will not affect your session at any other systems.",
"submit": "Log Out",
"deny": "Stay Logged In"
},

View File

@ -795,7 +795,7 @@
</div>
<div class="control-group" id="postLogoutRedirectUris">
<label class="control-label"><span class="label label-default nyi"><i class="icon-road icon-white"></i> NYI </span> <span data-i18n="client.client-form.post-logout">Post-Logout Redirect</span></label>
<label class="control-label"><span data-i18n="client.client-form.post-logout">Post-Logout Redirect</span></label>
<div class="controls">
</div>
</div>

View File

@ -23,7 +23,7 @@
<parent>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<build>

View File

@ -91,15 +91,19 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
JWT jwt = jwtAuth.getJwt();
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
if (!(jwt instanceof SignedJWT)) {
throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName());
}
// check the signature with nimbus
if (jwt instanceof SignedJWT) {
SignedJWT jws = (SignedJWT)jwt;
SignedJWT jws = (SignedJWT) jwt;
JWSAlgorithm alg = jws.getHeader().getAlgorithm();
if (client.getTokenEndpointAuthSigningAlg() != null &&
!client.getTokenEndpointAuthSigningAlg().equals(alg)) {
throw new InvalidClientException("Client's registered request object signing algorithm (" + client.getRequestObjectSigningAlg() + ") does not match request object's actual algorithm (" + alg.getName() + ")");
throw new AuthenticationServiceException("Client's registered token endpoint signing algorithm (" + client.getTokenEndpointAuthSigningAlg()
+ ") does not match token's actual algorithm (" + alg.getName() + ")");
}
if (client.getTokenEndpointAuthMethod() == null ||
@ -142,7 +146,6 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
} else {
throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
}
}
// check the issuer
if (jwtClaims.getIssuer() == null) {

View File

@ -151,6 +151,8 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
idClaims.claim("at_hash", at_hash);
}
addCustomIdTokenClaims(idClaims, client, request, sub, accessToken);
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
&& (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {
@ -335,4 +337,18 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
this.authenticationHolderRepository = authenticationHolderRepository;
}
/**
* Hook for subclasses that allows adding custom claims to the JWT
* that will be used as id token.
* @param idClaims the builder holding the current claims
* @param client information about the requesting client
* @param request request that caused the id token to be created
* @param sub subject auf the id token
* @param accessToken the access token
* @param authentication current authentication
*/
protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request,
String sub, OAuth2AccessTokenEntity accessToken) {
}
}

View File

@ -92,6 +92,8 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
builder.audience(Lists.newArrayList(audience));
}
addCustomAccessTokenClaims(builder, token, authentication);
JWTClaimsSet claims = builder.build();
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
@ -161,4 +163,14 @@ public class ConnectTokenEnhancer implements TokenEnhancer {
}
/**
* Hook for subclasses that allows adding custom claims to the JWT that will be used as access token.
* @param builder the builder holding the current claims
* @param token the un-enhanced token
* @param authentication current authentication
*/
protected void addCustomAccessTokenClaims(JWTClaimsSet.Builder builder, OAuth2AccessTokenEntity token,
OAuth2Authentication authentication) {
}
}

View File

@ -0,0 +1,414 @@
package org.mitre.openid.connect.assertion;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
@RunWith(MockitoJUnitRunner.class)
public class TestJWTBearerAuthenticationProvider {
private static final String CLIENT_ID = "client";
private static final String SUBJECT = "subject";
@Mock
private ClientKeyCacheService validators;
@Mock
private ClientDetailsEntityService clientService;
@Mock
private ConfigurationPropertiesBean config;
@InjectMocks
private JWTBearerAuthenticationProvider jwtBearerAuthenticationProvider;
@Mock
private JWTBearerAssertionAuthenticationToken token;
@Mock
private ClientDetailsEntity client;
@Mock
private JWTSigningAndValidationService validator;
private GrantedAuthority authority1 = new SimpleGrantedAuthority("1");
private GrantedAuthority authority2 = new SimpleGrantedAuthority("2");
private GrantedAuthority authority3 = new SimpleGrantedAuthority("3");
@Before
public void setup() {
when(clientService.loadClientByClientId(CLIENT_ID)).thenReturn(client);
when(token.getName()).thenReturn(CLIENT_ID);
when(client.getClientId()).thenReturn(CLIENT_ID);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.NONE);
when(client.getAuthorities()).thenReturn(ImmutableSet.of(authority1, authority2, authority3));
when(validators.getValidator(client, JWSAlgorithm.RS256)).thenReturn(validator);
when(validator.validateSignature(any(SignedJWT.class))).thenReturn(true);
when(config.getIssuer()).thenReturn("http://issuer.com/");
}
@Test
public void should_not_support_UsernamePasswordAuthenticationToken() {
assertThat(jwtBearerAuthenticationProvider.supports(UsernamePasswordAuthenticationToken.class), is(false));
}
@Test
public void should_support_JWTBearerAssertionAuthenticationToken() {
assertThat(jwtBearerAuthenticationProvider.supports(JWTBearerAssertionAuthenticationToken.class), is(true));
}
@Test
public void should_throw_UsernameNotFoundException_when_clientService_throws_InvalidClientException() {
when(clientService.loadClientByClientId(CLIENT_ID)).thenThrow(new InvalidClientException("invalid client"));
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(UsernameNotFoundException.class));
assertThat(thrown.getMessage(), is("Could not find client: " + CLIENT_ID));
}
@Test
public void should_throw_AuthenticationServiceException_for_PlainJWT() {
mockPlainJWTAuthAttempt();
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Unsupported JWT type: " + PlainJWT.class.getName()));
}
@Test
public void should_throw_AuthenticationServiceException_for_EncryptedJWT() {
mockEncryptedJWTAuthAttempt();
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Unsupported JWT type: " + EncryptedJWT.class.getName()));
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_signing_algorithms_do_not_match() {
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256);
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.ES384);
when(token.getJwt()).thenReturn(signedJWT);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Client's registered token endpoint signing algorithm (RS256) does not match token's actual algorithm (ES384)"));
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_unsupported_authentication_method_for_SignedJWT() {
List<AuthMethod> unsupportedAuthMethods =
Arrays.asList(null, AuthMethod.NONE, AuthMethod.SECRET_BASIC, AuthMethod.SECRET_POST);
for (AuthMethod unsupportedAuthMethod : unsupportedAuthMethods) {
SignedJWT signedJWT = createSignedJWT();
when(token.getJwt()).thenReturn(signedJWT);
when(client.getTokenEndpointAuthMethod()).thenReturn(unsupportedAuthMethod);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Client does not support this authentication method."));
}
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_PRIVATE_KEY_auth_method() {
List<JWSAlgorithm> invalidAlgorithms = Arrays.asList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512);
for (JWSAlgorithm algorithm : invalidAlgorithms) {
SignedJWT signedJWT = createSignedJWT(algorithm);
when(token.getJwt()).thenReturn(signedJWT);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY);
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method"));
}
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_SECRET_JWT_auth_method() {
List<JWSAlgorithm> invalidAlgorithms = Arrays.asList(
JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512,
JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512,
JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512);
for (JWSAlgorithm algorithm : invalidAlgorithms) {
SignedJWT signedJWT = createSignedJWT(algorithm);
when(token.getJwt()).thenReturn(signedJWT);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT);
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method"));
}
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_in_heart_mode_and_auth_method_is_not_PRIVATE_KEY() {
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.HS256);
when(token.getJwt()).thenReturn(signedJWT);
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.HS256);
when(config.isHeartMode()).thenReturn(true);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("[HEART mode] Invalid authentication method"));
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_null_validator() {
mockSignedJWTAuthAttempt();
when(validators.getValidator(any(ClientDetailsEntity.class), any(JWSAlgorithm.class))).thenReturn(null);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for client"));
}
@Test
public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_signature() {
SignedJWT signedJWT = mockSignedJWTAuthAttempt();
when(validator.validateSignature(signedJWT)).thenReturn(false);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Signature did not validate for presented JWT authentication."));
}
@Test
public void should_throw_AuthenticationServiceException_when_null_issuer() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(null).build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Assertion Token Issuer is null"));
}
@Test
public void should_throw_AuthenticationServiceException_when_not_matching_issuer() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer("not matching").build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Issuers do not match"));
}
@Test
public void should_throw_AuthenticationServiceException_when_null_expiration_time() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(null).build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), is("Assertion Token does not have required expiration claim"));
}
@Test
public void should_throw_AuthenticationServiceException_when_expired_jwt() {
Date expiredDate = new Date(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(500));
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(expiredDate).build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Assertion Token is expired"));
}
@Test
public void should_throw_AuthenticationServiceException_when_jwt_valid_in_future() {
Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500));
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).notBeforeTime(futureDate).build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Assertion Token not valid until"));
}
@Test
public void should_throw_AuthenticationServiceException_when_jwt_issued_in_future() {
Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500));
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).issueTime(futureDate).build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Assertion Token was issued in the future"));
}
@Test
public void should_throw_AuthenticationServiceException_when_unmatching_audience() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(new Date()).audience("invalid").build();
mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
assertThat(thrown, instanceOf(AuthenticationServiceException.class));
assertThat(thrown.getMessage(), startsWith("Audience does not match"));
}
@Test
public void should_return_valid_token_when_audience_contains_token_endpoint() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
.issuer(CLIENT_ID)
.subject(SUBJECT)
.expirationTime(new Date())
.audience(ImmutableList.of("http://issuer.com/token", "invalid"))
.build();
JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet);
Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token);
assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class));
JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication;
assertThat(token.getName(), is(SUBJECT));
assertThat(token.getJwt(), is(jwt));
assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3));
assertThat(token.getAuthorities().size(), is(4));
}
@Test
public void should_return_valid_token_when_issuer_does_not_end_with_slash_and_audience_contains_token_endpoint() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
.issuer(CLIENT_ID)
.subject(SUBJECT)
.expirationTime(new Date())
.audience(ImmutableList.of("http://issuer.com/token"))
.build();
JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet);
when(config.getIssuer()).thenReturn("http://issuer.com/");
Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token);
assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class));
JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication;
assertThat(token.getName(), is(SUBJECT));
assertThat(token.getJwt(), is(jwt));
assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3));
assertThat(token.getAuthorities().size(), is(4));
}
private void mockPlainJWTAuthAttempt() {
PlainJWT plainJWT = new PlainJWT(createJwtClaimsSet());
when(token.getJwt()).thenReturn(plainJWT);
}
private void mockEncryptedJWTAuthAttempt() {
JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256GCM).build();
EncryptedJWT encryptedJWT = new EncryptedJWT(jweHeader, createJwtClaimsSet());
when(token.getJwt()).thenReturn(encryptedJWT);
}
private SignedJWT mockSignedJWTAuthAttempt() {
return mockSignedJWTAuthAttempt(createJwtClaimsSet());
}
private SignedJWT mockSignedJWTAuthAttempt(JWTClaimsSet jwtClaimsSet) {
SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.RS256, jwtClaimsSet);
when(token.getJwt()).thenReturn(signedJWT);
when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY);
when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256);
return signedJWT;
}
private Throwable authenticateAndReturnThrownException() {
try {
jwtBearerAuthenticationProvider.authenticate(token);
} catch (Throwable throwable) {
return throwable;
}
throw new AssertionError("No exception thrown when expected");
}
private SignedJWT createSignedJWT() {
return createSignedJWT(JWSAlgorithm.RS256);
}
private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm) {
JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build();
JWTClaimsSet claims = createJwtClaimsSet();
return new SignedJWT(jwsHeader, claims);
}
private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm, JWTClaimsSet jwtClaimsSet) {
JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build();
return new SignedJWT(jwsHeader, jwtClaimsSet);
}
private JWTClaimsSet createJwtClaimsSet() {
return new JWTClaimsSet.Builder()
.issuer(CLIENT_ID)
.expirationTime(new Date())
.audience("http://issuer.com/")
.build();
}
}

View File

@ -0,0 +1,81 @@
/*******************************************************************************
* Copyright 2018 The MIT Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.mitre.openid.connect.service.impl;
import java.util.Date;
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.springframework.security.oauth2.provider.OAuth2Request;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class TestDefaultOIDCTokenService {
private static final String CLIENT_ID = "client";
private static final String KEY_ID = "key";
private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean();
private ClientDetailsEntity client = new ClientDetailsEntity();
private OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity();
private OAuth2Request request = new OAuth2Request(CLIENT_ID) { };
@Mock
private JWTSigningAndValidationService jwtService;
@Before
public void prepare() {
configBean.setIssuer("https://auth.example.org/");
client.setClientId(CLIENT_ID);
Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256);
Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID);
}
@Test
public void invokesCustomClaimsHook() throws java.text.ParseException {
DefaultOIDCTokenService s = new DefaultOIDCTokenService() {
@Override
protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request,
String sub, OAuth2AccessTokenEntity accessToken) {
idClaims.claim("test", "foo");
}
};
configure(s);
JWT token = s.createIdToken(client, request, new Date(), "sub", accessToken);
Assert.assertEquals("foo", token.getJWTClaimsSet().getClaim("test"));
}
private void configure(DefaultOIDCTokenService s) {
s.setConfigBean(configBean);
s.setJwtService(jwtService);
}
}

View File

@ -0,0 +1,108 @@
/*******************************************************************************
* Copyright 2017 The MIT Internet Trust Consortium
*
* Portions copyright 2011-2013 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.mitre.openid.connect.token;
import java.text.ParseException;
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.service.OIDCTokenService;
import org.mitre.openid.connect.service.UserInfoService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
@RunWith(MockitoJUnitRunner.class)
public class TestConnectTokenEnhancer {
private static final String CLIENT_ID = "client";
private static final String KEY_ID = "key";
private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean();
@Mock
private JWTSigningAndValidationService jwtService;
@Mock
private ClientDetailsEntityService clientService;
@Mock
private UserInfoService userInfoService;
@Mock
private OIDCTokenService connectTokenService;
@Mock
private OAuth2Authentication authentication;
private OAuth2Request request = new OAuth2Request(CLIENT_ID) { };
@InjectMocks
private ConnectTokenEnhancer enhancer = new ConnectTokenEnhancer();
@Before
public void prepare() {
configBean.setIssuer("https://auth.example.org/");
enhancer.setConfigBean(configBean);
ClientDetailsEntity client = new ClientDetailsEntity();
client.setClientId(CLIENT_ID);
Mockito.when(clientService.loadClientByClientId(Mockito.anyString())).thenReturn(client);
Mockito.when(authentication.getOAuth2Request()).thenReturn(request);
Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256);
Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID);
}
@Test
public void invokesCustomClaimsHook() throws ParseException {
configure(enhancer = new ConnectTokenEnhancer() {
@Override
protected void addCustomAccessTokenClaims(Builder builder, OAuth2AccessTokenEntity token,
OAuth2Authentication authentication) {
builder.claim("test", "foo");
}
});
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();
OAuth2AccessTokenEntity enhanced = (OAuth2AccessTokenEntity) enhancer.enhance(token, authentication);
Assert.assertEquals("foo", enhanced.getJwt().getJWTClaimsSet().getClaim("test"));
}
private void configure(ConnectTokenEnhancer e) {
e.setConfigBean(configBean);
e.setJwtService(jwtService);
e.setClientService(clientService);
}
}

View File

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<name>MITREid Connect</name>
<packaging>pom</packaging>
<parent>
@ -386,7 +386,7 @@
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>4.2.2.RELEASE</version>
<version>4.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -585,7 +585,7 @@
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>4.34.2</version>
<version>5.4</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>

View File

@ -19,7 +19,7 @@
<parent>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>uma-server-webapp</artifactId>

View File

@ -19,7 +19,7 @@
<parent>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version>
<version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>uma-server</artifactId>