use of org.keycloak.models.ClientModel in project keycloak by keycloak.
the class UserSessionLimitsAuthenticator method authenticate.
@Override
public void authenticate(AuthenticationFlowContext context) {
AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig();
Map<String, String> config = authenticatorConfig.getConfig();
// Get the configuration for this authenticator
behavior = config.get(UserSessionLimitsAuthenticatorFactory.BEHAVIOR);
int userRealmLimit = getIntConfigProperty(UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, config);
int userClientLimit = getIntConfigProperty(UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, config);
if (context.getRealm() != null && context.getUser() != null) {
// Get the session count in this realm for this specific user
List<UserSessionModel> userSessionsForRealm = session.sessions().getUserSessionsStream(context.getRealm(), context.getUser()).collect(Collectors.toList());
int userSessionCountForRealm = userSessionsForRealm.size();
// Get the session count related to the current client for this user
ClientModel currentClient = context.getAuthenticationSession().getClient();
logger.debugf("session-limiter's current keycloak clientId: %s", currentClient.getClientId());
List<UserSessionModel> userSessionsForClient = getUserSessionsForClientIfEnabled(userSessionsForRealm, currentClient, userClientLimit);
int userSessionCountForClient = userSessionsForClient.size();
logger.debugf("session-limiter's configured realm session limit: %s", userRealmLimit);
logger.debugf("session-limiter's configured client session limit: %s", userClientLimit);
logger.debugf("session-limiter's count of total user sessions for the entire realm (could be apps other than web apps): %s", userSessionCountForRealm);
logger.debugf("session-limiter's count of total user sessions for this keycloak client: %s", userSessionCountForClient);
// First check if the user has too many sessions in this realm
if (exceedsLimit(userSessionCountForRealm, userRealmLimit)) {
logger.infof("Too many session in this realm for the current user. Session count: %s", userSessionCountForRealm);
String eventDetails = String.format(realmEventDetailsTemplate, context.getRealm().getName(), userRealmLimit, userSessionCountForRealm, context.getUser().getId());
handleLimitExceeded(context, userSessionsForRealm, eventDetails);
} else // otherwise if the user is still allowed to create a new session in the realm, check if this applies for this specific client as well.
if (exceedsLimit(userSessionCountForClient, userClientLimit)) {
logger.infof("Too many sessions related to the current client for this user. Session count: %s", userSessionCountForRealm);
String eventDetails = String.format(clientEventDetailsTemplate, context.getRealm().getName(), userClientLimit, userSessionCountForClient, context.getUser().getId());
handleLimitExceeded(context, userSessionsForClient, eventDetails);
} else {
context.success();
}
} else {
context.success();
}
}
use of org.keycloak.models.ClientModel 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();
}
use of org.keycloak.models.ClientModel in project keycloak by keycloak.
the class ClientIdAndSecretAuthenticator method authenticateClient.
@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
String client_id = null;
String clientSecret = null;
String authorizationHeader = context.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
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;
if (authorizationHeader != null) {
String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader);
if (usernameSecret != null) {
client_id = usernameSecret[0];
clientSecret = usernameSecret[1];
} else {
// Don't send 401 if client_id parameter was sent in request. For example IE may automatically send "Authorization: Negotiate" in XHR requests even for public clients
if (formData != null && !formData.containsKey(OAuth2Constants.CLIENT_ID)) {
Response challengeResponse = Response.status(Response.Status.UNAUTHORIZED).header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getRealm().getName() + "\"").build();
context.challenge(challengeResponse);
return;
}
}
}
if (formData != null) {
// so we can also support clients overriding flows and using challenges (e.g: basic) to authenticate their users
if (formData.containsKey(OAuth2Constants.CLIENT_ID)) {
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
}
if (formData.containsKey(OAuth2Constants.CLIENT_SECRET)) {
clientSecret = formData.getFirst(OAuth2Constants.CLIENT_SECRET);
}
}
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;
}
context.getEvent().client(client_id);
ClientModel client = context.getSession().clients().getClientByClientId(context.getRealm(), client_id);
if (client == null) {
context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
return;
}
context.setClient(client);
if (!client.isEnabled()) {
context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
return;
}
// Skip client_secret validation for public client
if (client.isPublicClient()) {
context.success();
return;
}
if (clientSecret == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "unauthorized_client", "Client secret not provided in request");
context.challenge(challengeResponse);
return;
}
if (client.getSecret() == null) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "unauthorized_client", "Invalid client secret");
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
return;
}
if (!client.validateSecret(clientSecret)) {
Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "unauthorized_client", "Invalid client secret");
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse);
return;
}
context.success();
}
use of org.keycloak.models.ClientModel in project keycloak by keycloak.
the class SamlProtocol method authenticated.
@Override
public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
ClientModel client = clientSession.getClient();
SamlClient samlClient = new SamlClient(client);
String requestID = authSession.getClientNote(SAML_REQUEST_ID);
String relayState = authSession.getClientNote(GeneralConstants.RELAY_STATE);
String redirectUri = authSession.getRedirectUri();
String responseIssuer = getResponseIssuer(realm);
String nameIdFormat = getNameIdFormat(samlClient, authSession);
int assertionLifespan = samlClient.getAssertionLifespan();
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(assertionLifespan <= 0 ? realm.getAccessCodeLifespan() : assertionLifespan).subjectExpiration(assertionLifespan <= 0 ? realm.getAccessTokenLifespan() : assertionLifespan).sessionExpiration(realm.getSsoSessionMaxLifespan()).requestIssuer(clientSession.getClient().getClientId()).authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
builder.sessionIndex(sessionIndex);
if (!samlClient.includeAuthnStatement()) {
builder.disableAuthnStatement(true);
}
builder.includeOneTimeUseCondition(samlClient.includeOneTimeUseCondition());
List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers = new LinkedList<>();
List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> loginResponseMappers = new LinkedList<>();
AtomicReference<ProtocolMapperProcessor<SAMLRoleListMapper>> roleListMapper = new AtomicReference<>(null);
List<ProtocolMapperProcessor<SAMLNameIdMapper>> samlNameIdMappers = new LinkedList<>();
ProtocolMapperUtils.getSortedProtocolMappers(session, clientSessionCtx).forEach(entry -> {
ProtocolMapperModel mapping = entry.getKey();
ProtocolMapper mapper = entry.getValue();
if (mapper instanceof SAMLAttributeStatementMapper) {
attributeStatementMappers.add(new ProtocolMapperProcessor<>((SAMLAttributeStatementMapper) mapper, mapping));
}
if (mapper instanceof SAMLLoginResponseMapper) {
loginResponseMappers.add(new ProtocolMapperProcessor<>((SAMLLoginResponseMapper) mapper, mapping));
}
if (mapper instanceof SAMLRoleListMapper) {
roleListMapper.set(new ProtocolMapperProcessor<>((SAMLRoleListMapper) mapper, mapping));
}
if (mapper instanceof SAMLNameIdMapper) {
samlNameIdMappers.add(new ProtocolMapperProcessor<>((SAMLNameIdMapper) mapper, mapping));
}
});
Document samlDocument = null;
ResponseType samlModel = null;
KeyManager keyManager = session.keys();
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
boolean postBinding = isPostBinding(authSession);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
String nameId = getSAMLNameId(samlNameIdMappers, nameIdFormat, session, userSession, clientSession);
if (nameId == null) {
return samlErrorMessage(null, samlClient, isPostBinding(authSession), redirectUri, JBossSAMLURIConstants.STATUS_INVALID_NAMEIDPOLICY, relayState);
}
builder.nameIdentifier(nameIdFormat, nameId);
// save NAME_ID and format in clientSession as they may be persistent or
// transient or email and not username
// we'll need to send this back on a logout
clientSession.setNote(SAML_NAME_ID, nameId);
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
try {
if ((!postBinding) && samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
builder.addExtension(new KeycloakKeySamlExtensionGenerator(keyName));
}
samlModel = builder.buildModel();
final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession);
populateRoles(roleListMapper.get(), session, userSession, clientSessionCtx, attributeStatement);
// SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
if (attributeStatement.getAttributes().size() > 0) {
AssertionType assertion = samlModel.getAssertions().get(0).getAssertion();
assertion.addStatement(attributeStatement);
}
samlModel = transformLoginResponse(loginResponseMappers, samlModel, session, userSession, clientSessionCtx);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
}
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(session);
bindingBuilder.relayState(relayState);
if ("true".equals(clientSession.getNote(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.get()))) {
try {
return buildArtifactAuthenticatedResponse(clientSession, redirectUri, samlModel, bindingBuilder);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
}
}
if (samlClient.requiresRealmSignature() || samlClient.requiresAssertionSignature()) {
String canonicalization = samlClient.getCanonicalizationMethod();
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate());
if (samlClient.requiresRealmSignature())
bindingBuilder.signDocument();
if (samlClient.requiresAssertionSignature())
bindingBuilder.signAssertions();
}
if (samlClient.requiresEncryption()) {
PublicKey publicKey = null;
try {
publicKey = SamlProtocolUtils.getEncryptionKey(client);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
}
bindingBuilder.encrypt(publicKey);
}
try {
samlDocument = builder.buildDocument(samlModel);
return buildAuthenticatedResponse(clientSession, redirectUri, samlDocument, bindingBuilder);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
}
}
use of org.keycloak.models.ClientModel in project keycloak by keycloak.
the class SamlProtocol method frontchannelLogout.
@Override
public Response frontchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
SamlClient samlClient = new SamlClient(client);
try {
boolean postBinding = isLogoutPostBindingForClient(clientSession);
String bindingUri = getLogoutServiceUrl(session, client, postBinding ? SAML_POST_BINDING : SAML_REDIRECT_BINDING, false);
if (bindingUri == null) {
logger.warnf("Failed to logout client %s, skipping this client. Please configure the logout service url in the admin console for your client applications.", client.getClientId());
return null;
}
NodeGenerator[] extensions = new NodeGenerator[] {};
if (!postBinding) {
if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
extensions = new NodeGenerator[] { new KeycloakKeySamlExtensionGenerator(keyName) };
}
}
LogoutRequestType logoutRequest = createLogoutRequest(bindingUri, clientSession, client, extensions);
JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient, "true".equals(clientSession.getNote(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.get())));
// If this session uses artifact binding, send an artifact instead of the LogoutRequest
if ("true".equals(clientSession.getNote(JBossSAMLURIConstants.SAML_HTTP_ARTIFACT_BINDING.get())) && useArtifactForLogout(client)) {
clientSession.setAction(CommonClientSessionModel.Action.LOGGING_OUT.name());
return buildArtifactAuthenticatedResponse(clientSession, bindingUri, logoutRequest, binding);
}
Document samlDocument = SAML2Request.convert(logoutRequest);
if (postBinding) {
// This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
return binding.postBinding(samlDocument).request(bindingUri);
} else {
logger.debug("frontchannel redirect binding");
return binding.redirectBinding(samlDocument).request(bindingUri);
}
} catch (ConfigurationException | ProcessingException | IOException | ParsingException e) {
throw new RuntimeException(e);
}
}
Aggregations