Merge pull request #970 from pedroigor/master
[KEYCLOAK-883] - Code cleanup and refactoring.
This commit is contained in:
commit
2b7c7f63b6
30 changed files with 771 additions and 569 deletions
|
@ -31,6 +31,7 @@ public class FederatedIdentity {
|
|||
private String lastName;
|
||||
private String email;
|
||||
private String token;
|
||||
private String identityProviderId;
|
||||
|
||||
public FederatedIdentity(String id) {
|
||||
if (id == null) {
|
||||
|
@ -92,4 +93,25 @@ public class FederatedIdentity {
|
|||
public String getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public String getIdentityProviderId() {
|
||||
return this.identityProviderId;
|
||||
}
|
||||
|
||||
public void setIdentityProviderId(String identityProviderId) {
|
||||
this.identityProviderId = identityProviderId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"id='" + id + '\'' +
|
||||
", username='" + username + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", token='" + token + '\'' +
|
||||
", identityProviderId='" + identityProviderId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
/**
|
||||
* @author pedroigor
|
||||
*/
|
||||
public class IdentityBrokerException extends RuntimeException {
|
||||
|
||||
public IdentityBrokerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdentityBrokerException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
|
@ -68,5 +68,12 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||
*/
|
||||
AuthenticationResponse handleResponse(AuthenticationRequest request);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link javax.ws.rs.core.Response} containing the token previously stored during the authentication process for a
|
||||
* specific user.</p>
|
||||
*
|
||||
* @param identity
|
||||
* @return
|
||||
*/
|
||||
Response retrieveToken(FederatedIdentityModel identity);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
|||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -68,7 +69,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
|
||||
return AuthenticationResponse.temporaryRedirect(authorizationUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not create authentication request.", e);
|
||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,9 +86,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
|
||||
if (error != null) {
|
||||
if (error.equals("access_denied")) {
|
||||
throw new RuntimeException("Access denied.");
|
||||
throw new IdentityBrokerException("Access denied.");
|
||||
} else {
|
||||
throw new RuntimeException(error);
|
||||
throw new IdentityBrokerException(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,9 +112,9 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
return AuthenticationResponse.end(federatedIdentity);
|
||||
}
|
||||
|
||||
throw new RuntimeException("No authorization code from identity provider.");
|
||||
throw new IdentityBrokerException("No authorization code from identity provider.");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not process response from identity provider.", e);
|
||||
throw new IdentityBrokerException("Could not process response from identity provider.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +133,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
try {
|
||||
return mapper.readTree(response).get(tokenName).getTextValue();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
|
||||
throw new IdentityBrokerException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
|
||||
}
|
||||
} else {
|
||||
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
|
||||
|
@ -149,7 +150,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new RuntimeException("No access token from server.");
|
||||
throw new IdentityBrokerException("No access token from server.");
|
||||
}
|
||||
|
||||
return doGetFederatedIdentity(accessToken);
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.codehaus.jackson.JsonNode;
|
|||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
@ -62,7 +63,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new RuntimeException("No access_token from server.");
|
||||
throw new IdentityBrokerException("No access_token from server.");
|
||||
}
|
||||
|
||||
String idToken = extractTokenFromResponse(response, OIDC_PARAMETER_ID_TOKEN);
|
||||
|
@ -101,13 +102,13 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
|
||||
return identity;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not fetch attributes from userinfo endpoint.", e);
|
||||
throw new IdentityBrokerException("Could not fetch attributes from userinfo endpoint.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateIdToken(String idToken) {
|
||||
if (idToken == null) {
|
||||
throw new RuntimeException("No id_token from server.");
|
||||
throw new IdentityBrokerException("No id_token from server.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -131,10 +132,10 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Wrong issuer from id_token..");
|
||||
throw new IdentityBrokerException("Wrong issuer from id_token..");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not decode id token.", e);
|
||||
throw new IdentityBrokerException("Could not decode id token.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
|||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||
|
@ -112,11 +113,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
PublicKey publicKey = request.getRealm().getPublicKey();
|
||||
|
||||
if (privateKey == null) {
|
||||
throw new RuntimeException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a private key.");
|
||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a private key.");
|
||||
}
|
||||
|
||||
if (publicKey == null) {
|
||||
throw new RuntimeException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a public key.");
|
||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getName() + "] wants a signed authentication request. But the Realm [" + request.getRealm().getName() + "] does not have a public key.");
|
||||
}
|
||||
|
||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
||||
|
@ -131,7 +132,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
return AuthenticationResponse.fromResponse(authnRequestBuilder.redirectBinding().request());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not create authentication request.", e);
|
||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +146,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
|
||||
|
||||
if (samlResponse == null) {
|
||||
throw new RuntimeException("No response from SAML identity provider.");
|
||||
throw new IdentityBrokerException("No response from SAML identity provider.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -167,7 +168,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
|
||||
return AuthenticationResponse.end(identity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not process response from SAML identity provider.", e);
|
||||
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,7 +195,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
List<RTChoiceType> assertions = responseType.getAssertions();
|
||||
|
||||
if (assertions.isEmpty()) {
|
||||
throw new RuntimeException("No assertion from response.");
|
||||
throw new IdentityBrokerException("No assertion from response.");
|
||||
}
|
||||
|
||||
RTChoiceType rtChoiceType = assertions.get(0);
|
||||
|
@ -234,7 +235,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
detailMessage.append("none");
|
||||
}
|
||||
|
||||
throw new RuntimeException("Authentication failed with code [" + statusCode.getValue() + " and detail [" + detailMessage.toString() + ".");
|
||||
throw new IdentityBrokerException("Authentication failed with code [" + statusCode.getValue() + " and detail [" + detailMessage.toString() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,7 +247,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||
|
||||
if (enc == null) {
|
||||
throw new RuntimeException("No encrypted assertion found.");
|
||||
throw new IdentityBrokerException("No encrypted assertion found.");
|
||||
}
|
||||
|
||||
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
|
||||
|
@ -265,7 +266,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
|
||||
return responseType;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not decrypt assertion.", e);
|
||||
throw new IdentityBrokerException("Could not decrypt assertion.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ public interface Details {
|
|||
String REDIRECT_URI = "redirect_uri";
|
||||
String RESPONSE_TYPE = "response_type";
|
||||
String AUTH_METHOD = "auth_method";
|
||||
String IDENTITY_PROVIDER = "identity_provider";
|
||||
String IDENTITY_PROVIDER_IDENTITY = "identity_provider_identity";
|
||||
String REGISTER_METHOD = "register_method";
|
||||
String USERNAME = "username";
|
||||
String REMEMBER_ME = "remember_me";
|
||||
|
|
|
@ -35,7 +35,6 @@ public interface Errors {
|
|||
|
||||
String NOT_ALLOWED = "not_allowed";
|
||||
|
||||
String IDENTITY_PROVIDER_NOT_FOUND = "identity_provider_not_found";
|
||||
String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
|
||||
String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
|
||||
String FEDERATED_IDENTITY_DISABLED_REGISTRATION = "federated_identity_disabled_registration";
|
||||
|
|
|
@ -48,5 +48,14 @@ public enum EventType {
|
|||
UNREGISTER_NODE,
|
||||
|
||||
USER_INFO_REQUEST,
|
||||
USER_INFO_REQUEST_ERROR
|
||||
USER_INFO_REQUEST_ERROR,
|
||||
|
||||
IDENTITY_PROVIDER_LOGIN,
|
||||
IDENTITY_PROVIDER_LOGIN_ERROR,
|
||||
IDENTITY_PROVIDER_RESPONSE,
|
||||
IDENTITY_PROVIDER_RESPONSE_ERROR,
|
||||
IDENTITY_PROVIDER_RETRIEVE_TOKEN,
|
||||
IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR,
|
||||
IDENTITY_PROVIDER_ACCCOUNT_LINKING,
|
||||
IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR,
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|||
$scope.identity = Auth.getIdentity();
|
||||
|
||||
$scope.loadSocialProfile = function() {
|
||||
$http.get('http://localhost:8081/auth/broker/facebook-identity-provider-realm/facebook/token').success(function(data) {
|
||||
$http.get('http://localhost:8081/auth/realms/facebook-identity-provider-realm/broker/facebook/token').success(function(data) {
|
||||
var accessTokenParameter = 'access_token=';
|
||||
var accessToken = data.substring(data.indexOf(accessTokenParameter) + accessTokenParameter.length, data.indexOf('&'));
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ module.controller('GlobalCtrl', function($scope, $http, $location, Auth) {
|
|||
$scope.identity = Auth.getIdentity();
|
||||
|
||||
$scope.loadSocialProfile = function() {
|
||||
$http.get('http://localhost:8081/auth/broker/google-identity-provider-realm/google/token').success(function(data) {
|
||||
$http.get('http://localhost:8081/auth/realms/google-identity-provider-realm/broker/google/token').success(function(data) {
|
||||
var accessToken = data.access_token;
|
||||
|
||||
var req = {
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"name": "http://localhost:8080/auth/",
|
||||
"enabled": true,
|
||||
"redirectUris": [
|
||||
"http://localhost:8080/auth/broker/saml-broker-authentication-realm/saml-identity-provider"
|
||||
"http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider"
|
||||
],
|
||||
"attributes": {
|
||||
"saml.assertion.signature": "true",
|
||||
|
|
|
@ -114,7 +114,7 @@ public class TwitterShowUserServlet extends HttpServlet {
|
|||
}
|
||||
|
||||
private String getIdentityProviderTokenUrl() {
|
||||
return this.authServer + "/broker/" + this.realmName + "/" + this.identityProvider.getId() + "/token";
|
||||
return this.authServer + "/realms/" + this.realmName + "/broker/" + this.identityProvider.getId() + "/token";
|
||||
}
|
||||
|
||||
private void initKeyCloakClient(ServletConfig config) {
|
||||
|
|
|
@ -719,7 +719,7 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
|||
}
|
||||
}, true);
|
||||
|
||||
$scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/broker/") + realm.realm + "/" ;
|
||||
$scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/realms/") + realm.realm + "/broker/" ;
|
||||
|
||||
$scope.addProvider = function(provider) {
|
||||
$location.url("/create/identity-provider/" + realm.realm + "/" + provider.id);
|
||||
|
|
|
@ -61,7 +61,7 @@ public class IdentityProviderBean {
|
|||
}
|
||||
}
|
||||
|
||||
String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider, realm).toString();
|
||||
String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider.getId(), realm.getName()).toString();
|
||||
providers.add(new IdentityProvider(identityProvider.getId(), identityProvider.getName(), loginUrl));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -879,7 +879,7 @@ public class OpenIDConnectService {
|
|||
}
|
||||
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), identityProviderModel, realm, accessCode)).build();
|
||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), idpHint, this.realm.getName(), accessCode)).build();
|
||||
}
|
||||
|
||||
response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event);
|
||||
|
@ -898,7 +898,7 @@ public class OpenIDConnectService {
|
|||
if (!identityProviders.isEmpty()) {
|
||||
if (identityProviders.size() == 1) {
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), identityProviders.get(0), this.realm, accessCode))
|
||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), identityProviders.get(0).getId(), this.realm.getName(), accessCode))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.protocol.oidc.OpenIDConnect;
|
||||
|
@ -684,14 +683,9 @@ public class AccountService {
|
|||
clientSession.setRedirectUri(redirectUri);
|
||||
clientSession.setNote(OpenIDConnect.STATE_PARAM, UUID.randomUUID().toString());
|
||||
|
||||
URI url = UriBuilder.fromUri(this.uriInfo.getBaseUri())
|
||||
.path(AuthenticationBrokerResource.class)
|
||||
.path(AuthenticationBrokerResource.class, "performLogin")
|
||||
.queryParam("provider_id", providerId)
|
||||
.queryParam("code", clientSessionCode.getCode())
|
||||
.build(this.realm.getName());
|
||||
|
||||
return Response.temporaryRedirect(url).build();
|
||||
return Response.temporaryRedirect(
|
||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, realm.getName(), clientSessionCode.getCode()))
|
||||
.build();
|
||||
} catch (Exception spe) {
|
||||
setReferrerOnPage();
|
||||
return account.setError(Messages.IDENTITY_PROVIDER_REDIRECT_ERROR).createResponse(AccountPages.FEDERATED_IDENTITY);
|
||||
|
|
|
@ -1,495 +0,0 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OAuthClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.EventsManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.OPTIONS;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
||||
import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
|
||||
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP;
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
@Path("/broker")
|
||||
public class AuthenticationBrokerResource {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthenticationBrokerResource.class);
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
|
||||
@Context
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
private HttpRequest request;
|
||||
|
||||
@GET
|
||||
@Path("{realm}/login")
|
||||
public Response performLogin(@PathParam("realm") String realmName,
|
||||
@QueryParam("provider_id") String providerId,
|
||||
@QueryParam("code") String code) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
ClientSessionCode clientCode = isValidAuthorizationCode(code, realm);
|
||||
|
||||
if (clientCode == null) {
|
||||
return redirectToErrorPage(realm, "Invalid code, please login again through your application.");
|
||||
}
|
||||
|
||||
EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
|
||||
.event(EventType.LOGIN)
|
||||
.detail(Details.AUTH_METHOD, "unknown_id@" + providerId);
|
||||
|
||||
try {
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
ClientModel clientModel = clientSession.getClient();
|
||||
Response response = checkClientPermissions(clientModel, providerId);
|
||||
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
|
||||
|
||||
if (identityProvider == null) {
|
||||
event.error(Errors.IDENTITY_PROVIDER_NOT_FOUND);
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Identity Provider not found").createErrorPage();
|
||||
}
|
||||
|
||||
AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, code, realm,
|
||||
clientSession));
|
||||
|
||||
response = authenticationResponse.getResponse();
|
||||
|
||||
if (response != null) {
|
||||
event.success();
|
||||
return response;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Could not send authentication request to identity provider " + providerId, e);
|
||||
String message = "Could not send authentication request to identity provider";
|
||||
event.error(message);
|
||||
return redirectToErrorPage(realm, message);
|
||||
}
|
||||
|
||||
String message = "Could not proceed with authentication request to identity provider.";
|
||||
|
||||
event.error(message);
|
||||
|
||||
return redirectToErrorPage(realm, message);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{realm}/{provider_id}")
|
||||
public Response handleResponseGet(@PathParam("realm") final String realmName,
|
||||
@PathParam("provider_id") String providerId) {
|
||||
return handleResponse(realmName, providerId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{realm}/{provider_id}")
|
||||
public Response handleResponsePost(@PathParam("realm") final String realmName,
|
||||
@PathParam("provider_id") String providerId) {
|
||||
return handleResponse(realmName, providerId);
|
||||
}
|
||||
|
||||
@Path("{realm}/{provider_id}/token")
|
||||
@OPTIONS
|
||||
public Response retrieveTokenPreflight() {
|
||||
return Cors.add(this.request, Response.ok()).auth().preflight().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{realm}/{provider_id}/token")
|
||||
public Response retrieveToken(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId) {
|
||||
return getToken(realmName, providerId, false);
|
||||
}
|
||||
|
||||
private Response getToken(String realmName, String providerId, boolean forceRetrieval) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
AppAuthManager authManager = new AppAuthManager();
|
||||
AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, request.getHttpHeaders());
|
||||
|
||||
if (authResult != null) {
|
||||
String audience = authResult.getToken().getAudience();
|
||||
ClientModel clientModel = realm.findClient(audience);
|
||||
|
||||
if (clientModel == null) {
|
||||
return corsResponse(Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN), clientModel);
|
||||
}
|
||||
|
||||
if (!clientModel.hasIdentityProvider(providerId)) {
|
||||
return corsResponse(Flows.errors().error("Client [" + audience + "] not authorized.", Response.Status.FORBIDDEN), clientModel);
|
||||
}
|
||||
|
||||
if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
|
||||
return corsResponse(Flows.forms(session, realm, clientModel, uriInfo)
|
||||
.setClientSessionCode(authManager.extractAuthorizationHeaderToken(request.getHttpHeaders()))
|
||||
.setAccessRequest("Your information from " + providerId + " identity provider.")
|
||||
.setClient(clientModel)
|
||||
.setUriInfo(this.uriInfo)
|
||||
.setActionUri(this.uriInfo.getRequestUri())
|
||||
.createOAuthGrant(), clientModel);
|
||||
}
|
||||
|
||||
IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
|
||||
|
||||
if (identityProviderConfig.isStoreToken()) {
|
||||
FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, realm);
|
||||
|
||||
if (identity == null) {
|
||||
return corsResponse(Flows.errors().error("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerId + "].", Response.Status.FORBIDDEN), clientModel);
|
||||
}
|
||||
|
||||
return corsResponse(identityProvider.retrieveToken(identity), clientModel);
|
||||
}
|
||||
|
||||
return corsResponse(Flows.errors().error("Identity Provider [" + providerId + "] does not support this operation.", Response.Status.FORBIDDEN), clientModel);
|
||||
}
|
||||
|
||||
return Flows.errors().error("Invalid code.", Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{realm}/{provider_id}/token")
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response consentTokenRetrieval(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
if (formData.containsKey("cancel")) {
|
||||
return Flows.errors().error("Permission not approved.", Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
return getToken(realmName, providerId, true);
|
||||
}
|
||||
|
||||
private Response handleResponse(String realmName, String providerId) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
|
||||
|
||||
try {
|
||||
IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
|
||||
|
||||
if (identityProvider == null) {
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Social identityProvider not found").createErrorPage();
|
||||
}
|
||||
|
||||
String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null, realm, null));
|
||||
|
||||
if (relayState == null) {
|
||||
return redirectToErrorPage(realm, "No relay state from identity identityProvider.");
|
||||
}
|
||||
|
||||
ClientSessionCode clientCode = isValidAuthorizationCode(relayState, realm);
|
||||
|
||||
if (clientCode == null) {
|
||||
return redirectToErrorPage(realm, "Invalid authorization code, please login again through your application.");
|
||||
}
|
||||
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
ClientModel clientModel = clientSession.getClient();
|
||||
Response response = checkClientPermissions(clientModel, providerId);
|
||||
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession));
|
||||
|
||||
response = authenticationResponse.getResponse();
|
||||
|
||||
if (response != null) {
|
||||
return response;
|
||||
}
|
||||
|
||||
FederatedIdentity identity = authenticationResponse.getUser();
|
||||
|
||||
if (!identityProviderConfig.isStoreToken()) {
|
||||
identity.setToken(null);
|
||||
}
|
||||
|
||||
return performLocalAuthentication(realm, providerId, identity, clientCode);
|
||||
} catch (Exception e) {
|
||||
logger.error("Could not authenticate user against provider " + providerId, e);
|
||||
if (session.getTransaction().isActive()) {
|
||||
session.getTransaction().rollback();
|
||||
}
|
||||
|
||||
return Flows.forms(session, realm, null, uriInfo).setError("Authentication failed. Could not authenticate against Identity Provider [" + identityProviderConfig.getName() + "].").createErrorPage();
|
||||
} finally {
|
||||
if (session.getTransaction().isActive()) {
|
||||
session.getTransaction().commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Response performLocalAuthentication(RealmModel realm, String providerId, FederatedIdentity updatedIdentity, ClientSessionCode clientCode) {
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, updatedIdentity.getId(),
|
||||
updatedIdentity.getUsername(), updatedIdentity.getToken());
|
||||
UserModel federatedUser = session.users().getUserByFederatedIdentity(federatedIdentityModel, realm);
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
|
||||
|
||||
String authMethod = federatedIdentityModel.getUserId() + "@" + identityProviderConfig.getId();
|
||||
EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
|
||||
.event(EventType.LOGIN)
|
||||
.client(clientSession.getClient())
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.AUTH_METHOD, authMethod);
|
||||
|
||||
event.detail(Details.USERNAME, authMethod);
|
||||
|
||||
// Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
|
||||
if (clientSession.getUserSession() != null) {
|
||||
UserModel authenticatedUser = clientSession.getUserSession().getUser();
|
||||
|
||||
if (federatedUser != null) {
|
||||
String message = "The updatedIdentity returned by the Identity Provider [" + identityProviderConfig.getName() + "] is already linked to other user";
|
||||
event.error(message);
|
||||
return redirectToErrorPage(realm, message);
|
||||
}
|
||||
|
||||
if (!authenticatedUser.isEnabled()) {
|
||||
event.error(Errors.USER_DISABLED);
|
||||
return redirectToErrorPage(realm, "User is disabled");
|
||||
}
|
||||
|
||||
if (!authenticatedUser.hasRole(realm.getApplicationByName(ACCOUNT_MANAGEMENT_APP).getRole(MANAGE_ACCOUNT))) {
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return redirectToErrorPage(realm, "Insufficient permissions to link updatedIdentity");
|
||||
}
|
||||
|
||||
session.users().addFederatedIdentity(realm, authenticatedUser, federatedIdentityModel);
|
||||
|
||||
event.success();
|
||||
|
||||
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
|
||||
}
|
||||
|
||||
if (federatedUser == null) {
|
||||
|
||||
String errorMessage = null;
|
||||
|
||||
// Check if no user already exists with this username or email
|
||||
UserModel existingUser = session.users().getUserByEmail(updatedIdentity.getEmail(), realm);
|
||||
if (existingUser != null) {
|
||||
event.error(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
|
||||
errorMessage = "federatedIdentityEmailExists";
|
||||
} else {
|
||||
existingUser = session.users().getUserByUsername(updatedIdentity.getUsername(), realm);
|
||||
if (existingUser != null) {
|
||||
event.error(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
|
||||
errorMessage = "federatedIdentityUsernameExists";
|
||||
}
|
||||
}
|
||||
|
||||
// Check if realm registration is allowed
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
event.error(Errors.FEDERATED_IDENTITY_DISABLED_REGISTRATION);
|
||||
errorMessage = "federatedIdentityDisabledRegistration";
|
||||
}
|
||||
|
||||
if (errorMessage == null) {
|
||||
logger.debug("Creating user " + updatedIdentity.getUsername() + " and linking to federation provider " + providerId);
|
||||
federatedUser = session.users().addUser(realm, updatedIdentity.getUsername());
|
||||
federatedUser.setEnabled(true);
|
||||
federatedUser.setFirstName(updatedIdentity.getFirstName());
|
||||
federatedUser.setLastName(updatedIdentity.getLastName());
|
||||
federatedUser.setEmail(updatedIdentity.getEmail());
|
||||
|
||||
session.users().addFederatedIdentity(realm, federatedUser, federatedIdentityModel);
|
||||
|
||||
event.clone().user(federatedUser).event(EventType.REGISTER)
|
||||
.detail(Details.REGISTER_METHOD, authMethod)
|
||||
.detail(Details.EMAIL, federatedUser.getEmail())
|
||||
.removeDetail("auth_method")
|
||||
.success();
|
||||
|
||||
if (identityProviderConfig.isUpdateProfileFirstLogin()) {
|
||||
federatedUser.addRequiredAction(UPDATE_PROFILE);
|
||||
}
|
||||
} else {
|
||||
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
|
||||
.setClientSessionCode(clientCode.getCode())
|
||||
.setError(errorMessage)
|
||||
.createLogin();
|
||||
}
|
||||
}
|
||||
|
||||
federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, providerId, realm);
|
||||
|
||||
federatedIdentityModel.setToken(updatedIdentity.getToken());
|
||||
|
||||
this.session.users().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
|
||||
|
||||
event.user(federatedUser);
|
||||
|
||||
String username = federatedIdentityModel.getUserId() + "@" + identityProviderConfig.getName();
|
||||
|
||||
UserSessionModel userSession = session.sessions()
|
||||
.createUserSession(realm, federatedUser, username, clientConnection.getRemoteAddr(), "broker", false);
|
||||
|
||||
event.session(userSession);
|
||||
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
|
||||
AuthenticationManager authManager = new AuthenticationManager();
|
||||
|
||||
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request,
|
||||
uriInfo, event);
|
||||
}
|
||||
|
||||
private ClientSessionCode isValidAuthorizationCode(String code, RealmModel realm) {
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, realm);
|
||||
|
||||
if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
|
||||
return clientCode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AuthenticationRequest createAuthenticationRequest(String providerId, String code, RealmModel realm, ClientSessionModel clientSession) {
|
||||
return new AuthenticationRequest(this.session, realm, clientSession, this.request, this.uriInfo, code, getRedirectUri(providerId, realm));
|
||||
}
|
||||
|
||||
private String getRedirectUri(String providerId, RealmModel realm) {
|
||||
return UriBuilder.fromUri(this.uriInfo.getBaseUri())
|
||||
.path(AuthenticationBrokerResource.class)
|
||||
.path(AuthenticationBrokerResource.class, "handleResponseGet")
|
||||
.build(realm.getName(), providerId)
|
||||
.toString();
|
||||
}
|
||||
|
||||
private Response redirectToErrorPage(RealmModel realm, String message) {
|
||||
return Flows.forwardToSecurityFailurePage(this.session, realm, uriInfo, message);
|
||||
}
|
||||
|
||||
private IdentityProvider getIdentityProvider(RealmModel realm, String providerId) {
|
||||
IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(providerId);
|
||||
|
||||
if (identityProviderModel != null) {
|
||||
IdentityProviderFactory providerFactory = getIdentityProviderFactory(identityProviderModel);
|
||||
|
||||
if (providerFactory == null) {
|
||||
throw new RuntimeException("Could not find provider factory for identity provider [" + providerId + "].");
|
||||
}
|
||||
|
||||
return providerFactory.create(identityProviderModel);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IdentityProviderFactory getIdentityProviderFactory(IdentityProviderModel model) {
|
||||
Map<String, IdentityProviderFactory> availableProviders = new HashMap<String, IdentityProviderFactory>();
|
||||
List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
|
||||
|
||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
|
||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
|
||||
|
||||
for (ProviderFactory providerFactory : allProviders) {
|
||||
availableProviders.put(providerFactory.getId(), (IdentityProviderFactory) providerFactory);
|
||||
}
|
||||
|
||||
return availableProviders.get(model.getProviderId());
|
||||
}
|
||||
|
||||
private IdentityProviderModel getIdentityProviderConfig(RealmModel realm, String providerId) {
|
||||
for (IdentityProviderModel model : realm.getIdentityProviders()) {
|
||||
if (model.getId().equals(providerId)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Response checkClientPermissions(ClientModel clientModel, String providerId) {
|
||||
if (clientModel == null) {
|
||||
return Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (!clientModel.hasIdentityProvider(providerId)) {
|
||||
return Flows.errors().error("Client [" + clientModel.getClientId() + "] not authorized.", Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Response corsResponse(Response response, ClientModel clientModel) {
|
||||
return Cors.add(request, Response.fromResponse(response)).auth().allowedOrigins(clientModel).build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,605 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OAuthClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.managers.EventsManager;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.OPTIONS;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
||||
import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
|
||||
import static org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP;
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
|
||||
|
||||
/**
|
||||
* <p></p>
|
||||
*
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
@Path("/broker")
|
||||
public class IdentityBrokerService {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(IdentityBrokerService.class);
|
||||
|
||||
private final RealmModel realmModel;
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
|
||||
@Context
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
private HttpRequest request;
|
||||
private EventBuilder event;
|
||||
|
||||
public IdentityBrokerService(RealmModel realmModel) {
|
||||
if (realmModel == null) {
|
||||
throw new IllegalArgumentException("Realm can not be null.");
|
||||
}
|
||||
|
||||
this.realmModel = realmModel;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
this.event = new EventsManager(this.realmModel, this.session, this.clientConnection).createEventBuilder().event(EventType.IDENTITY_PROVIDER_LOGIN);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{provider_id}/login")
|
||||
public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
|
||||
this.event.detail(Details.IDENTITY_PROVIDER, providerId);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending authentication request to identity provider [%s].", providerId);
|
||||
}
|
||||
|
||||
try {
|
||||
ClientSessionCode clientSessionCode = parseClientSessionCode(code, providerId);
|
||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
||||
AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, clientSessionCode));
|
||||
|
||||
Response response = authenticationResponse.getResponse();
|
||||
|
||||
if (response != null) {
|
||||
this.event.success();
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Identity provider [%s] is going to send a request [%s].", identityProvider, response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
} catch (IdentityBrokerException e) {
|
||||
return redirectToErrorPage("Could not send authentication request to identity provider [" + providerId + "].", e);
|
||||
} catch (Exception e) {
|
||||
return redirectToErrorPage("Unexpected error when handling authentication request to identity provider [" + providerId + "].", e);
|
||||
}
|
||||
|
||||
return redirectToErrorPage("Could not proceed with authentication request to identity provider.");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{provider_id}")
|
||||
public Response handleResponseGet(@PathParam("provider_id") String providerId) {
|
||||
return handleResponse(providerId);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{provider_id}")
|
||||
public Response handleResponsePost(@PathParam("provider_id") String providerId) {
|
||||
return handleResponse(providerId);
|
||||
}
|
||||
|
||||
@Path("{provider_id}/token")
|
||||
@OPTIONS
|
||||
public Response retrieveTokenPreflight() {
|
||||
return Cors.add(this.request, Response.ok()).auth().preflight().build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{provider_id}/token")
|
||||
public Response retrieveToken(@PathParam("provider_id") String providerId) {
|
||||
return getToken(providerId, false);
|
||||
}
|
||||
|
||||
private Response getToken(String providerId, boolean forceRetrieval) {
|
||||
this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN);
|
||||
|
||||
try {
|
||||
AppAuthManager authManager = new AppAuthManager();
|
||||
AuthResult authResult = authManager.authenticateBearerToken(this.session, this.realmModel, this.uriInfo, this.clientConnection, this.request.getHttpHeaders());
|
||||
|
||||
if (authResult != null) {
|
||||
String audience = authResult.getToken().getAudience();
|
||||
ClientModel clientModel = this.realmModel.findClient(audience);
|
||||
|
||||
if (clientModel == null) {
|
||||
return badRequest("Invalid client.");
|
||||
}
|
||||
|
||||
if (!clientModel.hasIdentityProvider(providerId)) {
|
||||
return corsResponse(badRequest("Client [" + audience + "] not authorized."), clientModel);
|
||||
}
|
||||
|
||||
if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
|
||||
return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo)
|
||||
.setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders()))
|
||||
.setAccessRequest("Your information from " + providerId + " identity provider.")
|
||||
.setClient(clientModel)
|
||||
.setUriInfo(this.uriInfo)
|
||||
.setActionUri(this.uriInfo.getRequestUri())
|
||||
.createOAuthGrant(), clientModel);
|
||||
}
|
||||
|
||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
||||
|
||||
if (identityProviderConfig.isStoreToken()) {
|
||||
FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, this.realmModel);
|
||||
|
||||
if (identity == null) {
|
||||
return corsResponse(badRequest("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerId + "]."), clientModel);
|
||||
}
|
||||
|
||||
this.event.success();
|
||||
|
||||
return corsResponse(identityProvider.retrieveToken(identity), clientModel);
|
||||
}
|
||||
|
||||
return corsResponse(badRequest("Identity Provider [" + providerId + "] does not support this operation."), clientModel);
|
||||
}
|
||||
|
||||
return badRequest("Invalid token.");
|
||||
} catch (IdentityBrokerException e) {
|
||||
return redirectToErrorPage("Could not obtain token fron identity provider [" + providerId + "].", e);
|
||||
} catch (Exception e) {
|
||||
return redirectToErrorPage("Unexpected error when retrieving token from identity provider [" + providerId + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{provider_id}/token")
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response consentTokenRetrieval(@PathParam("provider_id") String providerId,
|
||||
MultivaluedMap<String, String> formData) {
|
||||
if (formData.containsKey("cancel")) {
|
||||
return redirectToErrorPage("Permission not approved.");
|
||||
}
|
||||
|
||||
return getToken(providerId, true);
|
||||
}
|
||||
|
||||
private Response handleResponse(String providerId) {
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Handling authentication response from identity provider [%s].", providerId);
|
||||
}
|
||||
this.event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
this.event.detail(Details.IDENTITY_PROVIDER, providerId);
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
||||
|
||||
try {
|
||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
||||
String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null));
|
||||
|
||||
if (relayState == null) {
|
||||
return redirectToErrorPage("No relay state in response from identity identity [" + providerId + ".");
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Relay state is valid: [%s].", relayState);
|
||||
}
|
||||
|
||||
ClientSessionCode clientSessionCode = parseClientSessionCode(relayState, providerId);
|
||||
AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, clientSessionCode));
|
||||
Response response = authenticationResponse.getResponse();
|
||||
|
||||
if (response != null) {
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Identity provider [%s] is going to send a response [%s].", identityProvider, response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
FederatedIdentity identity = authenticationResponse.getUser();
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Identity provider [%s] returned with identity [%s].", providerId, identity);
|
||||
}
|
||||
|
||||
if (!identityProviderConfig.isStoreToken()) {
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Token will not be stored for identity provider [%s].", providerId);
|
||||
}
|
||||
identity.setToken(null);
|
||||
}
|
||||
|
||||
identity.setIdentityProviderId(providerId);
|
||||
|
||||
return performLocalAuthentication(identity, clientSessionCode);
|
||||
} catch (IdentityBrokerException e) {
|
||||
rollback();
|
||||
return redirectToErrorPage("Authentication failed. Could not authenticate with identity provider [" + providerId + "].", e);
|
||||
} catch (Exception e) {
|
||||
rollback();
|
||||
return redirectToErrorPage("Unexpected error when handling response from identity provider [" + providerId + "].", e);
|
||||
} finally {
|
||||
if (this.session.getTransaction().isActive()) {
|
||||
this.session.getTransaction().commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Response performLocalAuthentication(FederatedIdentity updatedIdentity, ClientSessionCode clientCode) {
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(updatedIdentity.getIdentityProviderId());
|
||||
String providerId = identityProviderConfig.getId();
|
||||
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, updatedIdentity.getId(),
|
||||
updatedIdentity.getUsername(), updatedIdentity.getToken());
|
||||
|
||||
this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
|
||||
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||
.detail(Details.IDENTITY_PROVIDER_IDENTITY, updatedIdentity.getUsername());
|
||||
|
||||
UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
|
||||
|
||||
// Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
|
||||
if (clientSession.getUserSession() != null) {
|
||||
return performAccountLinking(clientSession, providerId, federatedIdentityModel, federatedUser);
|
||||
}
|
||||
|
||||
if (federatedUser == null) {
|
||||
try {
|
||||
federatedUser = createUser(updatedIdentity);
|
||||
|
||||
if (identityProviderConfig.isUpdateProfileFirstLogin()) {
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Identity provider requires update profile action.", federatedUser);
|
||||
}
|
||||
federatedUser.addRequiredAction(UPDATE_PROFILE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return redirectToLoginPage(e.getMessage(), clientCode);
|
||||
}
|
||||
}
|
||||
|
||||
updateFederatedIdentity(updatedIdentity, federatedUser);
|
||||
|
||||
UserSessionModel userSession = this.session.sessions()
|
||||
.createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false);
|
||||
|
||||
this.event.user(federatedUser);
|
||||
this.event.session(userSession);
|
||||
|
||||
TokenManager.attachClientSession(userSession, clientSession);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Performing local authentication for user [%s].", federatedUser);
|
||||
}
|
||||
|
||||
return AuthenticationManager.nextActionAfterAuthentication(this.session, userSession, clientSession, this.clientConnection, this.request,
|
||||
this.uriInfo, event);
|
||||
}
|
||||
|
||||
private Response performAccountLinking(ClientSessionModel clientSession, String providerId, FederatedIdentityModel federatedIdentityModel, UserModel federatedUser) {
|
||||
this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING);
|
||||
|
||||
if (federatedUser != null) {
|
||||
return redirectToErrorPage("The identity returned by the identity provider [" + providerId + "] is already linked to other user.");
|
||||
}
|
||||
|
||||
UserModel authenticatedUser = clientSession.getUserSession().getUser();
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Linking account [%s] from identity provider [%s] to user [%s].", federatedIdentityModel, providerId, authenticatedUser);
|
||||
}
|
||||
|
||||
if (!authenticatedUser.isEnabled()) {
|
||||
fireErrorEvent(Errors.USER_DISABLED);
|
||||
return redirectToErrorPage("User is disabled.");
|
||||
}
|
||||
|
||||
if (!authenticatedUser.hasRole(this.realmModel.getApplicationByName(ACCOUNT_MANAGEMENT_APP).getRole(MANAGE_ACCOUNT))) {
|
||||
fireErrorEvent(Errors.NOT_ALLOWED);
|
||||
return redirectToErrorPage("Insufficient permissions to link identities.");
|
||||
}
|
||||
|
||||
this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel);
|
||||
|
||||
this.event.success();
|
||||
|
||||
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
|
||||
}
|
||||
|
||||
private void updateFederatedIdentity(FederatedIdentity updatedIdentity, UserModel federatedUser) {
|
||||
FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, updatedIdentity.getIdentityProviderId(), this.realmModel);
|
||||
|
||||
federatedIdentityModel.setToken(updatedIdentity.getToken());
|
||||
|
||||
this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, updatedIdentity.getIdentityProviderId());
|
||||
}
|
||||
}
|
||||
|
||||
private ClientSessionCode parseClientSessionCode(String code, String providerId) {
|
||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
|
||||
|
||||
if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
|
||||
validateClientPermissions(clientCode, providerId);
|
||||
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||
|
||||
if (clientSession != null) {
|
||||
ClientModel client = clientSession.getClient();
|
||||
|
||||
if (client != null) {
|
||||
LOGGER.debugf("Got authorization code from client [%s].", client.getClientId());
|
||||
this.event.client(client);
|
||||
}
|
||||
|
||||
if (clientSession.getUserSession() != null) {
|
||||
this.event.session(clientSession.getUserSession());
|
||||
}
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization code is valid.");
|
||||
}
|
||||
|
||||
return clientCode;
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Invalid code, please login again through your application.");
|
||||
}
|
||||
|
||||
private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode clientSessionCode) {
|
||||
ClientSessionModel clientSession = null;
|
||||
String relayState = null;
|
||||
|
||||
if (clientSessionCode != null) {
|
||||
clientSession = clientSessionCode.getClientSession();
|
||||
relayState = clientSessionCode.getCode();
|
||||
}
|
||||
|
||||
return new AuthenticationRequest(this.session, this.realmModel, clientSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
|
||||
}
|
||||
|
||||
private String getRedirectUri(String providerId) {
|
||||
return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString();
|
||||
}
|
||||
|
||||
private Response redirectToErrorPage(String message) {
|
||||
return redirectToErrorPage(message, null);
|
||||
}
|
||||
|
||||
private Response redirectToErrorPage(String message, Throwable throwable) {
|
||||
fireErrorEvent(message, throwable);
|
||||
return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message);
|
||||
}
|
||||
|
||||
private Response badRequest(String message) {
|
||||
fireErrorEvent(message);
|
||||
return Flows.errors().error(message, Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private Response redirectToLoginPage(String message, ClientSessionCode clientCode) {
|
||||
fireErrorEvent(message);
|
||||
return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo)
|
||||
.setClientSessionCode(clientCode.getCode())
|
||||
.setError(message)
|
||||
.createLogin();
|
||||
}
|
||||
|
||||
private IdentityProvider getIdentityProvider(String providerId) {
|
||||
IdentityProviderModel identityProviderModel = this.realmModel.getIdentityProviderById(providerId);
|
||||
|
||||
if (identityProviderModel != null) {
|
||||
IdentityProviderFactory providerFactory = getIdentityProviderFactory(identityProviderModel);
|
||||
|
||||
if (providerFactory == null) {
|
||||
throw new IdentityBrokerException("Could not find factory for identity provider [" + providerId + "].");
|
||||
}
|
||||
|
||||
return providerFactory.create(identityProviderModel);
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Identity Provider [" + providerId + "] not found.");
|
||||
}
|
||||
|
||||
private IdentityProviderFactory getIdentityProviderFactory(IdentityProviderModel model) {
|
||||
Map<String, IdentityProviderFactory> availableProviders = new HashMap<String, IdentityProviderFactory>();
|
||||
List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
|
||||
|
||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
|
||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
|
||||
|
||||
for (ProviderFactory providerFactory : allProviders) {
|
||||
availableProviders.put(providerFactory.getId(), (IdentityProviderFactory) providerFactory);
|
||||
}
|
||||
|
||||
return availableProviders.get(model.getProviderId());
|
||||
}
|
||||
|
||||
private IdentityProviderModel getIdentityProviderConfig(String providerId) {
|
||||
for (IdentityProviderModel model : this.realmModel.getIdentityProviders()) {
|
||||
if (model.getId().equals(providerId)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
|
||||
}
|
||||
|
||||
private void validateClientPermissions(ClientSessionCode clientSessionCode, String providerId) {
|
||||
ClientSessionModel clientSession = clientSessionCode.getClientSession();
|
||||
ClientModel clientModel = clientSession.getClient();
|
||||
|
||||
if (clientModel == null) {
|
||||
throw new IdentityBrokerException("Invalid client.");
|
||||
}
|
||||
|
||||
if (!clientModel.hasIdentityProvider(providerId)) {
|
||||
throw new IdentityBrokerException("Client [" + clientModel.getClientId() + "] not authorized to authenticate with identity provider [" + providerId + "].");
|
||||
}
|
||||
}
|
||||
|
||||
private UserModel createUser(FederatedIdentity updatedIdentity) {
|
||||
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(updatedIdentity.getIdentityProviderId(), updatedIdentity.getId(),
|
||||
updatedIdentity.getUsername(), updatedIdentity.getToken());
|
||||
// Check if no user already exists with this username or email
|
||||
UserModel existingUser = this.session.users().getUserByEmail(updatedIdentity.getEmail(), this.realmModel);
|
||||
|
||||
if (existingUser != null) {
|
||||
fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
|
||||
throw new IdentityBrokerException("federatedIdentityEmailExists");
|
||||
}
|
||||
|
||||
existingUser = this.session.users().getUserByUsername(updatedIdentity.getUsername(), this.realmModel);
|
||||
|
||||
if (existingUser != null) {
|
||||
fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
|
||||
throw new IdentityBrokerException("federatedIdentityUsernameExists");
|
||||
}
|
||||
|
||||
// Check if realm registration is allowed
|
||||
if (!this.realmModel.isRegistrationAllowed()) {
|
||||
fireErrorEvent(Errors.FEDERATED_IDENTITY_DISABLED_REGISTRATION);
|
||||
throw new IdentityBrokerException("federatedIdentityDisabledRegistration");
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Creating account from identity [%s].", federatedIdentityModel);
|
||||
}
|
||||
|
||||
UserModel federatedUser = this.session.users().addUser(this.realmModel, updatedIdentity.getUsername());
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
LOGGER.debugf("Account [%s] created.", federatedUser);
|
||||
}
|
||||
|
||||
federatedUser.setEnabled(true);
|
||||
federatedUser.setFirstName(updatedIdentity.getFirstName());
|
||||
federatedUser.setLastName(updatedIdentity.getLastName());
|
||||
federatedUser.setEmail(updatedIdentity.getEmail());
|
||||
|
||||
this.session.users().addFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
|
||||
|
||||
this.event.clone().user(federatedUser).event(EventType.REGISTER)
|
||||
.detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
|
||||
.detail(Details.IDENTITY_PROVIDER_IDENTITY, updatedIdentity.getUsername())
|
||||
.removeDetail("auth_method")
|
||||
.success();
|
||||
|
||||
return federatedUser;
|
||||
}
|
||||
|
||||
private Response corsResponse(Response response, ClientModel clientModel) {
|
||||
return Cors.add(this.request, Response.fromResponse(response)).auth().allowedOrigins(clientModel).build();
|
||||
}
|
||||
|
||||
private void fireErrorEvent(String message, Throwable throwable) {
|
||||
if (!this.event.getEvent().getType().toString().endsWith("_ERROR")) {
|
||||
boolean newTransaction = !this.session.getTransaction().isActive();
|
||||
|
||||
try {
|
||||
if (newTransaction) {
|
||||
this.session.getTransaction().begin();
|
||||
}
|
||||
|
||||
this.event.error(message);
|
||||
|
||||
if (newTransaction) {
|
||||
this.session.getTransaction().commit();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Could not fire event.", e);
|
||||
rollback();
|
||||
}
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
LOGGER.error(message, throwable);
|
||||
} else {
|
||||
LOGGER.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void fireErrorEvent(String message) {
|
||||
fireErrorEvent(message, null);
|
||||
}
|
||||
|
||||
private boolean isDebugEnabled() {
|
||||
return LOGGER.isDebugEnabled();
|
||||
}
|
||||
|
||||
private void rollback() {
|
||||
if (this.session.getTransaction().isActive()) {
|
||||
this.session.getTransaction().rollback();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -72,7 +72,6 @@ public class KeycloakApplication extends Application {
|
|||
|
||||
singletons.add(new ServerVersionResource());
|
||||
singletons.add(new RealmsResource());
|
||||
singletons.add(new AuthenticationBrokerResource());
|
||||
singletons.add(new AdminRoot());
|
||||
classes.add(SkeletonKeyContextResolver.class);
|
||||
classes.add(QRCodeResource.class);
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.BadRequestException;
|
||||
import org.jboss.resteasy.spi.NotFoundException;
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -20,22 +17,18 @@ import org.keycloak.services.managers.AuthenticationManager;
|
|||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.EventsManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -190,7 +183,18 @@ public class RealmsResource {
|
|||
return realmResource;
|
||||
}
|
||||
|
||||
@Path("{realm}/broker")
|
||||
public IdentityBrokerService getBrokerService(final @PathParam("realm") String name) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = locateRealm(name, realmManager);
|
||||
|
||||
IdentityBrokerService brokerService = new IdentityBrokerService(realm);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(brokerService);
|
||||
|
||||
brokerService.init();
|
||||
|
||||
return brokerService;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -106,7 +106,12 @@ public class IdentityProvidersResource {
|
|||
String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
|
||||
String enabled = formDataMap.get("enabled").get(0).getBodyAsString();
|
||||
String updateProfileFirstLogin = formDataMap.get("updateProfileFirstLogin").get(0).getBodyAsString();
|
||||
String storeToken = formDataMap.get("storeToken").get(0).getBodyAsString();
|
||||
String storeToken = "false";
|
||||
|
||||
if (formDataMap.containsKey("storeToken")) {
|
||||
storeToken = formDataMap.get("storeToken").get(0).getBodyAsString();
|
||||
}
|
||||
|
||||
InputPart file = formDataMap.get("file").get(0);
|
||||
InputStream inputStream = file.getBody(InputStream.class, null);
|
||||
IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
|
||||
|
|
|
@ -22,12 +22,10 @@
|
|||
package org.keycloak.services.resources.flows;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.OpenIDConnect;
|
||||
import org.keycloak.protocol.oidc.OpenIDConnectService;
|
||||
import org.keycloak.services.resources.AccountService;
|
||||
import org.keycloak.services.resources.AuthenticationBrokerResource;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.resources.ThemeResource;
|
||||
|
@ -68,21 +66,31 @@ public class Urls {
|
|||
return accountBase(baseUri).path(AccountService.class, "processFederatedIdentityUpdate").build(realmName);
|
||||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseURI, IdentityProviderModel identityProvider, RealmModel realm, String accessCode) {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(baseURI)
|
||||
.path(AuthenticationBrokerResource.class)
|
||||
.path(AuthenticationBrokerResource.class, "performLogin")
|
||||
.replaceQueryParam("provider_id", identityProvider.getId());
|
||||
public static URI identityProviderAuthnResponse(URI baseUri, String providerId, String realmName) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "handleResponseGet")
|
||||
.build(realmName, providerId);
|
||||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode) {
|
||||
UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "performLogin");
|
||||
|
||||
if (accessCode != null) {
|
||||
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
|
||||
return uriBuilder.build(realm.getName());
|
||||
return uriBuilder.build(realmName, providerId);
|
||||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseURI, IdentityProviderModel identityProvider, RealmModel realm) {
|
||||
return identityProviderAuthnRequest(baseURI, identityProvider, realm, null);
|
||||
public static URI identityProviderRetrieveToken(URI baseUri, String providerId, String realmName) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "retrieveToken")
|
||||
.build(realmName, providerId);
|
||||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseURI, String providerId, String realmName) {
|
||||
return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
|
||||
}
|
||||
|
||||
public static URI accountTotpPage(URI baseUri, String realmId) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
|||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
||||
/**
|
||||
|
@ -61,7 +62,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp
|
|||
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
|
|||
import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new IdentityBrokerException("Could not obtain user profile from github.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
|||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
@ -68,7 +69,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
|
||||
return AuthenticationResponse.temporaryRedirect(authenticationUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new IdentityBrokerException("Could send authentication request to twitter.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
MultivaluedMap<String, String> queryParameters = request.getUriInfo().getQueryParameters();
|
||||
|
||||
if (queryParameters.getFirst("denied") != null) {
|
||||
throw new RuntimeException("Access denied.");
|
||||
throw new IdentityBrokerException("Access denied.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -121,7 +122,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
|
||||
return AuthenticationResponse.end(identity);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
throw new IdentityBrokerException("Could get user profile from twitter.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
|
||||
|
@ -53,8 +54,10 @@ import javax.ws.rs.client.ClientRequestFilter;
|
|||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -69,6 +72,8 @@ import static org.junit.Assert.assertTrue;
|
|||
*/
|
||||
public abstract class AbstractIdentityProviderTest {
|
||||
|
||||
private static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build();
|
||||
|
||||
@ClassRule
|
||||
public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule();
|
||||
|
||||
|
@ -308,7 +313,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||
|
||||
UserSessionStatus userSessionStatus = retrieveSessionStatus();
|
||||
String accessToken = userSessionStatus.getAccessTokenString();
|
||||
String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
|
||||
URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
|
||||
final String authHeader = "Bearer " + accessToken;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
|
@ -317,11 +322,10 @@ public abstract class AbstractIdentityProviderTest {
|
|||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
UriBuilder authBase = UriBuilder.fromUri(tokenEndpointUrl);
|
||||
WebTarget tokenEndpoint = client.target(authBase);
|
||||
WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
|
||||
Response response = tokenEndpoint.request().get();
|
||||
|
||||
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
assertEquals(Status.OK.getStatusCode(), response.getStatus());
|
||||
assertNotNull(response.readEntity(String.class));
|
||||
|
||||
driver.navigate().to("http://localhost:8081/test-app/logout");
|
||||
|
@ -364,14 +368,15 @@ public abstract class AbstractIdentityProviderTest {
|
|||
grantPage.accept();
|
||||
|
||||
assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
|
||||
|
||||
AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
|
||||
URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), getRealm().getName());
|
||||
String authHeader = "Bearer " + accessToken.getAccessToken();
|
||||
HtmlUnitDriver htmlUnitDriver = (WebRule.HtmlUnitDriver) this.driver;
|
||||
|
||||
htmlUnitDriver.getWebClient().addRequestHeader(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
|
||||
htmlUnitDriver.navigate().to(tokenEndpointUrl);
|
||||
htmlUnitDriver.navigate().to(tokenEndpointUrl.toString());
|
||||
|
||||
grantPage.assertCurrent();
|
||||
grantPage.accept();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"enabled": true,
|
||||
"secret": "secret",
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/auth/broker/realm-with-broker/kc-oidc-idp"
|
||||
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-oidc-idp"
|
||||
],
|
||||
"claims": {
|
||||
"name" : true,
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"name": "http://localhost:8081/auth/",
|
||||
"enabled": true,
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/auth/broker/realm-with-broker/kc-saml-signed-idp"
|
||||
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-signed-idp"
|
||||
],
|
||||
"attributes": {
|
||||
"saml.assertion.signature": "true",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"name": "http://localhost:8081/auth/",
|
||||
"enabled": true,
|
||||
"redirectUris": [
|
||||
"http://localhost:8081/auth/broker/realm-with-broker/kc-saml-idp-basic"
|
||||
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic"
|
||||
],
|
||||
"attributes": {
|
||||
"saml.authnstatement": "true"
|
||||
|
|
Loading…
Reference in a new issue