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: Unreleased:
- Added changelog
- Set default redirect URI resolver strict matching to true *1.3.2:
- Fixed XSS vulnerability on redirect URI display on approval page - 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*: *1.3.1*:
- Added End Session endpoint - Added End Session endpoint
- Fixed discovery endpoint - Fixed discovery endpoint
- Downgrade MySQL connector dependency version from developer preview to GA release - Downgrade MySQL connector dependency version from developer preview to GA release
*1.3.0*: *1.3.0*:
- Added device flow support - Added device flow support
- Added PKCE support - Added PKCE support
- Modularized UI to allow better overlay and extensions - Modularized UI to allow better overlay and extensions
- Modularized data import/export API - Modularized data import/export API
- Added software statements to dynamic client registration - Added software statements to dynamic client registration
- Added assertion processing framework - Added assertion processing framework
- Removed ID tokens from storage - Removed ID tokens from storage
- Removed structured scopes - Removed structured scopes
*1.2.6*: *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> <parent>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<version>1.3.2-SNAPSHOT</version> <version>1.3.3-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>openid-connect-client</artifactId> <artifactId>openid-connect-client</artifactId>

View File

@ -92,7 +92,7 @@ public class TestSignedAuthRequestUrlBuilder {
@Before @Before
public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { 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(); Map<String, JWK> keys = Maps.newHashMap();
keys.put("client", key); keys.put("client", key);

View File

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

View File

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

View File

@ -61,7 +61,7 @@ public class TestJWKSetKeyStore {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d "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 String RSAkid_rsa2 = "rsa_2";
private JWK RSAjwk_rsa2 = new RSAKey( private JWK RSAjwk_rsa2 = new RSAKey(
@ -78,7 +78,7 @@ public class TestJWKSetKeyStore {
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" +
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" +
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d "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<>(); List<JWK> keys_list = new LinkedList<>();

View File

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

View File

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

View File

@ -3,7 +3,7 @@
-- --
CREATE TABLE IF NOT EXISTS access_token ( CREATE TABLE IF NOT EXISTS access_token (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
token_value VARCHAR(4096), token_value VARCHAR(4096),
expiration TIMESTAMP, expiration TIMESTAMP,
token_type VARCHAR(256), token_type VARCHAR(256),
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS access_token_permissions (
); );
CREATE TABLE IF NOT EXISTS address ( CREATE TABLE IF NOT EXISTS address (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
formatted VARCHAR(256), formatted VARCHAR(256),
street_address VARCHAR(256), street_address VARCHAR(256),
locality VARCHAR(256), locality VARCHAR(256),
@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS address (
); );
CREATE TABLE IF NOT EXISTS approved_site ( CREATE TABLE IF NOT EXISTS approved_site (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(256), user_id VARCHAR(256),
client_id VARCHAR(256), client_id VARCHAR(256),
creation_date TIMESTAMP, creation_date TIMESTAMP,
@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS approved_site_scope (
); );
CREATE TABLE IF NOT EXISTS authentication_holder ( CREATE TABLE IF NOT EXISTS authentication_holder (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_auth_id BIGINT, user_auth_id BIGINT,
approved BOOLEAN, approved BOOLEAN,
redirect_uri VARCHAR(2048), 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 ( CREATE TABLE IF NOT EXISTS saved_user_auth (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024), name VARCHAR(1024),
authenticated BOOLEAN, authenticated BOOLEAN,
source_class VARCHAR(2048) source_class VARCHAR(2048)
@ -102,7 +102,7 @@ CREATE TABLE IF NOT EXISTS client_authority (
); );
CREATE TABLE IF NOT EXISTS authorization_code ( CREATE TABLE IF NOT EXISTS authorization_code (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
code VARCHAR(256), code VARCHAR(256),
auth_holder_id BIGINT, auth_holder_id BIGINT,
expiration TIMESTAMP expiration TIMESTAMP
@ -119,12 +119,12 @@ CREATE TABLE IF NOT EXISTS client_response_type (
); );
CREATE TABLE IF NOT EXISTS blacklisted_site ( CREATE TABLE IF NOT EXISTS blacklisted_site (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
uri VARCHAR(2048) uri VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS client_details ( CREATE TABLE IF NOT EXISTS client_details (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
client_description VARCHAR(1024), client_description VARCHAR(1024),
reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL, reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL,
@ -132,17 +132,17 @@ CREATE TABLE IF NOT EXISTS client_details (
allow_introspection BOOLEAN DEFAULT false NOT NULL, allow_introspection BOOLEAN DEFAULT false NOT NULL,
id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL, id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL,
device_code_validity_seconds BIGINT, device_code_validity_seconds BIGINT,
client_id VARCHAR(256), client_id VARCHAR(256),
client_secret VARCHAR(2048), client_secret VARCHAR(2048),
access_token_validity_seconds BIGINT, access_token_validity_seconds BIGINT,
refresh_token_validity_seconds BIGINT, refresh_token_validity_seconds BIGINT,
application_type VARCHAR(256), application_type VARCHAR(256),
client_name VARCHAR(256), client_name VARCHAR(256),
token_endpoint_auth_method VARCHAR(256), token_endpoint_auth_method VARCHAR(256),
subject_type VARCHAR(256), subject_type VARCHAR(256),
logo_uri VARCHAR(2048), logo_uri VARCHAR(2048),
policy_uri VARCHAR(2048), policy_uri VARCHAR(2048),
client_uri VARCHAR(2048), client_uri VARCHAR(2048),
@ -151,31 +151,31 @@ CREATE TABLE IF NOT EXISTS client_details (
jwks_uri VARCHAR(2048), jwks_uri VARCHAR(2048),
jwks VARCHAR(8192), jwks VARCHAR(8192),
sector_identifier_uri VARCHAR(2048), sector_identifier_uri VARCHAR(2048),
request_object_signing_alg VARCHAR(256), request_object_signing_alg VARCHAR(256),
user_info_signed_response_alg VARCHAR(256), user_info_signed_response_alg VARCHAR(256),
user_info_encrypted_response_alg VARCHAR(256), user_info_encrypted_response_alg VARCHAR(256),
user_info_encrypted_response_enc VARCHAR(256), user_info_encrypted_response_enc VARCHAR(256),
id_token_signed_response_alg VARCHAR(256), id_token_signed_response_alg VARCHAR(256),
id_token_encrypted_response_alg VARCHAR(256), id_token_encrypted_response_alg VARCHAR(256),
id_token_encrypted_response_enc VARCHAR(256), id_token_encrypted_response_enc VARCHAR(256),
token_endpoint_auth_signing_alg VARCHAR(256), token_endpoint_auth_signing_alg VARCHAR(256),
default_max_age BIGINT, default_max_age BIGINT,
require_auth_time BOOLEAN, require_auth_time BOOLEAN,
created_at TIMESTAMP, created_at TIMESTAMP,
initiate_login_uri VARCHAR(2048), initiate_login_uri VARCHAR(2048),
clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL, clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL,
software_statement VARCHAR(4096), software_statement VARCHAR(4096),
software_id VARCHAR(2048), software_id VARCHAR(2048),
software_version VARCHAR(2048), software_version VARCHAR(2048),
code_challenge_method VARCHAR(256), code_challenge_method VARCHAR(256),
UNIQUE (client_id) UNIQUE (client_id)
); );
@ -200,17 +200,17 @@ CREATE TABLE IF NOT EXISTS client_contact (
); );
CREATE TABLE IF NOT EXISTS client_redirect_uri ( CREATE TABLE IF NOT EXISTS client_redirect_uri (
owner_id BIGINT, owner_id BIGINT,
redirect_uri VARCHAR(2048) redirect_uri VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
owner_id BIGINT, owner_id BIGINT,
redirect_uri VARCHAR(2048) redirect_uri VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS refresh_token ( CREATE TABLE IF NOT EXISTS refresh_token (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
token_value VARCHAR(4096), token_value VARCHAR(4096),
expiration TIMESTAMP, expiration TIMESTAMP,
auth_holder_id BIGINT, auth_holder_id BIGINT,
@ -218,8 +218,8 @@ CREATE TABLE IF NOT EXISTS refresh_token (
); );
CREATE TABLE IF NOT EXISTS client_resource ( CREATE TABLE IF NOT EXISTS client_resource (
owner_id BIGINT, owner_id BIGINT,
resource_id VARCHAR(256) resource_id VARCHAR(256)
); );
CREATE TABLE IF NOT EXISTS client_scope ( CREATE TABLE IF NOT EXISTS client_scope (
@ -233,7 +233,7 @@ CREATE TABLE IF NOT EXISTS token_scope (
); );
CREATE TABLE IF NOT EXISTS system_scope ( CREATE TABLE IF NOT EXISTS system_scope (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
scope VARCHAR(256) NOT NULL, scope VARCHAR(256) NOT NULL,
description VARCHAR(4096), description VARCHAR(4096),
icon VARCHAR(256), icon VARCHAR(256),
@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS system_scope (
); );
CREATE TABLE IF NOT EXISTS user_info ( CREATE TABLE IF NOT EXISTS user_info (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
sub VARCHAR(256), sub VARCHAR(256),
preferred_username VARCHAR(256), preferred_username VARCHAR(256),
name VARCHAR(256), name VARCHAR(256),
@ -270,7 +270,7 @@ CREATE TABLE IF NOT EXISTS user_info (
); );
CREATE TABLE IF NOT EXISTS whitelisted_site ( CREATE TABLE IF NOT EXISTS whitelisted_site (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
creator_user_id VARCHAR(256), creator_user_id VARCHAR(256),
client_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 ( CREATE TABLE IF NOT EXISTS pairwise_identifier (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
identifier VARCHAR(256), identifier VARCHAR(256),
sub VARCHAR(256), sub VARCHAR(256),
sector_identifier VARCHAR(2048) sector_identifier VARCHAR(2048)
); );
CREATE TABLE IF NOT EXISTS resource_set ( CREATE TABLE IF NOT EXISTS resource_set (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024) NOT NULL, name VARCHAR(1024) NOT NULL,
uri VARCHAR(1024), uri VARCHAR(1024),
icon_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 ( CREATE TABLE IF NOT EXISTS permission_ticket (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
ticket VARCHAR(256) NOT NULL, ticket VARCHAR(256) NOT NULL,
permission_id BIGINT NOT NULL, permission_id BIGINT NOT NULL,
expiration TIMESTAMP expiration TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS permission ( CREATE TABLE IF NOT EXISTS permission (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
resource_set_id BIGINT resource_set_id BIGINT
); );
@ -320,7 +320,7 @@ CREATE TABLE IF NOT EXISTS permission_scope (
); );
CREATE TABLE IF NOT EXISTS claim ( CREATE TABLE IF NOT EXISTS claim (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(256), name VARCHAR(256),
friendly_name VARCHAR(1024), friendly_name VARCHAR(1024),
claim_type 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 ( CREATE TABLE IF NOT EXISTS policy (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name VARCHAR(1024), name VARCHAR(1024),
resource_set_id BIGINT resource_set_id BIGINT
); );
@ -359,19 +359,19 @@ CREATE TABLE IF NOT EXISTS claim_issuer (
); );
CREATE TABLE IF NOT EXISTS saved_registered_client ( CREATE TABLE IF NOT EXISTS saved_registered_client (
id SERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
issuer VARCHAR(1024), issuer VARCHAR(1024),
registered_client VARCHAR(8192) registered_client VARCHAR(8192)
); );
CREATE TABLE IF NOT EXISTS device_code ( CREATE TABLE IF NOT EXISTS device_code (
id BIGINT AUTO_INCREMENT PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
device_code VARCHAR(1024), device_code VARCHAR(1024),
user_code VARCHAR(1024), user_code VARCHAR(1024),
expiration TIMESTAMP NULL, expiration TIMESTAMP NULL,
client_id VARCHAR(256), client_id VARCHAR(256),
approved BOOLEAN, approved BOOLEAN,
auth_holder_id BIGINT auth_holder_id BIGINT
); );
CREATE TABLE IF NOT EXISTS device_code_scope ( CREATE TABLE IF NOT EXISTS device_code_scope (

View File

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

View File

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

View File

@ -211,7 +211,7 @@
"no-clients": "There are no registered clients on this server.", "no-clients": "There are no registered clients on this server.",
"no-matches": "There are no clients that match your search criteria.", "no-matches": "There are no clients that match your search criteria.",
"no-redirect": "NO REDIRECT URI", "no-redirect": "NO REDIRECT URI",
"registered": "Registrered", "registered": "Registered",
"search": "Search...", "search": "Search...",
"whitelist": "Whitelist", "whitelist": "Whitelist",
"unknown": "at an unknown time" "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.", "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", "new": "New Scope",
"text": "There are no system scopes defined. Clients may still have custom scopes.", "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." "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.", "associated-refresh": "This access token was issued with an associated refresh token.",
"click-to-display": "Click to display full token value", "click-to-display": "Click to display full token value",
"confirm": "Are you sure sure you would like to revoke this token?", "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", "expires": "Expires",
"no-access": "There are no active access tokens.", "no-access": "There are no active access tokens.",
"no-refresh": "There are no active refresh tokens.", "no-refresh": "There are no active refresh tokens.",
@ -522,7 +522,7 @@
"title": "Logout requested", "title": "Logout requested",
"header": "Logout Requested", "header": "Logout Requested",
"requested": "Logout has been requested by ", "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", "submit": "Log Out",
"deny": "Stay Logged In" "deny": "Stay Logged In"
}, },
@ -533,4 +533,4 @@
"loggedOut": "You have been logged out of the identity server." "loggedOut": "You have been logged out of the identity server."
} }
} }
} }

View File

@ -795,7 +795,7 @@
</div> </div>
<div class="control-group" id="postLogoutRedirectUris"> <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 class="controls">
</div> </div>
</div> </div>
@ -904,4 +904,4 @@
<% } else { %> <% } else { %>
<span class="label label-warning">?</span> <span class="label label-warning">?</span>
<% } %> <% } %>
</script> </script>

View File

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

View File

@ -91,57 +91,60 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
JWT jwt = jwtAuth.getJwt(); JWT jwt = jwtAuth.getJwt();
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet(); JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
if (!(jwt instanceof SignedJWT)) {
throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName());
}
// check the signature with nimbus // check the signature with nimbus
if (jwt instanceof SignedJWT) { SignedJWT jws = (SignedJWT) jwt;
SignedJWT jws = (SignedJWT)jwt;
JWSAlgorithm alg = jws.getHeader().getAlgorithm(); JWSAlgorithm alg = jws.getHeader().getAlgorithm();
if (client.getTokenEndpointAuthSigningAlg() != null && if (client.getTokenEndpointAuthSigningAlg() != null &&
!client.getTokenEndpointAuthSigningAlg().equals(alg)) { !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 ||
client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE) ||
client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC) ||
client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) {
// this client doesn't support this type of authentication
throw new AuthenticationServiceException("Client does not support this authentication method.");
} else if ((client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) &&
(alg.equals(JWSAlgorithm.RS256)
|| alg.equals(JWSAlgorithm.RS384)
|| alg.equals(JWSAlgorithm.RS512)
|| alg.equals(JWSAlgorithm.ES256)
|| alg.equals(JWSAlgorithm.ES384)
|| alg.equals(JWSAlgorithm.ES512)
|| alg.equals(JWSAlgorithm.PS256)
|| alg.equals(JWSAlgorithm.PS384)
|| alg.equals(JWSAlgorithm.PS512)))
|| (client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT) &&
(alg.equals(JWSAlgorithm.HS256)
|| alg.equals(JWSAlgorithm.HS384)
|| alg.equals(JWSAlgorithm.HS512)))) {
// double-check the method is asymmetrical if we're in HEART mode
if (config.isHeartMode() && !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) {
throw new AuthenticationServiceException("[HEART mode] Invalid authentication method");
} }
if (client.getTokenEndpointAuthMethod() == null || JWTSigningAndValidationService validator = validators.getValidator(client, alg);
client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE) ||
client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC) ||
client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) {
// this client doesn't support this type of authentication if (validator == null) {
throw new AuthenticationServiceException("Client does not support this authentication method."); throw new AuthenticationServiceException("Unable to create signature validator for client " + client + " and algorithm " + alg);
} else if ((client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) &&
(alg.equals(JWSAlgorithm.RS256)
|| alg.equals(JWSAlgorithm.RS384)
|| alg.equals(JWSAlgorithm.RS512)
|| alg.equals(JWSAlgorithm.ES256)
|| alg.equals(JWSAlgorithm.ES384)
|| alg.equals(JWSAlgorithm.ES512)
|| alg.equals(JWSAlgorithm.PS256)
|| alg.equals(JWSAlgorithm.PS384)
|| alg.equals(JWSAlgorithm.PS512)))
|| (client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT) &&
(alg.equals(JWSAlgorithm.HS256)
|| alg.equals(JWSAlgorithm.HS384)
|| alg.equals(JWSAlgorithm.HS512)))) {
// double-check the method is asymmetrical if we're in HEART mode
if (config.isHeartMode() && !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) {
throw new AuthenticationServiceException("[HEART mode] Invalid authentication method");
}
JWTSigningAndValidationService validator = validators.getValidator(client, alg);
if (validator == null) {
throw new AuthenticationServiceException("Unable to create signature validator for client " + client + " and algorithm " + alg);
}
if (!validator.validateSignature(jws)) {
throw new AuthenticationServiceException("Signature did not validate for presented JWT authentication.");
}
} else {
throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
} }
if (!validator.validateSignature(jws)) {
throw new AuthenticationServiceException("Signature did not validate for presented JWT authentication.");
}
} else {
throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
} }
// check the issuer // check the issuer

View File

@ -151,6 +151,8 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
idClaims.claim("at_hash", at_hash); idClaims.claim("at_hash", at_hash);
} }
addCustomIdTokenClaims(idClaims, client, request, sub, accessToken);
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) && client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
&& (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) { && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {
@ -335,4 +337,18 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
this.authenticationHolderRepository = authenticationHolderRepository; 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)); builder.audience(Lists.newArrayList(audience));
} }
addCustomAccessTokenClaims(builder, token, authentication);
JWTClaimsSet claims = builder.build(); JWTClaimsSet claims = builder.build();
JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); 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> <modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId> <groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId> <artifactId>openid-connect-parent</artifactId>
<version>1.3.2-SNAPSHOT</version> <version>1.3.3-SNAPSHOT</version>
<name>MITREid Connect</name> <name>MITREid Connect</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
@ -386,7 +386,7 @@
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId> <artifactId>spring-security-bom</artifactId>
<version>4.2.2.RELEASE</version> <version>4.2.4.RELEASE</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -585,7 +585,7 @@
<dependency> <dependency>
<groupId>com.nimbusds</groupId> <groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId> <artifactId>nimbus-jose-jwt</artifactId>
<version>4.34.2</version> <version>5.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>

View File

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

View File

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