Merge pull request #712 from patriot1burke/master

login refactor phase 2, still more to do
This commit is contained in:
Bill Burke 2014-09-29 16:16:10 -04:00
commit 6212403569
20 changed files with 498 additions and 443 deletions

View file

@ -36,7 +36,7 @@ public interface LoginFormsProvider extends Provider {
public Response createCode();
public LoginFormsProvider setAccessCode(String accessCode);
public LoginFormsProvider setClientSessionCode(String accessCode);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);

View file

@ -293,7 +293,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
@Override
public LoginFormsProvider setAccessCode(String accessCode) {
public LoginFormsProvider setClientSessionCode(String accessCode) {
this.accessCode = accessCode;
return this;
}

View file

@ -14,11 +14,6 @@
<description/>
<dependencies>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-federation</artifactId>
<version>2.7.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>

View file

@ -0,0 +1,37 @@
package org.keycloak.protocol;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.Provider;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.protocol.oidc.OAuthFlows;
import org.keycloak.services.managers.ClientSessionCode;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface LoginProtocol extends Provider {
OAuthFlows setSession(KeycloakSession session);
OAuthFlows setRealm(RealmModel realm);
OAuthFlows setRequest(HttpRequest request);
OAuthFlows setUriInfo(UriInfo uriInfo);
OAuthFlows setClientConnection(ClientConnection clientConnection);
Response cancelLogin(ClientSessionModel clientSession);
Response invalidSessionError(ClientSessionModel clientSession);
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
Response consentDenied(ClientSessionModel clientSession);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.protocol;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
}

View file

@ -0,0 +1,27 @@
package org.keycloak.protocol;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginProtocolSpi implements Spi {
@Override
public String getName() {
return "login-protocol";
}
@Override
public Class<? extends Provider> getProviderClass() {
return LoginProtocol.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return LoginProtocolFactory.class;
}
}

View file

@ -0,0 +1,178 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.protocol.oidc;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.services.resources.flows.Flows;
import javax.ws.rs.core.Cookie;
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.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OAuthFlows implements LoginProtocol {
public static final String LOGIN_PAGE_PROTOCOL = "openid-connect";
public static final String STATE_PARAM = "state";
public static final String SCOPE_PARAM = "scope";
public static final String RESPONSE_TYPE_PARAM = "response_type";
public static final String REDIRECT_URI_PARAM = "redirect_uri";
public static final String CLIENT_ID_PARAM = "client_id";
public static final String PROMPT_PARAM = "prompt";
public static final String LOGIN_HINT_PARAM = "login_hint";
private static final Logger log = Logger.getLogger(OAuthFlows.class);
protected KeycloakSession session;
protected RealmModel realm;
protected HttpRequest request;
protected UriInfo uriInfo;
protected ClientConnection clientConnection;
public OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo,
ClientConnection clientConnection) {
this.session = session;
this.realm = realm;
this.request = request;
this.uriInfo = uriInfo;
this.clientConnection = clientConnection;
}
public OAuthFlows() {
}
@Override
public OAuthFlows setSession(KeycloakSession session) {
this.session = session;
return this;
}
@Override
public OAuthFlows setRealm(RealmModel realm) {
this.realm = realm;
return this;
}
@Override
public OAuthFlows setRequest(HttpRequest request) {
this.request = request;
return this;
}
@Override
public OAuthFlows setUriInfo(UriInfo uriInfo) {
this.uriInfo = uriInfo;
return this;
}
@Override
public OAuthFlows setClientConnection(ClientConnection clientConnection) {
this.clientConnection = clientConnection;
return this;
}
@Override
public Response cancelLogin(ClientSessionModel clientSession) {
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null) {
redirectUri.queryParam(OAuth2Constants.STATE, state);
}
return Response.status(302).location(redirectUri.build()).build();
}
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
log.debugv("redirectAccessCode: state: {0}", state);
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
return location.build();
}
public Response consentDenied(ClientSessionModel clientSession) {
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
return location.build();
}
public Response invalidSessionError(ClientSessionModel clientSession) {
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OAuthFlows.STATE_PARAM);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null) {
redirectUri.queryParam(OAuth2Constants.STATE, state);
}
return Response.status(302).location(redirectUri.build()).build();
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,32 @@
package org.keycloak.protocol.oidc;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OpenIDConnectFactory implements LoginProtocolFactory {
@Override
public LoginProtocol create(KeycloakSession session) {
return new OAuthFlows().setSession(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "openid-connect";
}
}

View file

@ -1,20 +1,32 @@
package org.keycloak.services.managers;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.util.Time;
@ -22,12 +34,14 @@ import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Stateless object that manages authentication
@ -78,7 +92,7 @@ public class AuthenticationManager {
}
public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
public static AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
AccessToken token = new AccessToken();
token.id(KeycloakModelUtils.generateId());
token.issuedNow();
@ -93,7 +107,7 @@ public class AuthenticationManager {
return token;
}
public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
public static void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
String cookiePath = getIdentityCookiePath(realm, uriInfo);
AccessToken identityToken = createIdentityToken(realm, user, session);
String encoded = encodeToken(realm, identityToken);
@ -116,7 +130,7 @@ public class AuthenticationManager {
}
public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
public static void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
String path = getIdentityCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
// remember me cookie should be persistent (hardcoded to 365 days for now)
@ -124,7 +138,7 @@ public class AuthenticationManager {
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 31536000, secureOnly, true);
}
protected String encodeToken(RealmModel realm, Object token) {
protected static String encodeToken(RealmModel realm, Object token) {
String encodedToken = new JWSBuilder()
.jsonContent(token)
.rsa256(realm.getPrivateKey());
@ -180,6 +194,114 @@ public class AuthenticationManager {
return authResult;
}
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionModel clientSession,
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
String[] split = sessionCookie.getValue().split("/");
if (split.length >= 3) {
String oldSessionId = split[2];
if (!oldSessionId.equals(userSession.getId())) {
UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
if (oldSession != null) {
logger.debugv("Removing old user session: session: {0}", oldSessionId);
session.sessions().removeUserSession(realm, oldSession);
}
}
}
}
// refresh the cookies!
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setRequest(request)
.setUriInfo(uriInfo)
.setClientConnection(clientConnection);
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
}
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
ClientConnection clientConnection,
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
RealmModel realm = clientSession.getRealm();
UserModel user = userSession.getUser();
isTotpConfigurationRequired(realm, user);
isEmailVerificationRequired(realm, user);
ClientModel client = clientSession.getClient();
boolean isResource = client instanceof ApplicationModel;
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
logger.debugv("processAccessCode: isResource: {0}", isResource);
logger.debugv("processAccessCode: go to oauth page?: {0}",
!isResource);
event.detail(Details.CODE_ID, clientSession.getId());
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
UserModel.RequiredAction action = user.getRequiredActions().iterator().next();
accessCode.setRequiredAction(action);
LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
String key = UUID.randomUUID().toString();
clientSession.setNote("key", key);
loginFormsProvider.setVerifyCode(key);
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
}
return loginFormsProvider
.createResponse(action);
}
if (!isResource) {
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
for (RoleModel r : accessCode.getRequestedRoles()) {
if (r.getContainer() instanceof RealmModel) {
realmRoles.add(r);
} else {
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
}
}
return Flows.forms(session, realm, client, uriInfo)
.setClientSessionCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles)
.setClient(client)
.createOAuthGrant();
}
event.success();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
}
protected static void isTotpConfigurationRequired(RealmModel realm, UserModel user) {
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
logger.debug("User is required to configure totp");
}
}
}
protected static void isEmailVerificationRequired(RealmModel realm, UserModel user) {
if (realm.isVerifyEmail() && !user.isEmailVerified()) {
user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
logger.debug("User is required to verify email");
}
}
protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) {
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);

View file

@ -1,17 +0,0 @@
package org.keycloak.services.protocol;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OpenIdConnectProtocol {
public static final String LOGIN_PAGE_PROTOCOL = "oidc_login_page";
public static final String STATE_PARAM = "state";
public static final String SCOPE_PARAM = "scope";
public static final String RESPONSE_TYPE_PARAM = "response_type";
public static final String REDIRECT_URI_PARAM = "redirect_uri";
public static final String CLIENT_ID_PARAM = "client_id";
public static final String PROMPT_PARAM = "prompt";
public static final String LOGIN_HINT_PARAM = "login_hint";
}

View file

@ -25,7 +25,6 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.account.AccountPages;
import org.keycloak.account.AccountProvider;
import org.keycloak.events.EventBuilder;
@ -56,8 +55,8 @@ import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.protocol.oidc.OAuthFlows;
import org.keycloak.services.resources.flows.OAuthRedirect;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.util.CookieHelper;
@ -658,7 +657,7 @@ public class AccountService {
ClientSessionModel clientSession = auth.getClientSession();
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
clientSession.setRedirectUri(redirectUri);
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, UUID.randomUUID().toString());
clientSession.setNote(OAuthFlows.STATE_PARAM, UUID.randomUUID().toString());
ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession);
return Flows.social(realm, uriInfo, clientConnection, provider)
.redirectToSocialProvider(clientSessionCode);

View file

@ -24,8 +24,6 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Event;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -42,13 +40,12 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
@ -165,7 +162,7 @@ public class RequiredActionsService {
String error = Validation.validateUpdateProfileForm(formData);
if (error != null) {
return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PROFILE);
}
@ -212,11 +209,11 @@ public class RequiredActionsService {
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
if (Validation.isEmpty(totp)) {
return loginForms.setError(Messages.MISSING_TOTP)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.CONFIGURE_TOTP);
} else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
return loginForms.setError(Messages.INVALID_TOTP)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.CONFIGURE_TOTP);
}
@ -257,11 +254,11 @@ public class RequiredActionsService {
LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
if (Validation.isEmpty(passwordNew)) {
return loginForms.setError(Messages.MISSING_PASSWORD)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
} else if (!passwordNew.equals(passwordConfirm)) {
return loginForms.setError(Messages.NOTMATCH_PASSWORD)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
}
@ -269,7 +266,7 @@ public class RequiredActionsService {
session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
} catch (Exception ape) {
return loginForms.setError(ape.getMessage())
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
}
@ -330,7 +327,7 @@ public class RequiredActionsService {
initEvent(clientSession);
return Flows.forms(session, realm, null, uriInfo)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.setVerifyCode(verifyCode)
.setUser(userSession.getUser())
.createResponse(RequiredAction.VERIFY_EMAIL);
@ -356,11 +353,11 @@ public class RequiredActionsService {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Somebody is trying to illegally change your password.");
}
return Flows.forms(session, realm, null, uriInfo)
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createResponse(RequiredAction.UPDATE_PASSWORD);
} else {
return Flows.forms(session, realm, null, uriInfo)
.setAccessCode(code)
.setClientSessionCode(code)
.createPasswordReset();
}
}
@ -433,7 +430,7 @@ public class RequiredActionsService {
} catch (EmailException e) {
logger.error("Failed to send password reset email", e);
return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
.setAccessCode(accessCode.getCode())
.setClientSessionCode(accessCode.getCode())
.createErrorPage();
}
}
@ -442,36 +439,7 @@ public class RequiredActionsService {
}
private Response redirectOauth(UserModel user, ClientSessionCode accessCode, ClientSessionModel clientSession, UserSessionModel userSession) {
if (accessCode == null) {
return null;
}
Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
accessCode.setRequiredAction(requiredActions.iterator().next());
return Flows.forms(session, realm, null, uriInfo)
.setAccessCode(accessCode.getCode())
.setUser(user)
.createResponse(requiredActions.iterator().next());
} else {
logger.debugv("Redirecting to: {0}", clientSession.getRedirectUri());
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
AuthenticationManager authManager = new AuthenticationManager();
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
.redirectError(clientSession.getClient(), "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
}
event.session(userSession);
event.success();
return Flows.oauth(this.session, realm, request, uriInfo, clientConnection, authManager, tokenManager)
.redirectAccessCode(accessCode,
userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
}
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
}
private void initEvent(ClientSessionModel clientSession) {

View file

@ -25,12 +25,10 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
@ -48,7 +46,6 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.SocialAccessDeniedException;
@ -63,7 +60,6 @@ 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.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@ -156,7 +152,7 @@ public class SocialResource {
} catch (SocialAccessDeniedException e) {
event.error(Errors.REJECTED_BY_USER);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setAccessCode(clientCode.getCode()).setWarning("Access denied").createLogin();
return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setClientSessionCode(clientCode.getCode()).setWarning("Access denied").createLogin();
} catch (SocialProviderException e) {
logger.error("Failed to process social callback", e);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process social callback");
@ -230,8 +226,8 @@ public class SocialResource {
event.session(userSession);
TokenManager.attachClientSession(userSession, clientSession);
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, new AuthenticationManager(), tokenManager);
Response response = oauth.processAccessCode(clientSession, userSession, event);
AuthenticationManager authManager = new AuthenticationManager();
Response response = authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
if (session.getTransaction().isActive()) {
session.getTransaction().commit();
}
@ -239,7 +235,7 @@ public class SocialResource {
} catch (ModelDuplicateException e) {
// Assume email is the duplicate as there's nothing else atm
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.setError("socialEmailExists")
.createLogin();
}

View file

@ -31,6 +31,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
@ -40,12 +41,10 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
import org.keycloak.representations.PasswordToken;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.protocol.oidc.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.Base64Url;
@ -114,8 +113,6 @@ public class TokenService {
protected ResourceContext resourceContext;
*/
private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
public TokenService(RealmModel realm, TokenManager tokenManager, EventBuilder event, AuthenticationManager authManager) {
this.realm = realm;
this.tokenManager = tokenManager;
@ -492,7 +489,7 @@ public class TokenService {
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
event.client(clientSession.getClient()).error(Errors.INVALID_USER_CREDENTIALS);
return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.INVALID_USER)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createLogin();
}
@ -511,7 +508,6 @@ public class TokenService {
event.detail(Details.REMEMBER_ME, "true");
}
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
ClientModel client = clientSession.getClient();
if (client == null) {
@ -525,7 +521,12 @@ public class TokenService {
if (formData.containsKey("cancel")) {
event.error(Errors.REJECTED_BY_USER);
return oauth.redirectError(client, "access_denied", clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), clientSession.getRedirectUri());
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setRequest(request)
.setUriInfo(uriInfo)
.setClientConnection(clientConnection);
return protocol.cancelLogin(clientSession);
}
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
@ -547,20 +548,19 @@ public class TokenService {
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
TokenManager.attachClientSession(userSession, clientSession);
event.session(userSession);
return oauth.processAccessCode(clientSession, userSession, event);
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
case ACCOUNT_TEMPORARILY_DISABLED:
event.error(Errors.USER_TEMPORARILY_DISABLED);
return Flows.forms(this.session, realm, client, uriInfo)
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createLogin();
case ACCOUNT_DISABLED:
event.error(Errors.USER_DISABLED);
return Flows.forms(this.session, realm, client, uriInfo)
.setError(Messages.ACCOUNT_DISABLED)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.setFormData(formData).createLogin();
case MISSING_TOTP:
formData.remove(CredentialRepresentation.PASSWORD);
@ -570,19 +570,19 @@ public class TokenService {
return Flows.forms(this.session, realm, client, uriInfo)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createLoginTotp();
case INVALID_USER:
event.error(Errors.USER_NOT_FOUND);
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createLogin();
default:
event.error(Errors.INVALID_USER_CREDENTIALS);
return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createLogin();
}
}
@ -642,8 +642,6 @@ public class TokenService {
.detail(Details.EMAIL, email)
.detail(Details.REGISTER_METHOD, "form");
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
@ -676,7 +674,7 @@ public class TokenService {
return Flows.forms(session, realm, client, uriInfo)
.setError(error)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createRegistration();
}
@ -686,7 +684,7 @@ public class TokenService {
return Flows.forms(session, realm, client, uriInfo)
.setError(Messages.USERNAME_EXISTS)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createRegistration();
}
@ -696,7 +694,7 @@ public class TokenService {
return Flows.forms(session, realm, client, uriInfo)
.setError(Messages.EMAIL_EXISTS)
.setFormData(formData)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createRegistration();
}
@ -727,7 +725,7 @@ public class TokenService {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
return Flows.forms(session, realm, client, uriInfo)
.setError(passwordUpdateError)
.setAccessCode(clientCode.getCode())
.setClientSessionCode(clientCode.getCode())
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
}
}
@ -981,15 +979,15 @@ public class TokenService {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
}
clientSession = clientCode.getClientSession();
if (!clientSession.getAuthMethod().equals(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL)) {
if (!clientSession.getAuthMethod().equals(OAuthFlows.LOGIN_PAGE_PROTOCOL)) {
event.error(Errors.INVALID_CODE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid protocol, please login again through your application.");
}
state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
scopeParam = clientSession.getNote(OpenIdConnectProtocol.SCOPE_PARAM);
responseType = clientSession.getNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM);
loginHint = clientSession.getNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM);
prompt = clientSession.getNote(OpenIdConnectProtocol.PROMPT_PARAM);
state = clientSession.getNote(OAuthFlows.STATE_PARAM);
scopeParam = clientSession.getNote(OAuthFlows.SCOPE_PARAM);
responseType = clientSession.getNote(OAuthFlows.RESPONSE_TYPE_PARAM);
loginHint = clientSession.getNote(OAuthFlows.LOGIN_HINT_PARAM);
prompt = clientSession.getNote(OAuthFlows.PROMPT_PARAM);
} else {
if (state == null) {
event.error(Errors.STATE_PARAM_NOT_FOUND);
@ -1020,14 +1018,14 @@ public class TokenService {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
}
clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
clientSession.setRedirectUri(redirect);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
if (scopeParam != null) clientSession.setNote(OpenIdConnectProtocol.SCOPE_PARAM, scopeParam);
if (responseType != null) clientSession.setNote(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM, responseType);
if (loginHint != null) clientSession.setNote(OpenIdConnectProtocol.LOGIN_HINT_PARAM, loginHint);
if (prompt != null) clientSession.setNote(OpenIdConnectProtocol.PROMPT_PARAM, prompt);
clientSession.setNote(OAuthFlows.STATE_PARAM, state);
if (scopeParam != null) clientSession.setNote(OAuthFlows.SCOPE_PARAM, scopeParam);
if (responseType != null) clientSession.setNote(OAuthFlows.RESPONSE_TYPE_PARAM, responseType);
if (loginHint != null) clientSession.setNote(OAuthFlows.LOGIN_HINT_PARAM, loginHint);
if (prompt != null) clientSession.setNote(OAuthFlows.PROMPT_PARAM, prompt);
}
return null;
}
@ -1051,13 +1049,13 @@ public class TokenService {
@Path("login")
@GET
public Response loginPage(@QueryParam("code") String code,
@QueryParam(OpenIdConnectProtocol.RESPONSE_TYPE_PARAM) String responseType,
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
@QueryParam(OpenIdConnectProtocol.SCOPE_PARAM) String scopeParam,
@QueryParam(OpenIdConnectProtocol.STATE_PARAM) String state,
@QueryParam(OpenIdConnectProtocol.PROMPT_PARAM) String prompt,
@QueryParam(OpenIdConnectProtocol.LOGIN_HINT_PARAM) String loginHint) {
@QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
@QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
@QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
@QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
@QueryParam(OAuthFlows.STATE_PARAM) String state,
@QueryParam(OAuthFlows.PROMPT_PARAM) String prompt,
@QueryParam(OAuthFlows.LOGIN_HINT_PARAM) String loginHint) {
event.event(EventType.LOGIN);
FrontPageInitializer pageInitializer = new FrontPageInitializer();
pageInitializer.code = code;
@ -1081,23 +1079,23 @@ public class TokenService {
loginHint = pageInitializer.loginHint;
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers);
if (authResult != null) {
UserModel user = authResult.getUser();
UserSessionModel session = authResult.getSession();
TokenManager.attachClientSession(session, clientSession);
event.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
return oauth.processAccessCode(clientSession, session, event);
UserSessionModel userSession = authResult.getSession();
TokenManager.attachClientSession(userSession, clientSession);
event.user(user).session(userSession).detail(Details.AUTH_METHOD, "sso");
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
}
if (prompt != null && prompt.equals("none")) {
return oauth.redirectError(clientSession.getClient(), "access_denied", state, redirect);
OAuthFlows oauth = new OAuthFlows(session, realm, request, uriInfo, clientConnection);
return oauth.cancelLogin(clientSession);
}
LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode());
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
String rememberMeUsername = null;
if (realm.isRememberMe()) {
@ -1136,11 +1134,11 @@ public class TokenService {
@Path("registrations")
@GET
public Response registerPage(@QueryParam("code") String code,
@QueryParam("response_type") String responseType,
@QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirect,
@QueryParam(OpenIdConnectProtocol.CLIENT_ID_PARAM) String clientId,
@QueryParam("scope") String scopeParam,
@QueryParam("state") String state) {
@QueryParam(OAuthFlows.RESPONSE_TYPE_PARAM) String responseType,
@QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirect,
@QueryParam(OAuthFlows.CLIENT_ID_PARAM) String clientId,
@QueryParam(OAuthFlows.SCOPE_PARAM) String scopeParam,
@QueryParam(OAuthFlows.STATE_PARAM) String state) {
event.event(EventType.REGISTER);
if (!realm.isRegistrationAllowed()) {
event.error(Errors.REGISTRATION_DISABLED);
@ -1162,7 +1160,7 @@ public class TokenService {
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
.setAccessCode(new ClientSessionCode(realm, clientSession).getCode())
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
.createRegistration();
}
@ -1175,7 +1173,7 @@ public class TokenService {
@Path("logout")
@GET
@NoCache
public Response logout(final @QueryParam(OpenIdConnectProtocol.REDIRECT_URI_PARAM) String redirectUri) {
public Response logout(final @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirectUri) {
event.event(EventType.LOGOUT);
if (redirectUri != null) {
event.detail(Details.REDIRECT_URI, redirectUri);
@ -1189,7 +1187,6 @@ public class TokenService {
if (redirectUri != null) {
String validatedRedirect = verifyRealmRedirectUri(uriInfo, redirectUri, realm);
if (validatedRedirect == null) {
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
}
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
@ -1267,7 +1264,6 @@ public class TokenService {
public Response processOAuth(final MultivaluedMap<String, String> formData) {
event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
if (!checkSsl()) {
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
@ -1306,15 +1302,19 @@ public class TokenService {
}
event.session(userSession);
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setRequest(request)
.setUriInfo(uriInfo)
.setClientConnection(clientConnection);
if (formData.containsKey("cancel")) {
event.error(Errors.REJECTED_BY_USER);
return redirectAccessDenied(redirect, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM));
return protocol.consentDenied(clientSession);
}
event.success();
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
return oauth.redirectAccessCode(accessCode, userSession, clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM), redirect);
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
}
@Path("oauth/oob")
@ -1322,20 +1322,12 @@ public class TokenService {
public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo);
if (code != null) {
return forms.setAccessCode(code).createCode();
return forms.setClientSessionCode(code).createCode();
} else {
return forms.setError(error).createCode();
}
}
protected Response redirectAccessDenied(String redirect, String state) {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
return location.build();
}
public static boolean matchesRedirects(Set<String> validRedirects, String redirect) {
for (String validRedirect : validRedirects) {
if (validRedirect.endsWith("*")) {

View file

@ -20,7 +20,6 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
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.RepresentationToModel;
import org.keycloak.representations.adapters.action.UserStats;
@ -31,14 +30,13 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.managers.UserManager;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.protocol.oidc.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import javax.ws.rs.Consumes;
@ -897,7 +895,7 @@ public class UsersResource {
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
//audit.session(userSession);
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAuthMethod(OpenIdConnectProtocol.LOGIN_PAGE_PROTOCOL);
clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL);
clientSession.setRedirectUri(redirect);
clientSession.setUserSession(userSession);
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);

View file

@ -21,14 +21,11 @@
*/
package org.keycloak.services.resources.flows;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.social.SocialProvider;
import javax.ws.rs.core.Response;
@ -46,11 +43,6 @@ public class Flows {
return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client);
}
public static OAuthFlows oauth(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager,
TokenManager tokenManager) {
return new OAuthFlows(session, realm, request, uriInfo, clientConnection, authManager, tokenManager);
}
public static SocialRedirectFlows social(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, SocialProvider provider) {
return new SocialRedirectFlows(realm, uriInfo, clientConnection, provider);
}

View file

@ -1,276 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.services.resources.flows;
import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import javax.ws.rs.core.Cookie;
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.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OAuthFlows {
private static final Logger log = Logger.getLogger(OAuthFlows.class);
private final KeycloakSession session;
private final RealmModel realm;
private final HttpRequest request;
private final UriInfo uriInfo;
private ClientConnection clientConnection;
private final AuthenticationManager authManager;
private final TokenManager tokenManager;
OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager,
TokenManager tokenManager) {
this.session = session;
this.realm = realm;
this.request = request;
this.uriInfo = uriInfo;
this.clientConnection = clientConnection;
this.authManager = authManager;
this.tokenManager = tokenManager;
}
public Response redirectAccessCode(ClientSessionCode accessCode, UserSessionModel userSession, String state, String redirect) {
String code = accessCode.getCode();
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
log.debugv("redirectAccessCode: state: {0}", state);
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
String[] split = sessionCookie.getValue().split("/");
if (split.length >= 3) {
String oldSessionId = split[2];
if (!oldSessionId.equals(userSession.getId())) {
UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId);
if (oldSession != null) {
log.debugv("Removing old user session: session: {0}", oldSessionId);
session.sessions().removeUserSession(realm, oldSession);
}
}
}
}
// refresh the cookies!
authManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
return location.build();
}
public Response redirectError(ClientModel client, String error, String state, String redirect) {
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error);
if (state != null) {
redirectUri.queryParam(OAuth2Constants.STATE, state);
}
return Response.status(302).location(redirectUri.build()).build();
}
public Response processAccessCode(ClientSessionModel clientSession, UserSessionModel session, EventBuilder event) {
UserModel user = session.getUser();
isTotpConfigurationRequired(user);
isEmailVerificationRequired(user);
ClientModel client = clientSession.getClient();
boolean isResource = client instanceof ApplicationModel;
ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
log.debugv("processAccessCode: isResource: {0}", isResource);
log.debugv("processAccessCode: go to oauth page?: {0}",
!isResource);
event.detail(Details.CODE_ID, clientSession.getId());
Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
RequiredAction action = user.getRequiredActions().iterator().next();
accessCode.setRequiredAction(action);
LoginFormsProvider loginFormsProvider = Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user);
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
String key = UUID.randomUUID().toString();
clientSession.setNote("key", key);
loginFormsProvider.setVerifyCode(key);
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
}
return loginFormsProvider
.createResponse(action);
}
if (!isResource) {
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
for (RoleModel r : accessCode.getRequestedRoles()) {
if (r.getContainer() instanceof RealmModel) {
realmRoles.add(r);
} else {
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
}
}
return Flows.forms(this.session, realm, client, uriInfo)
.setAccessCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles)
.setClient(client)
.createOAuthGrant();
}
String redirect = clientSession.getRedirectUri();
if (redirect != null) {
event.success();
String state = clientSession.getNote(OpenIdConnectProtocol.STATE_PARAM);
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
return redirectAccessCode(accessCode, session, state, redirect);
} else {
return null;
}
}
/*
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, EventBuilder event) {
isTotpConfigurationRequired(user);
isEmailVerificationRequired(user);
boolean isResource = client instanceof ApplicationModel;
AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session);
log.debugv("processAccessCode: isResource: {0}", isResource);
log.debugv("processAccessCode: go to oauth page?: {0}",
!isResource);
event.detail(Details.CODE_ID, accessCode.getCodeId());
Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
RequiredAction action = user.getRequiredActions().iterator().next();
accessCode.setRequiredAction(action);
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
}
return Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user)
.createResponse(action);
}
if (!isResource) {
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
for (RoleModel r : accessCode.getRequestedRoles()) {
if (r.getContainer() instanceof RealmModel) {
realmRoles.add(r);
} else {
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
}
}
return Flows.forms(this.session, realm, client, uriInfo)
.setAccessCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles)
.setClient(client)
.createOAuthGrant();
}
if (redirect != null) {
event.success();
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
return redirectAccessCode(accessCode, session, state, redirect);
} else {
return null;
}
}
*/
/*
public Response forwardToSecurityFailure(String message) {
return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage();
}
*/
private void isTotpConfigurationRequired(UserModel user) {
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) {
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
log.debug("User is required to configure totp");
}
}
}
private void isEmailVerificationRequired(UserModel user) {
if (realm.isVerifyEmail() && !user.isEmailVerified()) {
user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
log.debug("User is required to verify email");
}
}
}

View file

@ -0,0 +1 @@
org.keycloak.protocol.oidc.OpenIDConnectFactory

View file

@ -0,0 +1 @@
org.keycloak.protocol.LoginProtocolSpi

View file

@ -10,7 +10,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.protocol.OpenIdConnectProtocol;
import org.keycloak.protocol.oidc.OAuthFlows;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.util.Time;
@ -74,7 +74,7 @@ public class UserSessionProviderTest {
assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
assertEquals(sessions[0].getId(), session.getUserSession().getId());
assertEquals("http://redirect", session.getRedirectUri());
assertEquals("state", session.getNote(OpenIdConnectProtocol.STATE_PARAM));
assertEquals("state", session.getNote(OAuthFlows.STATE_PARAM));
assertEquals(2, session.getRoles().size());
assertTrue(session.getRoles().contains("one"));
assertTrue(session.getRoles().contains("two"));
@ -250,7 +250,7 @@ public class UserSessionProviderTest {
clientSession.setUserSession(userSession);
clientSession.setRedirectUri("http://redirect");
clientSession.setRoles(new HashSet<String>());
clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, "state");
clientSession.setNote(OAuthFlows.STATE_PARAM, "state");
}
resetSession();
@ -289,7 +289,7 @@ public class UserSessionProviderTest {
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OpenIdConnectProtocol.STATE_PARAM, state);
if (state != null) clientSession.setNote(OAuthFlows.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
return clientSession;
}