Moved rememberMe, usernameUsed fields from AccessCode to UserSessions. Change AccessCode to have a single action instead of a list of required actions, also used action for codes that needs user to grant permissions first. Finally removed expiration time on access code and use timestamp directly

This commit is contained in:
Stian Thorgersen
2014-07-22 17:19:49 +01:00
parent 38b575d91b
commit d6f5bf6cde
26 changed files with 348 additions and 197 deletions

View File

@@ -11,15 +11,11 @@ public class AccessCode {
protected String id;
protected String clientId;
protected String userId;
protected String usernameUsed;
protected String state;
protected String sessionState;
protected String redirectUri;
protected boolean rememberMe;
protected String authMethod;
protected int timestamp;
protected int expiration;
protected Set<String> requiredActions;
protected Action action;
protected Set<String> requestedRoles;
public String getId() {
@@ -70,30 +66,6 @@ public class AccessCode {
this.redirectUri = redirectUri;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public int getExpiration() {
return expiration;
}
public void setExpiration(int expiration) {
this.expiration = expiration;
}
public int getTimestamp() {
return timestamp;
}
@@ -102,20 +74,12 @@ public class AccessCode {
this.timestamp = timestamp;
}
public Set<String> getRequiredActions() {
return requiredActions;
public Action getAction() {
return action;
}
public void setRequiredActions(Set<String> requiredActions) {
this.requiredActions = requiredActions;
}
public String getUsernameUsed() {
return usernameUsed;
}
public void setUsernameUsed(String usernameUsed) {
this.usernameUsed = usernameUsed;
public void setAction(Action action) {
this.action = action;
}
public Set<String> getRequestedRoles() {
@@ -125,4 +89,13 @@ public class AccessCode {
public void setRequestedRoles(Set<String> requestedRoles) {
this.requestedRoles = requestedRoles;
}
public static enum Action {
OAUTH_GRANT,
VERIFY_EMAIL,
UPDATE_PROFILE,
CONFIGURE_TOTP,
UPDATE_PASSWORD
}
}

View File

@@ -15,10 +15,22 @@ public interface UserSessionModel {
void setUser(UserModel user);
String getLoginUsername();
void setLoginUsername(String loginUsername);
String getIpAddress();
void setIpAddress(String ipAddress);
String getAuthMethod();
void setAuthMethod(String authMethod);
boolean isRememberMe();
void setRememberMe(boolean rememberMe);
int getStarted();
void setStarted(int started);
@@ -32,4 +44,5 @@ public interface UserSessionModel {
List<ClientModel> getClientAssociations();
void removeAssociatedClient(ClientModel client);
}

View File

@@ -10,7 +10,7 @@ import java.util.List;
*/
public interface UserSessionProvider extends Provider {
UserSessionModel createUserSession(RealmModel realm, UserModel user, String ipAddress);
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe);
UserSessionModel getUserSession(RealmModel realm, String id);
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);

View File

@@ -63,12 +63,15 @@ public class JpaUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String ipAddress) {
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(KeycloakModelUtils.generateId());
entity.setRealmId(realm.getId());
entity.setUserId(user.getId());
entity.setLoginUsername(loginUsername);
entity.setIpAddress(ipAddress);
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
int currentTime = Time.currentTime();

View File

@@ -53,6 +53,16 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setUserId(user.getId());
}
@Override
public String getLoginUsername() {
return entity.getLoginUsername();
}
@Override
public void setLoginUsername(String loginUsername) {
entity.setLoginUsername(loginUsername);
}
@Override
public String getIpAddress() {
return entity.getIpAddress();
@@ -63,6 +73,26 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setIpAddress(ipAddress);
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String authMethod) {
entity.setAuthMethod(authMethod);
}
@Override
public boolean isRememberMe() {
return entity.isRememberMe();
}
@Override
public void setRememberMe(boolean rememberMe) {
entity.setRememberMe(rememberMe);
}
@Override
public int getStarted() {
return entity.getStarted();

View File

@@ -35,12 +35,21 @@ public class UserSessionEntity {
@Column(name="USER_ID")
protected String userId;
@Column(name="LOGIN_USERNAME")
protected String loginUsername;
@Column(name="REALM_ID")
protected String realmId;
@Column(name="IP_ADDRESS")
protected String ipAddress;
@Column(name="AUTH_METHOD")
protected String authMethod;
@Column(name="REMEMBER_ME")
protected boolean rememberMe;
@Column(name="STARTED")
protected int started;
@@ -66,6 +75,14 @@ public class UserSessionEntity {
this.userId = userId;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.loginUsername = loginUsername;
}
public String getRealmId() {
return realmId;
}
@@ -82,6 +99,22 @@ public class UserSessionEntity {
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}

View File

@@ -37,14 +37,17 @@ public class MemUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String ipAddress) {
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
String id = KeycloakModelUtils.generateId();
UserSessionEntity entity = new UserSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
entity.setIpAddress(ipAddress);
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
int currentTime = Time.currentTime();

View File

@@ -43,6 +43,16 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setUser(user.getId());
}
@Override
public String getLoginUsername() {
return entity.getLoginUsername();
}
@Override
public void setLoginUsername(String loginUsername) {
entity.setLoginUsername(loginUsername);
}
public String getIpAddress() {
return entity.getIpAddress();
}
@@ -51,6 +61,26 @@ public class UserSessionAdapter implements UserSessionModel {
entity.setIpAddress(ipAddress);
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String authMethod) {
entity.setAuthMethod(authMethod);
}
@Override
public boolean isRememberMe() {
return entity.isRememberMe();
}
@Override
public void setRememberMe(boolean rememberMe) {
entity.setRememberMe(rememberMe);
}
public int getStarted() {
return entity.getStarted();
}

View File

@@ -11,7 +11,10 @@ public class UserSessionEntity {
private String id;
private String realm;
private String user;
private String loginUsername;
private String ipAddress;
private String authMethod;
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
private List<String> clients = new LinkedList<String>();
@@ -40,6 +43,14 @@ public class UserSessionEntity {
this.user = user;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.loginUsername = loginUsername;
}
public String getIpAddress() {
return ipAddress;
}
@@ -48,6 +59,22 @@ public class UserSessionEntity {
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}

View File

@@ -35,11 +35,14 @@ public class MongoUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String ipAddress) {
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
MongoUserSessionEntity entity = new MongoUserSessionEntity();
entity.setRealmId(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
entity.setIpAddress(ipAddress);
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
int currentTime = Time.currentTime();

View File

@@ -58,6 +58,17 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
updateMongoEntity();
}
@Override
public String getLoginUsername() {
return entity.getLoginUsername();
}
@Override
public void setLoginUsername(String loginUsername) {
entity.setLoginUsername(loginUsername);
updateMongoEntity();
}
@Override
public String getIpAddress() {
return entity.getIpAddress();
@@ -69,6 +80,28 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
updateMongoEntity();
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
}
@Override
public void setAuthMethod(String authMethod) {
entity.setAuthMethod(authMethod);
updateMongoEntity();
}
@Override
public boolean isRememberMe() {
return entity.isRememberMe();
}
@Override
public void setRememberMe(boolean rememberMe) {
entity.setRememberMe(rememberMe);
updateMongoEntity();
}
@Override
public int getStarted() {
return entity.getStarted();

View File

@@ -18,8 +18,14 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
private String user;
private String loginUsername;
private String ipAddress;
private String authMethod;
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
@@ -42,6 +48,14 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
this.user = user;
}
public String getLoginUsername() {
return loginUsername;
}
public void setLoginUsername(String loginUsername) {
this.loginUsername = loginUsername;
}
public String getIpAddress() {
return ipAddress;
}
@@ -50,6 +64,22 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
this.ipAddress = ipAddress;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public int getStarted() {
return started;
}

View File

@@ -9,7 +9,6 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.representations.AccessCode;
import org.keycloak.representations.AccessToken;
import org.keycloak.util.Time;
import java.util.HashSet;
@@ -42,12 +41,9 @@ public class AccessCodeEntry {
return accessCode.getSessionState();
}
public void setSessionState(String state) {
accessCode.setSessionState(state);
}
public boolean isExpired() {
return accessCode.getExpiration() != 0 && Time.currentTime() > accessCode.getExpiration();
int lifespan = accessCode.getAction() == null ? realm.getAccessCodeLifespan() : realm.getAccessCodeLifespanUserAction();
return accessCode.getTimestamp() + lifespan < Time.currentTime();
}
public Set<RoleModel> getRequestedRoles() {
@@ -78,61 +74,52 @@ public class AccessCodeEntry {
return accessCode.getRedirectUri();
}
public boolean isRememberMe() {
return accessCode.isRememberMe();
public AccessCode.Action getAction() {
return accessCode.getAction();
}
public void setRememberMe(boolean remember) {
accessCode.setRememberMe(remember);
public void setAction(AccessCode.Action action) {
accessCode.setAction(action);
accessCode.setTimestamp(Time.currentTime());
}
public String getAuthMethod() {
return accessCode.getAuthMethod();
}
public String getUsernameUsed() {
return accessCode.getUsernameUsed();
}
public void setUsernameUsed(String username) {
accessCode.setUsernameUsed(username);
}
public void resetExpiration() {
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
}
public void setAuthMethod(String authMethod) {
accessCode.setAuthMethod(authMethod);
}
public Set<RequiredAction> getRequiredActions() {
Set<RequiredAction> set = new HashSet<RequiredAction>();
for (String action : accessCode.getRequiredActions()) {
set.add(RequiredAction.valueOf(action));
public RequiredAction getRequiredAction() {
AccessCode.Action action = accessCode.getAction();
if (action != null) {
switch (action) {
case CONFIGURE_TOTP:
return RequiredAction.CONFIGURE_TOTP;
case UPDATE_PASSWORD:
return RequiredAction.UPDATE_PASSWORD;
case UPDATE_PROFILE:
return RequiredAction.UPDATE_PROFILE;
case VERIFY_EMAIL:
return RequiredAction.VERIFY_EMAIL;
}
}
return set;
return null;
}
public boolean hasRequiredAction(RequiredAction action) {
return accessCode.getRequiredActions().contains(action.toString());
}
public void removeRequiredAction(RequiredAction action) {
accessCode.getRequiredActions().remove(action.toString());
}
public void setRequiredActions(Set<RequiredAction> set) {
Set<String> newSet = new HashSet<String>();
for (RequiredAction action : set) {
newSet.add(action.toString());
public void setRequiredAction(RequiredAction requiredAction) {
switch (requiredAction) {
case CONFIGURE_TOTP:
setAction(AccessCode.Action.CONFIGURE_TOTP);
break;
case UPDATE_PASSWORD:
setAction(AccessCode.Action.UPDATE_PASSWORD);
break;
case UPDATE_PROFILE:
setAction(AccessCode.Action.UPDATE_PROFILE);
break;
case VERIFY_EMAIL:
setAction(AccessCode.Action.VERIFY_EMAIL);
break;
default:
throw new IllegalArgumentException("Unknown required action " + requiredAction);
}
accessCode.setRequiredActions(newSet);
}
public String getCode() {
return new JWSBuilder().jsonContent(accessCode).rsa256(realm.getPrivateKey());
return new JWSBuilder().jsonContent(accessCode).rsa256(realm.getPrivateKey());
}
}

View File

@@ -21,11 +21,9 @@ public class AppAuthManager extends AuthenticationManager {
public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
AuthResult authResult = super.authenticateIdentityCookie(session, realm, uriInfo, headers);
if (authResult == null) return null;
Cookie remember = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
boolean rememberMe = remember != null;
// refresh the cookies!
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, rememberMe);
if (rememberMe) createRememberMeCookie(realm, uriInfo);
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo);
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, uriInfo);
return authResult;
}

View File

@@ -94,7 +94,7 @@ public class AuthenticationManager {
return token;
}
public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
public void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo) {
logger.info("createLoginCookie");
String cookiePath = getIdentityCookiePath(realm, uriInfo);
AccessToken identityToken = createIdentityToken(realm, user, session);
@@ -102,7 +102,7 @@ public class AuthenticationManager {
boolean secureOnly = !realm.isSslNotRequired();
logger.debugv("creatingLoginCookie - name: {0} path: {1}", KEYCLOAK_IDENTITY_COOKIE, cookiePath);
int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (rememberMe) {
if (session.isRememberMe()) {
maxAge = realm.getSsoSessionIdleTimeout();
logger.info("createLoginCookie maxAge: " + maxAge);
}

View File

@@ -81,7 +81,6 @@ public class TokenManager {
code.setClientId(client.getClientId());
code.setUserId(user.getId());
code.setTimestamp(Time.currentTime());
code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
code.setSessionState(session != null ? session.getId() : null);
code.setRedirectUri(redirect);
code.setState(state);

View File

@@ -63,7 +63,6 @@ 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.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -131,7 +130,6 @@ public class RequiredActionsService {
user.setEmail(email);
user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
accessCode.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
audit.clone().event(EventType.UPDATE_PROFILE).success();
if (emailChanged) {
@@ -173,7 +171,6 @@ public class RequiredActionsService {
user.setTotp(true);
user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
accessCode.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
audit.clone().event(EventType.UPDATE_TOTP).success();
@@ -218,20 +215,15 @@ public class RequiredActionsService {
logger.debug("updatePassword updated credential");
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
if (accessCode != null) {
accessCode.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
}
audit.clone().event(EventType.UPDATE_PASSWORD).success();
// Password reset through email won't have an associated session
// Redirect to account management to login if password reset was initiated by admin
if (accessCode.getSessionState() == null) {
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserById(accessCode.getUser().getId(), realm), clientConnection.getRemoteAddr());
accessCode.setSessionState(userSession.getId());
audit.session(userSession);
return Response.seeOther(Urls.accountPage(uriInfo.getBaseUri(), realm.getId())).build();
} else {
return redirectOauth(user, accessCode);
}
return redirectOauth(user, accessCode);
}
@@ -240,8 +232,7 @@ public class RequiredActionsService {
public Response emailVerification() {
if (uriInfo.getQueryParameters().containsKey("key")) {
AccessCodeEntry accessCode = tokenManager.parseCode(uriInfo.getQueryParameters().getFirst("key"), session, realm);
if (accessCode == null || accessCode.isExpired()
|| !accessCode.hasRequiredAction(RequiredAction.VERIFY_EMAIL)) {
if (accessCode == null || accessCode.isExpired() || !RequiredAction.VERIFY_EMAIL.equals(accessCode.getRequiredAction())) {
return unauthorized();
}
@@ -252,7 +243,6 @@ public class RequiredActionsService {
user.setEmailVerified(true);
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
accessCode.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
audit.clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
@@ -276,9 +266,7 @@ public class RequiredActionsService {
public Response passwordReset() {
if (uriInfo.getQueryParameters().containsKey("key")) {
AccessCodeEntry accessCode = tokenManager.parseCode(uriInfo.getQueryParameters().getFirst("key"), session, realm);
accessCode.setAuthMethod("form");
if (accessCode == null || accessCode.isExpired()
|| !accessCode.hasRequiredAction(RequiredAction.UPDATE_PASSWORD)) {
if (accessCode == null || accessCode.isExpired() || !RequiredAction.UPDATE_PASSWORD.equals(accessCode.getRequiredAction())) {
return unauthorized();
}
@@ -326,13 +314,11 @@ public class RequiredActionsService {
logger.warn("Failed to send password reset email: user not found");
audit.error(Errors.USER_NOT_FOUND);
} else {
Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
requiredActions.add(RequiredAction.UPDATE_PASSWORD);
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
audit.session(userSession);
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, session, realm, client, user, null);
accessCode.setRequiredActions(requiredActions);
accessCode.setAuthMethod("form");
accessCode.setUsernameUsed(username);
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, session, realm, client, user, userSession);
accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
try {
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());
@@ -372,8 +358,8 @@ public class RequiredActionsService {
return null;
}
if (accessCodeEntry.getRequiredActions() == null || !accessCodeEntry.getRequiredActions().contains(requiredAction)) {
logger.debugv("getAccessCodeEntry required actions null || entry does not contain required action: {0}|{1}", (accessCodeEntry.getRequiredActions() == null),!accessCodeEntry.getRequiredActions().contains(requiredAction) );
if (!requiredAction.equals(accessCodeEntry.getRequiredAction())) {
logger.debugv("Invalid access code action: {0}", requiredAction);
return null;
}
@@ -391,11 +377,12 @@ public class RequiredActionsService {
Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
accessCode.setRequiredAction(requiredActions.iterator().next());
return Flows.forms(session, realm, uriInfo).setAccessCode(accessCode.getCode()).setUser(user)
.createResponse(requiredActions.iterator().next());
} else {
logger.debugv("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
accessCode.resetExpiration();
accessCode.setAction(null);
AuthenticationManager authManager = new AuthenticationManager();
@@ -419,12 +406,16 @@ public class RequiredActionsService {
.session(accessCode.getSessionState())
.detail(Details.CODE_ID, accessCode.getCodeId())
.detail(Details.REDIRECT_URI, accessCode.getRedirectUri())
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, accessCode.getAuthMethod())
.detail(Details.USERNAME, accessCode.getUsernameUsed());
.detail(Details.RESPONSE_TYPE, "code");
if (accessCode.isRememberMe()) {
audit.detail(Details.REMEMBER_ME, "true");
UserSessionModel userSession = accessCode.getSessionState() != null ? session.sessions().getUserSession(realm, accessCode.getSessionState()) : null;
if (userSession != null) {
audit.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
audit.detail(Details.USERNAME, userSession.getLoginUsername());
if (userSession.isRememberMe()) {
audit.detail(Details.REMEMBER_ME, "true");
}
}
}

View File

@@ -116,6 +116,7 @@ public class SocialResource {
SocialProvider provider = SocialLoader.load(initialRequest.getProvider());
String realmName = initialRequest.getRealm();
String authMethod = "social@" + provider.getId();
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName);
@@ -123,7 +124,7 @@ public class SocialResource {
Audit audit = new AuditManager(realm, session, clientConnection).createAudit()
.event(EventType.LOGIN)
.detail(Details.RESPONSE_TYPE, initialRequest.get(OAuth2Constants.RESPONSE_TYPE))
.detail(Details.AUTH_METHOD, "social@" + provider.getId());
.detail(Details.AUTH_METHOD, authMethod);
AuthenticationManager authManager = new AuthenticationManager();
OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, authManager, tokenManager);
@@ -251,10 +252,12 @@ public class SocialResource {
return oauth.forwardToSecurityFailure("Your account is not enabled.");
}
UserSessionModel userSession = session.sessions().createUserSession(realm, user, clientConnection.getRemoteAddr());
String username = socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider();
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), authMethod, false);
audit.session(userSession);
return oauth.processAccessCode(scope, state, redirectUri, client, user, userSession, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social@" + provider.getId(), audit);
return oauth.processAccessCode(scope, state, redirectUri, client, user, userSession, audit);
}
@GET

View File

@@ -28,6 +28,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.representations.AccessCode;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -279,7 +280,7 @@ public class TokenService {
String scope = form.getFirst(OAuth2Constants.SCOPE);
UserSessionModel userSession = session.sessions().createUserSession(realm, user, clientConnection.getRemoteAddr());
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false);
userSession.associateClient(client);
audit.session(userSession);
@@ -426,10 +427,10 @@ public class TokenService {
switch (status) {
case SUCCESS:
case ACTIONS_REQUIRED:
UserSessionModel userSession = session.sessions().createUserSession(realm, user, clientConnection.getRemoteAddr());
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
audit.session(userSession);
return oauth.processAccessCode(scopeParam, state, redirect, client, user, userSession, username, remember, "form", audit);
return oauth.processAccessCode(scopeParam, state, redirect, client, user, userSession, audit);
case ACCOUNT_TEMPORARILY_DISABLED:
audit.error(Errors.USER_TEMPORARILY_DISABLED);
return Flows.forms(this.session, realm, uriInfo).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).setFormData(formData).createLogin();
@@ -642,6 +643,14 @@ public class TokenService {
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
.build();
}
if (accessCode.getAction() != null) {
Map<String, String> res = new HashMap<String, String>();
res.put(OAuth2Constants.ERROR, "invalid_grant");
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is not active");
audit.error(Errors.INVALID_CODE);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
.build();
}
audit.user(accessCode.getUser());
audit.session(accessCode.getSessionState());
@@ -834,7 +843,7 @@ public class TokenService {
logger.debug(user.getUsername() + " already logged in.");
audit.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
return oauth.processAccessCode(scopeParam, state, redirect, client, user, session, null, false, "sso", audit);
return oauth.processAccessCode(scopeParam, state, redirect, client, user, session, audit);
}
if (prompt != null && prompt.equals("none")) {
@@ -974,7 +983,7 @@ public class TokenService {
String code = formData.getFirst(OAuth2Constants.CODE);
AccessCodeEntry accessCodeEntry = tokenManager.parseCode(code, session, realm);
if (accessCodeEntry == null) {
if (accessCodeEntry == null || !AccessCode.Action.OAUTH_GRANT.equals(accessCodeEntry.getAction())) {
audit.error(Errors.INVALID_CODE);
return oauth.forwardToSecurityFailure("Unknown access code.");
}
@@ -986,15 +995,17 @@ public class TokenService {
audit.client(accessCodeEntry.getClient())
.user(accessCodeEntry.getUser())
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, accessCodeEntry.getAuthMethod())
.detail(Details.REDIRECT_URI, redirect)
.detail(Details.USERNAME, accessCodeEntry.getUsernameUsed());
if (accessCodeEntry.isRememberMe()) {
audit.detail(Details.REMEMBER_ME, "true");
}
.detail(Details.REDIRECT_URI, redirect);
UserSessionModel userSession = session.sessions().getUserSession(realm, accessCodeEntry.getSessionState());
if (userSession != null) {
audit.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
audit.detail(Details.USERNAME, userSession.getLoginUsername());
if (userSession.isRememberMe()) {
audit.detail(Details.REMEMBER_ME, "true");
}
}
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.logout(session, realm, userSession, uriInfo);
audit.error(Errors.INVALID_CODE);
@@ -1009,7 +1020,7 @@ public class TokenService {
audit.success();
accessCodeEntry.resetExpiration();
accessCodeEntry.setAction(null);
return oauth.redirectAccessCode(accessCodeEntry, userSession, state, redirect);
}

View File

@@ -820,13 +820,8 @@ public class UsersResource {
return Flows.errors().error("AccountProvider management not enabled", Response.Status.INTERNAL_SERVER_ERROR);
}
Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>(user.getRequiredActions());
requiredActions.add(UserModel.RequiredAction.UPDATE_PASSWORD);
AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, session, realm, client, user, null);
accessCode.setRequiredActions(requiredActions);
accessCode.setUsernameUsed(username);
accessCode.resetExpiration();
accessCode.setRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
try {
UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri());

View File

@@ -37,7 +37,7 @@ 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.AccessToken;
import org.keycloak.representations.AccessCode;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
@@ -48,10 +48,8 @@ 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.Map;
import java.util.Set;
/**
@@ -84,12 +82,7 @@ public class OAuthFlows {
this.tokenManager = tokenManager;
}
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect) {
return redirectAccessCode(accessCode, session, state, redirect, false);
}
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel userSession, String state, String redirect, boolean rememberMe) {
public Response redirectAccessCode(AccessCodeEntry 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);
@@ -97,7 +90,6 @@ public class OAuthFlows {
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
rememberMe = rememberMe || remember != null;
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
@@ -112,8 +104,8 @@ public class OAuthFlows {
}
// refresh the cookies!
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, rememberMe);
if (rememberMe) authManager.createRememberMeCookie(realm, uriInfo);
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo);
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo);
return location.build();
}
@@ -125,15 +117,12 @@ public class OAuthFlows {
return Response.status(302).location(redirectUri.build()).build();
}
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, Audit audit) {
isTotpConfigurationRequired(user);
isEmailVerificationRequired(user);
boolean isResource = client instanceof ApplicationModel;
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session);
accessCode.setRememberMe(rememberMe);
accessCode.setAuthMethod(authMethod);
accessCode.setUsernameUsed(username);
log.debugv("processAccessCode: isResource: {0}", isResource);
log.debugv("processAccessCode: go to oauth page?: {0}",
@@ -143,10 +132,9 @@ public class OAuthFlows {
Set<RequiredAction> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) {
accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
accessCode.resetExpiration();
RequiredAction action = user.getRequiredActions().iterator().next();
accessCode.setRequiredAction(action);
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
}
@@ -156,7 +144,7 @@ public class OAuthFlows {
}
if (!isResource) {
accessCode.resetExpiration();
accessCode.setAction(AccessCode.Action.OAUTH_GRANT);
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
@@ -177,7 +165,7 @@ public class OAuthFlows {
if (redirect != null) {
audit.success();
return redirectAccessCode(accessCode, session, state, redirect, rememberMe);
return redirectAccessCode(accessCode, session, state, redirect);
} else {
return null;
}

View File

@@ -100,7 +100,7 @@ public class AdapterTest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
return tm.encodeToken(adminRealm, token);
} finally {

View File

@@ -87,7 +87,7 @@ public class RelativeUriAdapterTest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "user", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
adminToken = tm.encodeToken(adminRealm, token);

View File

@@ -79,7 +79,7 @@ public class AdminAPITest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
return tm.encodeToken(adminRealm, token);
} finally {

View File

@@ -126,7 +126,7 @@ public class ResetPasswordTest {
resetPasswordPage.assertCurrent();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).session((String) null).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@@ -143,16 +143,15 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPassword", "resetPassword");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session((String) null).detail(Details.USERNAME, username).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent();
String sessionId = loginEvent.getSessionId();
events.expectLogin().user(userId).detail(Details.USERNAME, username).session(sessionId).assertEvent();
oauth.openLogout();
events.expectLogout(loginEvent.getSessionId()).user(userId).session(sessionId).assertEvent();
events.expectLogout(sessionId).user(userId).session(sessionId).assertEvent();
loginPage.open();
@@ -210,7 +209,7 @@ public class ResetPasswordTest {
String body = (String) message.getContent();
String changePasswordUrl = MailUtil.getLink(body);
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").session((String) null).assertEvent();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
driver.navigate().to(changePasswordUrl.trim());
@@ -222,16 +221,15 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session((String) null).detail(Details.USERNAME, "login-test").assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
String sessionId = loginEvent.getSessionId();
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").session(sessionId).assertEvent();
oauth.openLogout();
events.expectLogout(loginEvent.getSessionId()).user(userId).session(sessionId).assertEvent();
events.expectLogout(sessionId).user(userId).session(sessionId).assertEvent();
loginPage.open();

View File

@@ -55,8 +55,8 @@ public class UserSessionProviderTest {
int started = Time.currentTime();
UserSessionModel[] sessions = createSessions();
assertSession(session.sessions().getUserSession(realm, sessions[0].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
assertSession(session.sessions().getUserSession(realm, sessions[1].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
assertSession(session.sessions().getUserSession(realm, sessions[0].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
assertSession(session.sessions().getUserSession(realm, sessions[1].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
assertSession(session.sessions().getUserSession(realm, sessions[2].getId()), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started);
}
@@ -116,7 +116,7 @@ public class UserSessionProviderTest {
@Test
public void testGetByClientPaginated() {
for (int i = 0; i < 25; i++) {
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "127.0.0." + i);
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false);
userSession.setStarted(Time.currentTime() + i);
userSession.associateClient(realm.findClient("test-app"));
}
@@ -157,14 +157,14 @@ public class UserSessionProviderTest {
private UserSessionModel[] createSessions() {
UserSessionModel[] sessions = new UserSessionModel[4];
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "127.0.0.1");
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true);
sessions[0].associateClient(realm.findClient("test-app"));
sessions[0].associateClient(realm.findClient("third-party"));
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "127.0.0.2");
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true);
sessions[1].associateClient(realm.findClient("test-app"));
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "127.0.0.3");
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true);
resetSession();
@@ -197,6 +197,9 @@ public class UserSessionProviderTest {
public void assertSession(UserSessionModel session, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
assertEquals(user.getId(), session.getUser().getId());
assertEquals(ipAddress, session.getIpAddress());
assertEquals(user.getUsername(), session.getLoginUsername());
assertEquals("form", session.getAuthMethod());
assertEquals(true, session.isRememberMe());
assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);