\nPour plus d'informations sur le projet, voir \nMITREid Connect sur GitHub. \nVous pouvez y soumettre des rapports de bogues, donner des retours d'informations ou même contribuer à des correctifs de code pour des fonctionnalités supplémentaires que vous aimeriez voir apparaître."
+ },
+ "statistics": {
+ "title": "Statistiques",
+ "number_users": "Nombre d'utilisateurs : {0}",
+ "number_clients": "Clients autorisés : {0}",
+ "number_approvals": "Sites approuvés : {0}"
+ },
+ "home": {
+ "title": "Accueil",
+ "welcome": {
+ "title": "Bienvenue !",
+ "body": "\nOpenID Connect est un protocole d'identité fédérée à l'échelle de l'Internet basé sur le framework d'autorisation OAuth2. \nOpenID Connect vous permet de vous connecter à un site distant en utilisant votre identité sans révéler vos identifiants, comme un nom d'utilisateur et un mot de passe.
\n
En savoir plus »"
+ },
+ "more": "Plus",
+ "about": {
+ "title": "A prppos",
+ "body": "Ce service OpenID Connect est construit à partir du projet Open Source MITREid Connect, du \nthe MIT Internet Trust Consortium."
+ },
+ "contact": {
+ "title": "Contact",
+ "body": "\nPour plus d'informations ou du support, contactez les administrateurs de ce système.
\n
Email »"
+ },
+ "statistics": {
+ "title": "Statistiques actuelles",
+ "loading": "Chargement en cours...",
+ "number_users": "Nombre d'utilisateurs : {0}",
+ "number_clients": "Clients autorisés : {0}",
+ "number_approvals": "Sites approuvés : {0}"
+ }
+ },
+ "contact": {
+ "title": "Contact",
+ "body": "Pour signaler des bogues avec le logiciel MITREid Connect lui-même, utilisez \nGitHub issue tracker. \nPour les problèmes relatifs à ce serveur, contactez l'administrateur du serveur."
+ },
+ "topbar": {
+ "about": "A propos",
+ "contact": "Contact",
+ "statistics": "Statistiques",
+ "home": "Accueil",
+ "login": "Se connecter",
+ "logout": "Se déconnecter"
+ },
+ "sidebar": {
+ "administrative": {
+ "title": "Administratif",
+ "manage_clients": "Gérer les Clients",
+ "whitelisted_clients": "Clients sur liste blanche",
+ "blacklisted_clients": "Clients sur liste noire",
+ "system_scopes": "Scopes Système"
+ },
+ "personal": {
+ "title": "Personnel",
+ "approved_sites": "Gérer les Sites Approuvés",
+ "active_tokens": "Gérer les Jetons Actifs",
+ "profile_information": "Voir les Informations du Profil"
+ },
+ "developer": {
+ "title": "Développeur",
+ "client_registration": "Inscription en libre-service de clients",
+ "resource_registration": "Enregistrement en libre-service de ressources protégées"
+ }
+ },
+ "manage": {
+ "ok": "OK",
+ "loading": "Chargement en cours",
+ "title": "Console de Gestion"
+ },
+ "approve": {
+ "dynamically-registered-unknown": "à un moment inconnu",
+ "title": "Autoriser l'accès",
+ "error": {
+ "not_granted": "L'accès ne pouvait pas être accordé."
+ },
+ "required_for": "Approbation requise pour",
+ "dynamically_registered": "Ce client a été enregistré dynamiquement {0}.",
+ "caution": {
+ "title": "Attention",
+ "message": {
+ "none": "Cela n'a jamais été approuvé préalablement.",
+ "singular": "Cela a été approuvé {0} fois préalablement.",
+ "plural": "Cela a été approuvé {0} fois préalablement."
+ }
+ },
+ "more_information": "Plus d'information",
+ "home_page": "Page d'Accueil",
+ "policy": "Politique",
+ "terms": "Conditions d'utilisation",
+ "contacts": "Contacts Administratifs",
+ "warning": "Avertissement",
+ "no_redirect_uri": "Ce client n' a pas d'URIs de redirection enregistrées et quelqu'un pourrait utiliser un URI malveillant ici.",
+ "redirect_uri": "Vous serez redirigé vers la page suivante si vous cliquez sur Approuver: {0}",
+ "pairwise": "Ce client utilise un identificateur de pair, ce qui rend plus difficile la corrélation de votre identité entre les sites.",
+ "no_scopes": "Ce client n' a pas de scopes enregistrés et est donc autorisés à demander tous les scopes disponibles sur le système. Procédez avec prudence.",
+ "access_to": "Accéder à",
+ "remember": {
+ "title": "Se souvenir de cette décision",
+ "until_revoke": "Se souvenir de cette décision jusqu'à ce que je la révoque",
+ "one_hour": "Se souvenir de cette décision pour une heure",
+ "next_time": "Me le redemander la prochaine fois"
+ },
+ "do_authorize": "Autorisez-vous",
+ "label": {
+ "authorize": "Autoriser",
+ "deny": "Refuser"
+ }
+ },
+ "error": {
+ "title": "Erreur",
+ "header": "Erreur",
+ "header-with-message": "Erreur : ",
+ "reload": "De plus, on dirait que vous n'êtes pas connecté. Rechargez la page pour réessayer. Vous risquez de perdre tout travail non sauvegardé.",
+ "reload-button": "Recharger",
+ "message": "Il y a eu une erreur dans le traitement de votre demande.",
+ "server-message": "Le serveur a dit : "
+ },
+ "login": {
+ "login_with_username_and_password": "Se connecter avec nom d'utilisateur et mot de passe",
+ "username": "Nom d'utilisateur",
+ "password": "Mot de passe",
+ "login-button": "Se connecter",
+ "error": "Le système n'a pas pu vous identifier. Veuillez réessayer."
+ },
+ "device": {
+ "request_code": {
+ "title": "Entrez le Code",
+ "header": "Entrez le Code",
+ "description": "Entrez le code afficher sur votre appareil dans le champ ci-dessous et pressez Envoyer",
+ "submit": "Envoyer"
+ },
+ "error": {
+ "noUserCode": "Le code que vous avez entré n'a pas été trouvé.",
+ "expiredUserCode": "Le code que vous avez entré a expiré. Retournez sur votre appareil et demandez un nouveau code.",
+ "userCodeAlreadyApproved": "Le code que vous avez entré a déjà été utilisé.",
+ "userCodeMismatch": "Il y a eu une erreur dans le traitement du code que vous avez entré. Essayez de rafraîchir la page et revenez sur votre appareil pour demander un nouveau code.",
+ "error": "Il y a eu une erreur dans le traitement du code que vous avez entré. Retourner à votre appareil et demander un nouveau code."
+ },
+ "approve": {
+ "approved": "L'appareil a été approuvé.",
+ "notApproved": "L'appareil n'a pas été approuvé."
+ }
+ },
+ "logout": {
+ "confirmation": {
+ "title": "Déconnexion demandée",
+ "header": "Déconnexion demandée",
+ "requested": "La déconnexion a été demandée par ",
+ "explanation": "Voulez-vous vous déconnecter du fournisseur d'identité ? Cela n'affectera pas votre session sur d'autres systèmes.",
+ "submit": "Se déconnecter",
+ "deny": "Rester connecté"
+ },
+ "post": {
+ "title": "Se déconnecter",
+ "header": "Se déconnecter",
+ "notLoggedOut": "Vous n'avez pas été déconnecté du serveur d'identité. Vous pouvez fermer ce navigateur ou vous reconnecter.",
+ "loggedOut": "Vous avez été déconnecté du serveur d'identité."
+ }
+ }
+}
From 01eb1401a39e664e76920528b858ed51a71ac8cf Mon Sep 17 00:00:00 2001
From: Stefan Bodewig
Date: Fri, 12 Jan 2018 15:22:37 +0100
Subject: [PATCH 09/58] add hook for custom JWT claims to
DefaultOIDCTokenService
---
.../service/impl/DefaultOIDCTokenService.java | 16 ++++
.../impl/TestDefaultOIDCTokenService.java | 81 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java
index 1d0697204c..aea5bfb26e 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java
+++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java
@@ -151,6 +151,8 @@ public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date
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 void setAuthenticationHolderRepository(
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) {
+ }
+
}
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java
new file mode 100644
index 0000000000..01405474c0
--- /dev/null
+++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java
@@ -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);
+ }
+}
From a1a45aa36a1edbb7a1d45271dc0ae65a67914462 Mon Sep 17 00:00:00 2001
From: Mark Janssen
Date: Sat, 20 Jan 2018 13:27:16 +0100
Subject: [PATCH 10/58] Fix interface for issuer URI without trailing slash
---
.../src/main/webapp/WEB-INF/views/home.jsp | 5 ++++-
.../src/main/webapp/resources/js/admin.js | 3 +++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
index fc83dabedb..5fa2495a53 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
+++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
@@ -64,7 +64,10 @@
$(document).ready(function() {
$('#stats').hide();
- var base = $('base').attr('href');
+ var base = $('base').attr('href');
+ if (base.substr(-1) !== '/') {
+ base += '/';
+ }
$.getJSON(base + 'api/stats/summary', function(data) {
var stats = data;
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/admin.js b/openid-connect-server-webapp/src/main/webapp/resources/js/admin.js
index d1bc2d8836..8a2458c356 100644
--- a/openid-connect-server-webapp/src/main/webapp/resources/js/admin.js
+++ b/openid-connect-server-webapp/src/main/webapp/resources/js/admin.js
@@ -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);
From f7da25fbe8a1d27d67d924661ecf3a0e35419deb Mon Sep 17 00:00:00 2001
From: Brady Mulhollem
Date: Mon, 5 Feb 2018 13:28:48 -0500
Subject: [PATCH 11/58] Upgrade nimbus-jose-jwt to 5.4.
---
.../service/impl/TestSignedAuthRequestUrlBuilder.java | 2 +-
.../service/impl/SymmetricKeyJWTValidatorCacheService.java | 6 ++++--
.../src/test/java/org/mitre/jose/TestJWKSetKeyStore.java | 4 ++--
.../impl/TestDefaultJWTEncryptionAndDecryptionService.java | 6 +++---
pom.xml | 2 +-
5 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java
index f2afad099f..a82f6925e8 100644
--- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java
+++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java
@@ -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 keys = Maps.newHashMap();
keys.put("client", key);
diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
index 55018c781a..8d29fff33c 100644
--- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
+++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
@@ -99,8 +99,10 @@ public JWTSigningAndValidationService load(String key) throws Exception {
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 keys = ImmutableMap.of(id, jwk);
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keys);
diff --git a/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java b/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java
index 01f24fe766..c8ffacf1fb 100644
--- a/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java
+++ b/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java
@@ -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 keys_list = new LinkedList<>();
diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java b/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
index faaf99d6d6..edfc127266 100644
--- a/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
+++ b/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
@@ -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 keys = new ImmutableMap.Builder()
diff --git a/pom.xml b/pom.xml
index 4d121709f4..4ef0bc8c03 100644
--- a/pom.xml
+++ b/pom.xml
@@ -585,7 +585,7 @@
com.nimbusdsnimbus-jose-jwt
- 4.34.2
+ 5.4org.bouncycastle
From 36ec1b82e64e46b4b05227d8311c8a935fcf7a3a Mon Sep 17 00:00:00 2001
From: Marco Descher
Date: Tue, 6 Feb 2018 08:41:14 +0100
Subject: [PATCH 12/58] Minor type (Registrered -> Registered)
---
.../src/main/webapp/resources/js/locale/en/messages.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json b/openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
index 0385c6211d..06cdd34b07 100644
--- a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
+++ b/openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
@@ -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"
From c38b9d7a42c03b76cc3331d58a9298b47efeb547 Mon Sep 17 00:00:00 2001
From: Tomasz Borowiec
Date: Mon, 5 Feb 2018 14:19:36 +0100
Subject: [PATCH 13/58] added PlainJWT and EncryptedJWT support + tests
---
.../JWTBearerAuthenticationProvider.java | 14 +-
.../TestJWTBearerAuthenticationProvider.java | 473 ++++++++++++++++++
2 files changed, 484 insertions(+), 3 deletions(-)
create mode 100644 openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
index a5d7d3f342..928e120761 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
+++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
@@ -46,6 +46,7 @@
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
/**
@@ -91,15 +92,20 @@ public Authentication authenticate(Authentication authentication) throws Authent
JWT jwt = jwtAuth.getJwt();
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
- // check the signature with nimbus
- if (jwt instanceof SignedJWT) {
+ if (jwt instanceof PlainJWT) {
+ if (!AuthMethod.NONE.equals(client.getTokenEndpointAuthMethod())) {
+ throw new AuthenticationServiceException("Client does not support this authentication method.");
+ }
+ } else if (jwt instanceof SignedJWT) {
+ // check the signature with nimbus
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,6 +148,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
} else {
throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
}
+ } else {
+ throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName());
}
// check the issuer
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
new file mode 100644
index 0000000000..1a2eb08458
--- /dev/null
+++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
@@ -0,0 +1,473 @@
+package org.mitre.openid.connect.assertion;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.core.Is.is;
+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_when_AuthMethod_is_different_than_NONE() {
+ mockPlainJWTAuthAttempt();
+ List unsupportedAuthMethods = Arrays.asList(
+ null, AuthMethod.PRIVATE_KEY, AuthMethod.PRIVATE_KEY, AuthMethod.SECRET_BASIC, AuthMethod.SECRET_JWT, AuthMethod.SECRET_POST
+ );
+
+ for (AuthMethod authMethod : unsupportedAuthMethods) {
+ when(client.getTokenEndpointAuthMethod()).thenReturn(authMethod);
+
+ 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_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 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 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 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_for_EncryptedJWT() {
+ EncryptedJWT encryptedJWT = createEncryptedJWT();
+ when(token.getJwt()).thenReturn(encryptedJWT);
+
+ Throwable thrown = authenticateAndReturnThrownException();
+
+ assertThat(thrown, instanceOf(AuthenticationServiceException.class));
+ assertThat(thrown.getMessage(), is("Unsupported JWT type: " + EncryptedJWT.class.getName()));
+ }
+
+ @Test
+ public void should_throw_AuthenticationServiceException_when_null_issuer() {
+ JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(null).build();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(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();
+ mockPlainJWTAuthAttempt(jwtClaimsSet);
+
+ Throwable thrown = authenticateAndReturnThrownException();
+
+ assertThat(thrown, instanceOf(AuthenticationServiceException.class));
+ assertThat(thrown.getMessage(), startsWith("Audience does not match"));
+ }
+
+ @Test
+ public void should_return_valid_token_for_PlainJWT_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();
+ PlainJWT jwt = mockPlainJWTAuthAttempt(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_for_SignedJWT_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_for_PlainJWT_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();
+ PlainJWT jwt = mockPlainJWTAuthAttempt(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));
+ }
+
+ @Test
+ public void should_return_valid_token_for_SignedJWT_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 PlainJWT mockPlainJWTAuthAttempt() {
+ return mockPlainJWTAuthAttempt(createJwtClaimsSet());
+ }
+
+ private PlainJWT mockPlainJWTAuthAttempt(JWTClaimsSet jwtClaimsSet) {
+ PlainJWT plainJWT = createPlainJWT(jwtClaimsSet);
+ when(token.getJwt()).thenReturn(plainJWT);
+ return plainJWT;
+ }
+
+ 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 PlainJWT createPlainJWT(JWTClaimsSet jwtClaimsSet) {
+ return new PlainJWT(jwtClaimsSet);
+ }
+
+ 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 EncryptedJWT createEncryptedJWT() {
+ JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256GCM).build();
+ return new EncryptedJWT(jweHeader, createJwtClaimsSet());
+ }
+
+ private JWTClaimsSet createJwtClaimsSet() {
+ return new JWTClaimsSet.Builder()
+ .issuer(CLIENT_ID)
+ .expirationTime(new Date())
+ .audience("http://issuer.com/")
+ .build();
+ }
+
+}
From 37fba622b9c9f621bc563f6da193748ad420d8ca Mon Sep 17 00:00:00 2001
From: Tomasz Borowiec
Date: Wed, 7 Feb 2018 10:45:28 +0100
Subject: [PATCH 14/58] Throwing exception on all other JWT types than
SignedJWT
---
.../JWTBearerAuthenticationProvider.java | 99 +++++++--------
.../TestJWTBearerAuthenticationProvider.java | 119 +++++-------------
2 files changed, 77 insertions(+), 141 deletions(-)
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
index 928e120761..aa110ea2c9 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
+++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
@@ -46,7 +46,6 @@
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
/**
@@ -92,64 +91,60 @@ public Authentication authenticate(Authentication authentication) throws Authent
JWT jwt = jwtAuth.getJwt();
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
- if (jwt instanceof PlainJWT) {
- if (!AuthMethod.NONE.equals(client.getTokenEndpointAuthMethod())) {
- throw new AuthenticationServiceException("Client does not support this authentication method.");
+ if (!(jwt instanceof SignedJWT)) {
+ throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName());
+ }
+
+ // check the signature with nimbus
+ SignedJWT jws = (SignedJWT) jwt;
+
+ JWSAlgorithm alg = jws.getHeader().getAlgorithm();
+
+ if (client.getTokenEndpointAuthSigningAlg() != null &&
+ !client.getTokenEndpointAuthSigningAlg().equals(alg)) {
+ 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");
}
- } else if (jwt instanceof SignedJWT) {
- // check the signature with nimbus
- SignedJWT jws = (SignedJWT)jwt;
- JWSAlgorithm alg = jws.getHeader().getAlgorithm();
+ JWTSigningAndValidationService validator = validators.getValidator(client, alg);
- if (client.getTokenEndpointAuthSigningAlg() != null &&
- !client.getTokenEndpointAuthSigningAlg().equals(alg)) {
- throw new AuthenticationServiceException("Client's registered token endpoint signing algorithm (" + client.getTokenEndpointAuthSigningAlg()
- + ") does not match token's actual algorithm (" + alg.getName() + ")");
+ if (validator == null) {
+ throw new AuthenticationServiceException("Unable to create signature validator for client " + client + " and algorithm " + alg);
}
- 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");
- }
-
- 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("Unsupported JWT type: " + jwt.getClass().getName());
+ throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg);
}
// check the issuer
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
index 1a2eb08458..fde99f499e 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
+++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
@@ -2,8 +2,8 @@
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.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
@@ -110,20 +110,23 @@ public void should_throw_UsernameNotFoundException_when_clientService_throws_Inv
}
@Test
- public void should_throw_AuthenticationServiceException_for_PlainJWT_when_AuthMethod_is_different_than_NONE() {
+ public void should_throw_AuthenticationServiceException_for_PlainJWT() {
mockPlainJWTAuthAttempt();
- List unsupportedAuthMethods = Arrays.asList(
- null, AuthMethod.PRIVATE_KEY, AuthMethod.PRIVATE_KEY, AuthMethod.SECRET_BASIC, AuthMethod.SECRET_JWT, AuthMethod.SECRET_POST
- );
- for (AuthMethod authMethod : unsupportedAuthMethods) {
- when(client.getTokenEndpointAuthMethod()).thenReturn(authMethod);
+ Throwable thrown = authenticateAndReturnThrownException();
- Throwable thrown = authenticateAndReturnThrownException();
+ assertThat(thrown, instanceOf(AuthenticationServiceException.class));
+ assertThat(thrown.getMessage(), is("Unsupported JWT type: " + PlainJWT.class.getName()));
+ }
- assertThat(thrown, instanceOf(AuthenticationServiceException.class));
- assertThat(thrown.getMessage(), is("Client does not support this authentication method."));
- }
+ @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
@@ -228,21 +231,10 @@ public void should_throw_AuthenticationServiceException_for_SignedJWT_when_inval
assertThat(thrown.getMessage(), is("Signature did not validate for presented JWT authentication."));
}
- @Test
- public void should_throw_AuthenticationServiceException_for_EncryptedJWT() {
- EncryptedJWT encryptedJWT = createEncryptedJWT();
- when(token.getJwt()).thenReturn(encryptedJWT);
-
- Throwable thrown = authenticateAndReturnThrownException();
-
- assertThat(thrown, instanceOf(AuthenticationServiceException.class));
- assertThat(thrown.getMessage(), is("Unsupported JWT type: " + EncryptedJWT.class.getName()));
- }
-
@Test
public void should_throw_AuthenticationServiceException_when_null_issuer() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(null).build();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -253,7 +245,7 @@ public void should_throw_AuthenticationServiceException_when_null_issuer() {
@Test
public void should_throw_AuthenticationServiceException_when_not_matching_issuer() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer("not matching").build();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -264,7 +256,7 @@ public void should_throw_AuthenticationServiceException_when_not_matching_issuer
@Test
public void should_throw_AuthenticationServiceException_when_null_expiration_time() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(null).build();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -276,7 +268,7 @@ public void should_throw_AuthenticationServiceException_when_null_expiration_tim
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();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -288,7 +280,7 @@ public void should_throw_AuthenticationServiceException_when_expired_jwt() {
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();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -300,7 +292,7 @@ public void should_throw_AuthenticationServiceException_when_jwt_valid_in_future
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();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -311,7 +303,7 @@ public void should_throw_AuthenticationServiceException_when_jwt_issued_in_futur
@Test
public void should_throw_AuthenticationServiceException_when_unmatching_audience() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(new Date()).audience("invalid").build();
- mockPlainJWTAuthAttempt(jwtClaimsSet);
+ mockSignedJWTAuthAttempt(jwtClaimsSet);
Throwable thrown = authenticateAndReturnThrownException();
@@ -320,28 +312,7 @@ public void should_throw_AuthenticationServiceException_when_unmatching_audience
}
@Test
- public void should_return_valid_token_for_PlainJWT_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();
- PlainJWT jwt = mockPlainJWTAuthAttempt(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_for_SignedJWT_when_audience_contains_token_endpoint() {
+ public void should_return_valid_token_when_audience_contains_token_endpoint() {
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
.issuer(CLIENT_ID)
.subject(SUBJECT)
@@ -362,29 +333,7 @@ public void should_return_valid_token_for_SignedJWT_when_audience_contains_token
}
@Test
- public void should_return_valid_token_for_PlainJWT_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();
- PlainJWT jwt = mockPlainJWTAuthAttempt(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));
- }
-
- @Test
- public void should_return_valid_token_for_SignedJWT_when_issuer_does_not_end_with_slash_and_audience_contains_token_endpoint() {
+ 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)
@@ -405,14 +354,15 @@ public void should_return_valid_token_for_SignedJWT_when_issuer_does_not_end_wit
assertThat(token.getAuthorities().size(), is(4));
}
- private PlainJWT mockPlainJWTAuthAttempt() {
- return mockPlainJWTAuthAttempt(createJwtClaimsSet());
+ private void mockPlainJWTAuthAttempt() {
+ PlainJWT plainJWT = new PlainJWT(createJwtClaimsSet());
+ when(token.getJwt()).thenReturn(plainJWT);
}
- private PlainJWT mockPlainJWTAuthAttempt(JWTClaimsSet jwtClaimsSet) {
- PlainJWT plainJWT = createPlainJWT(jwtClaimsSet);
- when(token.getJwt()).thenReturn(plainJWT);
- return 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() {
@@ -436,10 +386,6 @@ private Throwable authenticateAndReturnThrownException() {
throw new AssertionError("No exception thrown when expected");
}
- private PlainJWT createPlainJWT(JWTClaimsSet jwtClaimsSet) {
- return new PlainJWT(jwtClaimsSet);
- }
-
private SignedJWT createSignedJWT() {
return createSignedJWT(JWSAlgorithm.RS256);
}
@@ -457,11 +403,6 @@ private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm, JWTClaimsSet jwtCla
return new SignedJWT(jwsHeader, jwtClaimsSet);
}
- private EncryptedJWT createEncryptedJWT() {
- JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256GCM).build();
- return new EncryptedJWT(jweHeader, createJwtClaimsSet());
- }
-
private JWTClaimsSet createJwtClaimsSet() {
return new JWTClaimsSet.Builder()
.issuer(CLIENT_ID)
From 7dc309c5affbb990e49ca73489ce54a335f5eea4 Mon Sep 17 00:00:00 2001
From: Justin Richer
Date: Wed, 7 Feb 2018 09:03:09 -0500
Subject: [PATCH 15/58] Update CHANGELOG.md
---
CHANGELOG.md | 35 ++++++++++++++++++++---------------
1 file changed, 20 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea08bd5614..c2bb3c65f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,22 +1,27 @@
Unreleased:
- - Added changelog
- - Set default redirect URI resolver strict matching to true
- - 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
*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
From 6497af40e813cbd2da2b747159f5d769e5114129 Mon Sep 17 00:00:00 2001
From: Justin Richer
Date: Wed, 7 Feb 2018 09:05:43 -0500
Subject: [PATCH 16/58] removed erroneous not yet implemented tag from client
page
---
CHANGELOG.md | 1 +
.../src/main/webapp/resources/template/client.html | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2bb3c65f6..2d1bc15d86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ Unreleased:
- 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
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/client.html b/openid-connect-server-webapp/src/main/webapp/resources/template/client.html
index fa4b250af3..c7ceb88d46 100644
--- a/openid-connect-server-webapp/src/main/webapp/resources/template/client.html
+++ b/openid-connect-server-webapp/src/main/webapp/resources/template/client.html
@@ -795,7 +795,7 @@