use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.
the class DescriptionConverter method toExternalResponse.
public static OIDCClientRepresentation toExternalResponse(KeycloakSession session, ClientRepresentation client, URI uri) {
OIDCClientRepresentation response = new OIDCClientRepresentation();
response.setClientId(client.getClientId());
if ("none".equals(client.getClientAuthenticatorType())) {
response.setTokenEndpointAuthMethod("none");
} else {
ClientAuthenticatorFactory clientAuth = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, client.getClientAuthenticatorType());
Set<String> oidcClientAuthMethods = clientAuth.getProtocolAuthenticatorMethods(OIDCLoginProtocol.LOGIN_PROTOCOL);
if (oidcClientAuthMethods != null && !oidcClientAuthMethods.isEmpty()) {
response.setTokenEndpointAuthMethod(oidcClientAuthMethods.iterator().next());
}
}
if (client.getClientAuthenticatorType().equals(ClientIdAndSecretAuthenticator.PROVIDER_ID)) {
response.setClientSecret(client.getSecret());
response.setClientSecretExpiresAt(0);
}
response.setClientName(client.getName());
response.setClientUri(client.getBaseUrl());
response.setRedirectUris(client.getRedirectUris());
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
response.setRegistrationClientUri(uri.toString());
response.setResponseTypes(getOIDCResponseTypes(client));
response.setGrantTypes(getOIDCGrantTypes(client));
List<String> scopes = client.getOptionalClientScopes();
if (scopes != null)
response.setScope(scopes.stream().collect(Collectors.joining(" ")));
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
if (config.isUserInfoSignatureRequired()) {
response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString());
}
if (config.getRequestObjectSignatureAlg() != null) {
response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
}
if (config.getRequestObjectEncryptionAlg() != null) {
response.setRequestObjectEncryptionAlg(config.getRequestObjectEncryptionAlg());
}
if (config.getRequestObjectEncryptionEnc() != null) {
response.setRequestObjectEncryptionEnc(config.getRequestObjectEncryptionEnc());
}
if (config.isUseJwksUrl()) {
response.setJwksUri(config.getJwksUrl());
}
if (config.isUseJwksString()) {
try {
response.setJwks(JsonSerialization.readValue(config.getJwksString(), JSONWebKeySet.class));
} catch (IOException e) {
throw new ClientRegistrationException("Illegal jwks format");
}
}
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
if (config.isUseMtlsHokToken()) {
response.setTlsClientCertificateBoundAccessTokens(Boolean.TRUE);
} else {
response.setTlsClientCertificateBoundAccessTokens(Boolean.FALSE);
}
if (config.getTlsClientAuthSubjectDn() != null) {
response.setTlsClientAuthSubjectDn(config.getTlsClientAuthSubjectDn());
}
if (config.getIdTokenSignedResponseAlg() != null) {
response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg());
}
if (config.getIdTokenEncryptedResponseAlg() != null) {
response.setIdTokenEncryptedResponseAlg(config.getIdTokenEncryptedResponseAlg());
}
if (config.getIdTokenEncryptedResponseEnc() != null) {
response.setIdTokenEncryptedResponseEnc(config.getIdTokenEncryptedResponseEnc());
}
if (config.getAuthorizationSignedResponseAlg() != null) {
response.setAuthorizationSignedResponseAlg(config.getAuthorizationSignedResponseAlg());
}
if (config.getAuthorizationEncryptedResponseAlg() != null) {
response.setAuthorizationEncryptedResponseAlg(config.getAuthorizationEncryptedResponseAlg());
}
if (config.getAuthorizationEncryptedResponseEnc() != null) {
response.setAuthorizationEncryptedResponseEnc(config.getAuthorizationEncryptedResponseEnc());
}
if (config.getRequestUris() != null) {
response.setRequestUris(config.getRequestUris());
}
if (config.getTokenEndpointAuthSigningAlg() != null) {
response.setTokenEndpointAuthSigningAlg(config.getTokenEndpointAuthSigningAlg());
}
response.setBackchannelLogoutUri(config.getBackchannelLogoutUrl());
response.setBackchannelLogoutSessionRequired(config.isBackchannelLogoutSessionRequired());
response.setBackchannelLogoutSessionRequired(config.getBackchannelLogoutRevokeOfflineTokens());
if (client.getAttributes() != null) {
String mode = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE_PER_CLIENT);
if (StringUtil.isNotBlank(mode)) {
response.setBackchannelTokenDeliveryMode(mode);
}
String clientNotificationEndpoint = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT);
if (StringUtil.isNotBlank(clientNotificationEndpoint)) {
response.setBackchannelClientNotificationEndpoint(clientNotificationEndpoint);
}
String alg = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG);
if (StringUtil.isNotBlank(alg)) {
response.setBackchannelAuthenticationRequestSigningAlg(alg);
}
Boolean requirePushedAuthorizationRequests = Boolean.valueOf(client.getAttributes().get(ParConfig.REQUIRE_PUSHED_AUTHORIZATION_REQUESTS));
response.setRequirePushedAuthorizationRequests(requirePushedAuthorizationRequests.booleanValue());
}
List<ProtocolMapperRepresentation> foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
SubjectType subjectType = foundPairwiseMappers.isEmpty() ? SubjectType.PUBLIC : SubjectType.PAIRWISE;
response.setSubjectType(subjectType.toString().toLowerCase());
if (subjectType.equals(SubjectType.PAIRWISE)) {
// Get sectorIdentifier from 1st found
String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwiseMappers.get(0));
response.setSectorIdentifierUri(sectorIdentifierUri);
}
response.setFrontChannelLogoutUri(config.getFrontChannelLogoutUrl());
List<String> defaultAcrValues = config.getAttributeMultivalued(Constants.DEFAULT_ACR_VALUES);
if (!defaultAcrValues.isEmpty()) {
response.setDefaultAcrValues(defaultAcrValues);
}
return response;
}
use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.
the class DescriptionConverter method toInternal.
public static ClientRepresentation toInternal(KeycloakSession session, OIDCClientRepresentation clientOIDC) throws ClientRegistrationException {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientOIDC.getClientId());
client.setName(clientOIDC.getClientName());
client.setRedirectUris(clientOIDC.getRedirectUris());
client.setBaseUrl(clientOIDC.getClientUri());
client.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
String scopeParam = clientOIDC.getScope();
if (scopeParam != null)
client.setOptionalClientScopes(new ArrayList<>(Arrays.asList(scopeParam.split(" "))));
List<String> oidcResponseTypes = clientOIDC.getResponseTypes();
if (oidcResponseTypes == null || oidcResponseTypes.isEmpty()) {
oidcResponseTypes = Collections.singletonList(OIDCResponseType.CODE);
}
List<String> oidcGrantTypes = clientOIDC.getGrantTypes();
try {
OIDCResponseType responseType = OIDCResponseType.parse(oidcResponseTypes);
client.setStandardFlowEnabled(responseType.hasResponseType(OIDCResponseType.CODE));
client.setImplicitFlowEnabled(responseType.isImplicitOrHybridFlow());
if (oidcGrantTypes != null) {
client.setDirectAccessGrantsEnabled(oidcGrantTypes.contains(OAuth2Constants.PASSWORD));
client.setServiceAccountsEnabled(oidcGrantTypes.contains(OAuth2Constants.CLIENT_CREDENTIALS));
setOidcCibaGrantEnabled(client, oidcGrantTypes.contains(OAuth2Constants.CIBA_GRANT_TYPE));
}
} catch (IllegalArgumentException iae) {
throw new ClientRegistrationException(iae.getMessage(), iae);
}
String authMethod = clientOIDC.getTokenEndpointAuthMethod();
client.setPublicClient(Boolean.FALSE);
if ("none".equals(authMethod)) {
client.setClientAuthenticatorType("none");
client.setPublicClient(Boolean.TRUE);
} else {
ClientAuthenticatorFactory clientAuthFactory;
if (authMethod == null) {
clientAuthFactory = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, KeycloakModelUtils.getDefaultClientAuthenticatorType());
} else {
clientAuthFactory = AuthorizeClientUtil.findClientAuthenticatorForOIDCAuthMethod(session, authMethod);
}
if (clientAuthFactory == null) {
throw new ClientRegistrationException("Not found clientAuthenticator for requested token_endpoint_auth_method");
}
client.setClientAuthenticatorType(clientAuthFactory.getId());
}
boolean publicKeySet = setPublicKey(clientOIDC, client);
if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT) && !publicKeySet) {
throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
}
OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
if (clientOIDC.getUserinfoSignedResponseAlg() != null) {
Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getUserinfoSignedResponseAlg());
configWrapper.setUserInfoSignedResponseAlg(algorithm);
}
if (clientOIDC.getRequestObjectSigningAlg() != null) {
Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getRequestObjectSigningAlg());
configWrapper.setRequestObjectSignatureAlg(algorithm);
}
// KEYCLOAK-6771 Certificate Bound Token
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
Boolean tlsClientCertificateBoundAccessTokens = clientOIDC.getTlsClientCertificateBoundAccessTokens();
if (tlsClientCertificateBoundAccessTokens != null) {
if (tlsClientCertificateBoundAccessTokens.booleanValue())
configWrapper.setUseMtlsHoKToken(true);
else
configWrapper.setUseMtlsHoKToken(false);
}
if (clientOIDC.getTlsClientAuthSubjectDn() != null) {
configWrapper.setTlsClientAuthSubjectDn(clientOIDC.getTlsClientAuthSubjectDn());
// According to specification, attribute tls_client_auth_subject_dn has subject DN in the exact expected format. There is no reason for support regex comparisons
configWrapper.setAllowRegexPatternComparison(false);
}
if (clientOIDC.getIdTokenSignedResponseAlg() != null) {
configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg());
}
if (clientOIDC.getIdTokenEncryptedResponseAlg() != null) {
configWrapper.setIdTokenEncryptedResponseAlg(clientOIDC.getIdTokenEncryptedResponseAlg());
}
if (clientOIDC.getIdTokenEncryptedResponseEnc() != null) {
configWrapper.setIdTokenEncryptedResponseEnc(clientOIDC.getIdTokenEncryptedResponseEnc());
}
configWrapper.setAuthorizationSignedResponseAlg(clientOIDC.getAuthorizationSignedResponseAlg());
configWrapper.setAuthorizationEncryptedResponseAlg(clientOIDC.getAuthorizationEncryptedResponseAlg());
configWrapper.setAuthorizationEncryptedResponseEnc(clientOIDC.getAuthorizationEncryptedResponseEnc());
if (clientOIDC.getRequestUris() != null) {
configWrapper.setRequestUris(clientOIDC.getRequestUris());
}
configWrapper.setTokenEndpointAuthSigningAlg(clientOIDC.getTokenEndpointAuthSigningAlg());
configWrapper.setBackchannelLogoutUrl(clientOIDC.getBackchannelLogoutUri());
if (clientOIDC.getBackchannelLogoutSessionRequired() == null) {
configWrapper.setBackchannelLogoutSessionRequired(true);
} else {
configWrapper.setBackchannelLogoutSessionRequired(clientOIDC.getBackchannelLogoutSessionRequired());
}
if (clientOIDC.getBackchannelLogoutRevokeOfflineTokens() == null) {
configWrapper.setBackchannelLogoutRevokeOfflineTokens(false);
} else {
configWrapper.setBackchannelLogoutRevokeOfflineTokens(clientOIDC.getBackchannelLogoutRevokeOfflineTokens());
}
if (clientOIDC.getLogoUri() != null) {
configWrapper.setLogoUri(clientOIDC.getLogoUri());
}
if (clientOIDC.getPolicyUri() != null) {
configWrapper.setPolicyUri(clientOIDC.getPolicyUri());
}
if (clientOIDC.getTosUri() != null) {
configWrapper.setTosUri(clientOIDC.getTosUri());
}
// CIBA
String backchannelTokenDeliveryMode = clientOIDC.getBackchannelTokenDeliveryMode();
if (backchannelTokenDeliveryMode != null) {
Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
attr.put(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE_PER_CLIENT, backchannelTokenDeliveryMode);
client.setAttributes(attr);
}
String backchannelClientNotificationEndpoint = clientOIDC.getBackchannelClientNotificationEndpoint();
if (backchannelClientNotificationEndpoint != null) {
Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
attr.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, backchannelClientNotificationEndpoint);
client.setAttributes(attr);
}
String backchannelAuthenticationRequestSigningAlg = clientOIDC.getBackchannelAuthenticationRequestSigningAlg();
if (backchannelAuthenticationRequestSigningAlg != null) {
Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
attr.put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, backchannelAuthenticationRequestSigningAlg);
client.setAttributes(attr);
}
// PAR
Boolean requirePushedAuthorizationRequests = clientOIDC.getRequirePushedAuthorizationRequests();
if (requirePushedAuthorizationRequests != null) {
Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
attr.put(ParConfig.REQUIRE_PUSHED_AUTHORIZATION_REQUESTS, requirePushedAuthorizationRequests.toString());
client.setAttributes(attr);
}
configWrapper.setFrontChannelLogoutUrl(Optional.ofNullable(clientOIDC.getFrontChannelLogoutUri()).orElse(null));
if (clientOIDC.getDefaultAcrValues() != null) {
configWrapper.setAttributeMultivalued(Constants.DEFAULT_ACR_VALUES, clientOIDC.getDefaultAcrValues());
}
return client;
}
use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.
the class DescriptionConverter method setPublicKey.
private static boolean setPublicKey(OIDCClientRepresentation clientOIDC, ClientRepresentation clientRep) {
OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
if (clientOIDC.getJwks() != null) {
if (clientOIDC.getJwksUri() != null) {
throw new ClientRegistrationException("Illegal to use both jwks_uri and jwks");
}
JSONWebKeySet keySet = clientOIDC.getJwks();
JWK publicKeyJWk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
try {
configWrapper.setJwksString(JsonSerialization.writeValueAsPrettyString(clientOIDC.getJwks()));
} catch (IOException e) {
throw new ClientRegistrationException("Illegal jwks format");
}
configWrapper.setUseJwksString(true);
configWrapper.setUseJwksUrl(false);
if (publicKeyJWk == null) {
return false;
}
PublicKey publicKey = JWKParser.create(publicKeyJWk).toPublicKey();
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
CertificateRepresentation rep = new CertificateRepresentation();
rep.setPublicKey(publicKeyPem);
rep.setKid(publicKeyJWk.getKeyId());
CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, rep, JWTClientAuthenticator.ATTR_PREFIX);
return true;
} else if (clientOIDC.getJwksUri() != null) {
configWrapper.setUseJwksUrl(true);
configWrapper.setJwksUrl(clientOIDC.getJwksUri());
configWrapper.setUseJwksString(false);
return true;
}
return false;
}
use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.
the class OIDCBackwardsCompatibilityTest method testExcludeSessionStateParameter.
// KEYCLOAK-6286
@Test
public void testExcludeSessionStateParameter() {
// Open login form and login successfully. Assert session_state is present
OAuthClient.AuthorizationEndpointResponse authzResponse = oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(authzResponse.getSessionState());
// Switch "exclude session_state" to on
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
ClientRepresentation clientRep = client.toRepresentation();
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
config.setExcludeSessionStateFromAuthResponse(true);
client.update(clientRep);
// Open login again and assert session_state not present
driver.navigate().to(oauth.getLoginFormUrl());
org.keycloak.testsuite.Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
Assert.assertNull(authzResponse.getSessionState());
// Revert
config.setExcludeSessionStateFromAuthResponse(false);
client.update(clientRep);
}
use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.
the class X509ClientAuthenticator method authenticateClient.
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
if (provider == null) {
logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?", X509ClientCertificateLookup.class);
return;
}
X509Certificate[] certs = null;
ClientModel client = null;
try {
certs = provider.getCertificateChain(context.getHttpRequest());
String client_id = null;
MediaType mediaType = context.getHttpRequest().getHttpHeaders().getMediaType();
boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
if (formData != null) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
}
if (client_id == null && queryParams != null) {
client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
}
if (client_id == null) {
client_id = context.getSession().getAttribute("client_id", String.class);
}
if (client_id == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
context.challenge(challengeResponse);
return;
}
client = context.getRealm().getClientByClientId(client_id);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return;
}
context.getEvent().client(client_id);
context.setClient(client);
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return;
}
} catch (GeneralSecurityException e) {
logger.errorf("[X509ClientCertificateAuthenticator:authenticate] Exception: %s", e.getMessage());
context.attempted();
return;
}
if (certs == null || certs.length == 0) {
// No x509 client cert, fall through and
// continue processing the rest of the authentication flow
logger.debug("[X509ClientCertificateAuthenticator:authenticate] x509 client certificate is not available for mutual SSL.");
context.attempted();
return;
}
OIDCAdvancedConfigWrapper clientCfg = OIDCAdvancedConfigWrapper.fromClientModel(client);
String subjectDNRegexp = client.getAttribute(ATTR_SUBJECT_DN);
if (subjectDNRegexp == null || subjectDNRegexp.length() == 0) {
logger.errorf("[X509ClientCertificateAuthenticator:authenticate] " + ATTR_SUBJECT_DN + " is null or empty");
context.attempted();
return;
}
Optional<String> matchedCertificate;
if (clientCfg.getAllowRegexPatternComparison()) {
Pattern subjectDNPattern = Pattern.compile(subjectDNRegexp);
matchedCertificate = Arrays.stream(certs).map(certificate -> certificate.getSubjectDN().getName()).filter(subjectdn -> subjectDNPattern.matcher(subjectdn).matches()).findFirst();
} else {
// OIDC/OAuth2 does not use regex comparison as it expects exact DN given in the format according to RFC4514. See RFC8705 for the details.
// We allow custom OIDs attributes to be "expanded" or not expanded in the given Subject DN
X500Principal expectedDNPrincipal = new X500Principal(subjectDNRegexp, CUSTOM_OIDS_REVERSED);
matchedCertificate = Arrays.stream(certs).filter(certificate -> expectedDNPrincipal.getName(X500Principal.RFC2253, CUSTOM_OIDS).equals(certificate.getSubjectX500Principal().getName(X500Principal.RFC2253, CUSTOM_OIDS))).map(certificate -> certificate.getSubjectDN().getName()).findFirst();
}
if (!matchedCertificate.isPresent()) {
// We do quite expensive operation here, so better check the logging level beforehand.
if (logger.isDebugEnabled()) {
logger.debug("[X509ClientCertificateAuthenticator:authenticate] Couldn't match any certificate for expected Subject DN '" + subjectDNRegexp + "' with allow regex pattern '" + clientCfg.getAllowRegexPatternComparison() + "'.");
logger.debug("[X509ClientCertificateAuthenticator:authenticate] Available SubjectDNs: " + Arrays.stream(certs).map(cert -> cert.getSubjectDN().getName()).collect(Collectors.toList()));
}
context.attempted();
return;
} else {
logger.debug("[X509ClientCertificateAuthenticator:authenticate] Matched " + matchedCertificate.get() + " certificate.");
}
context.success();
}
Aggregations