From fc867e59ea7e6bf71793fef93779919d6f3ad028 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 29 Sep 2014 16:57:58 -0400 Subject: [PATCH] action url refactor --- .../org/keycloak/protocol/LoginProtocol.java | 14 +- .../{OAuthFlows.java => OpenIDConnect.java} | 48 +- .../protocol/oidc/OpenIDConnectFactory.java | 2 +- .../services/resources/AccountService.java | 4 +- ...sService.java => LoginActionsService.java} | 394 +++++++++++++++- .../services/resources/RealmsResource.java | 14 + .../services/resources/TokenService.java | 430 ++---------------- .../resources/admin/UsersResource.java | 4 +- .../services/resources/flows/Urls.java | 22 +- .../model/UserSessionProviderTest.java | 8 +- .../testsuite/perf/AccessTokenPerfTest.java | 3 +- 11 files changed, 463 insertions(+), 480 deletions(-) rename services/src/main/java/org/keycloak/protocol/oidc/{OAuthFlows.java => OpenIDConnect.java} (73%) rename services/src/main/java/org/keycloak/services/resources/{RequiredActionsService.java => LoginActionsService.java} (51%) diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java index 6790adcb91..8a66f166cb 100755 --- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java @@ -2,14 +2,12 @@ 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.protocol.oidc.OpenIDConnect; 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; @@ -20,15 +18,15 @@ import javax.ws.rs.core.UriInfo; * @version $Revision: 1 $ */ public interface LoginProtocol extends Provider { - OAuthFlows setSession(KeycloakSession session); + OpenIDConnect setSession(KeycloakSession session); - OAuthFlows setRealm(RealmModel realm); + OpenIDConnect setRealm(RealmModel realm); - OAuthFlows setRequest(HttpRequest request); + OpenIDConnect setRequest(HttpRequest request); - OAuthFlows setUriInfo(UriInfo uriInfo); + OpenIDConnect setUriInfo(UriInfo uriInfo); - OAuthFlows setClientConnection(ClientConnection clientConnection); + OpenIDConnect setClientConnection(ClientConnection clientConnection); Response cancelLogin(ClientSessionModel clientSession); Response invalidSessionError(ClientSessionModel clientSession); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java similarity index 73% rename from services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java rename to services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java index 14960705bb..baf5763b49 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OAuthFlows.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnect.java @@ -22,45 +22,25 @@ 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 Bill Burke * @author Stian Thorgersen */ -public class OAuthFlows implements LoginProtocol { +public class OpenIDConnect implements LoginProtocol { public static final String LOGIN_PAGE_PROTOCOL = "openid-connect"; public static final String STATE_PARAM = "state"; @@ -70,7 +50,7 @@ public class OAuthFlows implements LoginProtocol { 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); + private static final Logger log = Logger.getLogger(OpenIDConnect.class); protected KeycloakSession session; @@ -82,8 +62,8 @@ public class OAuthFlows implements LoginProtocol { protected ClientConnection clientConnection; - public OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, - ClientConnection clientConnection) { + public OpenIDConnect(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, + ClientConnection clientConnection) { this.session = session; this.realm = realm; this.request = request; @@ -91,35 +71,35 @@ public class OAuthFlows implements LoginProtocol { this.clientConnection = clientConnection; } - public OAuthFlows() { + public OpenIDConnect() { } @Override - public OAuthFlows setSession(KeycloakSession session) { + public OpenIDConnect setSession(KeycloakSession session) { this.session = session; return this; } @Override - public OAuthFlows setRealm(RealmModel realm) { + public OpenIDConnect setRealm(RealmModel realm) { this.realm = realm; return this; } @Override - public OAuthFlows setRequest(HttpRequest request) { + public OpenIDConnect setRequest(HttpRequest request) { this.request = request; return this; } @Override - public OAuthFlows setUriInfo(UriInfo uriInfo) { + public OpenIDConnect setUriInfo(UriInfo uriInfo) { this.uriInfo = uriInfo; return this; } @Override - public OAuthFlows setClientConnection(ClientConnection clientConnection) { + public OpenIDConnect setClientConnection(ClientConnection clientConnection) { this.clientConnection = clientConnection; return this; } @@ -127,7 +107,7 @@ public class OAuthFlows implements LoginProtocol { @Override public Response cancelLogin(ClientSessionModel clientSession) { String redirect = clientSession.getRedirectUri(); - String state = clientSession.getNote(OAuthFlows.STATE_PARAM); + String state = clientSession.getNote(OpenIDConnect.STATE_PARAM); UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied"); if (state != null) { redirectUri.queryParam(OAuth2Constants.STATE, state); @@ -139,7 +119,7 @@ public class OAuthFlows implements LoginProtocol { public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) { ClientSessionModel clientSession = accessCode.getClientSession(); String redirect = clientSession.getRedirectUri(); - String state = clientSession.getNote(OAuthFlows.STATE_PARAM); + String state = clientSession.getNote(OpenIDConnect.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); @@ -152,7 +132,7 @@ public class OAuthFlows implements LoginProtocol { public Response consentDenied(ClientSessionModel clientSession) { String redirect = clientSession.getRedirectUri(); - String state = clientSession.getNote(OAuthFlows.STATE_PARAM); + String state = clientSession.getNote(OpenIDConnect.STATE_PARAM); UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied"); if (state != null) redirectUri.queryParam(OAuth2Constants.STATE, state); @@ -163,7 +143,7 @@ public class OAuthFlows implements LoginProtocol { public Response invalidSessionError(ClientSessionModel clientSession) { String redirect = clientSession.getRedirectUri(); - String state = clientSession.getNote(OAuthFlows.STATE_PARAM); + String state = clientSession.getNote(OpenIDConnect.STATE_PARAM); UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied"); if (state != null) { redirectUri.queryParam(OAuth2Constants.STATE, state); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java index 5c35daf6a0..9182cd4508 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectFactory.java @@ -12,7 +12,7 @@ import org.keycloak.protocol.LoginProtocolFactory; public class OpenIDConnectFactory implements LoginProtocolFactory { @Override public LoginProtocol create(KeycloakSession session) { - return new OAuthFlows().setSession(session); + return new OpenIDConnect().setSession(session); } @Override diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index d6cc1b8c61..8283723f93 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -47,6 +47,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.TimeBasedOTP; +import org.keycloak.protocol.oidc.OpenIDConnect; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.ForbiddenException; @@ -56,7 +57,6 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; 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; @@ -657,7 +657,7 @@ public class AccountService { ClientSessionModel clientSession = auth.getClientSession(); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); clientSession.setRedirectUri(redirectUri); - clientSession.setNote(OAuthFlows.STATE_PARAM, UUID.randomUUID().toString()); + clientSession.setNote(OpenIDConnect.STATE_PARAM, UUID.randomUUID().toString()); ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession); return Flows.social(realm, uriInfo, clientConnection, provider) .redirectToSocialProvider(clientSessionCode); diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java similarity index 51% rename from services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java rename to services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 5fb47e217b..da5f763d44 100755 --- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -24,23 +24,28 @@ 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.EventBuilder; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; +import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.login.LoginFormsProvider; 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.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.LoginProtocol; +import org.keycloak.representations.PasswordToken; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; @@ -63,16 +68,17 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; -import java.util.Set; +import java.util.LinkedList; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @author Stian Thorgersen */ -public class RequiredActionsService { +public class LoginActionsService { - protected static final Logger logger = Logger.getLogger(RequiredActionsService.class); + protected static final Logger logger = Logger.getLogger(LoginActionsService.class); private RealmModel realm; @@ -94,13 +100,42 @@ public class RequiredActionsService { @Context protected KeycloakSession session; - private TokenManager tokenManager; + private AuthenticationManager authManager; private EventBuilder event; - public RequiredActionsService(RealmModel realm, TokenManager tokenManager, EventBuilder event) { + public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) { + UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); + return loginActionsBaseUrl(baseUriBuilder); + } + + public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) { + return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService"); + } + + public static UriBuilder processLoginUrl(UriInfo uriInfo) { + UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); + return processLoginUrl(baseUriBuilder); + } + + public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) { + UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder); + return uriBuilder.path(TokenService.class, "processLogin"); + } + + public static UriBuilder processOAuthUrl(UriInfo uriInfo) { + UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); + return processOAuthUrl(baseUriBuilder); + } + + public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) { + UriBuilder uriBuilder = loginActionsBaseUrl(baseUriBuilder); + return uriBuilder.path(TokenService.class, "processOAuth"); + } + + public LoginActionsService(RealmModel realm, AuthenticationManager authManager, EventBuilder event) { this.realm = realm; - this.tokenManager = tokenManager; + this.authManager = authManager; this.event = event; } @@ -142,6 +177,344 @@ public class RequiredActionsService { } } + /** + * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! + * + * @param code + * @param formData + * @return + */ + @Path("request/login") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response processLogin(@QueryParam("code") String code, + final MultivaluedMap formData) { + event.event(EventType.LOGIN); + if (!checkSsl()) { + event.error(Errors.SSL_REQUIRED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + } + + if (!realm.isEnabled()) { + event.error(Errors.REALM_DISABLED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + } + ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); + if (clientCode == null) { + event.error(Errors.INVALID_CODE); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + } + ClientSessionModel clientSession = clientCode.getClientSession(); + if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { + 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) + .setClientSessionCode(clientCode.getCode()) + .createLogin(); + } + + String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); + + String rememberMe = formData.getFirst("rememberMe"); + boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on"); + + event.client(clientSession.getClient().getClientId()) + .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) + .detail(Details.RESPONSE_TYPE, "code") + .detail(Details.AUTH_METHOD, "form") + .detail(Details.USERNAME, username); + + if (remember) { + event.detail(Details.REMEMBER_ME, "true"); + } + + + ClientModel client = clientSession.getClient(); + if (client == null) { + event.error(Errors.CLIENT_NOT_FOUND); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + } + if (!client.isEnabled()) { + event.error(Errors.CLIENT_NOT_FOUND); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + } + + if (formData.containsKey("cancel")) { + event.error(Errors.REJECTED_BY_USER); + LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); + protocol.setRealm(realm) + .setRequest(request) + .setUriInfo(uriInfo) + .setClientConnection(clientConnection); + return protocol.cancelLogin(clientSession); + } + + AuthenticationManager.AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData); + + if (remember) { + authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection); + } else { + authManager.expireRememberMeCookie(realm, uriInfo, clientConnection); + } + + UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username); + if (user != null) { + event.user(user); + } + + switch (status) { + case SUCCESS: + case ACTIONS_REQUIRED: + UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember); + TokenManager.attachClientSession(userSession, clientSession); + event.session(userSession); + 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) + .setClientSessionCode(clientCode.getCode()) + .createLogin(); + case ACCOUNT_DISABLED: + event.error(Errors.USER_DISABLED); + return Flows.forms(this.session, realm, client, uriInfo) + .setError(Messages.ACCOUNT_DISABLED) + .setClientSessionCode(clientCode.getCode()) + .setFormData(formData).createLogin(); + case MISSING_TOTP: + formData.remove(CredentialRepresentation.PASSWORD); + + String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); + formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); + + return Flows.forms(this.session, realm, client, uriInfo) + .setFormData(formData) + .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) + .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) + .setClientSessionCode(clientCode.getCode()) + .createLogin(); + } + } + + /** + * Registration + * + * @param code + * @param formData + * @return + */ + @Path("request/registration") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response processRegister(@QueryParam("code") String code, + final MultivaluedMap formData) { + event.event(EventType.REGISTER); + if (!checkSsl()) { + event.error(Errors.SSL_REQUIRED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + } + + if (!realm.isEnabled()) { + event.error(Errors.REALM_DISABLED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + } + if (!realm.isRegistrationAllowed()) { + event.error(Errors.REGISTRATION_DISABLED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + } + ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); + if (clientCode == null) { + event.error(Errors.INVALID_CODE); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + } + if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { + event.error(Errors.INVALID_CODE); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + } + + String username = formData.getFirst("username"); + String email = formData.getFirst("email"); + ClientSessionModel clientSession = clientCode.getClientSession(); + event.client(clientSession.getClient()) + .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) + .detail(Details.RESPONSE_TYPE, "code") + .detail(Details.USERNAME, username) + .detail(Details.EMAIL, email) + .detail(Details.REGISTER_METHOD, "form"); + + if (!realm.isEnabled()) { + event.error(Errors.REALM_DISABLED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + } + ClientModel client = clientSession.getClient(); + if (client == null) { + event.error(Errors.CLIENT_NOT_FOUND); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + } + + if (!client.isEnabled()) { + event.error(Errors.CLIENT_DISABLED); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + } + + + List requiredCredentialTypes = new LinkedList(); + for (RequiredCredentialModel m : realm.getRequiredCredentials()) { + requiredCredentialTypes.add(m.getType()); + } + + // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm + String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes); + if (error == null) { + error = Validation.validatePassword(formData, realm.getPasswordPolicy()); + } + + if (error != null) { + event.error(Errors.INVALID_REGISTRATION); + return Flows.forms(session, realm, client, uriInfo) + .setError(error) + .setFormData(formData) + .setClientSessionCode(clientCode.getCode()) + .createRegistration(); + } + + // Validate that user with this username doesn't exist in realm or any federation provider + if (session.users().getUserByUsername(username, realm) != null) { + event.error(Errors.USERNAME_IN_USE); + return Flows.forms(session, realm, client, uriInfo) + .setError(Messages.USERNAME_EXISTS) + .setFormData(formData) + .setClientSessionCode(clientCode.getCode()) + .createRegistration(); + } + + // Validate that user with this email doesn't exist in realm or any federation provider + if (session.users().getUserByEmail(email, realm) != null) { + event.error(Errors.EMAIL_IN_USE); + return Flows.forms(session, realm, client, uriInfo) + .setError(Messages.EMAIL_EXISTS) + .setFormData(formData) + .setClientSessionCode(clientCode.getCode()) + .createRegistration(); + } + + UserModel user = session.users().addUser(realm, username); + user.setEnabled(true); + user.setFirstName(formData.getFirst("firstName")); + user.setLastName(formData.getFirst("lastName")); + + user.setEmail(email); + + if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) { + UserCredentialModel credentials = new UserCredentialModel(); + credentials.setType(CredentialRepresentation.PASSWORD); + credentials.setValue(formData.getFirst("password")); + + boolean passwordUpdateSuccessful; + String passwordUpdateError = null; + try { + session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password"))); + passwordUpdateSuccessful = true; + } catch (Exception ape) { + passwordUpdateSuccessful = false; + passwordUpdateError = ape.getMessage(); + } + + // User already registered, but force him to update password + if (!passwordUpdateSuccessful) { + user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + return Flows.forms(session, realm, client, uriInfo) + .setError(passwordUpdateError) + .setClientSessionCode(clientCode.getCode()) + .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); + } + } + + event.user(user).success(); + event.reset(); + + return processLogin(code, formData); + } + + /** + * OAuth grant page. You should not invoked this directly! + * + * @param formData + * @return + */ + @Path("consent") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response processConsent(final MultivaluedMap formData) { + event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code"); + + + if (!checkSsl()) { + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + } + + String code = formData.getFirst("code"); + + ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); + if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { + event.error(Errors.INVALID_CODE); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code."); + } + ClientSessionModel clientSession = accessCode.getClientSession(); + event.detail(Details.CODE_ID, clientSession.getId()); + + String redirect = clientSession.getRedirectUri(); + + event.client(clientSession.getClient()) + .user(clientSession.getUserSession().getUser()) + .detail(Details.RESPONSE_TYPE, "code") + .detail(Details.REDIRECT_URI, redirect); + + UserSessionModel userSession = clientSession.getUserSession(); + if (userSession != null) { + event.detail(Details.AUTH_METHOD, userSession.getAuthMethod()); + event.detail(Details.USERNAME, userSession.getLoginUsername()); + if (userSession.isRememberMe()) { + event.detail(Details.REMEMBER_ME, "true"); + } + } + + if (!AuthenticationManager.isSessionValid(realm, userSession)) { + AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); + event.error(Errors.INVALID_CODE); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active"); + } + 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 protocol.consentDenied(clientSession); + } + + event.success(); + + return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection); + } + + + + @Path("profile") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @@ -274,15 +647,6 @@ public class RequiredActionsService { event.clone().event(EventType.UPDATE_PASSWORD).success(); return redirectOauth(user, accessCode, clientSession, userSession); - - // Redirect to account management to login if password reset was initiated by admin - /* here while refactoring, ok to remove when you want - if (accessCode.getSessionState() == null) { - return Response.seeOther(Urls.accountPage(uriInfo.getBaseUri(), realm.getId())).build(); - } else { - return redirectOauth(user, accessCode); - } - */ } diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index e618c9e681..dc741f82ae 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -156,6 +156,20 @@ public class RealmsResource { return tokenService; } + @Path("{realm}/login-actions") + public LoginActionsService getLoginActionsService(final @PathParam("realm") String name) { + RealmManager realmManager = new RealmManager(session); + RealmModel realm = locateRealm(name, realmManager); + EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder(); + AuthenticationManager authManager = new AuthenticationManager(protector); + LoginActionsService service = new LoginActionsService(realm, authManager, event); + ResteasyProviderFactory.getInstance().injectProperties(service); + + //resourceContext.initResource(service); + return service; + } + + protected RealmModel locateRealm(String name, RealmManager realmManager) { RealmModel realm = realmManager.getRealmByName(name); if (realm == null) { diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index 17ec212e6a..f45ecedb1c 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -13,11 +13,10 @@ import org.keycloak.ClientConnection; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.RSATokenVerifier; -import org.keycloak.events.EventBuilder; import org.keycloak.events.Details; import org.keycloak.events.Errors; +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; @@ -26,27 +25,20 @@ import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuthClientModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -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.protocol.oidc.OpenIDConnect; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.RefreshToken; -import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.ForbiddenException; 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.TokenManager; -import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; -import org.keycloak.protocol.oidc.OAuthFlows; import org.keycloak.services.resources.flows.Urls; -import org.keycloak.services.validation.Validation; import org.keycloak.util.Base64Url; import org.keycloak.util.BasicAuthHelper; @@ -71,8 +63,6 @@ import javax.ws.rs.ext.Providers; import java.net.URI; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Set; @@ -176,26 +166,6 @@ public class TokenService { return uriBuilder.path(TokenService.class, "logout"); } - public static UriBuilder processLoginUrl(UriInfo uriInfo) { - UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); - return processLoginUrl(baseUriBuilder); - } - - public static UriBuilder processLoginUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(TokenService.class, "processLogin"); - } - - public static UriBuilder processOAuthUrl(UriInfo uriInfo) { - UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); - return processOAuthUrl(baseUriBuilder); - } - - public static UriBuilder processOAuthUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(TokenService.class, "processOAuth"); - } - public static UriBuilder refreshUrl(UriBuilder baseUriBuilder) { UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); return uriBuilder.path(TokenService.class, "refreshAccessToken"); @@ -457,285 +427,6 @@ public class TokenService { return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - /** - * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! - * - * @param code - * @param formData - * @return - */ - @Path("auth/request/login") - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response processLogin(@QueryParam("code") String code, - final MultivaluedMap formData) { - event.event(EventType.LOGIN); - if (!checkSsl()) { - event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); - } - - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); - } - ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); - if (clientCode == null) { - event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); - } - ClientSessionModel clientSession = clientCode.getClientSession(); - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { - 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) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - } - - String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); - - String rememberMe = formData.getFirst("rememberMe"); - boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on"); - - event.client(clientSession.getClient().getClientId()) - .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) - .detail(Details.RESPONSE_TYPE, "code") - .detail(Details.AUTH_METHOD, "form") - .detail(Details.USERNAME, username); - - if (remember) { - event.detail(Details.REMEMBER_ME, "true"); - } - - - ClientModel client = clientSession.getClient(); - if (client == null) { - event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); - } - if (!client.isEnabled()) { - event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); - } - - if (formData.containsKey("cancel")) { - event.error(Errors.REJECTED_BY_USER); - 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); - - if (remember) { - authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection); - } else { - authManager.expireRememberMeCookie(realm, uriInfo, clientConnection); - } - - UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username); - if (user != null) { - event.user(user); - } - - switch (status) { - case SUCCESS: - case ACTIONS_REQUIRED: - UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember); - TokenManager.attachClientSession(userSession, clientSession); - event.session(userSession); - 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) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - case ACCOUNT_DISABLED: - event.error(Errors.USER_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) - .setError(Messages.ACCOUNT_DISABLED) - .setClientSessionCode(clientCode.getCode()) - .setFormData(formData).createLogin(); - case MISSING_TOTP: - formData.remove(CredentialRepresentation.PASSWORD); - - String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); - formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); - - return Flows.forms(this.session, realm, client, uriInfo) - .setFormData(formData) - .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) - .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) - .setClientSessionCode(clientCode.getCode()) - .createLogin(); - } - } - - @Path("auth/request/login-actions") - public RequiredActionsService getRequiredActionsService() { - RequiredActionsService service = new RequiredActionsService(realm, tokenManager, event); - ResteasyProviderFactory.getInstance().injectProperties(service); - - //resourceContext.initResource(service); - return service; - } - - /** - * Registration - * - * @param code - * @param formData - * @return - */ - @Path("registrations") - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response processRegister(@QueryParam("code") String code, - final MultivaluedMap formData) { - event.event(EventType.REGISTER); - if (!checkSsl()) { - event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); - } - - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); - } - if (!realm.isRegistrationAllowed()) { - event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); - } - ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); - if (clientCode == null) { - event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); - } - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { - event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); - } - - String username = formData.getFirst("username"); - String email = formData.getFirst("email"); - ClientSessionModel clientSession = clientCode.getClientSession(); - event.client(clientSession.getClient()) - .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) - .detail(Details.RESPONSE_TYPE, "code") - .detail(Details.USERNAME, username) - .detail(Details.EMAIL, email) - .detail(Details.REGISTER_METHOD, "form"); - - if (!realm.isEnabled()) { - event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); - } - ClientModel client = clientSession.getClient(); - if (client == null) { - event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); - } - - if (!client.isEnabled()) { - event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); - } - - - List requiredCredentialTypes = new LinkedList(); - for (RequiredCredentialModel m : realm.getRequiredCredentials()) { - requiredCredentialTypes.add(m.getType()); - } - - // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm - String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes); - if (error == null) { - error = Validation.validatePassword(formData, realm.getPasswordPolicy()); - } - - if (error != null) { - event.error(Errors.INVALID_REGISTRATION); - return Flows.forms(session, realm, client, uriInfo) - .setError(error) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createRegistration(); - } - - // Validate that user with this username doesn't exist in realm or any federation provider - if (session.users().getUserByUsername(username, realm) != null) { - event.error(Errors.USERNAME_IN_USE); - return Flows.forms(session, realm, client, uriInfo) - .setError(Messages.USERNAME_EXISTS) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createRegistration(); - } - - // Validate that user with this email doesn't exist in realm or any federation provider - if (session.users().getUserByEmail(email, realm) != null) { - event.error(Errors.EMAIL_IN_USE); - return Flows.forms(session, realm, client, uriInfo) - .setError(Messages.EMAIL_EXISTS) - .setFormData(formData) - .setClientSessionCode(clientCode.getCode()) - .createRegistration(); - } - - UserModel user = session.users().addUser(realm, username); - user.setEnabled(true); - user.setFirstName(formData.getFirst("firstName")); - user.setLastName(formData.getFirst("lastName")); - - user.setEmail(email); - - if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) { - UserCredentialModel credentials = new UserCredentialModel(); - credentials.setType(CredentialRepresentation.PASSWORD); - credentials.setValue(formData.getFirst("password")); - - boolean passwordUpdateSuccessful; - String passwordUpdateError = null; - try { - session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password"))); - passwordUpdateSuccessful = true; - } catch (Exception ape) { - passwordUpdateSuccessful = false; - passwordUpdateError = ape.getMessage(); - } - - // User already registered, but force him to update password - if (!passwordUpdateSuccessful) { - user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - return Flows.forms(session, realm, client, uriInfo) - .setError(passwordUpdateError) - .setClientSessionCode(clientCode.getCode()) - .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); - } - } - - event.user(user).success(); - event.reset(); - - return processLogin(code, formData); - } - /** * CORS preflight path for access code to token * @@ -979,15 +670,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(OAuthFlows.LOGIN_PAGE_PROTOCOL)) { + if (!clientSession.getAuthMethod().equals(OpenIDConnect.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(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); + state = clientSession.getNote(OpenIDConnect.STATE_PARAM); + scopeParam = clientSession.getNote(OpenIDConnect.SCOPE_PARAM); + responseType = clientSession.getNote(OpenIDConnect.RESPONSE_TYPE_PARAM); + loginHint = clientSession.getNote(OpenIDConnect.LOGIN_HINT_PARAM); + prompt = clientSession.getNote(OpenIDConnect.PROMPT_PARAM); } else { if (state == null) { event.error(Errors.STATE_PARAM_NOT_FOUND); @@ -1018,14 +709,14 @@ public class TokenService { return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri."); } clientSession = session.sessions().createClientSession(realm, client); - clientSession.setAuthMethod(OAuthFlows.LOGIN_PAGE_PROTOCOL); + clientSession.setAuthMethod(OpenIDConnect.LOGIN_PAGE_PROTOCOL); clientSession.setRedirectUri(redirect); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); - 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); + clientSession.setNote(OpenIDConnect.STATE_PARAM, state); + if (scopeParam != null) clientSession.setNote(OpenIDConnect.SCOPE_PARAM, scopeParam); + if (responseType != null) clientSession.setNote(OpenIDConnect.RESPONSE_TYPE_PARAM, responseType); + if (loginHint != null) clientSession.setNote(OpenIDConnect.LOGIN_HINT_PARAM, loginHint); + if (prompt != null) clientSession.setNote(OpenIDConnect.PROMPT_PARAM, prompt); } return null; } @@ -1049,13 +740,13 @@ public class TokenService { @Path("login") @GET public Response loginPage(@QueryParam("code") String code, - @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) { + @QueryParam(OpenIDConnect.RESPONSE_TYPE_PARAM) String responseType, + @QueryParam(OpenIDConnect.REDIRECT_URI_PARAM) String redirect, + @QueryParam(OpenIDConnect.CLIENT_ID_PARAM) String clientId, + @QueryParam(OpenIDConnect.SCOPE_PARAM) String scopeParam, + @QueryParam(OpenIDConnect.STATE_PARAM) String state, + @QueryParam(OpenIDConnect.PROMPT_PARAM) String prompt, + @QueryParam(OpenIDConnect.LOGIN_HINT_PARAM) String loginHint) { event.event(EventType.LOGIN); FrontPageInitializer pageInitializer = new FrontPageInitializer(); pageInitializer.code = code; @@ -1090,7 +781,7 @@ public class TokenService { } if (prompt != null && prompt.equals("none")) { - OAuthFlows oauth = new OAuthFlows(session, realm, request, uriInfo, clientConnection); + OpenIDConnect oauth = new OpenIDConnect(session, realm, request, uriInfo, clientConnection); return oauth.cancelLogin(clientSession); } @@ -1134,11 +825,11 @@ public class TokenService { @Path("registrations") @GET public Response registerPage(@QueryParam("code") String code, - @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(OpenIDConnect.RESPONSE_TYPE_PARAM) String responseType, + @QueryParam(OpenIDConnect.REDIRECT_URI_PARAM) String redirect, + @QueryParam(OpenIDConnect.CLIENT_ID_PARAM) String clientId, + @QueryParam(OpenIDConnect.SCOPE_PARAM) String scopeParam, + @QueryParam(OpenIDConnect.STATE_PARAM) String state) { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); @@ -1173,7 +864,7 @@ public class TokenService { @Path("logout") @GET @NoCache - public Response logout(final @QueryParam(OAuthFlows.REDIRECT_URI_PARAM) String redirectUri) { + public Response logout(final @QueryParam(OpenIDConnect.REDIRECT_URI_PARAM) String redirectUri) { event.event(EventType.LOGOUT); if (redirectUri != null) { event.detail(Details.REDIRECT_URI, redirectUri); @@ -1252,71 +943,6 @@ public class TokenService { event.user(userSession.getUser()).session(userSession).success(); } - /** - * OAuth grant page. You should not invoked this directly! - * - * @param formData - * @return - */ - @Path("oauth/grant") - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response processOAuth(final MultivaluedMap formData) { - event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code"); - - - if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); - } - - String code = formData.getFirst(OAuth2Constants.CODE); - - ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); - if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { - event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code."); - } - ClientSessionModel clientSession = accessCode.getClientSession(); - event.detail(Details.CODE_ID, clientSession.getId()); - - String redirect = clientSession.getRedirectUri(); - - event.client(clientSession.getClient()) - .user(clientSession.getUserSession().getUser()) - .detail(Details.RESPONSE_TYPE, "code") - .detail(Details.REDIRECT_URI, redirect); - - UserSessionModel userSession = clientSession.getUserSession(); - if (userSession != null) { - event.detail(Details.AUTH_METHOD, userSession.getAuthMethod()); - event.detail(Details.USERNAME, userSession.getLoginUsername()); - if (userSession.isRememberMe()) { - event.detail(Details.REMEMBER_ME, "true"); - } - } - - if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); - event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active"); - } - 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 protocol.consentDenied(clientSession); - } - - event.success(); - - return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection); - } - @Path("oauth/oob") @GET public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 3fc6f685c5..bf2656a494 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -22,6 +22,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.oidc.OpenIDConnect; import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.idm.ApplicationMappingsRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -36,7 +37,6 @@ import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.managers.UserManager; 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; @@ -895,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(OAuthFlows.LOGIN_PAGE_PROTOCOL); + clientSession.setAuthMethod(OpenIDConnect.LOGIN_PAGE_PROTOCOL); clientSession.setRedirectUri(redirect); clientSession.setUserSession(userSession); ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java index d766d6a937..bfef56f36a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java @@ -22,8 +22,8 @@ package org.keycloak.services.resources.flows; import org.keycloak.services.resources.AccountService; +import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.RealmsResource; -import org.keycloak.services.resources.RequiredActionsService; import org.keycloak.services.resources.SocialResource; import org.keycloak.services.resources.ThemeResource; import org.keycloak.services.resources.TokenService; @@ -93,15 +93,15 @@ public class Urls { } public static URI loginActionUpdatePassword(URI baseUri, String realmId) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "updatePassword").build(realmId); } public static URI loginActionUpdateTotp(URI baseUri, String realmId) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updateTotp").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateTotp").build(realmId); } public static URI loginActionUpdateProfile(URI baseUri, String realmId) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updateProfile").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "updateProfile").build(realmId); } public static URI loginActionEmailVerification(URI baseUri, String realmId) { @@ -109,7 +109,7 @@ public class Urls { } public static UriBuilder loginActionEmailVerificationBuilder(URI baseUri) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "emailVerification"); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "emailVerification"); } public static URI loginPasswordReset(URI baseUri, String realmId) { @@ -117,7 +117,7 @@ public class Urls { } public static UriBuilder loginPasswordResetBuilder(URI baseUri) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "passwordReset"); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "passwordReset"); } public static URI loginUsernameReminder(URI baseUri, String realmId) { @@ -125,7 +125,7 @@ public class Urls { } public static UriBuilder loginUsernameReminderBuilder(URI baseUri) { - return requiredActionsBase(baseUri).path(RequiredActionsService.class, "usernameReminder"); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "usernameReminder"); } private static UriBuilder realmBase(URI baseUri) { @@ -133,7 +133,7 @@ public class Urls { } public static URI realmLoginAction(URI baseUri, String realmId) { - return tokenBase(baseUri).path(TokenService.class, "processLogin").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "processLogin").build(realmId); } public static URI realmLoginPage(URI baseUri, String realmId) { @@ -145,7 +145,7 @@ public class Urls { } public static URI realmRegisterAction(URI baseUri, String realmId) { - return tokenBase(baseUri).path(TokenService.class, "processRegister").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "processRegister").build(realmId); } public static URI realmRegisterPage(URI baseUri, String realmId) { @@ -157,7 +157,7 @@ public class Urls { } public static URI realmOauthAction(URI baseUri, String realmId) { - return tokenBase(baseUri).path(TokenService.class, "processOAuth").build(realmId); + return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId); } public static URI realmCode(URI baseUri, String realmId) { @@ -182,7 +182,7 @@ public class Urls { } private static UriBuilder requiredActionsBase(URI baseUri) { - return tokenBase(baseUri).path(TokenService.class, "getRequiredActionsService"); + return realmBase(baseUri).path(RealmsResource.class, "getLoginActionsService"); } private static UriBuilder tokenBase(URI baseUri) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java index 5d4dce0637..79cad485e5 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java @@ -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.protocol.oidc.OAuthFlows; +import org.keycloak.protocol.oidc.OpenIDConnect; 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(OAuthFlows.STATE_PARAM)); + assertEquals("state", session.getNote(OpenIDConnect.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()); - clientSession.setNote(OAuthFlows.STATE_PARAM, "state"); + clientSession.setNote(OpenIDConnect.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(OAuthFlows.STATE_PARAM, state); + if (state != null) clientSession.setNote(OpenIDConnect.STATE_PARAM, state); if (roles != null) clientSession.setRoles(roles); return clientSession; } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java index 887762f8ef..bbba99c95f 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java @@ -34,6 +34,7 @@ import org.junit.ClassRule; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.adapters.HttpClientBuilder; +import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.TokenService; import org.keycloak.testsuite.Constants; import org.keycloak.testsuite.OAuthClient; @@ -152,7 +153,7 @@ public class AccessTokenPerfTest { } public String getProcessLoginUrl(String state) { - UriBuilder b = TokenService.processLoginUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = LoginActionsService.processLoginUrl(UriBuilder.fromUri(baseUrl)); if (clientId != null) { b.queryParam(OAuth2Constants.CLIENT_ID, clientId); }