From 62ee3fa8d0c8bfd1c50cc4449205a13d62b31a02 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Thu, 28 Oct 2021 01:41:55 +0200 Subject: [PATCH 1/8] FISH-5741 fix NPE in processing claim set --- .../fish/payara/security/openid/domain/AccessTokenImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java b/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java index 5dae4c37..d12651f1 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java @@ -54,7 +54,6 @@ import fish.payara.security.openid.controller.JWTValidator; import java.util.Date; -import java.util.Optional; import static java.util.Objects.nonNull; @@ -88,7 +87,7 @@ public AccessTokenImpl(String tokenType, String token, Long expiresIn, String sc try { this.tokenJWT = JWTParser.parse(token); jwtClaimsSet = tokenJWT.getJWTClaimsSet(); - this.claims = jwtClaimsSet.getClaims(); + this.claims = jwtClaimsSet == null ? null : jwtClaimsSet.getClaims(); } catch (ParseException ex) { // Access token doesn't need to be JWT at all } From 77425c69a69ca0fd197b8309343835abd768c458 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 2 Nov 2021 14:55:27 +0100 Subject: [PATCH 2/8] FISH-5741 don't load autoconfiguration if provider url not provided --- .../controller/ConfigurationController.java | 21 ++++++- .../controller/ProviderMetadataContoller.java | 63 +++++++++++-------- .../openid/domain/OpenIdProviderMetadata.java | 11 ++++ 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java index c49789cf..4547ee70 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java @@ -53,9 +53,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; @@ -69,6 +67,7 @@ import fish.payara.security.annotations.LogoutDefinition; import fish.payara.security.openid.OpenIdUtil; import fish.payara.security.openid.api.OpenIdConstant; +import java.util.Collections; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -217,9 +216,25 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition int tokenMinValidity = OpenIdUtil.getConfiguredValue(Integer.class, definition.tokenMinValidity(), provider, OpenIdAuthenticationDefinition.OPENID_MP_TOKEN_MIN_VALIDITY); boolean userClaimsFromIDToken = OpenIdUtil.getConfiguredValue(Boolean.class, definition.userClaimsFromIDToken(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USER_CLAIMS_FROM_ID_TOKEN); + fish.payara.security.openid.domain.OpenIdProviderMetadata openIdProviderMetadata; + if (providerDocument != null) { + openIdProviderMetadata = new fish.payara.security.openid.domain.OpenIdProviderMetadata(providerDocument); + } else { + // FIXME: necessary to provide meaningful default data + openIdProviderMetadata = new fish.payara.security.openid.domain.OpenIdProviderMetadata( + null, + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet()); + } + OpenIdConfiguration configuration = new OpenIdConfiguration() .setProviderMetadata( - new fish.payara.security.openid.domain.OpenIdProviderMetadata(providerDocument) + openIdProviderMetadata .setAuthorizationEndpoint(authorizationEndpoint) .setTokenEndpoint(tokenEndpoint) .setUserinfoEndpoint(userinfoEndpoint) diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java index f3cd6e05..5a5f875c 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java @@ -40,7 +40,6 @@ import java.io.StringReader; import java.util.HashMap; import java.util.Map; -import static java.util.Objects.isNull; import javax.enterprise.context.ApplicationScoped; import javax.json.Json; import javax.json.JsonObject; @@ -77,37 +76,47 @@ public class ProviderMetadataContoller { * */ public JsonObject getDocument(String providerURI) { - if (isNull(providerDocuments.get(providerURI))) { - if (providerURI.endsWith("/")) { - providerURI = providerURI.substring(0, providerURI.length() - 1); + if (!providerDocuments.containsKey(providerURI)) { + JsonObject responseObject; + if ("".equals(providerURI)) { + // no providerURI provided, data must be load from @OpenIdProviderMetadata + responseObject = JsonObject.EMPTY_JSON_OBJECT; + } else { + responseObject = downloadWellKnownUris(providerURI); } + providerDocuments.put(providerURI, responseObject); + } + return providerDocuments.get(providerURI); + } - if (!providerURI.endsWith(WELL_KNOWN_PREFIX)) { - providerURI = providerURI + WELL_KNOWN_PREFIX; - } + private JsonObject downloadWellKnownUris(String providerURI) { + if (providerURI.endsWith("/")) { + providerURI = providerURI.substring(0, providerURI.length() - 1); + } - Client client = ClientBuilder.newClient(); - WebTarget target = client.target(providerURI); - Response response = target.request() - .accept(APPLICATION_JSON) - .get(); + if (!providerURI.endsWith(WELL_KNOWN_PREFIX)) { + providerURI = providerURI + WELL_KNOWN_PREFIX; + } - if (response.getStatus() == Status.OK.getStatusCode()) { - // Get back the result of the REST request - String responseBody = response.readEntity(String.class); - try (JsonReader reader = Json.createReader(new StringReader(responseBody))) { - JsonObject responseObject = reader.readObject(); - providerDocuments.put(providerURI, responseObject); - } - } else { - throw new IllegalStateException(String.format( - "Unable to retrieve OpenID Provider's [%s] configuration document, HTTP respons code : [%s] ", - providerURI, - response.getStatus() - )); + Client client = ClientBuilder.newClient(); + WebTarget target = client.target(providerURI); + Response response = target.request() + .accept(APPLICATION_JSON) + .get(); + + if (response.getStatus() == Status.OK.getStatusCode()) { + // Get back the result of the REST request + String responseBody = response.readEntity(String.class); + try (JsonReader reader = Json.createReader(new StringReader(responseBody))) { + JsonObject responseObject = reader.readObject(); + return responseObject; } + } else { + throw new IllegalStateException(String.format( + "Unable to retrieve OpenID Provider's [%s] configuration document, HTTP respons code : [%s] ", + providerURI, + response.getStatus() + )); } - return providerDocuments.get(providerURI); } - } diff --git a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java index 2d87df40..df4277e0 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java @@ -77,6 +77,17 @@ public class OpenIdProviderMetadata { private final Set idTokenEncryptionMethodsSupported; private final Set subjectTypesSupported; + public OpenIdProviderMetadata(String issuerURI, Set scopesSupported, Set claimsSupported, Set responseTypeSupported, Set idTokenSigningAlgorithmsSupported, Set idTokenEncryptionAlgorithmsSupported, Set idTokenEncryptionMethodsSupported, Set subjectTypesSupported) { + this.issuerURI = issuerURI; + this.scopesSupported = scopesSupported; + this.claimsSupported = claimsSupported; + this.responseTypeSupported = responseTypeSupported; + this.idTokenSigningAlgorithmsSupported = idTokenSigningAlgorithmsSupported; + this.idTokenEncryptionAlgorithmsSupported = idTokenEncryptionAlgorithmsSupported; + this.idTokenEncryptionMethodsSupported = idTokenEncryptionMethodsSupported; + this.subjectTypesSupported = subjectTypesSupported; + } + public OpenIdProviderMetadata(JsonObject document) { this.document = document; this.issuerURI = document.getString(ISSUER); From 077c5ef1c4e5fbedca81f59c19ea403d90d292c0 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 9 Nov 2021 12:04:25 +0100 Subject: [PATCH 3/8] FISH-5743 prevent null pointer during logout if not logged in --- .../security/openid/domain/OpenIdContextImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java index 7433bcf3..535d0b6a 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java @@ -224,14 +224,18 @@ public void logout(HttpServletRequest request, HttpServletResponse response) { if (logout == null) { LOGGER.log(WARNING, "Logout invoked on session without OpenID session"); redirect(response, request.getContextPath()); + return; } - /** + /* * See section 5. RP-Initiated Logout * https://openid.net/specs/openid-connect-session-1_0.html#RPLogout */ + + String endSessionEndpoint = configuration.getProviderMetadata().getEndSessionEndpoint(); if (logout.isNotifyProvider() - && !OpenIdUtil.isEmpty(configuration.getProviderMetadata().getEndSessionEndpoint())) { - UriBuilder logoutURI = UriBuilder.fromUri(configuration.getProviderMetadata().getEndSessionEndpoint()) + && !OpenIdUtil.isEmpty(endSessionEndpoint) + && getIdentityToken() != null) { + UriBuilder logoutURI = UriBuilder.fromUri(endSessionEndpoint) .queryParam(OpenIdConstant.ID_TOKEN_HINT, getIdentityToken().getToken()); if (!OpenIdUtil.isEmpty(logout.getRedirectURI())) { // User Agent redirected to POST_LOGOUT_REDIRECT_URI after a logout operation performed in OP. From dce908a0546277d13b0da3b7602a9691ec3c4c03 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 9 Nov 2021 12:08:32 +0100 Subject: [PATCH 4/8] FISH-5743 allow openid local metadata instead of autoconfiguration User can now bypass autoconfiguration from .well-know address to fill openid client metadata in @OpenIdProviderMetadata. --- .../payara/security/openid/OpenIdUtil.java | 68 ++++++++ .../controller/ConfigurationController.java | 115 +++++++------- .../openid/controller/JWTValidator.java | 4 +- .../openid/domain/OpenIdProviderMetadata.java | 77 +++------ .../annotations/OpenIdProviderMetadata.java | 148 +++++++++++++++++- 5 files changed, 290 insertions(+), 122 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java b/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java index 6940467b..0b99b3a8 100644 --- a/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java +++ b/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java @@ -37,23 +37,91 @@ */ package fish.payara.security.openid; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import static java.util.Objects.isNull; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.el.ELProcessor; import javax.enterprise.inject.spi.BeanManager; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonString; +import static javax.json.JsonValue.ValueType.STRING; import javax.naming.InitialContext; import javax.naming.NamingException; import org.eclipse.microprofile.config.Config; /** + * Utility class for evaluation of OpenId values. * * @author Gaurav Gupta + * @author Petr Aubrecht */ public final class OpenIdUtil { + private static final String SET_DELIMITER = "|"; + private static final String SET_DELIMITER_REGEX = "[|]"; + private OpenIdUtil() { } + public static String readConfiguredValueFromMetadataOrProvider(String metadataValue, JsonObject providerDocument, String openIdConstant, Config provider, String openIdProviderMetadataName) { + String value; + if (isEmpty(metadataValue) && providerDocument.containsKey(openIdConstant)) { + value = getConfiguredValue(String.class, providerDocument.getString(openIdConstant), provider, openIdProviderMetadataName); + } else { + value = getConfiguredValue(String.class, metadataValue, provider, openIdProviderMetadataName); + } + return value; + } + + public static Set readConfiguredValueFromMetadataOrProvider(String[] metadataValue, JsonObject providerDocument, String openIdConstant, Config provider, String openIdProviderMetadataName) { + Set value; + // PayaraConfig can contain strings from microprofile config, e.g. parse set with '|' as separator. + if (metadataValue.length == 0 && providerDocument.containsKey(openIdConstant)) { + value = parseSet(getConfiguredValue(String.class, null, provider, openIdProviderMetadataName)); + if (value == null) { + value = getValues(providerDocument, openIdConstant); + } + } else { + Set metadataValueSet = Stream.of(metadataValue).collect(Collectors.toSet()); + value = parseSet(getConfiguredValue(String.class, null, provider, openIdProviderMetadataName)); + if (value == null) { + value = metadataValueSet; + } + } + return value; + } + + private static Set parseSet(String val) { + if (val == null) { + return null; + } else { + Set set = new HashSet<>(); + set.addAll(Arrays.asList(val.split(SET_DELIMITER_REGEX))); + return set; + } + } + + private static Set getValues(JsonObject document, String key) { + JsonArray jsonArray = document.getJsonArray(key); + if (isNull(jsonArray)) { + return Collections.emptySet(); + } else { + return jsonArray + .stream() + .filter(element -> element.getValueType() == STRING) + .map(element -> (JsonString) element) + .map(JsonString::getString) + .collect(Collectors.toSet()); + } + } + public static T getConfiguredValue(Class type, T value, Config provider, String mpConfigKey) { T result = value; Optional configResult = provider.getOptionalValue(mpConfigKey, type); diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java index 4547ee70..2c57d9f5 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java @@ -67,7 +67,6 @@ import fish.payara.security.annotations.LogoutDefinition; import fish.payara.security.openid.OpenIdUtil; import fish.payara.security.openid.api.OpenIdConstant; -import java.util.Collections; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -115,47 +114,50 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition String providerURI; JsonObject providerDocument; + String issuerURI; String authorizationEndpoint; String tokenEndpoint; String userinfoEndpoint; String endSessionEndpoint; String jwksURI; URL jwksURL; + Set scopesSupported; + Set responseTypesSupported; + Set subjectTypesSupported; + Set idTokenSigningAlgValuesSupported; + Set idTokenEncryptionAlgValuesSupported; + Set idTokenEncryptionEncValuesSupported; + Set claimsSupported; providerURI = OpenIdUtil.getConfiguredValue(String.class, definition.providerURI(), provider, OpenIdAuthenticationDefinition.OPENID_MP_PROVIDER_URI); fish.payara.security.annotations.OpenIdProviderMetadata providerMetadata = definition.providerMetadata(); providerDocument = providerMetadataContoller.getDocument(providerURI); - if (OpenIdUtil.isEmpty(providerMetadata.authorizationEndpoint()) && providerDocument.containsKey(OpenIdConstant.AUTHORIZATION_ENDPOINT)) { - authorizationEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerDocument.getString(OpenIdConstant.AUTHORIZATION_ENDPOINT), provider, OpenIdProviderMetadata.OPENID_MP_AUTHORIZATION_ENDPOINT); - } else { - authorizationEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerMetadata.authorizationEndpoint(), provider, OpenIdProviderMetadata.OPENID_MP_AUTHORIZATION_ENDPOINT); - } - if (OpenIdUtil.isEmpty(providerMetadata.tokenEndpoint()) && providerDocument.containsKey(OpenIdConstant.TOKEN_ENDPOINT)) { - tokenEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerDocument.getString(OpenIdConstant.TOKEN_ENDPOINT), provider, OpenIdProviderMetadata.OPENID_MP_TOKEN_ENDPOINT); - } else { - tokenEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerMetadata.tokenEndpoint(), provider, OpenIdProviderMetadata.OPENID_MP_TOKEN_ENDPOINT); - } - if (OpenIdUtil.isEmpty(providerMetadata.userinfoEndpoint()) && providerDocument.containsKey(OpenIdConstant.USERINFO_ENDPOINT)) { - userinfoEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerDocument.getString(OpenIdConstant.USERINFO_ENDPOINT), provider, OpenIdProviderMetadata.OPENID_MP_USERINFO_ENDPOINT); - } else { - userinfoEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerMetadata.userinfoEndpoint(), provider, OpenIdProviderMetadata.OPENID_MP_USERINFO_ENDPOINT); - } - if (OpenIdUtil.isEmpty(providerMetadata.endSessionEndpoint()) && providerDocument.containsKey(OpenIdConstant.END_SESSION_ENDPOINT)) { - endSessionEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerDocument.getString(OpenIdConstant.END_SESSION_ENDPOINT), provider, OpenIdProviderMetadata.OPENID_MP_END_SESSION_ENDPOINT); - } else { - endSessionEndpoint = OpenIdUtil.getConfiguredValue(String.class, providerMetadata.endSessionEndpoint(), provider, OpenIdProviderMetadata.OPENID_MP_END_SESSION_ENDPOINT); - } - if (OpenIdUtil.isEmpty(providerMetadata.jwksURI()) && providerDocument.containsKey(OpenIdConstant.JWKS_URI)) { - jwksURI = OpenIdUtil.getConfiguredValue(String.class, providerDocument.getString(OpenIdConstant.JWKS_URI), provider, OpenIdProviderMetadata.OPENID_MP_JWKS_URI); - } else { - jwksURI = OpenIdUtil.getConfiguredValue(String.class, providerMetadata.jwksURI(), provider, OpenIdProviderMetadata.OPENID_MP_JWKS_URI); + // collect metadata either from the metadata annotation or from the autoconfiguration providerDocument + issuerURI = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.issuer(), providerDocument, OpenIdConstant.ISSUER, provider, OpenIdProviderMetadata.OPENID_MP_ISSUER); + + if (issuerURI == null || "".equals(issuerURI)) { + throw new IllegalStateException("issuer URL is not available, specify it either in @OpenIdProviderMetadata or by providerURI and autoconfiguration"); } + + authorizationEndpoint = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.authorizationEndpoint(), providerDocument, OpenIdConstant.AUTHORIZATION_ENDPOINT, provider, OpenIdProviderMetadata.OPENID_MP_AUTHORIZATION_ENDPOINT); + tokenEndpoint = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.tokenEndpoint(), providerDocument, OpenIdConstant.TOKEN_ENDPOINT, provider, OpenIdProviderMetadata.OPENID_MP_TOKEN_ENDPOINT); + userinfoEndpoint = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.userinfoEndpoint(), providerDocument, OpenIdConstant.USERINFO_ENDPOINT, provider, OpenIdProviderMetadata.OPENID_MP_USERINFO_ENDPOINT); + endSessionEndpoint = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.endSessionEndpoint(), providerDocument, OpenIdConstant.END_SESSION_ENDPOINT, provider, OpenIdProviderMetadata.OPENID_MP_END_SESSION_ENDPOINT); + jwksURI = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.jwksURI(), providerDocument, OpenIdConstant.JWKS_URI, provider, OpenIdProviderMetadata.OPENID_MP_JWKS_URI); try { jwksURL = new URL(jwksURI); } catch (MalformedURLException ex) { - throw new IllegalStateException("jwksURI is invalid", ex); + throw new IllegalStateException("jwksURI is not a valid URL: " + jwksURI, ex); } + scopesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.scopesSupported(), providerDocument, OpenIdConstant.SCOPES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_SCOPES_SUPPORTED); + responseTypesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.responseTypesSupported(), providerDocument, OpenIdConstant.RESPONSE_TYPES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_RESPONSE_TYPES_SUPPORTED); + subjectTypesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.subjectTypesSupported(), providerDocument, OpenIdConstant.SUBJECT_TYPES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_SUBJECT_TYPES_SUPPORTED); + idTokenSigningAlgValuesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.idTokenSigningAlgValuesSupported(), providerDocument, OpenIdConstant.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); + idTokenEncryptionAlgValuesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.idTokenEncryptionAlgValuesSupported(), providerDocument, OpenIdConstant.ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED); + idTokenEncryptionEncValuesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.idTokenEncryptionEncValuesSupported(), providerDocument, OpenIdConstant.ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED); + claimsSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.claimsSupported(), providerDocument, OpenIdConstant.CLAIMS_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_CLAIMS_SUPPORTED); + String clientId = OpenIdUtil.getConfiguredValue(String.class, definition.clientId(), provider, OpenIdAuthenticationDefinition.OPENID_MP_CLIENT_ID); char[] clientSecret = OpenIdUtil.getConfiguredValue(String.class, definition.clientSecret(), provider, OpenIdAuthenticationDefinition.OPENID_MP_CLIENT_SECRET).toCharArray(); String redirectURI = OpenIdUtil.getConfiguredValue(String.class, definition.redirectURI(), provider, OpenIdAuthenticationDefinition.OPENID_MP_REDIRECT_URI); @@ -194,8 +196,8 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition extraParameters.put(key, value); } - boolean nonce = OpenIdUtil.getConfiguredValue(Boolean.class, definition.useNonce(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USE_NONCE); - boolean session = OpenIdUtil.getConfiguredValue(Boolean.class, definition.useSession(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USE_SESSION); + boolean useNonce = OpenIdUtil.getConfiguredValue(Boolean.class, definition.useNonce(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USE_NONCE); + boolean useSession = OpenIdUtil.getConfiguredValue(Boolean.class, definition.useSession(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USE_SESSION); int jwksConnectTimeout = OpenIdUtil.getConfiguredValue(Integer.class, definition.jwksConnectTimeout(), provider, OpenIdAuthenticationDefinition.OPENID_MP_JWKS_CONNECT_TIMEOUT); int jwksReadTimeout = OpenIdUtil.getConfiguredValue(Integer.class, definition.jwksReadTimeout(), provider, OpenIdAuthenticationDefinition.OPENID_MP_JWKS_READ_TIMEOUT); @@ -216,30 +218,25 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition int tokenMinValidity = OpenIdUtil.getConfiguredValue(Integer.class, definition.tokenMinValidity(), provider, OpenIdAuthenticationDefinition.OPENID_MP_TOKEN_MIN_VALIDITY); boolean userClaimsFromIDToken = OpenIdUtil.getConfiguredValue(Boolean.class, definition.userClaimsFromIDToken(), provider, OpenIdAuthenticationDefinition.OPENID_MP_USER_CLAIMS_FROM_ID_TOKEN); - fish.payara.security.openid.domain.OpenIdProviderMetadata openIdProviderMetadata; - if (providerDocument != null) { - openIdProviderMetadata = new fish.payara.security.openid.domain.OpenIdProviderMetadata(providerDocument); - } else { - // FIXME: necessary to provide meaningful default data - openIdProviderMetadata = new fish.payara.security.openid.domain.OpenIdProviderMetadata( - null, - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet(), - Collections.emptySet()); - } + fish.payara.security.openid.domain.OpenIdProviderMetadata openIdProviderMetadata = new fish.payara.security.openid.domain.OpenIdProviderMetadata( + providerDocument, + issuerURI, + scopesSupported, + claimsSupported, + responseTypesSupported, + idTokenSigningAlgValuesSupported, + idTokenEncryptionAlgValuesSupported, + idTokenEncryptionEncValuesSupported, + subjectTypesSupported) + .setAuthorizationEndpoint(authorizationEndpoint) + .setTokenEndpoint(tokenEndpoint) + .setUserinfoEndpoint(userinfoEndpoint) + .setEndSessionEndpoint(endSessionEndpoint) + .setJwksURL(jwksURL); OpenIdConfiguration configuration = new OpenIdConfiguration() .setProviderMetadata( openIdProviderMetadata - .setAuthorizationEndpoint(authorizationEndpoint) - .setTokenEndpoint(tokenEndpoint) - .setUserinfoEndpoint(userinfoEndpoint) - .setEndSessionEndpoint(endSessionEndpoint) - .setJwksURL(jwksURL) ) .setClaimsConfiguration( new ClaimsConfiguration() @@ -267,8 +264,8 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition .setExtraParameters(extraParameters) .setPrompt(prompt) .setDisplay(display) - .setUseNonce(nonce) - .setUseSession(session) + .setUseNonce(useNonce) + .setUseSession(useSession) .setJwksConnectTimeout(jwksConnectTimeout) .setJwksReadTimeout(jwksReadTimeout) .setTokenAutoRefresh(tokenAutoRefresh) @@ -296,10 +293,18 @@ private void validateConfiguration(OpenIdConfiguration configuration) { private List validateProviderMetadata(OpenIdConfiguration configuration) { List errorMessages = new ArrayList<>(); + String issuerURI = configuration.getProviderMetadata().getIssuerURI(); - if (OpenIdUtil.isEmpty(configuration.getProviderMetadata().getIssuerURI())) { + if (OpenIdUtil.isEmpty(issuerURI)) { errorMessages.add("issuer metadata is mandatory"); } + try { + // only try to parse issuerURI + new URL(issuerURI); + } catch (MalformedURLException ex) { + errorMessages.add("issuer metadata is not a valid URL: " + ex.getMessage()); + } + if (OpenIdUtil.isEmpty(configuration.getProviderMetadata().getAuthorizationEndpoint())) { errorMessages.add("authorization_endpoint metadata is mandatory"); } @@ -309,13 +314,13 @@ private List validateProviderMetadata(OpenIdConfiguration configuration) if (configuration.getProviderMetadata().getJwksURL() == null) { errorMessages.add("jwks_uri metadata is mandatory"); } - if (configuration.getProviderMetadata().getResponseTypeSupported().isEmpty()) { + if (configuration.getProviderMetadata().getResponseTypesSupported().isEmpty()) { errorMessages.add("response_types_supported metadata is mandatory"); } - if (configuration.getProviderMetadata().getResponseTypeSupported().isEmpty()) { + if (configuration.getProviderMetadata().getResponseTypesSupported().isEmpty()) { errorMessages.add("subject_types_supported metadata is mandatory"); } - if (configuration.getProviderMetadata().getIdTokenSigningAlgorithmsSupported().isEmpty()) { + if (configuration.getProviderMetadata().getIdTokenSigningAlgValuesSupported().isEmpty()) { errorMessages.add("id_token_signing_alg_values_supported metadata is mandatory"); } return errorMessages; @@ -339,7 +344,7 @@ private List validateClientConfiguration(OpenIdConfiguration configurati if (OpenIdUtil.isEmpty(configuration.getResponseType())) { errorMessages.add("The response type must contain at least one value"); - } else if (!configuration.getProviderMetadata().getResponseTypeSupported().contains(configuration.getResponseType()) + } else if (!configuration.getProviderMetadata().getResponseTypesSupported().contains(configuration.getResponseType()) && !OpenIdConstant.AUTHORIZATION_CODE_FLOW_TYPES.contains(configuration.getResponseType()) && !OpenIdConstant.IMPLICIT_FLOW_TYPES.contains(configuration.getResponseType()) && !OpenIdConstant.HYBRID_FLOW_TYPES.contains(configuration.getResponseType())) { diff --git a/openid/src/main/java/fish/payara/security/openid/controller/JWTValidator.java b/openid/src/main/java/fish/payara/security/openid/controller/JWTValidator.java index ba023d3a..0bb4ca14 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/JWTValidator.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/JWTValidator.java @@ -170,13 +170,13 @@ private JWEKeySelector createJweKeySelector() { if (isNull(jwsAlg)) { throw new IllegalStateException("Missing JWE encryption algorithm "); } - if (!configuration.getProviderMetadata().getIdTokenEncryptionAlgorithmsSupported().contains(jwsAlg.getName())) { + if (!configuration.getProviderMetadata().getIdTokenEncryptionAlgValuesSupported().contains(jwsAlg.getName())) { throw new IllegalStateException("Unsupported ID tokens algorithm :" + jwsAlg.getName()); } if (isNull(jweEnc)) { throw new IllegalStateException("Missing JWE encryption method"); } - if (!configuration.getProviderMetadata().getIdTokenEncryptionMethodsSupported().contains(jweEnc.getName())) { + if (!configuration.getProviderMetadata().getIdTokenEncryptionEncValuesSupported().contains(jweEnc.getName())) { throw new IllegalStateException("Unsupported ID tokens encryption method :" + jweEnc.getName()); } diff --git a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java index df4277e0..ea2f0ed3 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java @@ -37,26 +37,12 @@ */ package fish.payara.security.openid.domain; -import static fish.payara.security.openid.api.OpenIdConstant.CLAIMS_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.ISSUER; -import static fish.payara.security.openid.api.OpenIdConstant.RESPONSE_TYPES_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.SCOPES_SUPPORTED; -import static fish.payara.security.openid.api.OpenIdConstant.SUBJECT_TYPES_SUPPORTED; import java.net.URL; -import static java.util.Collections.emptySet; -import static java.util.Objects.isNull; import java.util.Set; -import static java.util.stream.Collectors.toSet; -import javax.json.JsonArray; import javax.json.JsonObject; -import javax.json.JsonString; -import static javax.json.JsonValue.ValueType.STRING; /** - * OpenId Connect provider information + * OpenId Connect provider information. * * @author Gaurav Gupta */ @@ -71,35 +57,24 @@ public class OpenIdProviderMetadata { private URL jwksURL; private final Set scopesSupported; private final Set claimsSupported; - private final Set responseTypeSupported; - private final Set idTokenSigningAlgorithmsSupported; - private final Set idTokenEncryptionAlgorithmsSupported; - private final Set idTokenEncryptionMethodsSupported; + private final Set responseTypesSupported; + private final Set idTokenSigningAlgValuesSupported; + private final Set idTokenEncryptionAlgValuesSupported; + private final Set idTokenEncryptionEncValuesSupported; private final Set subjectTypesSupported; - public OpenIdProviderMetadata(String issuerURI, Set scopesSupported, Set claimsSupported, Set responseTypeSupported, Set idTokenSigningAlgorithmsSupported, Set idTokenEncryptionAlgorithmsSupported, Set idTokenEncryptionMethodsSupported, Set subjectTypesSupported) { + public OpenIdProviderMetadata(JsonObject providerDocument, String issuerURI, Set scopesSupported, Set claimsSupported, Set responseTypesSupported, Set idTokenSigningAlgValuesSupported, Set idTokenEncryptionAlgValuesSupported, Set idTokenEncryptionEncValuesSupported, Set subjectTypesSupported) { + this.document = providerDocument; this.issuerURI = issuerURI; this.scopesSupported = scopesSupported; this.claimsSupported = claimsSupported; - this.responseTypeSupported = responseTypeSupported; - this.idTokenSigningAlgorithmsSupported = idTokenSigningAlgorithmsSupported; - this.idTokenEncryptionAlgorithmsSupported = idTokenEncryptionAlgorithmsSupported; - this.idTokenEncryptionMethodsSupported = idTokenEncryptionMethodsSupported; + this.responseTypesSupported = responseTypesSupported; + this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported; + this.idTokenEncryptionAlgValuesSupported = idTokenEncryptionAlgValuesSupported; + this.idTokenEncryptionEncValuesSupported = idTokenEncryptionEncValuesSupported; this.subjectTypesSupported = subjectTypesSupported; } - public OpenIdProviderMetadata(JsonObject document) { - this.document = document; - this.issuerURI = document.getString(ISSUER); - this.scopesSupported = getValues(SCOPES_SUPPORTED); - this.claimsSupported = getValues(CLAIMS_SUPPORTED); - this.responseTypeSupported = getValues(RESPONSE_TYPES_SUPPORTED); - this.idTokenSigningAlgorithmsSupported = getValues(ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); - this.idTokenEncryptionAlgorithmsSupported = getValues(ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED); - this.idTokenEncryptionMethodsSupported = getValues(ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED); - this.subjectTypesSupported = getValues(SUBJECT_TYPES_SUPPORTED); - } - public String getIssuerURI() { return issuerURI; } @@ -166,38 +141,24 @@ public Set getClaimsSupported() { return claimsSupported; } - public Set getResponseTypeSupported() { - return responseTypeSupported; + public Set getResponseTypesSupported() { + return responseTypesSupported; } public Set getSubjectTypesSupported() { return subjectTypesSupported; } - public Set getIdTokenSigningAlgorithmsSupported() { - return idTokenSigningAlgorithmsSupported; - } - - public Set getIdTokenEncryptionAlgorithmsSupported() { - return idTokenEncryptionAlgorithmsSupported; + public Set getIdTokenSigningAlgValuesSupported() { + return idTokenSigningAlgValuesSupported; } - public Set getIdTokenEncryptionMethodsSupported() { - return idTokenEncryptionMethodsSupported; + public Set getIdTokenEncryptionAlgValuesSupported() { + return idTokenEncryptionAlgValuesSupported; } - private Set getValues(String key) { - JsonArray jsonArray = document.getJsonArray(key); - if (isNull(jsonArray)) { - return emptySet(); - } else { - return jsonArray - .stream() - .filter(element -> element.getValueType() == STRING) - .map(element -> (JsonString) element) - .map(JsonString::getString) - .collect(toSet()); - } + public Set getIdTokenEncryptionEncValuesSupported() { + return idTokenEncryptionEncValuesSupported; } @Override diff --git a/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java b/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java index 090b3b91..20da36e9 100644 --- a/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java +++ b/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -41,14 +41,28 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** - * {@link OpenIdProviderMetadata} annotation overrides the openid connect - * provider's endpoint value, discovered using providerUri. + * {@link OpenIdProviderMetadata} annotation overrides the openid connect provider's endpoint value, discovered using + * providerUri. + * + * The documentation: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata * * @author Gaurav Gupta + * @author Petr Aubrecht */ @Retention(RUNTIME) public @interface OpenIdProviderMetadata { + /** + * Required, FIXME: keep optional for backward compatibility. The base address of OpenId Connect Provider. + *

+ * URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. + *

+ * To set this using Microprofile Config use {@code payara.security.openid.provider.issuer} + * + * @return + */ + String issuer() default ""; + /** * Required. The URL for the OAuth2 provider to provide authentication *

@@ -110,9 +124,96 @@ */ String jwksURI() default ""; + //NOT USED: registration_endpoint + //RECOMMENDED. URL of the OP's Dynamic Client Registration Endpoint. + // /** - * The Microprofile Config key for the auth endpoint is - * {@value} + * Recommended. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. + * + * To set this using Microprofile Config use {@code payara.security.openid.provider.scopesSupported} + * + * @return + */ + String[] scopesSupported() default {};//{"openid"}; + + /** + * Required. JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. + * + * To set this using Microprofile Config use {@code payara.security.openid.provider.responseTypeSupported} + * + * @return + */ + String[] responseTypesSupported() default {};//{"code", "id_token", "token id_token"}; + + // NOT USED + // response_modes_supported + // OPTIONAL. JSON array containing a list of the OAuth 2.0 response_mode values that this OP supports, as specified in OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses]. If omitted, the default for Dynamic OpenID Providers is ["query", "fragment"]. + // grant_types_supported + // OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Type values that this OP supports. Dynamic OpenID Providers MUST support the authorization_code and implicit Grant Type values and MAY support other Grant Types. If omitted, the default value is ["authorization_code", "implicit"]. + // acr_values_supported + // OPTIONAL. JSON array containing a list of the Authentication Context Class References that this OP supports. + // + /** + * Required. JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include + * pairwise and public. + * + * To set this using Microprofile Config use {@code payara.security.openid.provider.subjectTypesSupported} + * + * @return + */ + String[] subjectTypesSupported() default {};//{"public"}; + + /** + * Required. REQUIRED. JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP + * for the ID Token to encode the Claims in a JWT. + * + * To set this using Microprofile Config use + * {@code payara.security.openid.provider.idTokenSigningAlgorithmsSupported} + * + * @return + */ + String[] idTokenSigningAlgValuesSupported() default {};//{"RS256"}; + + /** + * Optional. JSON array containing a list of the JWE encryption algorithms (alg values) supported by the OP for the + * ID Token to encode the Claims in a JWT. + * + * To set this using Microprofile Config use + * {@code payara.security.openid.provider.idTokenEncryptionAlgValuesSupported} + * + * @return + */ + String[] idTokenEncryptionAlgValuesSupported() default {}; + + /** + * Optional. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the + * ID Token to encode the Claims in a JWT. + * + * To set this using Microprofile Config use + * {@code payara.security.openid.provider.idTokenEncryptionEncValuesSupported} + * + * @return + */ + String[] idTokenEncryptionEncValuesSupported() default {}; + + /** + * Recommended. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able + * to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list. + * + * To set this using Microprofile Config use + * {@code payara.security.openid.provider.claimsSupported} + * + * @return + */ + String[] claimsSupported() default {}; + + /** + * The Microprofile Config key for the issuer url is {@value}. + */ + String OPENID_MP_ISSUER = "payara.security.openid.provider.issuer"; + + /** + * The Microprofile Config key for the auth endpoint is {@value} */ String OPENID_MP_AUTHORIZATION_ENDPOINT = "payara.security.openid.provider.authorizationEndpoint"; @@ -129,8 +230,7 @@ String OPENID_MP_USERINFO_ENDPOINT = "payara.security.openid.provider.userinfoEndpoint"; /** - * The Microprofile Config key for the end session Endpoint is - * {@value} + * The Microprofile Config key for the end session Endpoint is * {@value} */ public static final String OPENID_MP_END_SESSION_ENDPOINT = "payara.security.openid.provider.endSessionEndpoint"; @@ -139,4 +239,38 @@ */ String OPENID_MP_JWKS_URI = "payara.security.openid.provider.jwksURI"; + /** + * The Microprofile Config key for the scopes supported is {@value} + */ + String OPENID_MP_SCOPES_SUPPORTED = "payara.security.openid.provider.scopesSupported"; + + /** + * The Microprofile Config key for the response types supported is {@value} + */ + String OPENID_MP_RESPONSE_TYPES_SUPPORTED = "payara.security.openid.provider.responseTypesSupported"; + + /** + * The Microprofile Config key for the subjects types supported is {@value} + */ + String OPENID_MP_SUBJECT_TYPES_SUPPORTED = "payara.security.openid.provider.subjectTypesSupported"; + + /** + * The Microprofile Config key for the signing algorithms supported is {@value} + */ + String OPENID_MP_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = "payara.security.openid.provider.idTokenSigningAlgValuesSupported"; + /** + * The Microprofile Config key for the encryption algorighm alg - values supported is {@value} + */ + String OPENID_MP_ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED = "payara.security.openid.provider.idTokenEncryptionAlgValuesSupported"; + + /** + * The Microprofile Config key for the encryption algorighm enc - values supported is {@value} + */ + String OPENID_MP_ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED = "payara.security.openid.provider.idTokenEncryptionEncValuesSupported"; + + /** + * The Microprofile Config key for the supported claims supported is {@value} + */ + String OPENID_MP_CLAIMS_SUPPORTED = "payara.security.openid.provider.claimsSupported"; + } From 10d4ca576a5e0b2f3a6ebc4dcfade7228cadcbe4 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 9 Nov 2021 12:14:12 +0100 Subject: [PATCH 5/8] FISH-5743 use OpenIdAuthenticationException for configuration errors --- .../openid/OpenIdAuthenticationException.java | 55 +++++++++++++++++++ .../controller/ConfigurationController.java | 7 ++- 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationException.java diff --git a/openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationException.java b/openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationException.java new file mode 100644 index 00000000..a769f769 --- /dev/null +++ b/openid/src/main/java/fish/payara/security/openid/OpenIdAuthenticationException.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.security.openid; + +/** + * Exception thrown during @OpenIdAuthenticationDefinition processing. + * + * @author Petr Aubrecht + */ +public class OpenIdAuthenticationException extends RuntimeException { + + public OpenIdAuthenticationException(String message) { + super(message); + } + + public OpenIdAuthenticationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java index 2c57d9f5..4f622e90 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java @@ -65,6 +65,7 @@ import fish.payara.security.annotations.ClaimsDefinition; import fish.payara.security.annotations.LogoutDefinition; +import fish.payara.security.openid.OpenIdAuthenticationException; import fish.payara.security.openid.OpenIdUtil; import fish.payara.security.openid.api.OpenIdConstant; import org.eclipse.microprofile.config.Config; @@ -137,7 +138,7 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition issuerURI = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.issuer(), providerDocument, OpenIdConstant.ISSUER, provider, OpenIdProviderMetadata.OPENID_MP_ISSUER); if (issuerURI == null || "".equals(issuerURI)) { - throw new IllegalStateException("issuer URL is not available, specify it either in @OpenIdProviderMetadata or by providerURI and autoconfiguration"); + throw new OpenIdAuthenticationException("issuer URL is not available, specify it either in @OpenIdProviderMetadata or by providerURI and autoconfiguration"); } authorizationEndpoint = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.authorizationEndpoint(), providerDocument, OpenIdConstant.AUTHORIZATION_ENDPOINT, provider, OpenIdProviderMetadata.OPENID_MP_AUTHORIZATION_ENDPOINT); @@ -148,7 +149,7 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition try { jwksURL = new URL(jwksURI); } catch (MalformedURLException ex) { - throw new IllegalStateException("jwksURI is not a valid URL: " + jwksURI, ex); + throw new OpenIdAuthenticationException("jwksURI is not a valid URL: " + jwksURI, ex); } scopesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.scopesSupported(), providerDocument, OpenIdConstant.SCOPES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_SCOPES_SUPPORTED); responseTypesSupported = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.responseTypesSupported(), providerDocument, OpenIdConstant.RESPONSE_TYPES_SUPPORTED, provider, OpenIdProviderMetadata.OPENID_MP_RESPONSE_TYPES_SUPPORTED); @@ -287,7 +288,7 @@ private void validateConfiguration(OpenIdConfiguration configuration) { errorMessages.addAll(validateClientConfiguration(configuration)); if (!errorMessages.isEmpty()) { - throw new IllegalStateException(errorMessages.toString()); + throw new OpenIdAuthenticationException(errorMessages.toString()); } } From 4a4e87662d679c6854f66922e73911680fca3b6f Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 9 Nov 2021 13:09:08 +0100 Subject: [PATCH 6/8] FISH-5743 copyright years update --- .../src/main/java/fish/payara/security/openid/OpenIdUtil.java | 2 +- .../security/openid/controller/ConfigurationController.java | 2 +- .../security/openid/controller/ProviderMetadataContoller.java | 2 +- .../fish/payara/security/openid/domain/AccessTokenImpl.java | 2 +- .../fish/payara/security/openid/domain/OpenIdContextImpl.java | 2 +- .../payara/security/openid/domain/OpenIdProviderMetadata.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java b/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java index 0b99b3a8..f2f150b3 100644 --- a/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java +++ b/openid/src/main/java/fish/payara/security/openid/OpenIdUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java index 4f622e90..2041d2b5 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java index 5a5f875c..553f8792 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java b/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java index d12651f1..3061e613 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/AccessTokenImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java index 535d0b6a..70d055e7 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java index ea2f0ed3..8661a0cd 100644 --- a/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java +++ b/openid/src/main/java/fish/payara/security/openid/domain/OpenIdProviderMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2020-2021] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development From 738cf37bc4353cc4323bf9e280c3709d59bbc0b7 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Tue, 9 Nov 2021 16:17:03 +0100 Subject: [PATCH 7/8] FISH-5743 cleanup of metadata annotation javadoc --- .../annotations/OpenIdProviderMetadata.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java b/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java index 20da36e9..732900e1 100644 --- a/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java +++ b/security-connectors-api/src/main/java/fish/payara/security/annotations/OpenIdProviderMetadata.java @@ -53,7 +53,7 @@ public @interface OpenIdProviderMetadata { /** - * Required, FIXME: keep optional for backward compatibility. The base address of OpenId Connect Provider. + * Required: The base address of OpenId Connect Provider. *

* URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. *

@@ -64,7 +64,7 @@ String issuer() default ""; /** - * Required. The URL for the OAuth2 provider to provide authentication + * Required: The URL for the OAuth2 provider to provide authentication. *

* This must be a https endpoint. *

@@ -76,7 +76,7 @@ String authorizationEndpoint() default ""; /** - * Required. The URL for the OAuth2 provider to give the authorization token + * Required: The URL for the OAuth2 provider to give the authorization token. *

* To set this using Microprofile Config use * {@code payara.security.openid.provider.tokenEndpoint} @@ -87,8 +87,7 @@ String tokenEndpoint() default ""; /** - * Required. An OAuth 2.0 Protected Resource that returns Claims about the - * authenticated End-User. + * Required: An OAuth 2.0 Protected Resource that returns Claims about the * authenticated End-User. *

* To set this using Microprofile Config use * {@code payara.security.openid.provider.userinfoEndpoint} @@ -99,8 +98,7 @@ String userinfoEndpoint() default ""; /** - * Optional. OP endpoint to notify that the End-User has logged out of the - * site and might want to log out of the OP as well. + * Optional: OP endpoint to notify that the End-User has logged out of the * site and might want to log out of the OP as well. *

* To set this using Microprofile Config use * {@code payara.security.openid.provider.endSessionEndpoint} @@ -111,7 +109,7 @@ String endSessionEndpoint() default ""; /** - * Required. An OpenId Connect Provider's JSON Web Key Set document + * Required: An OpenId Connect Provider's JSON Web Key Set document. *

* This contains the signing key(s) the RP uses to validate signatures from * the OP. The JWK Set may also contain the Server's encryption key(s), @@ -128,7 +126,7 @@ //RECOMMENDED. URL of the OP's Dynamic Client Registration Endpoint. // /** - * Recommended. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. + * Recommended: List of the OAuth 2.0 scope values that this server supports. * * To set this using Microprofile Config use {@code payara.security.openid.provider.scopesSupported} * @@ -137,7 +135,7 @@ String[] scopesSupported() default {};//{"openid"}; /** - * Required. JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. + * Required: List of the OAuth 2.0 response_type values that this OP supports. * * To set this using Microprofile Config use {@code payara.security.openid.provider.responseTypeSupported} * @@ -147,15 +145,18 @@ // NOT USED // response_modes_supported - // OPTIONAL. JSON array containing a list of the OAuth 2.0 response_mode values that this OP supports, as specified in OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses]. If omitted, the default for Dynamic OpenID Providers is ["query", "fragment"]. + // OPTIONAL. List of the OAuth 2.0 response_mode values that this OP supports, as + // specified in OAuth 2.0 Multiple Response Type Encoding Practices [OAuth.Responses]. If omitted, the default for + // Dynamic OpenID Providers is ["query", "fragment"]. // grant_types_supported - // OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Type values that this OP supports. Dynamic OpenID Providers MUST support the authorization_code and implicit Grant Type values and MAY support other Grant Types. If omitted, the default value is ["authorization_code", "implicit"]. + // OPTIONAL. List of the OAuth 2.0 Grant Type values that this OP supports. Dynamic + // OpenID Providers MUST support the authorization_code and implicit Grant Type values and MAY support other Grant + // Types. If omitted, the default value is ["authorization_code", "implicit"]. // acr_values_supported - // OPTIONAL. JSON array containing a list of the Authentication Context Class References that this OP supports. + // OPTIONAL. List of the Authentication Context Class References that this OP supports. // /** - * Required. JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include - * pairwise and public. + * Required: List of the Subject Identifier types that this OP supports. Valid types include pairwise and public. * * To set this using Microprofile Config use {@code payara.security.openid.provider.subjectTypesSupported} * @@ -164,8 +165,8 @@ String[] subjectTypesSupported() default {};//{"public"}; /** - * Required. REQUIRED. JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP - * for the ID Token to encode the Claims in a JWT. + * Required: List of the JWS signing algorithms (alg values) supported by the OP for the ID Token to encode the + * Claims in a JWT. * * To set this using Microprofile Config use * {@code payara.security.openid.provider.idTokenSigningAlgorithmsSupported} @@ -175,8 +176,8 @@ String[] idTokenSigningAlgValuesSupported() default {};//{"RS256"}; /** - * Optional. JSON array containing a list of the JWE encryption algorithms (alg values) supported by the OP for the - * ID Token to encode the Claims in a JWT. + * Optional: List of the JWE encryption algorithms (alg values) supported by the OP for the ID Token to encode the + * Claims in a JWT. * * To set this using Microprofile Config use * {@code payara.security.openid.provider.idTokenEncryptionAlgValuesSupported} @@ -186,8 +187,8 @@ String[] idTokenEncryptionAlgValuesSupported() default {}; /** - * Optional. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the OP for the - * ID Token to encode the Claims in a JWT. + * Optional: List of the JWE encryption algorithms (enc values) supported by the OP for the ID Token to encode the + * Claims in a JWT. * * To set this using Microprofile Config use * {@code payara.security.openid.provider.idTokenEncryptionEncValuesSupported} @@ -197,8 +198,8 @@ String[] idTokenEncryptionEncValuesSupported() default {}; /** - * Recommended. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able - * to supply values for. Note that for privacy or other reasons, this might not be an exhaustive list. + * Recommended: List of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. + * Note that for privacy or other reasons, this might not be an exhaustive list. * * To set this using Microprofile Config use * {@code payara.security.openid.provider.claimsSupported} From d971cbd98d2a909844215fa1b9f1f3c3fa629750 Mon Sep 17 00:00:00 2001 From: Petr Aubrecht Date: Thu, 11 Nov 2021 11:26:01 +0100 Subject: [PATCH 8/8] FISH-5743 polishing code style No functional change, code style after review. --- .../openid/controller/ConfigurationController.java | 2 +- .../openid/controller/ProviderMetadataContoller.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java index 2041d2b5..f84db878 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ConfigurationController.java @@ -137,7 +137,7 @@ public OpenIdConfiguration buildConfig(OpenIdAuthenticationDefinition definition // collect metadata either from the metadata annotation or from the autoconfiguration providerDocument issuerURI = OpenIdUtil.readConfiguredValueFromMetadataOrProvider(providerMetadata.issuer(), providerDocument, OpenIdConstant.ISSUER, provider, OpenIdProviderMetadata.OPENID_MP_ISSUER); - if (issuerURI == null || "".equals(issuerURI)) { + if (issuerURI == null || issuerURI.isEmpty()) { throw new OpenIdAuthenticationException("issuer URL is not available, specify it either in @OpenIdProviderMetadata or by providerURI and autoconfiguration"); } diff --git a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java index 553f8792..fc7f8f15 100644 --- a/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java +++ b/openid/src/main/java/fish/payara/security/openid/controller/ProviderMetadataContoller.java @@ -59,7 +59,7 @@ @ApplicationScoped public class ProviderMetadataContoller { - private static final String WELL_KNOWN_PREFIX = "/.well-known/openid-configuration"; + private static final String WELL_KNOWN_CONFIGURATION_ADDRESS = "/.well-known/openid-configuration"; private final Map providerDocuments = new HashMap<>(); @@ -78,7 +78,7 @@ public class ProviderMetadataContoller { public JsonObject getDocument(String providerURI) { if (!providerDocuments.containsKey(providerURI)) { JsonObject responseObject; - if ("".equals(providerURI)) { + if (providerURI.isEmpty()) { // no providerURI provided, data must be load from @OpenIdProviderMetadata responseObject = JsonObject.EMPTY_JSON_OBJECT; } else { @@ -94,8 +94,8 @@ private JsonObject downloadWellKnownUris(String providerURI) { providerURI = providerURI.substring(0, providerURI.length() - 1); } - if (!providerURI.endsWith(WELL_KNOWN_PREFIX)) { - providerURI = providerURI + WELL_KNOWN_PREFIX; + if (!providerURI.endsWith(WELL_KNOWN_CONFIGURATION_ADDRESS)) { + providerURI = providerURI + WELL_KNOWN_CONFIGURATION_ADDRESS; } Client client = ClientBuilder.newClient(); @@ -113,7 +113,7 @@ private JsonObject downloadWellKnownUris(String providerURI) { } } else { throw new IllegalStateException(String.format( - "Unable to retrieve OpenID Provider's [%s] configuration document, HTTP respons code : [%s] ", + "Unable to retrieve OpenID Provider's [%s] configuration document, HTTP response code : [%s] ", providerURI, response.getStatus() ));