Merge pull request #372 from stianst/login-session
KEYCLOAK-432 Added user sessions
This commit is contained in:
commit
59747500aa
66 changed files with 1634 additions and 249 deletions
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.ProviderFactoryLoader;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -61,6 +62,16 @@ public class Audit {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Audit session(UserSessionModel session) {
|
||||
event.setSessionId(session.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit session(String sessionId) {
|
||||
event.setSessionId(sessionId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Audit ipAddress(String ipAddress) {
|
||||
event.setIpAddress(ipAddress);
|
||||
return this;
|
||||
|
|
|
@ -34,4 +34,8 @@ public interface Errors {
|
|||
String SOCIAL_PROVIDER_NOT_FOUND = "social_provider_not_found";
|
||||
String SOCIAL_ID_IN_USE = "social_id_in_use";
|
||||
|
||||
String USER_NOT_LOGGED_IN = "user_not_logged_in";
|
||||
String USER_SESSION_NOT_FOUND = "user_session_not_found";
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ public class Event {
|
|||
|
||||
private String userId;
|
||||
|
||||
private String sessionId;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private String error;
|
||||
|
@ -64,6 +66,14 @@ public class Event {
|
|||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
@ -95,6 +105,7 @@ public class Event {
|
|||
clone.realmId = realmId;
|
||||
clone.clientId = clientId;
|
||||
clone.userId = userId;
|
||||
clone.sessionId = sessionId;
|
||||
clone.ipAddress = ipAddress;
|
||||
clone.error = error;
|
||||
clone.details = details != null ? new HashMap<String, String>(details) : null;
|
||||
|
|
|
@ -23,6 +23,8 @@ public class EventEntity {
|
|||
|
||||
private String userId;
|
||||
|
||||
private String sessionId;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private String error;
|
||||
|
@ -78,6 +80,14 @@ public class EventEntity {
|
|||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ public class JpaAuditProvider implements AuditProvider {
|
|||
e.setRealmId(o.getRealmId());
|
||||
e.setClientId(o.getClientId());
|
||||
e.setUserId(o.getUserId());
|
||||
e.setSessionId(o.getSessionId());
|
||||
e.setIpAddress(o.getIpAddress());
|
||||
e.setError(o.getError());
|
||||
try {
|
||||
|
@ -100,6 +101,7 @@ public class JpaAuditProvider implements AuditProvider {
|
|||
e.setRealmId(o.getRealmId());
|
||||
e.setClientId(o.getClientId());
|
||||
e.setUserId(o.getUserId());
|
||||
e.setSessionId(o.getSessionId());
|
||||
e.setIpAddress(o.getIpAddress());
|
||||
e.setError(o.getError());
|
||||
try {
|
||||
|
|
|
@ -60,6 +60,7 @@ public class MongoAuditProvider implements AuditProvider {
|
|||
e.put("realmId", o.getRealmId());
|
||||
e.put("clientId", o.getClientId());
|
||||
e.put("userId", o.getUserId());
|
||||
e.put("sessionId", o.getSessionId());
|
||||
e.put("ipAddress", o.getIpAddress());
|
||||
e.put("error", o.getError());
|
||||
|
||||
|
@ -81,6 +82,7 @@ public class MongoAuditProvider implements AuditProvider {
|
|||
e.setRealmId(o.getString("realmId"));
|
||||
e.setClientId(o.getString("clientId"));
|
||||
e.setUserId(o.getString("userId"));
|
||||
e.setSessionId(o.getString("sessionId"));
|
||||
e.setIpAddress(o.getString("ipAddress"));
|
||||
e.setError(o.getString("error"));
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -88,6 +88,9 @@ public class IDToken extends JsonWebToken {
|
|||
@JsonProperty("claims_locales")
|
||||
protected String claimsLocales;
|
||||
|
||||
@JsonProperty("session_state")
|
||||
protected String sessionState;
|
||||
|
||||
public String getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
@ -303,4 +306,12 @@ public class IDToken extends JsonWebToken {
|
|||
public void setClaimsLocales(String claimsLocales) {
|
||||
this.claimsLocales = claimsLocales;
|
||||
}
|
||||
|
||||
public String getSessionState() {
|
||||
return sessionState;
|
||||
}
|
||||
|
||||
public void setSessionState(String sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ public class RefreshToken extends AccessToken {
|
|||
}
|
||||
|
||||
/**
|
||||
* Deep copies issuer, subject, issuedFor, realmAccess, and resourceAccess
|
||||
* Deep copies issuer, subject, issuedFor, sessionState, realmAccess, and resourceAccess
|
||||
* from AccessToken.
|
||||
*
|
||||
* @param token
|
||||
|
@ -23,6 +23,7 @@ public class RefreshToken extends AccessToken {
|
|||
this.issuer = token.issuer;
|
||||
this.subject = token.subject;
|
||||
this.issuedFor = token.issuedFor;
|
||||
this.sessionState = token.sessionState;
|
||||
if (token.realmAccess != null) {
|
||||
realmAccess = token.realmAccess.clone();
|
||||
}
|
||||
|
|
|
@ -7,14 +7,16 @@ package org.keycloak.representations.adapters.action;
|
|||
public class LogoutAction extends AdminAction {
|
||||
public static final String LOGOUT = "LOGOUT";
|
||||
protected String user;
|
||||
private String session;
|
||||
protected int notBefore;
|
||||
|
||||
public LogoutAction() {
|
||||
}
|
||||
|
||||
public LogoutAction(String id, int expiration, String resource, String user, int notBefore) {
|
||||
public LogoutAction(String id, int expiration, String resource, String user, String session, int notBefore) {
|
||||
super(id, expiration, resource, LOGOUT);
|
||||
this.user = user;
|
||||
this.session = session;
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
|
@ -26,6 +28,14 @@ public class LogoutAction extends AdminAction {
|
|||
this.user = user;
|
||||
}
|
||||
|
||||
public String getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(String session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,12 @@ public class Config {
|
|||
|
||||
public static final String MODEL_PROVIDER_KEY = "keycloak.model";
|
||||
|
||||
public static final String USER_EXPIRATION_SCHEDULE_KEY = "keycloak.scheduled.clearExpiredUserSessions";
|
||||
public static final String USER_EXPIRATION_SCHEDULE_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(15));
|
||||
|
||||
public static final String AUDIT_PROVIDER_KEY = "keycloak.audit";
|
||||
public static final String AUDIT_PROVIDER_DEFAULT = "jpa";
|
||||
public static final String AUDIT_EXPIRATION_SCHEDULE_KEY = "keycloak.audit.expirationSchedule";
|
||||
public static final String AUDIT_EXPIRATION_SCHEDULE_KEY = "keycloak.scheduled.clearExpiredAuditEvents";
|
||||
public static final String AUDIT_EXPIRATION_SCHEDULE_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(15));
|
||||
|
||||
public static final String PICKETLINK_PROVIDER_KEY = "keycloak.picketlink";
|
||||
|
@ -67,6 +70,14 @@ public class Config {
|
|||
System.setProperty(AUDIT_EXPIRATION_SCHEDULE_KEY, schedule);
|
||||
}
|
||||
|
||||
public static String getUserExpirationSchedule() {
|
||||
return System.getProperty(USER_EXPIRATION_SCHEDULE_KEY, USER_EXPIRATION_SCHEDULE_DEFAULT);
|
||||
}
|
||||
|
||||
public static void setUserExpirationSchedule(String schedule) {
|
||||
System.setProperty(USER_EXPIRATION_SCHEDULE_KEY, schedule);
|
||||
}
|
||||
|
||||
public static String getModelProvider() {
|
||||
return System.getProperty(MODEL_PROVIDER_KEY);
|
||||
}
|
||||
|
@ -168,4 +179,5 @@ public class Config {
|
|||
public static void setExportImportZipPassword(String exportImportZipPassword) {
|
||||
System.setProperty(EXPORT_IMPORT_ZIP_PASSWORD, exportImportZipPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -249,4 +249,14 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
|||
|
||||
void setAdminApp(ApplicationModel app);
|
||||
|
||||
UserSessionModel createUserSession(UserModel user, String ipAddress);
|
||||
|
||||
UserSessionModel getUserSession(String id);
|
||||
|
||||
void removeUserSession(UserSessionModel session);
|
||||
|
||||
void removeUserSessions(UserModel user);
|
||||
|
||||
void removeExpiredUserSessions();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface UserSessionModel {
|
||||
|
||||
String getId();
|
||||
|
||||
void setId(String id);
|
||||
|
||||
UserModel getUser();
|
||||
|
||||
void setUser(UserModel user);
|
||||
|
||||
String getIpAddress();
|
||||
|
||||
void setIpAddress(String ipAddress);
|
||||
|
||||
int getStarted();
|
||||
|
||||
void setStarted(int started);
|
||||
|
||||
int getExpires();
|
||||
|
||||
void setExpires(int expires);
|
||||
|
||||
}
|
|
@ -88,7 +88,7 @@ public class JpaKeycloakSession implements KeycloakSession {
|
|||
adapter.removeOAuthClient(oauth.getId());
|
||||
}
|
||||
|
||||
for (UserEntity u : em.createQuery("from UserEntity", UserEntity.class).getResultList()) {
|
||||
for (UserEntity u : em.createQuery("from UserEntity u where u.realm = :realm", UserEntity.class).setParameter("realm", realm).getResultList()) {
|
||||
adapter.removeUser(u.getLoginName());
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationProviderModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UsernameLoginFailureModel;
|
||||
import org.keycloak.models.jpa.entities.ApplicationEntity;
|
||||
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
|
||||
|
@ -19,6 +20,7 @@ import org.keycloak.models.jpa.entities.RoleEntity;
|
|||
import org.keycloak.models.jpa.entities.ScopeMappingEntity;
|
||||
import org.keycloak.models.jpa.entities.SocialLinkEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.jpa.entities.UserSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
|
||||
import org.keycloak.models.jpa.entities.UsernameLoginFailureEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -33,6 +35,7 @@ import org.keycloak.models.SocialLinkModel;
|
|||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
|
@ -49,6 +52,7 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.*;
|
||||
|
||||
/**
|
||||
|
@ -452,7 +456,7 @@ public class RealmAdapter implements RealmModel {
|
|||
query.setParameter("email", email);
|
||||
query.setParameter("realm", realm);
|
||||
List<UserEntity> results = query.getResultList();
|
||||
return results.isEmpty()? null : new UserAdapter(results.get(0));
|
||||
return results.isEmpty() ? null : new UserAdapter(results.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -504,6 +508,8 @@ public class RealmAdapter implements RealmModel {
|
|||
}
|
||||
|
||||
private void removeUser(UserEntity user) {
|
||||
removeUserSessions(user);
|
||||
|
||||
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||
if (user.getAuthenticationLink() != null) {
|
||||
|
@ -719,7 +725,7 @@ public class RealmAdapter implements RealmModel {
|
|||
em.remove(entity);
|
||||
em.flush();
|
||||
return true;
|
||||
} else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -848,7 +854,7 @@ public class RealmAdapter implements RealmModel {
|
|||
public boolean removeOAuthClient(String id) {
|
||||
OAuthClientModel oauth = getOAuthClientById(id);
|
||||
if (oauth == null) return false;
|
||||
OAuthClientEntity client = (OAuthClientEntity)((OAuthClientAdapter)oauth).getEntity();
|
||||
OAuthClientEntity client = (OAuthClientEntity) ((OAuthClientAdapter) oauth).getEntity();
|
||||
em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where client = :client").setParameter("client", client).executeUpdate();
|
||||
em.remove(client);
|
||||
return true;
|
||||
|
@ -1001,7 +1007,7 @@ public class RealmAdapter implements RealmModel {
|
|||
}
|
||||
if (!role.getContainer().equals(this)) return false;
|
||||
|
||||
RoleEntity roleEntity = ((RoleAdapter)role).getRole();
|
||||
RoleEntity roleEntity = ((RoleAdapter) role).getRole();
|
||||
realm.getRoles().remove(role);
|
||||
realm.getDefaultRoles().remove(role);
|
||||
|
||||
|
@ -1030,10 +1036,10 @@ public class RealmAdapter implements RealmModel {
|
|||
RoleEntity entity = em.find(RoleEntity.class, id);
|
||||
if (entity == null) return null;
|
||||
if (entity instanceof RealmRoleEntity) {
|
||||
RealmRoleEntity roleEntity = (RealmRoleEntity)entity;
|
||||
RealmRoleEntity roleEntity = (RealmRoleEntity) entity;
|
||||
if (!roleEntity.getRealm().getId().equals(getId())) return null;
|
||||
} else {
|
||||
ApplicationRoleEntity roleEntity = (ApplicationRoleEntity)entity;
|
||||
ApplicationRoleEntity roleEntity = (ApplicationRoleEntity) entity;
|
||||
if (!roleEntity.getApplication().getRealm().getId().equals(getId())) return null;
|
||||
}
|
||||
return new RoleAdapter(this, em, entity);
|
||||
|
@ -1069,7 +1075,6 @@ public class RealmAdapter implements RealmModel {
|
|||
}
|
||||
|
||||
|
||||
|
||||
protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(UserAdapter user, RoleAdapter role) {
|
||||
TypedQuery<UserRoleMappingEntity> query = em.createNamedQuery("userHasRole", UserRoleMappingEntity.class);
|
||||
query.setParameter("user", user.getUser());
|
||||
|
@ -1082,7 +1087,7 @@ public class RealmAdapter implements RealmModel {
|
|||
if (hasRole(user, role)) return;
|
||||
UserRoleMappingEntity entity = new UserRoleMappingEntity();
|
||||
entity.setUser(((UserAdapter) user).getUser());
|
||||
entity.setRole(((RoleAdapter)role).getRole());
|
||||
entity.setRole(((RoleAdapter) role).getRole());
|
||||
em.persist(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
@ -1095,7 +1100,7 @@ public class RealmAdapter implements RealmModel {
|
|||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof RealmModel) {
|
||||
realmRoles.add(role);
|
||||
realmRoles.add(role);
|
||||
}
|
||||
}
|
||||
return realmRoles;
|
||||
|
@ -1105,7 +1110,7 @@ public class RealmAdapter implements RealmModel {
|
|||
@Override
|
||||
public Set<RoleModel> getRoleMappings(UserModel user) {
|
||||
TypedQuery<UserRoleMappingEntity> query = em.createNamedQuery("userRoleMappings", UserRoleMappingEntity.class);
|
||||
query.setParameter("user", ((UserAdapter)user).getUser());
|
||||
query.setParameter("user", ((UserAdapter) user).getUser());
|
||||
List<UserRoleMappingEntity> entities = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (UserRoleMappingEntity entity : entities) {
|
||||
|
@ -1135,7 +1140,7 @@ public class RealmAdapter implements RealmModel {
|
|||
for (RoleModel role : roleMappings) {
|
||||
RoleContainerModel container = role.getContainer();
|
||||
if (container instanceof RealmModel) {
|
||||
if (((RealmModel)container).getId().equals(getId())) {
|
||||
if (((RealmModel) container).getId().equals(getId())) {
|
||||
appRoles.add(role);
|
||||
}
|
||||
}
|
||||
|
@ -1148,7 +1153,7 @@ public class RealmAdapter implements RealmModel {
|
|||
@Override
|
||||
public Set<RoleModel> getScopeMappings(ClientModel client) {
|
||||
TypedQuery<ScopeMappingEntity> query = em.createNamedQuery("clientScopeMappings", ScopeMappingEntity.class);
|
||||
query.setParameter("client", ((ClientAdapter)client).getEntity());
|
||||
query.setParameter("client", ((ClientAdapter) client).getEntity());
|
||||
List<ScopeMappingEntity> entities = query.getResultList();
|
||||
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||
for (ScopeMappingEntity entity : entities) {
|
||||
|
@ -1162,7 +1167,7 @@ public class RealmAdapter implements RealmModel {
|
|||
if (hasScope(client, role)) return;
|
||||
ScopeMappingEntity entity = new ScopeMappingEntity();
|
||||
entity.setClient(((ClientAdapter) client).getEntity());
|
||||
entity.setRole(((RoleAdapter)role).getRole());
|
||||
entity.setRole(((RoleAdapter) role).getRole());
|
||||
em.persist(entity);
|
||||
}
|
||||
|
||||
|
@ -1179,13 +1184,13 @@ public class RealmAdapter implements RealmModel {
|
|||
protected TypedQuery<ScopeMappingEntity> getRealmScopeMappingQuery(ClientAdapter client, RoleAdapter role) {
|
||||
TypedQuery<ScopeMappingEntity> query = em.createNamedQuery("hasScope", ScopeMappingEntity.class);
|
||||
query.setParameter("client", client.getEntity());
|
||||
query.setParameter("role", ((RoleAdapter)role).getRole());
|
||||
query.setParameter("role", ((RoleAdapter) role).getRole());
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validatePassword(UserModel user, String password) {
|
||||
for (CredentialEntity cred : ((UserAdapter)user).getUser().getCredentials()) {
|
||||
for (CredentialEntity cred : ((UserAdapter) user).getUser().getCredentials()) {
|
||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
return new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue());
|
||||
}
|
||||
|
@ -1196,7 +1201,7 @@ public class RealmAdapter implements RealmModel {
|
|||
@Override
|
||||
public boolean validateTOTP(UserModel user, String password, String token) {
|
||||
if (!validatePassword(user, password)) return false;
|
||||
for (CredentialEntity cred : ((UserAdapter)user).getUser().getCredentials()) {
|
||||
for (CredentialEntity cred : ((UserAdapter) user).getUser().getCredentials()) {
|
||||
if (cred.getType().equals(UserCredentialModel.TOTP)) {
|
||||
return new TimeBasedOTP().validate(token, cred.getValue().getBytes());
|
||||
}
|
||||
|
@ -1297,7 +1302,7 @@ public class RealmAdapter implements RealmModel {
|
|||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof RealmAdapter)) return false;
|
||||
RealmAdapter r = (RealmAdapter)o;
|
||||
RealmAdapter r = (RealmAdapter) o;
|
||||
return r.getId().equals(getId());
|
||||
}
|
||||
|
||||
|
@ -1367,4 +1372,45 @@ public class RealmAdapter implements RealmModel {
|
|||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(UserModel user, String ipAddress) {
|
||||
UserSessionEntity entity = new UserSessionEntity();
|
||||
entity.setUser(((UserAdapter) user).getUser());
|
||||
entity.setIpAddress(ipAddress);
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
int expires = currentTime + realm.getCentralLoginLifespan();
|
||||
|
||||
entity.setStarted(currentTime);
|
||||
entity.setExpires(expires);
|
||||
|
||||
em.persist(entity);
|
||||
return new UserSessionAdapter(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession(String id) {
|
||||
UserSessionEntity entity = em.find(UserSessionEntity.class, id);
|
||||
return entity != null ? new UserSessionAdapter(entity) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSession(UserSessionModel session) {
|
||||
em.remove(((UserSessionAdapter) session).getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(UserModel user) {
|
||||
removeUserSessions(((UserAdapter) user).getUser());
|
||||
}
|
||||
|
||||
private void removeUserSessions(UserEntity user) {
|
||||
em.createNamedQuery("removeUserSessionByUser").setParameter("user", user).executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredUserSessions() {
|
||||
em.createNamedQuery("removeUserSessionExpired").setParameter("currentTime", Time.currentTime()).executeUpdate();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.jpa.entities.UserSessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionAdapter implements UserSessionModel {
|
||||
|
||||
private UserSessionEntity entity;
|
||||
|
||||
public UserSessionAdapter(UserSessionEntity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public UserSessionEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
entity.setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return new UserAdapter(entity.getUser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(UserModel user) {
|
||||
entity.setUser(((UserAdapter) user).getUser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIpAddress(String ipAddress) {
|
||||
entity.setIpAddress(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStarted(int started) {
|
||||
entity.setStarted(started);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpires() {
|
||||
return entity.getExpires();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpires(int expires) {
|
||||
entity.setExpires(expires);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
|
||||
@NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.expires < :currentTime")
|
||||
})
|
||||
public class UserSessionEntity {
|
||||
|
||||
@Id
|
||||
@GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||
@GeneratedValue(generator = "uuid_generator")
|
||||
private String id;
|
||||
|
||||
@ManyToOne
|
||||
private UserEntity user;
|
||||
|
||||
String ipAddress;
|
||||
|
||||
int started;
|
||||
|
||||
int expires;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public UserEntity getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(UserEntity user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public int getStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
public void setStarted(int started) {
|
||||
this.started = started;
|
||||
}
|
||||
|
||||
public int getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
public void setExpires(int expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
|
|||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,8 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
AuthenticationLinkEntity.class,
|
||||
MongoApplicationEntity.class,
|
||||
MongoOAuthClientEntity.class,
|
||||
MongoUsernameLoginFailureEntity.class
|
||||
MongoUsernameLoginFailureEntity.class,
|
||||
MongoUserSessionEntity.class
|
||||
};
|
||||
|
||||
private final MongoClientProvider mongoClientProvider;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.jboss.logging.Logger;
|
||||
|
@ -16,6 +17,7 @@ import org.keycloak.models.SocialLinkModel;
|
|||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UsernameLoginFailureModel;
|
||||
import org.keycloak.models.entities.AuthenticationLinkEntity;
|
||||
import org.keycloak.models.entities.AuthenticationProviderEntity;
|
||||
|
@ -28,11 +30,13 @@ import org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity;
|
|||
import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUsernameLoginFailureEntity;
|
||||
import org.keycloak.models.mongo.utils.MongoModelUtils;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
@ -1333,4 +1337,51 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
public MongoRealmEntity getMongoEntity() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel createUserSession(UserModel user, String ipAddress) {
|
||||
MongoUserSessionEntity entity = new MongoUserSessionEntity();
|
||||
entity.setUser(user.getId());
|
||||
entity.setIpAddress(ipAddress);
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
int expires = currentTime + realm.getCentralLoginLifespan();
|
||||
|
||||
entity.setStarted(currentTime);
|
||||
entity.setExpires(expires);
|
||||
|
||||
getMongoStore().insertEntity(entity, invocationContext);
|
||||
return new UserSessionAdapter(entity, this, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSession(String id) {
|
||||
MongoUserSessionEntity entity = getMongoStore().loadEntity(MongoUserSessionEntity.class, id, invocationContext);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new UserSessionAdapter(entity, this, invocationContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSession(UserSessionModel session) {
|
||||
getMongoStore().removeEntity(((UserSessionAdapter) session).getEntity(), invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(UserModel user) {
|
||||
DBObject query = new BasicDBObject("user", user.getId());
|
||||
getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredUserSessions() {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("expires").lessThan(Time.currentTime())
|
||||
.get();
|
||||
|
||||
getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package org.keycloak.models.mongo.keycloak.adapters;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.mongo.keycloak.entities.MongoUserSessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class UserSessionAdapter implements UserSessionModel {
|
||||
|
||||
private MongoUserSessionEntity entity;
|
||||
private RealmAdapter realm;
|
||||
private MongoStoreInvocationContext invContext;
|
||||
|
||||
public UserSessionAdapter(MongoUserSessionEntity entity, RealmAdapter realm, MongoStoreInvocationContext invContext) {
|
||||
this.entity = entity;
|
||||
this.realm = realm;
|
||||
this.invContext = invContext;
|
||||
}
|
||||
|
||||
public MongoUserSessionEntity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
entity.setId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return realm.getUserById(entity.getUser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUser(UserModel user) {
|
||||
entity.setUser(user.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIpAddress() {
|
||||
return entity.getIpAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIpAddress(String ipAddress) {
|
||||
entity.setIpAddress(ipAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStarted() {
|
||||
return entity.getStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStarted(int started) {
|
||||
entity.setStarted(started);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpires() {
|
||||
return entity.getExpires();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpires(int expires) {
|
||||
entity.setExpires(expires);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.models.entities.UserEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
|
@ -27,6 +29,10 @@ public class MongoUserEntity extends UserEntity implements MongoIdentifiableEnti
|
|||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("userId").is(getId())
|
||||
.get();
|
||||
|
||||
invocationContext.getMongoStore().removeEntities(MongoUserSessionEntity.class, query, invocationContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.models.mongo.keycloak.entities;
|
||||
|
||||
import org.keycloak.models.entities.AbstractIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.MongoCollection;
|
||||
import org.keycloak.models.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "sessions")
|
||||
public class MongoUserSessionEntity extends AbstractIdentifiableEntity implements MongoIdentifiableEntity {
|
||||
|
||||
private String user;
|
||||
|
||||
private String ipAddress;
|
||||
|
||||
private int started;
|
||||
|
||||
private int expires;
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public int getStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
public void setStarted(int started) {
|
||||
this.started = started;
|
||||
}
|
||||
|
||||
public int getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
public void setExpires(int expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRemove(MongoStoreInvocationContext invocationContext) {
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.SocialLinkModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.OAuthClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -24,6 +25,9 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
|
@ -46,7 +50,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
realmModel.addDefaultRole("foo");
|
||||
|
||||
realmModel = realmManager.getRealm(realmModel.getId());
|
||||
Assert.assertNotNull(realmModel);
|
||||
assertNotNull(realmModel);
|
||||
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
|
||||
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
|
||||
Assert.assertEquals(realmModel.getAccessTokenLifespan(), 1000);
|
||||
|
@ -73,7 +77,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
realmModel.addDefaultRole("foo");
|
||||
|
||||
realmModel = realmManager.getRealm(realmModel.getId());
|
||||
Assert.assertNotNull(realmModel);
|
||||
assertNotNull(realmModel);
|
||||
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
|
||||
Assert.assertEquals(600, realmModel.getAccessCodeLifespanUserAction());
|
||||
Assert.assertEquals(realmModel.getAccessTokenLifespan(), 1000);
|
||||
|
@ -169,7 +173,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
realmModel = identitySession.getRealm("JUGGLER");
|
||||
Assert.assertTrue(realmModel.removeUser("bburke"));
|
||||
Assert.assertFalse(realmModel.removeUser("bburke"));
|
||||
Assert.assertNull(realmModel.getUser("bburke"));
|
||||
assertNull(realmModel.getUser("bburke"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -191,7 +195,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
|
||||
Assert.assertTrue(realmModel.removeApplication(app.getId()));
|
||||
Assert.assertFalse(realmModel.removeApplication(app.getId()));
|
||||
Assert.assertNull(realmModel.getApplicationById(app.getId()));
|
||||
assertNull(realmModel.getApplicationById(app.getId()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -226,7 +230,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
|
||||
Assert.assertTrue(realmManager.removeRealm(realmModel));
|
||||
Assert.assertFalse(realmManager.removeRealm(realmModel));
|
||||
Assert.assertNull(realmManager.getRealm(realmModel.getId()));
|
||||
assertNull(realmManager.getRealm(realmModel.getId()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -253,11 +257,11 @@ public class AdapterTest extends AbstractModelTest {
|
|||
|
||||
Assert.assertTrue(realmModel.removeRoleById(realmRole.getId()));
|
||||
Assert.assertFalse(realmModel.removeRoleById(realmRole.getId()));
|
||||
Assert.assertNull(realmModel.getRole(realmRole.getName()));
|
||||
assertNull(realmModel.getRole(realmRole.getName()));
|
||||
|
||||
Assert.assertTrue(realmModel.removeRoleById(appRole.getId()));
|
||||
Assert.assertFalse(realmModel.removeRoleById(appRole.getId()));
|
||||
Assert.assertNull(app.getRole(appRole.getName()));
|
||||
assertNull(app.getRole(appRole.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -435,7 +439,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
realmModel.grantRole(user, realmUserRole);
|
||||
Assert.assertTrue(realmModel.hasRole(user, realmUserRole));
|
||||
RoleModel found = realmModel.getRoleById(realmUserRole.getId());
|
||||
Assert.assertNotNull(found);
|
||||
assertNotNull(found);
|
||||
assertRolesEquals(found, realmUserRole);
|
||||
|
||||
// Test app roles
|
||||
|
@ -445,10 +449,10 @@ public class AdapterTest extends AbstractModelTest {
|
|||
Set<RoleModel> appRoles = application.getRoles();
|
||||
Assert.assertEquals(2, appRoles.size());
|
||||
RoleModel appBarRole = application.getRole("bar");
|
||||
Assert.assertNotNull(appBarRole);
|
||||
assertNotNull(appBarRole);
|
||||
|
||||
found = realmModel.getRoleById(appBarRole.getId());
|
||||
Assert.assertNotNull(found);
|
||||
assertNotNull(found);
|
||||
assertRolesEquals(found, appBarRole);
|
||||
|
||||
realmModel.grantRole(user, appBarRole);
|
||||
|
@ -719,4 +723,43 @@ public class AdapterTest extends AbstractModelTest {
|
|||
resetSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userSessions() throws InterruptedException {
|
||||
realmManager.createRealm("userSessions");
|
||||
realmManager.getRealmByName("userSessions").setCentralLoginLifespan(5);
|
||||
|
||||
UserModel user = realmManager.getRealmByName("userSessions").addUser("userSessions1");
|
||||
|
||||
UserSessionModel userSession = realmManager.getRealmByName("userSessions").createUserSession(user, "127.0.0.1");
|
||||
commit();
|
||||
|
||||
assertNotNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||
commit();
|
||||
|
||||
realmManager.getRealmByName("userSessions").removeUserSession(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||
commit();
|
||||
|
||||
assertNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||
|
||||
userSession = realmManager.getRealmByName("userSessions").createUserSession(user, "127.0.0.1");
|
||||
commit();
|
||||
|
||||
realmManager.getRealmByName("userSessions").removeUserSessions(user);
|
||||
commit();
|
||||
|
||||
assertNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||
|
||||
realmManager.getRealmByName("userSessions").setCentralLoginLifespan(1);
|
||||
|
||||
userSession = realmManager.getRealmByName("userSessions").createUserSession(user, "127.0.0.1");
|
||||
commit();
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
realmManager.getRealmByName("userSessions").removeExpiredUserSessions();
|
||||
commit();
|
||||
|
||||
assertNull(realmManager.getRealmByName("userSessions").getUserSession(userSession.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
|
@ -23,6 +24,7 @@ public class AccessCodeEntry {
|
|||
protected String id = UUID.randomUUID().toString() + System.currentTimeMillis();
|
||||
protected String code;
|
||||
protected String state;
|
||||
protected String sessionState;
|
||||
protected String redirectUri;
|
||||
protected boolean rememberMe;
|
||||
protected String authMethod;
|
||||
|
@ -117,6 +119,14 @@ public class AccessCodeEntry {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public String getSessionState() {
|
||||
return sessionState;
|
||||
}
|
||||
|
||||
public void setSessionState(String sessionState) {
|
||||
this.sessionState = sessionState;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
|
|
@ -31,9 +31,12 @@ public class AppAuthManager extends AuthenticationManager {
|
|||
}
|
||||
|
||||
public UserModel authenticateRequest(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||
UserModel user = authenticateIdentityCookie(realm, uriInfo, headers);
|
||||
if (user != null) return user;
|
||||
return authenticateBearerToken(realm, uriInfo, headers);
|
||||
AuthResult authResult = authenticateIdentityCookie(realm, uriInfo, headers);
|
||||
if (authResult != null) {
|
||||
return authResult.getUser();
|
||||
} else {
|
||||
return authenticateBearerToken(realm, uriInfo, headers);
|
||||
}
|
||||
}
|
||||
|
||||
public String extractAuthorizationHeaderToken(HttpHeaders headers) {
|
||||
|
@ -51,7 +54,8 @@ public class AppAuthManager extends AuthenticationManager {
|
|||
public UserModel authenticateBearerToken(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||
String tokenString = extractAuthorizationHeaderToken(headers);
|
||||
if (tokenString == null) return null;
|
||||
return verifyIdentityToken(realm, uriInfo, true, tokenString);
|
||||
AuthResult authResult = verifyIdentityToken(realm, uriInfo, true, tokenString);
|
||||
return authResult != null ? authResult.getUser() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -55,28 +56,31 @@ public class AuthenticationManager {
|
|||
this.protector = protector;
|
||||
}
|
||||
|
||||
public AccessToken createIdentityToken(RealmModel realm, UserModel user) {
|
||||
public AccessToken createIdentityToken(RealmModel realm, UserModel user, UserSessionModel session) {
|
||||
logger.info("createIdentityToken");
|
||||
AccessToken token = new AccessToken();
|
||||
token.id(KeycloakModelUtils.generateId());
|
||||
token.issuedNow();
|
||||
token.subject(user.getId());
|
||||
token.audience(realm.getName());
|
||||
if (session != null) {
|
||||
token.setSessionState(session.getId());
|
||||
}
|
||||
if (realm.getCentralLoginLifespan() > 0) {
|
||||
token.expiration(Time.currentTime() + realm.getCentralLoginLifespan());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo, boolean rememberMe) {
|
||||
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, boolean rememberMe) {
|
||||
logger.info("createLoginCookie");
|
||||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
||||
return createLoginCookie(realm, user, null, cookieName, cookiePath, rememberMe);
|
||||
return createLoginCookie(realm, user, session, null, cookieName, cookiePath, rememberMe);
|
||||
}
|
||||
|
||||
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, ClientModel client, String cookieName, String cookiePath, boolean rememberMe) {
|
||||
AccessToken identityToken = createIdentityToken(realm, user);
|
||||
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, ClientModel client, String cookieName, String cookiePath, boolean rememberMe) {
|
||||
AccessToken identityToken = createIdentityToken(realm, user, session);
|
||||
if (client != null) {
|
||||
identityToken.issuedFor(client.getClientId());
|
||||
}
|
||||
|
@ -136,17 +140,17 @@ public class AuthenticationManager {
|
|||
response.addNewCookie(expireIt);
|
||||
}
|
||||
|
||||
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||
public AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||
return authenticateIdentityCookie(realm, uriInfo, headers, true);
|
||||
}
|
||||
|
||||
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, boolean checkActive) {
|
||||
public AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, boolean checkActive) {
|
||||
logger.info("authenticateIdentityCookie");
|
||||
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
|
||||
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName, checkActive);
|
||||
}
|
||||
|
||||
protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
|
||||
private AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
|
||||
logger.info("authenticateIdentityCookie");
|
||||
Cookie cookie = headers.getCookies().get(cookieName);
|
||||
if (cookie == null) {
|
||||
|
@ -155,14 +159,14 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
String tokenString = cookie.getValue();
|
||||
UserModel user = verifyIdentityToken(realm, uriInfo, checkActive, tokenString);
|
||||
if (user == null) {
|
||||
AuthResult authResult = verifyIdentityToken(realm, uriInfo, checkActive, tokenString);
|
||||
if (authResult == null) {
|
||||
expireIdentityCookie(realm, uriInfo);
|
||||
}
|
||||
return user;
|
||||
return authResult;
|
||||
}
|
||||
|
||||
protected UserModel verifyIdentityToken(RealmModel realm, UriInfo uriInfo, boolean checkActive, String tokenString) {
|
||||
protected AuthResult verifyIdentityToken(RealmModel realm, UriInfo uriInfo, boolean checkActive, String tokenString) {
|
||||
try {
|
||||
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
|
||||
logger.info("identity token verified");
|
||||
|
@ -188,10 +192,16 @@ public class AuthenticationManager {
|
|||
if (token.getIssuedAt() < user.getNotBefore()) {
|
||||
logger.info("Stale cookie");
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return user;
|
||||
UserSessionModel session = realm.getUserSession(token.getSessionState());
|
||||
if (session == null || session.getExpires() < Time.currentTime()) {
|
||||
logger.info("User session not active");
|
||||
expireIdentityCookie(realm, uriInfo);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AuthResult(user, session);
|
||||
} catch (VerificationException e) {
|
||||
logger.info("Failed to verify identity token", e);
|
||||
}
|
||||
|
@ -328,4 +338,22 @@ public class AuthenticationManager {
|
|||
SUCCESS, ACCOUNT_TEMPORARILY_DISABLED, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
|
||||
}
|
||||
|
||||
public class AuthResult {
|
||||
private final UserModel user;
|
||||
private final UserSessionModel session;
|
||||
|
||||
public AuthResult(UserModel user, UserSessionModel session) {
|
||||
this.user = user;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public UserSessionModel getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public UserModel getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.client.ClientRequest;
|
||||
import org.jboss.resteasy.client.ClientResponse;
|
||||
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.TokenIdGenerator;
|
||||
import org.keycloak.adapters.AdapterConstants;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
|
@ -146,7 +146,7 @@ public class ResourceAdminManager {
|
|||
|
||||
}
|
||||
|
||||
public void logoutUser(URI requestUri, RealmModel realm, UserModel user) {
|
||||
public void logoutUser(URI requestUri, RealmModel realm, String user, String session) {
|
||||
ApacheHttpClient4Executor executor = createExecutor();
|
||||
|
||||
try {
|
||||
|
@ -154,7 +154,7 @@ public class ResourceAdminManager {
|
|||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debugv("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
logoutApplication(requestUri, realm, resource, user.getId(), executor, 0);
|
||||
logoutApplication(requestUri, realm, resource, user, session, executor, 0);
|
||||
}
|
||||
} finally {
|
||||
executor.getHttpClient().getConnectionManager().shutdown();
|
||||
|
@ -168,19 +168,19 @@ public class ResourceAdminManager {
|
|||
List<ApplicationModel> resources = realm.getApplications();
|
||||
logger.debugv("logging out {0} resources ", resources.size());
|
||||
for (ApplicationModel resource : resources) {
|
||||
logoutApplication(requestUri, realm, resource, null, executor, realm.getNotBefore());
|
||||
logoutApplication(requestUri, realm, resource, null, null, executor, realm.getNotBefore());
|
||||
}
|
||||
} finally {
|
||||
executor.getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user) {
|
||||
public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session) {
|
||||
ApacheHttpClient4Executor executor = createExecutor();
|
||||
|
||||
try {
|
||||
resource.setNotBefore(Time.currentTime());
|
||||
logoutApplication(requestUri, realm, resource, user, executor, resource.getNotBefore());
|
||||
logoutApplication(requestUri, realm, resource, user, session, executor, resource.getNotBefore());
|
||||
} finally {
|
||||
executor.getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
|
@ -188,10 +188,10 @@ public class ResourceAdminManager {
|
|||
}
|
||||
|
||||
|
||||
protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, ApacheHttpClient4Executor client, int notBefore) {
|
||||
protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, String session, ApacheHttpClient4Executor client, int notBefore) {
|
||||
String managementUrl = getManagementUrl(requestUri, resource);
|
||||
if (managementUrl != null) {
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, notBefore);
|
||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, session, notBefore);
|
||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||
logger.infov("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
|
||||
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
|
||||
|
@ -209,7 +209,7 @@ public class ResourceAdminManager {
|
|||
response.releaseConnection();
|
||||
}
|
||||
} else {
|
||||
logger.info("Can't logout" + resource.getName() + " no mgmt url.");
|
||||
logger.info("Can't logout " + resource.getName() + " no mgmt url.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
|
@ -70,18 +71,23 @@ public class TokenManager {
|
|||
|
||||
|
||||
|
||||
public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, ClientModel client, UserModel user) {
|
||||
AccessCodeEntry code = createAccessCodeEntry(scopeParam, state, redirect, realm, client, user);
|
||||
public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
AccessCodeEntry code = createAccessCodeEntry(scopeParam, state, redirect, realm, client, user, session);
|
||||
accessCodeMap.put(code.getId(), code);
|
||||
return code;
|
||||
}
|
||||
|
||||
private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, RealmModel realm, ClientModel client, UserModel user) {
|
||||
private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
AccessCodeEntry code = new AccessCodeEntry();
|
||||
if (session != null) {
|
||||
code.setSessionState(session.getId());
|
||||
}
|
||||
|
||||
List<RoleModel> realmRolesRequested = code.getRealmRolesRequested();
|
||||
MultivaluedMap<String, RoleModel> resourceRolesRequested = code.getResourceRolesRequested();
|
||||
|
||||
AccessToken token = createClientAccessToken(scopeParam, realm, client, user, realmRolesRequested, resourceRolesRequested);
|
||||
AccessToken token = createClientAccessToken(scopeParam, realm, client, user, session, realmRolesRequested, resourceRolesRequested);
|
||||
token.setSessionState(code.getSessionState());
|
||||
|
||||
code.setToken(token);
|
||||
code.setRealm(realm);
|
||||
|
@ -119,7 +125,7 @@ public class TokenManager {
|
|||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
|
||||
}
|
||||
|
||||
audit.user(refreshToken.getSubject()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
|
||||
audit.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
|
||||
|
||||
UserModel user = realm.getUserById(refreshToken.getSubject());
|
||||
if (user == null) {
|
||||
|
@ -128,12 +134,15 @@ public class TokenManager {
|
|||
|
||||
if (!user.isEnabled()) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User disabled", "User disabled");
|
||||
}
|
||||
|
||||
UserSessionModel session = realm.getUserSession(refreshToken.getSessionState());
|
||||
if (session == null || session.getExpires() < Time.currentTime()) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
|
||||
}
|
||||
|
||||
if (!client.getClientId().equals(refreshToken.getIssuedFor())) {
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Unmatching clients", "Unmatching clients");
|
||||
|
||||
}
|
||||
|
||||
if (refreshToken.getIssuedAt() < client.getNotBefore() || refreshToken.getIssuedAt() < user.getNotBefore()) {
|
||||
|
@ -179,17 +188,17 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
AccessToken accessToken = initToken(realm, client, user);
|
||||
AccessToken accessToken = initToken(realm, client, user, session);
|
||||
accessToken.setRealmAccess(refreshToken.getRealmAccess());
|
||||
accessToken.setResourceAccess(refreshToken.getResourceAccess());
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user) {
|
||||
return createClientAccessToken(scopeParam, realm, client, user, new LinkedList<RoleModel>(), new MultivaluedMapImpl<String, RoleModel>());
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
return createClientAccessToken(scopeParam, realm, client, user, session, new LinkedList<RoleModel>(), new MultivaluedMapImpl<String, RoleModel>());
|
||||
}
|
||||
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
|
||||
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
|
||||
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
|
||||
|
||||
Set<RoleModel> roleMappings = realm.getRoleMappings(user);
|
||||
|
@ -217,7 +226,7 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
AccessToken token = initToken(realm, client, user);
|
||||
AccessToken token = initToken(realm, client, user, session);
|
||||
|
||||
if (realmRolesRequested.size() > 0) {
|
||||
for (RoleModel role : realmRolesRequested) {
|
||||
|
@ -270,7 +279,7 @@ public class TokenManager {
|
|||
|
||||
|
||||
|
||||
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user) {
|
||||
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
AccessToken token = new AccessToken();
|
||||
token.id(KeycloakModelUtils.generateId());
|
||||
token.subject(user.getId());
|
||||
|
@ -278,6 +287,9 @@ public class TokenManager {
|
|||
token.issuedNow();
|
||||
token.issuedFor(client.getClientId());
|
||||
token.issuer(realm.getName());
|
||||
if (session != null) {
|
||||
token.setSessionState(session.getId());
|
||||
}
|
||||
if (realm.getAccessTokenLifespan() > 0) {
|
||||
token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
|
||||
}
|
||||
|
@ -351,8 +363,8 @@ public class TokenManager {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user) {
|
||||
accessToken = createClientAccessToken(scopeParam, realm, client, user);
|
||||
public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user, UserSessionModel session) {
|
||||
accessToken = createClientAccessToken(scopeParam, realm, client, user, session);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,9 @@ import org.keycloak.services.managers.SocialRequestManager;
|
|||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.models.utils.ModelProviderUtils;
|
||||
import org.keycloak.services.scheduled.ClearExpiredAuditEvents;
|
||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||
import org.keycloak.services.scheduled.ScheduledTaskRunner;
|
||||
import org.keycloak.timer.TimerProvider;
|
||||
import org.keycloak.timer.TimerProviderFactory;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -155,32 +158,8 @@ public class KeycloakApplication extends Application {
|
|||
return;
|
||||
}
|
||||
TimerProvider timer = timerFactory.create(null);
|
||||
|
||||
final ProviderFactory<AuditProvider> auditFactory = providerSessionFactory.getProviderFactory(AuditProvider.class);
|
||||
if (auditFactory != null) {
|
||||
timer.schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
KeycloakSession keycloakSession = keycloakSessionFactory.createSession();
|
||||
ProviderSession providerSession = providerSessionFactory.createSession();
|
||||
AuditProvider audit = providerSession.getProvider(AuditProvider.class);
|
||||
try {
|
||||
for (RealmModel realm : keycloakSession.getRealms()) {
|
||||
if (realm.isAuditEnabled() && realm.getAuditExpiration() > 0) {
|
||||
long olderThan = System.currentTimeMillis() - realm.getAuditExpiration() * 1000;
|
||||
log.info("Expiring audit events for " + realm.getName() + " older than " + new Date(olderThan));
|
||||
audit.clear(realm.getId(), olderThan);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
keycloakSession.close();
|
||||
audit.close();
|
||||
}
|
||||
}
|
||||
}, Config.getAuditExpirationSchedule());
|
||||
} else {
|
||||
log.info("Not scheduling audit expiration, no audit provider found");
|
||||
}
|
||||
timer.schedule(new ScheduledTaskRunner(keycloakSessionFactory, providerSessionFactory, new ClearExpiredAuditEvents()), Config.getAuditExpirationSchedule());
|
||||
timer.schedule(new ScheduledTaskRunner(keycloakSessionFactory, providerSessionFactory, new ClearExpiredUserSessions()), Config.getUserExpirationSchedule());
|
||||
}
|
||||
|
||||
public KeycloakSessionFactory getFactory() {
|
||||
|
@ -204,7 +183,6 @@ public class KeycloakApplication extends Application {
|
|||
public void importRealms(ServletContext context) {
|
||||
importRealmFile();
|
||||
importRealmResources(context);
|
||||
|
||||
}
|
||||
|
||||
public void importRealmResources(ServletContext context) {
|
||||
|
|
|
@ -36,9 +36,11 @@ import org.keycloak.models.RealmModel;
|
|||
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.TimeBasedOTP;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.ClientConnection;
|
||||
import org.keycloak.services.email.EmailException;
|
||||
import org.keycloak.services.email.EmailSender;
|
||||
import org.keycloak.services.managers.AccessCodeEntry;
|
||||
|
@ -62,7 +64,9 @@ import javax.ws.rs.core.MultivaluedMap;
|
|||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -82,6 +86,9 @@ public class RequiredActionsService {
|
|||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
protected Providers providers;
|
||||
|
||||
|
@ -317,7 +324,10 @@ public class RequiredActionsService {
|
|||
Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
|
||||
requiredActions.add(RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
|
||||
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
|
||||
audit.session(session);
|
||||
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user, session);
|
||||
accessCode.setRequiredActions(requiredActions);
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
accessCode.setAuthMethod("form");
|
||||
|
@ -395,18 +405,25 @@ public class RequiredActionsService {
|
|||
logger.debugv("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
|
||||
audit.success();
|
||||
|
||||
AuthenticationManager authManager = new AuthenticationManager(providerSession);
|
||||
|
||||
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
||||
if (session == null || session.getExpires() < Time.currentTime()) {
|
||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectError(accessCode.getClient(), "access_denied", accessCode.getState(), accessCode.getRedirectUri());
|
||||
}
|
||||
audit.session(session);
|
||||
|
||||
audit.success();
|
||||
|
||||
return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
|
||||
accessCode.getState(), accessCode.getRedirectUri());
|
||||
session, accessCode.getState(), accessCode.getRedirectUri());
|
||||
}
|
||||
}
|
||||
|
||||
private void initAudit(AccessCodeEntry accessCode) {
|
||||
audit.event(Events.LOGIN).client(accessCode.getClient())
|
||||
.user(accessCode.getUser())
|
||||
.session(accessCode.getSessionState())
|
||||
.detail(Details.CODE_ID, accessCode.getId())
|
||||
.detail(Details.REDIRECT_URI, accessCode.getRedirectUri())
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.SocialLinkModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.ClientConnection;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
@ -254,7 +255,10 @@ public class SocialResource {
|
|||
return oauth.forwardToSecurityFailure("Your account is not enabled.");
|
||||
}
|
||||
|
||||
return oauth.processAccessCode(scope, state, redirectUri, client, user, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social@" + provider.getId(), audit);
|
||||
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
|
||||
audit.session(session);
|
||||
|
||||
return oauth.processAccessCode(scope, state, redirectUri, client, user, session, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social@" + provider.getId(), audit);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
|
|
@ -26,6 +26,7 @@ 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.provider.ProviderSession;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -210,6 +211,16 @@ public class TokenService {
|
|||
|
||||
audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
|
||||
|
||||
String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
if (username == null) {
|
||||
audit.error(Errors.USERNAME_MISSING);
|
||||
throw new UnauthorizedException("No username");
|
||||
}
|
||||
audit.detail(Details.USERNAME, username);
|
||||
|
||||
UserModel user = realm.getUser(username);
|
||||
audit.user(user);
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, form, audit);
|
||||
|
||||
if (client.isPublicClient()) {
|
||||
|
@ -218,38 +229,49 @@ public class TokenService {
|
|||
throw new ForbiddenException("Public clients are not allowed to invoke grants/access");
|
||||
}
|
||||
|
||||
String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||
if (username == null) {
|
||||
audit.error(Errors.USERNAME_MISSING);
|
||||
throw new UnauthorizedException("No username");
|
||||
}
|
||||
audit.detail(Details.USERNAME, username);
|
||||
if (!realm.isEnabled()) {
|
||||
audit.error(Errors.REALM_DISABLED);
|
||||
throw new UnauthorizedException("Disabled realm");
|
||||
}
|
||||
|
||||
AuthenticationStatus authenticationStatus = authManager.authenticateForm(clientConnection, realm, form);
|
||||
Map<String, String> err;
|
||||
|
||||
switch (authenticationStatus) {
|
||||
case SUCCESS:
|
||||
break;
|
||||
case ACCOUNT_TEMPORARILY_DISABLED:
|
||||
case ACTIONS_REQUIRED:
|
||||
err = new HashMap<String, String>();
|
||||
err.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Account temporarily disabled");
|
||||
audit.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
return Response.status(503).type(MediaType.TEXT_PLAIN).entity("Account temporarily disabled").build();
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
|
||||
.build();
|
||||
case ACCOUNT_DISABLED:
|
||||
return Response.status(403).type(MediaType.TEXT_PLAIN).entity("Account disabled").build();
|
||||
err = new HashMap<String, String>();
|
||||
err.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Account disabled");
|
||||
audit.error(Errors.USER_DISABLED);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
|
||||
.build();
|
||||
default:
|
||||
err = new HashMap<String, String>();
|
||||
err.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
err.put(OAuth2Constants.ERROR_DESCRIPTION, "Invalid user credentials");
|
||||
audit.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
throw new UnauthorizedException("Auth failed");
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(err)
|
||||
.build();
|
||||
}
|
||||
|
||||
UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
|
||||
String scope = form.getFirst(OAuth2Constants.SCOPE);
|
||||
|
||||
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
|
||||
audit.session(session);
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
.generateAccessToken(scope, client, user)
|
||||
.generateAccessToken(scope, client, user, session)
|
||||
.generateRefreshToken()
|
||||
.generateIDToken()
|
||||
.build();
|
||||
|
||||
|
@ -362,8 +384,12 @@ public class TokenService {
|
|||
case SUCCESS:
|
||||
case ACTIONS_REQUIRED:
|
||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||
audit.user(user);
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, username, remember, "form", audit);
|
||||
audit.user(user);
|
||||
|
||||
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
|
||||
audit.session(session);
|
||||
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, session, username, remember, "form", audit);
|
||||
case ACCOUNT_TEMPORARILY_DISABLED:
|
||||
audit.error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
return Flows.forms(realm, uriInfo).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).setFormData(formData).createLogin();
|
||||
|
@ -561,6 +587,7 @@ public class TokenService {
|
|||
}
|
||||
|
||||
audit.user(accessCode.getUser());
|
||||
audit.session(accessCode.getSessionState());
|
||||
|
||||
ClientModel client = authorizeClient(authorizationHeader, formData, audit);
|
||||
|
||||
|
@ -589,6 +616,35 @@ public class TokenService {
|
|||
.build();
|
||||
}
|
||||
|
||||
UserModel user = realm.getUserById(accessCode.getUser().getId());
|
||||
if (user == null) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "User not found");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (!user.isEnabled()) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "User disabled");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
UserSessionModel session = realm.getUserSession(accessCode.getSessionState());
|
||||
if (session == null || session.getExpires() < Time.currentTime()) {
|
||||
Map<String, String> res = new HashMap<String, String>();
|
||||
res.put(OAuth2Constants.ERROR, "invalid_grant");
|
||||
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Session not active");
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
|
||||
.build();
|
||||
}
|
||||
|
||||
logger.debug("accessRequest SUCCESS");
|
||||
|
||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
|
||||
|
@ -693,11 +749,14 @@ public class TokenService {
|
|||
}
|
||||
|
||||
logger.info("Checking cookie...");
|
||||
UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
|
||||
if (user != null) {
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
|
||||
if (authResult != null) {
|
||||
UserModel user = authResult.getUser();
|
||||
UserSessionModel session = authResult.getSession();
|
||||
|
||||
logger.debug(user.getLoginName() + " already logged in.");
|
||||
audit.user(user).detail(Details.AUTH_METHOD, "sso");
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, null, false, "sso", audit);
|
||||
audit.user(user).session(session).detail(Details.AUTH_METHOD, "sso");
|
||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, session, null, false, "sso", audit);
|
||||
}
|
||||
|
||||
if (prompt != null && prompt.equals("none")) {
|
||||
|
@ -760,25 +819,52 @@ public class TokenService {
|
|||
@Path("logout")
|
||||
@GET
|
||||
@NoCache
|
||||
public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
|
||||
public Response logout(final @QueryParam("session_state") String sessionState, final @QueryParam("redirect_uri") String redirectUri) {
|
||||
// todo do we care if anybody can trigger this?
|
||||
|
||||
audit.event(Events.LOGOUT).detail(Details.REDIRECT_URI, redirectUri);
|
||||
audit.event(Events.LOGOUT);
|
||||
if (redirectUri != null) {
|
||||
audit.detail(Details.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
if (sessionState != null) {
|
||||
audit.session(sessionState);
|
||||
}
|
||||
|
||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||
UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers, false);
|
||||
if (user != null) {
|
||||
logger.infov("Logging out: {0}", user.getLoginName());
|
||||
authManager.expireIdentityCookie(realm, uriInfo);
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
resourceAdminManager.logoutUser(uriInfo.getRequestUri(), realm, user);
|
||||
|
||||
audit.user(user).success();
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(realm, uriInfo, headers, false);
|
||||
if (authResult != null) {
|
||||
logout(authResult.getSession());
|
||||
} else if (sessionState != null) {
|
||||
UserSessionModel userSession = realm.getUserSession(sessionState);
|
||||
if (userSession != null) {
|
||||
logout(userSession);
|
||||
} else {
|
||||
audit.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
logger.info("No user logged in for logout");
|
||||
audit.error(Errors.USER_NOT_LOGGED_IN);
|
||||
}
|
||||
// todo manage legal redirects
|
||||
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||
|
||||
if (redirectUri != null) {
|
||||
// todo manage legal redirects
|
||||
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||
} else {
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
private void logout(UserSessionModel session) {
|
||||
UserModel user = session.getUser();
|
||||
|
||||
logger.infov("Logging out: {0} ({1})", user.getLoginName(), session.getId());
|
||||
|
||||
realm.removeUserSession(session);
|
||||
authManager.expireIdentityCookie(realm, uriInfo);
|
||||
authManager.expireRememberMeCookie(realm, uriInfo);
|
||||
|
||||
resourceAdminManager.logoutUser(uriInfo.getRequestUri(), realm, user.getId(), session.getId());
|
||||
|
||||
audit.user(user).session(session).success();
|
||||
}
|
||||
|
||||
@Path("oauth/grant")
|
||||
|
@ -828,6 +914,13 @@ public class TokenService {
|
|||
audit.detail(Details.REMEMBER_ME, "true");
|
||||
}
|
||||
|
||||
UserSessionModel session = realm.getUserSession(accessCodeEntry.getSessionState());
|
||||
if (session == null || session.getExpires() < Time.currentTime()) {
|
||||
audit.error(Errors.INVALID_CODE);
|
||||
return oauth.forwardToSecurityFailure("Session not active");
|
||||
}
|
||||
audit.session(session);
|
||||
|
||||
if (formData.containsKey("cancel")) {
|
||||
audit.error(Errors.REJECTED_BY_USER);
|
||||
return redirectAccessDenied(redirect, state);
|
||||
|
@ -836,7 +929,7 @@ public class TokenService {
|
|||
audit.success();
|
||||
|
||||
accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
|
||||
return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
|
||||
return oauth.redirectAccessCode(accessCodeEntry, session, state, redirect);
|
||||
}
|
||||
|
||||
protected Response redirectAccessDenied(String redirect, String state) {
|
||||
|
|
|
@ -234,7 +234,7 @@ public class ApplicationResource {
|
|||
@POST
|
||||
public void logoutAll() {
|
||||
auth.requireManage();
|
||||
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null);
|
||||
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null, null);
|
||||
}
|
||||
|
||||
@Path("logout-user/{username}")
|
||||
|
@ -245,7 +245,7 @@ public class ApplicationResource {
|
|||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, user.getId());
|
||||
new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, user.getId(), null);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ public class UsersResource {
|
|||
}
|
||||
// set notBefore so that user will be forced to log in.
|
||||
user.setNotBefore(Time.currentTime());
|
||||
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user);
|
||||
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), null);
|
||||
}
|
||||
|
||||
|
||||
|
@ -540,7 +540,7 @@ public class UsersResource {
|
|||
Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>(user.getRequiredActions());
|
||||
requiredActions.add(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user);
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, realm, client, user, null);
|
||||
accessCode.setRequiredActions(requiredActions);
|
||||
accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RequiredCredentialModel;
|
||||
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.AccessCodeEntry;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
@ -74,12 +75,12 @@ public class OAuthFlows {
|
|||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
|
||||
return redirectAccessCode(accessCode, state, redirect, false);
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect) {
|
||||
return redirectAccessCode(accessCode, session, state, redirect, false);
|
||||
}
|
||||
|
||||
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect, boolean rememberMe) {
|
||||
public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel session, String state, String redirect, boolean rememberMe) {
|
||||
String code = accessCode.getCode();
|
||||
|
||||
if (Constants.INSTALLED_APP_URN.equals(redirect)) {
|
||||
|
@ -92,7 +93,7 @@ public class OAuthFlows {
|
|||
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
|
||||
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||
rememberMe = rememberMe || remember != null;
|
||||
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo, rememberMe));
|
||||
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), session, uriInfo, rememberMe));
|
||||
return location.build();
|
||||
}
|
||||
}
|
||||
|
@ -109,17 +110,12 @@ public class OAuthFlows {
|
|||
}
|
||||
}
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, Audit audit) {
|
||||
return processAccessCode(scopeParam, state, redirect, client, user, null, false, "form", audit);
|
||||
}
|
||||
|
||||
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, String username, boolean rememberMe, String authMethod, Audit audit) {
|
||||
public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, String username, boolean rememberMe, String authMethod, Audit audit) {
|
||||
isTotpConfigurationRequired(user);
|
||||
isEmailVerificationRequired(user);
|
||||
|
||||
boolean isResource = client instanceof ApplicationModel;
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
|
||||
AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user, session);
|
||||
accessCode.setUsername(username);
|
||||
accessCode.setRememberMe(rememberMe);
|
||||
accessCode.setAuthMethod(authMethod);
|
||||
|
@ -155,7 +151,7 @@ public class OAuthFlows {
|
|||
|
||||
if (redirect != null) {
|
||||
audit.success();
|
||||
return redirectAccessCode(accessCode, state, redirect, rememberMe);
|
||||
return redirectAccessCode(accessCode, session, state, redirect, rememberMe);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.services.scheduled;
|
||||
|
||||
import org.keycloak.audit.AuditProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClearExpiredAuditEvents implements ScheduledTask {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession keycloakSession, ProviderSession providerSession) {
|
||||
AuditProvider audit = providerSession.getProvider(AuditProvider.class);
|
||||
if (audit != null) {
|
||||
for (RealmModel realm : keycloakSession.getRealms()) {
|
||||
if (realm.isAuditEnabled() && realm.getAuditExpiration() > 0) {
|
||||
long olderThan = System.currentTimeMillis() - realm.getAuditExpiration() * 1000;
|
||||
audit.clear(realm.getId(), olderThan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.keycloak.services.scheduled;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClearExpiredUserSessions implements ScheduledTask {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession keycloakSession, ProviderSession providerSession) {
|
||||
for (RealmModel realm : keycloakSession.getRealms()) {
|
||||
realm.removeExpiredUserSessions();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.keycloak.services.scheduled;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface ScheduledTask {
|
||||
|
||||
public void run(KeycloakSession keycloakSession, ProviderSession providerSession);
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.keycloak.services.scheduled;
|
||||
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.provider.ProviderSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ScheduledTaskRunner implements Runnable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ScheduledTaskRunner.class);
|
||||
|
||||
private final KeycloakSessionFactory keycloakSessionFactory;
|
||||
private final ProviderSessionFactory providerSessionFactory;
|
||||
private final ScheduledTask task;
|
||||
|
||||
public ScheduledTaskRunner(KeycloakSessionFactory keycloakSessionFactory, ProviderSessionFactory providerSessionFactory, ScheduledTask task) {
|
||||
this.keycloakSessionFactory = keycloakSessionFactory;
|
||||
this.providerSessionFactory = providerSessionFactory;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
KeycloakSession keycloakSession = keycloakSessionFactory.createSession();
|
||||
ProviderSession providerSession = providerSessionFactory.createSession();
|
||||
try {
|
||||
keycloakSession.getTransaction().begin();
|
||||
task.run(keycloakSession, providerSession);
|
||||
keycloakSession.getTransaction().commit();
|
||||
|
||||
logger.debug("Executed scheduled task " + task.getClass().getSimpleName());
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to run scheduled task " + task.getClass().getSimpleName(), t);
|
||||
|
||||
keycloakSession.getTransaction().rollback();
|
||||
} finally {
|
||||
try {
|
||||
keycloakSession.close();
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to close KeycloakSession", t);
|
||||
}
|
||||
try {
|
||||
providerSession.close();
|
||||
} catch (Throwable t) {
|
||||
logger.error("Failed to close ProviderSession", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.keycloak.audit.Event;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -115,7 +116,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
}
|
||||
|
||||
public ExpectedEvent expectRequiredAction(String event) {
|
||||
return expectLogin().event(event);
|
||||
return expectLogin().event(event).session(isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectLogin() {
|
||||
|
@ -124,26 +125,30 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
.detail(Details.USERNAME, DEFAULT_USERNAME)
|
||||
.detail(Details.RESPONSE_TYPE, "code")
|
||||
.detail(Details.AUTH_METHOD, "form")
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
|
||||
.session(isUUID());
|
||||
}
|
||||
|
||||
public ExpectedEvent expectCodeToToken(String codeId) {
|
||||
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
|
||||
return expect("code_to_token")
|
||||
.detail(Details.CODE_ID, codeId)
|
||||
.detail(Details.TOKEN_ID, isUUID())
|
||||
.detail(Details.REFRESH_TOKEN_ID, isUUID());
|
||||
.detail(Details.REFRESH_TOKEN_ID, isUUID())
|
||||
.session(sessionId);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectRefresh(String refreshTokenId) {
|
||||
public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) {
|
||||
return expect("refresh_token")
|
||||
.detail(Details.TOKEN_ID, isUUID())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
|
||||
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID());
|
||||
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID())
|
||||
.session(sessionId);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectLogout() {
|
||||
public ExpectedEvent expectLogout(String sessionId) {
|
||||
return expect("logout").client((String) null)
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
|
||||
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
|
||||
.session(sessionId);
|
||||
}
|
||||
|
||||
public ExpectedEvent expectRegister(String username, String email) {
|
||||
|
@ -162,7 +167,13 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
}
|
||||
|
||||
public ExpectedEvent expect(String event) {
|
||||
return new ExpectedEvent().realm(DEFAULT_REALM).client(DEFAULT_CLIENT_ID).user(keycloak.getUser(DEFAULT_REALM, DEFAULT_USERNAME).getId()).ipAddress(DEFAULT_IP_ADDRESS).event(event);
|
||||
return new ExpectedEvent()
|
||||
.realm(DEFAULT_REALM)
|
||||
.client(DEFAULT_CLIENT_ID)
|
||||
.user(keycloak.getUser(DEFAULT_REALM, DEFAULT_USERNAME).getId())
|
||||
.ipAddress(DEFAULT_IP_ADDRESS)
|
||||
.session((String) null)
|
||||
.event(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -193,6 +204,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
public static class ExpectedEvent {
|
||||
private Event expected = new Event();
|
||||
private Matcher<String> userId;
|
||||
private Matcher<String> sessionId;
|
||||
private HashMap<String, Matcher<String>> details;
|
||||
|
||||
public ExpectedEvent realm(RealmModel realm) {
|
||||
|
@ -216,7 +228,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
}
|
||||
|
||||
public ExpectedEvent user(UserModel user) {
|
||||
return user(CoreMatchers.equalTo(user.getId()));
|
||||
return user(user.getId());
|
||||
}
|
||||
|
||||
public ExpectedEvent user(String userId) {
|
||||
|
@ -228,6 +240,19 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent session(UserSessionModel session) {
|
||||
return session(session.getId());
|
||||
}
|
||||
|
||||
public ExpectedEvent session(String sessionId) {
|
||||
return session(CoreMatchers.equalTo(sessionId));
|
||||
}
|
||||
|
||||
public ExpectedEvent session(Matcher<String> sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExpectedEvent ipAddress(String ipAddress) {
|
||||
expected.setIpAddress(ipAddress);
|
||||
return this;
|
||||
|
@ -277,8 +302,9 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
Assert.assertEquals(expected.getError(), actual.getError());
|
||||
Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress());
|
||||
Assert.assertThat(actual.getUserId(), userId);
|
||||
Assert.assertThat(actual.getSessionId(), sessionId);
|
||||
|
||||
if (details == null) {
|
||||
if (details == null || details.isEmpty()) {
|
||||
Assert.assertNull(actual.getDetails());
|
||||
} else {
|
||||
Assert.assertNotNull(actual.getDetails());
|
||||
|
@ -288,9 +314,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
|
|||
Assert.fail(d.getKey() + " missing");
|
||||
}
|
||||
|
||||
if (!d.getValue().matches(actualValue)) {
|
||||
Assert.fail(d.getKey() + " doesn't match");
|
||||
}
|
||||
Assert.assertThat("Unexpected value for " + d.getKey(), actualValue, d.getValue());
|
||||
}
|
||||
|
||||
for (String k : actual.getDetails().keySet()) {
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
*/
|
||||
package org.keycloak.testsuite;
|
||||
|
||||
import net.iharder.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
|
@ -36,9 +38,12 @@ import org.junit.Assert;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.services.resources.TokenService;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
@ -46,6 +51,7 @@ import org.openqa.selenium.By;
|
|||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -144,6 +150,35 @@ public class OAuthClient {
|
|||
}
|
||||
}
|
||||
|
||||
public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password) throws Exception {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl());
|
||||
|
||||
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
|
||||
post.setHeader("Authorization", authorization);
|
||||
|
||||
List<NameValuePair> parameters = new LinkedList<NameValuePair>();
|
||||
parameters.add(new BasicNameValuePair("username", username));
|
||||
parameters.add(new BasicNameValuePair("password", password));
|
||||
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
post.setEntity(formEntity);
|
||||
|
||||
return new AccessTokenResponse(client.execute(post));
|
||||
}
|
||||
|
||||
public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
|
||||
|
||||
return client.execute(get);
|
||||
}
|
||||
|
||||
public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(getRefreshTokenUrl());
|
||||
|
@ -163,7 +198,7 @@ public class OAuthClient {
|
|||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
|
||||
}
|
||||
|
||||
UrlEncodedFormEntity formEntity = null;
|
||||
UrlEncodedFormEntity formEntity;
|
||||
try {
|
||||
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
@ -267,6 +302,22 @@ public class OAuthClient {
|
|||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getLogoutUrl(String redirectUri, String sessionState) {
|
||||
UriBuilder b = TokenService.logoutUrl(UriBuilder.fromUri(baseUrl));
|
||||
if (redirectUri != null) {
|
||||
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
|
||||
}
|
||||
if (sessionState != null) {
|
||||
b.queryParam("session_state", sessionState);
|
||||
}
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getResourceOwnerPasswordCredentialGrantUrl() {
|
||||
UriBuilder b = TokenService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl));
|
||||
return b.build(realm).toString();
|
||||
}
|
||||
|
||||
public String getRefreshTokenUrl() {
|
||||
UriBuilder b = TokenService.refreshUrl(UriBuilder.fromUri(baseUrl));
|
||||
return b.build(realm).toString();
|
||||
|
|
|
@ -194,7 +194,7 @@ public class AccountTest {
|
|||
changePasswordPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
|
||||
String sessionId = events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent().getSessionId();
|
||||
|
||||
changePasswordPage.changePassword("", "new-password", "new-password");
|
||||
|
||||
|
@ -212,14 +212,14 @@ public class AccountTest {
|
|||
|
||||
changePasswordPage.logout();
|
||||
|
||||
events.expectLogout().detail(Details.REDIRECT_URI, ACCOUNT_URL).assertEvent();
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, ACCOUNT_URL).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectLogin().user((String) null).session((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "new-password");
|
||||
|
|
|
@ -121,6 +121,7 @@ public class RequiredActionEmailVerificationTest {
|
|||
String verificationUrl = m.group(1);
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
|
||||
String sessionId = sendEvent.getSessionId();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
|
@ -128,11 +129,11 @@ public class RequiredActionEmailVerificationTest {
|
|||
|
||||
driver.navigate().to(verificationUrl.trim());
|
||||
|
||||
events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
events.expectRequiredAction("verify_email").session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
events.expectLogin().session(sessionId).detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -156,6 +157,7 @@ public class RequiredActionEmailVerificationTest {
|
|||
m.matches();
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").assertEvent();
|
||||
String sessionId = sendEvent.getSessionId();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
|
@ -165,9 +167,9 @@ public class RequiredActionEmailVerificationTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectRequiredAction("verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
events.expectRequiredAction("verify_email").user(userId).session(sessionId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
events.expectLogin().user(userId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
events.expectLogin().user(userId).session(sessionId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -180,6 +182,7 @@ public class RequiredActionEmailVerificationTest {
|
|||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
|
||||
String sessionId = sendEvent.getSessionId();
|
||||
|
||||
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
|
@ -195,7 +198,7 @@ public class RequiredActionEmailVerificationTest {
|
|||
Matcher m = p.matcher(body);
|
||||
m.matches();
|
||||
|
||||
events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent(sendEvent);
|
||||
events.expectRequiredAction("send_verify_email").session(sessionId).detail("email", "test-user@localhost").assertEvent(sendEvent);
|
||||
|
||||
String verificationUrl = m.group(1);
|
||||
|
||||
|
@ -203,9 +206,9 @@ public class RequiredActionEmailVerificationTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
events.expectRequiredAction("verify_email").session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
events.expectLogin().session(sessionId).assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,36 +89,46 @@ public class RequiredActionMultipleActionsTest {
|
|||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
String sessionId = null;
|
||||
if (changePasswordPage.isCurrent()) {
|
||||
updatePassword();
|
||||
sessionId = updatePassword(sessionId);
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
updateProfile();
|
||||
updateProfile(sessionId);
|
||||
} else if (updateProfilePage.isCurrent()) {
|
||||
updateProfile();
|
||||
sessionId = updateProfile(sessionId);
|
||||
|
||||
changePasswordPage.assertCurrent();
|
||||
updatePassword();
|
||||
updatePassword(sessionId);
|
||||
} else {
|
||||
Assert.fail("Expected to update password and profile before login");
|
||||
}
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
events.expectLogin().session(sessionId).assertEvent();
|
||||
}
|
||||
|
||||
public void updatePassword() {
|
||||
public String updatePassword(String sessionId) {
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
|
||||
events.expectRequiredAction("update_password").assertEvent();
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction("update_password");
|
||||
if (sessionId != null) {
|
||||
expectedEvent.session(sessionId);
|
||||
}
|
||||
return expectedEvent.assertEvent().getSessionId();
|
||||
}
|
||||
|
||||
public void updateProfile() {
|
||||
public String updateProfile(String sessionId) {
|
||||
updateProfilePage.update("New first", "New last", "new@email.com");
|
||||
|
||||
events.expectRequiredAction("update_profile").assertEvent();
|
||||
events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction("update_profile");
|
||||
if (sessionId != null) {
|
||||
expectedEvent.session(sessionId);
|
||||
}
|
||||
sessionId = expectedEvent.assertEvent().getSessionId();
|
||||
events.expectRequiredAction("update_email").session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
|
@ -92,15 +93,15 @@ public class RequiredActionResetPasswordTest {
|
|||
changePasswordPage.assertCurrent();
|
||||
changePasswordPage.changePassword("new-password", "new-password");
|
||||
|
||||
events.expectRequiredAction("update_password").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("update_password").assertEvent().getSessionId();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
Event loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "new-password");
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -108,11 +109,11 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp").assertEvent().getSessionId();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
|
||||
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -126,15 +127,15 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
totpPage.configure(totp.generate(totpSecret));
|
||||
|
||||
events.expectRequiredAction("update_totp").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("update_totp").assertEvent().getSessionId();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
Event loginEvent = events.expectLogin().session(sessionId).assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
@ -165,11 +166,11 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).user(userId).assertEvent();
|
||||
|
||||
// Try to login after logout
|
||||
loginPage.open();
|
||||
|
@ -182,7 +183,7 @@ public class RequiredActionTotpSetupTest {
|
|||
// Login with one-time password
|
||||
loginTotpPage.login(totp.generate(totpCode));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
|
||||
// Open account page
|
||||
accountTotpPage.open();
|
||||
|
@ -195,7 +196,7 @@ public class RequiredActionTotpSetupTest {
|
|||
|
||||
// Logout
|
||||
oauth.openLogout();
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).user(userId).assertEvent();
|
||||
|
||||
// Try to login
|
||||
loginPage.open();
|
||||
|
@ -205,11 +206,11 @@ public class RequiredActionTotpSetupTest {
|
|||
totpPage.assertCurrent();
|
||||
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
|
||||
|
||||
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setupTotp2").assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,12 +87,12 @@ public class RequiredActionUpdateProfileTest {
|
|||
|
||||
updateProfilePage.update("New first", "New last", "new@email.com");
|
||||
|
||||
events.expectRequiredAction("update_profile").assertEvent();
|
||||
events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("update_profile").assertEvent().getSessionId();
|
||||
events.expectRequiredAction("update_email").session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
events.expectLogin().session(sessionId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.ApplicationModel;
|
|||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.action.SessionStats;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -83,7 +84,8 @@ public class AdapterTest {
|
|||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = adminRealm.getUser("admin");
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin);
|
||||
UserSessionModel session = adminRealm.createUserSession(admin, null);
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, session);
|
||||
adminToken = tm.encodeToken(adminRealm, token);
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.models.ApplicationModel;
|
|||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.action.SessionStats;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -85,7 +86,8 @@ public class RelativeUriAdapterTest {
|
|||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||
TokenManager tm = new TokenManager();
|
||||
UserModel admin = adminRealm.getUser("admin");
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin);
|
||||
UserSessionModel session = adminRealm.createUserSession(admin, null);
|
||||
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, session);
|
||||
adminToken = tm.encodeToken(adminRealm, token);
|
||||
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectLogin().user((String) null).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -106,7 +106,7 @@ public class LoginTest {
|
|||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().user((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -139,7 +139,7 @@ public class LoginTest {
|
|||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ public class LoginTotpTest {
|
|||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).user((String) null).assertEvent();
|
||||
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).user((String) null).session((String) null).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source.
|
||||
* Copyright 2012, Red Hat, Inc., and individual contributors
|
||||
* as indicated by the @author tags. See the copyright.txt file in the
|
||||
* distribution for a full listing of individual contributors.
|
||||
*
|
||||
* This is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2.1 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this software; if not, write to the Free
|
||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||
*/
|
||||
package org.keycloak.testsuite.forms;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.Cookie;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LogoutTest {
|
||||
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected AppPage appPage;
|
||||
|
||||
@WebResource
|
||||
protected LoginPage loginPage;
|
||||
|
||||
@Test
|
||||
public void logoutRedirect() {
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
String redirectUri = AppPage.baseUrl + "?logout";
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl(redirectUri, null);
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
|
||||
|
||||
assertEquals(redirectUri, driver.getCurrentUrl());
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId2 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutSession() {
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
String logoutUrl = oauth.getLogoutUrl(null, sessionId);
|
||||
driver.navigate().to(logoutUrl);
|
||||
|
||||
events.expectLogout(sessionId).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||
|
||||
assertEquals(logoutUrl, driver.getCurrentUrl());
|
||||
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId2 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logoutMultipleSessions() throws IOException {
|
||||
// Login session 1
|
||||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
assertTrue(appPage.isCurrent());
|
||||
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
// Login session 2
|
||||
WebDriver driver2 = WebRule.createWebDriver();
|
||||
|
||||
OAuthClient oauth2 = new OAuthClient(driver2);
|
||||
oauth2.doLogin("test-user@localhost", "password");
|
||||
|
||||
String sessionId2 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId2);
|
||||
|
||||
// Check session 1 logged-in
|
||||
oauth.openLoginForm();
|
||||
events.expectLogin().session(sessionId).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Check session 2 logged-in
|
||||
oauth2.openLoginForm();
|
||||
events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Logout session 1 by redirect
|
||||
driver.navigate().to(oauth.getLogoutUrl(AppPage.baseUrl, null));
|
||||
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AppPage.baseUrl).assertEvent();
|
||||
|
||||
// Check session 2 logged-in
|
||||
oauth2.openLoginForm();
|
||||
events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Check session 1 not logged-in
|
||||
oauth.openLoginForm();
|
||||
assertEquals(oauth.getLoginFormUrl(), driver.getCurrentUrl());
|
||||
|
||||
// Login session 3
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String sessionId3 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId3);
|
||||
assertNotEquals(sessionId2, sessionId3);
|
||||
|
||||
// Logout session 2 by session_state
|
||||
oauth2.doLogout(null, sessionId2);
|
||||
events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||
|
||||
// Check session 3 logged-in
|
||||
oauth.openLoginForm();
|
||||
events.expectLogin().session(sessionId3).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent();
|
||||
|
||||
// Check session 2 not logged-in
|
||||
oauth2.openLoginForm();
|
||||
assertEquals(oauth2.getLoginFormUrl(), driver2.getCurrentUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ import org.junit.ClassRule;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
@ -123,7 +124,7 @@ public class ResetPasswordTest {
|
|||
|
||||
resetPasswordPage.assertCurrent();
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("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());
|
||||
|
||||
|
@ -140,15 +141,15 @@ public class ResetPasswordTest {
|
|||
|
||||
updatePasswordPage.changePassword("resetPassword", "resetPassword");
|
||||
|
||||
events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, username).assertEvent();
|
||||
events.expectRequiredAction("update_password").user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent();
|
||||
Event loginEvent = events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).user(userId).session(sessionId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
|
@ -176,7 +177,7 @@ public class ResetPasswordTest {
|
|||
|
||||
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
|
||||
events.expectRequiredAction("send_reset_password").user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -206,7 +207,7 @@ public class ResetPasswordTest {
|
|||
String body = (String) message.getContent();
|
||||
String changePasswordUrl = body.split("\n")[3];
|
||||
|
||||
events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
|
||||
String sessionId = events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
|
||||
|
||||
driver.navigate().to(changePasswordUrl.trim());
|
||||
|
||||
|
@ -218,15 +219,15 @@ public class ResetPasswordTest {
|
|||
|
||||
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
|
||||
|
||||
events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
events.expectRequiredAction("update_password").user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
Event loginEvent = events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
|
||||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
events.expectLogout(loginEvent.getSessionId()).user(userId).session(sessionId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ import org.openqa.selenium.WebDriver;
|
|||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -76,22 +79,34 @@ public class SSOTest {
|
|||
loginPage.open();
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().assertEvent();
|
||||
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||
|
||||
appPage.open();
|
||||
|
||||
oauth.openLoginForm();
|
||||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
profilePage.open();
|
||||
|
||||
Assert.assertTrue(profilePage.isCurrent());
|
||||
|
||||
events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent();
|
||||
String sessionId2 = events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId();
|
||||
|
||||
assertEquals(sessionId, sessionId2);
|
||||
|
||||
// Expire session
|
||||
keycloakRule.removeUserSession(sessionId);
|
||||
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String sessionId4 = events.expectLogin().assertEvent().getSessionId();
|
||||
assertNotEquals(sessionId, sessionId4);
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
|
@ -41,6 +42,8 @@ import org.openqa.selenium.WebDriver;
|
|||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -69,7 +72,10 @@ public class AccessTokenTest {
|
|||
public void accessTokenRequest() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
@ -85,33 +91,61 @@ public class AccessTokenTest {
|
|||
Assert.assertEquals(keycloakRule.getUser("test", "test-user@localhost").getId(), token.getSubject());
|
||||
Assert.assertNotEquals("test-user@localhost", token.getSubject());
|
||||
|
||||
Assert.assertEquals(sessionId, token.getSessionState());
|
||||
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));
|
||||
|
||||
Assert.assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
Assert.assertTrue(token.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
|
||||
|
||||
Event event = events.expectCodeToToken(codeId).assertEvent();
|
||||
Event event = events.expectCodeToToken(codeId, sessionId).assertEvent();
|
||||
Assert.assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
|
||||
Assert.assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
|
||||
Assert.assertEquals(sessionId, token.getSessionState());
|
||||
|
||||
response = oauth.doAccessTokenRequest(code, "password");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
||||
events.expectCodeToToken(codeId).error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null).assertEvent();
|
||||
events.expectCodeToToken(codeId, null).error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenInvalidClientCredentials() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
||||
events.expectCodeToToken(codeId).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).assertEvent();
|
||||
events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenUserSessionExpired() {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
keycloakRule.removeUserSession(sessionId);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
|
||||
events.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent();
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
|
@ -40,6 +41,8 @@ import org.openqa.selenium.WebDriver;
|
|||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
|
||||
*/
|
||||
|
@ -82,22 +85,25 @@ public class OAuthGrantTest {
|
|||
|
||||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
|
||||
|
||||
String codeId = events.expectLogin().client("third-party").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().client("third-party").assertEvent();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
|
||||
AccessToken token = oauth.verifyToken(accessToken.getAccessToken());
|
||||
assertEquals(sessionId, token.getSessionState());
|
||||
|
||||
AccessToken.Access realmAccess = token.getRealmAccess();
|
||||
Assert.assertEquals(1, realmAccess.getRoles().size());
|
||||
assertEquals(1, realmAccess.getRoles().size());
|
||||
Assert.assertTrue(realmAccess.isUserInRole("user"));
|
||||
|
||||
Map<String,AccessToken.Access> resourceAccess = token.getResourceAccess();
|
||||
Assert.assertEquals(1, resourceAccess.size());
|
||||
Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
|
||||
assertEquals(1, resourceAccess.size());
|
||||
assertEquals(1, resourceAccess.get("test-app").getRoles().size());
|
||||
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
|
||||
|
||||
events.expectCodeToToken(codeId).client("third-party").assertEvent();
|
||||
events.expectCodeToToken(codeId, loginEvent.getSessionId()).client("third-party").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,7 +118,7 @@ public class OAuthGrantTest {
|
|||
grantPage.cancel();
|
||||
|
||||
Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.ERROR));
|
||||
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
|
||||
|
||||
events.expectLogin().client("third-party").error("rejected_by_user").assertEvent();
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
@ -43,6 +44,8 @@ import org.openqa.selenium.WebDriver;
|
|||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -71,7 +74,10 @@ public class RefreshTokenTest {
|
|||
public void refreshTokenRequest() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
|
@ -80,7 +86,7 @@ public class RefreshTokenTest {
|
|||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
|
||||
|
||||
Event tokenEvent = events.expectCodeToToken(codeId).assertEvent();
|
||||
Event tokenEvent = events.expectCodeToToken(codeId, sessionId).assertEvent();
|
||||
|
||||
Assert.assertNotNull(refreshTokenString);
|
||||
|
||||
|
@ -89,6 +95,8 @@ public class RefreshTokenTest {
|
|||
Assert.assertThat(token.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
Assert.assertThat(refreshToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(35950), lessThanOrEqualTo(36000)));
|
||||
|
||||
Assert.assertEquals(sessionId, refreshToken.getSessionState());
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
|
||||
|
@ -97,6 +105,9 @@ public class RefreshTokenTest {
|
|||
|
||||
Assert.assertEquals(200, response.getStatusCode());
|
||||
|
||||
Assert.assertEquals(sessionId, refreshedToken.getSessionState());
|
||||
Assert.assertEquals(sessionId, refreshedRefreshToken.getSessionState());
|
||||
|
||||
Assert.assertThat(response.getExpiresIn(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
Assert.assertThat(refreshedToken.getExpiration() - Time.currentTime(), allOf(greaterThanOrEqualTo(250), lessThanOrEqualTo(300)));
|
||||
|
||||
|
@ -117,9 +128,37 @@ public class RefreshTokenTest {
|
|||
Assert.assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
|
||||
Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
|
||||
|
||||
Event refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID)).assertEvent();
|
||||
Event refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId).assertEvent();
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserSessionExpired() {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
Event loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
events.poll();
|
||||
|
||||
String refreshId = oauth.verifyRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||
|
||||
keycloakRule.removeUserSession(sessionId);
|
||||
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
|
||||
events.expectRefresh(refreshId, sessionId).error(Errors.INVALID_TOKEN);
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import net.iharder.Base64;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Errors;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.ApplicationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.OAuthClient;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebResource;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||
|
||||
@ClassRule
|
||||
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
ApplicationModel app = appRealm.addApplication("resource-owner");
|
||||
app.setSecret("secret");
|
||||
}
|
||||
});
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(keycloakRule);
|
||||
|
||||
@Rule
|
||||
public WebRule webRule = new WebRule(this);
|
||||
|
||||
@WebResource
|
||||
protected WebDriver driver;
|
||||
|
||||
@WebResource
|
||||
protected OAuthClient oauth;
|
||||
|
||||
@Test
|
||||
public void grantAccessToken() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
||||
.detail(Details.RESPONSE_TYPE, "token")
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.assertEvent();
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
|
||||
|
||||
OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret");
|
||||
|
||||
AccessToken refreshedAccessToken = oauth.verifyToken(refreshedResponse.getAccessToken());
|
||||
RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(refreshedResponse.getRefreshToken());
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState());
|
||||
assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState());
|
||||
|
||||
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).client("resource-owner").assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenLogout() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
||||
.detail(Details.RESPONSE_TYPE, "token")
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.assertEvent();
|
||||
|
||||
HttpResponse logoutResponse = oauth.doLogout(null, accessToken.getSessionState());
|
||||
assertEquals(200, logoutResponse.getStatusLine().getStatusCode());
|
||||
events.expectLogout(accessToken.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||
|
||||
logoutResponse = oauth.doLogout(null, accessToken.getSessionState());
|
||||
assertEquals(200, logoutResponse.getStatusLine().getStatusCode());
|
||||
events.expectLogout(accessToken.getSessionState()).user((String) null).removeDetail(Details.REDIRECT_URI).error(Errors.USER_SESSION_NOT_FOUND).assertEvent();
|
||||
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret");
|
||||
assertEquals(400, response.getStatusCode());
|
||||
assertEquals("invalid_grant", response.getError());
|
||||
|
||||
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).client("resource-owner")
|
||||
.removeDetail(Details.TOKEN_ID)
|
||||
.removeDetail(Details.UPDATED_REFRESH_TOKEN_ID)
|
||||
.error(Errors.INVALID_TOKEN).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenInvalidClientCredentials() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("invalid", "test-user@localhost", "password");
|
||||
|
||||
assertEquals(400, response.getStatusCode());
|
||||
|
||||
assertEquals("unauthorized_client", response.getError());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session((String) null)
|
||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
||||
.detail(Details.RESPONSE_TYPE, "token")
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.error(Errors.INVALID_CLIENT_CREDENTIALS)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenInvalidUserCredentials() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "invalid");
|
||||
|
||||
assertEquals(400, response.getStatusCode());
|
||||
|
||||
assertEquals("invalid_grant", response.getError());
|
||||
|
||||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session((String) null)
|
||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
||||
.detail(Details.RESPONSE_TYPE, "token")
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.error(Errors.INVALID_USER_CREDENTIALS)
|
||||
.assertEvent();
|
||||
}
|
||||
|
||||
}
|
|
@ -24,10 +24,13 @@ package org.keycloak.testsuite.rule;
|
|||
import org.keycloak.models.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.ProviderSession;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.ApplicationServlet;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
|
@ -78,6 +81,28 @@ public class KeycloakRule extends AbstractKeycloakRule {
|
|||
}
|
||||
}
|
||||
|
||||
public KeycloakSession startSession() {
|
||||
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
|
||||
session.getTransaction().begin();
|
||||
return session;
|
||||
}
|
||||
|
||||
public void stopSession(KeycloakSession session, boolean commit) {
|
||||
if (commit) {
|
||||
session.getTransaction().commit();
|
||||
}
|
||||
session.close();
|
||||
}
|
||||
|
||||
public void removeUserSession(String sessionId) {
|
||||
KeycloakSession keycloakSession = startSession();
|
||||
RealmModel realm = keycloakSession.getRealm("test");
|
||||
UserSessionModel session = realm.getUserSession(sessionId);
|
||||
assertNotNull(session);
|
||||
realm.removeUserSession(session);
|
||||
stopSession(keycloakSession, true);
|
||||
}
|
||||
|
||||
public abstract static class KeycloakSetup {
|
||||
|
||||
protected ProviderSession providerSession;
|
||||
|
|
|
@ -47,6 +47,13 @@ public class WebRule extends ExternalResource {
|
|||
|
||||
@Override
|
||||
protected void before() throws Throwable {
|
||||
driver = createWebDriver();
|
||||
oauth = new OAuthClient(driver);
|
||||
initWebResources(test);
|
||||
}
|
||||
|
||||
public static WebDriver createWebDriver() {
|
||||
WebDriver driver;
|
||||
String browser = "htmlunit";
|
||||
if (System.getProperty("browser") != null) {
|
||||
browser = System.getProperty("browser");
|
||||
|
@ -64,10 +71,7 @@ public class WebRule extends ExternalResource {
|
|||
} else {
|
||||
throw new RuntimeException("Unsupported browser " + browser);
|
||||
}
|
||||
|
||||
oauth = new OAuthClient(driver);
|
||||
|
||||
initWebResources(test);
|
||||
return driver;
|
||||
}
|
||||
|
||||
protected void initWebResources(Object o) {
|
||||
|
@ -122,7 +126,7 @@ public class WebRule extends ExternalResource {
|
|||
driver.close();
|
||||
}
|
||||
|
||||
public class HtmlUnitDriver extends org.openqa.selenium.htmlunit.HtmlUnitDriver {
|
||||
public static class HtmlUnitDriver extends org.openqa.selenium.htmlunit.HtmlUnitDriver {
|
||||
|
||||
@Override
|
||||
public WebClient getWebClient() {
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.audit.Details;
|
||||
import org.keycloak.audit.Event;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -116,16 +117,21 @@ public class SocialLoginTest {
|
|||
.detail(Details.REGISTER_METHOD, "social@dummy")
|
||||
.detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
|
||||
.detail(Details.USERNAME, "1@dummy")
|
||||
.session((String) null)
|
||||
.assertEvent().getUserId();
|
||||
|
||||
String codeId = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social@dummy").assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
|
||||
events.expectCodeToToken(codeId).user(userId).assertEvent();
|
||||
events.expectCodeToToken(codeId, sessionId).user(userId).assertEvent();
|
||||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
Assert.assertEquals(36, token.getSubject().length());
|
||||
Assert.assertEquals(sessionId, token.getSessionState());
|
||||
|
||||
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
|
||||
Assert.assertEquals(36, profile.getUsername().length());
|
||||
|
@ -136,7 +142,7 @@ public class SocialLoginTest {
|
|||
|
||||
oauth.openLogout();
|
||||
|
||||
events.expectLogout().user(userId).assertEvent();
|
||||
events.expectLogout(sessionId).user(userId).assertEvent();
|
||||
|
||||
loginPage.open();
|
||||
|
||||
|
@ -160,7 +166,7 @@ public class SocialLoginTest {
|
|||
Assert.assertTrue(loginPage.isCurrent());
|
||||
Assert.assertEquals("Access denied", loginPage.getWarning());
|
||||
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).detail(Details.AUTH_METHOD, "social@dummy").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).detail(Details.AUTH_METHOD, "social@dummy").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
||||
|
@ -212,12 +218,13 @@ public class SocialLoginTest {
|
|||
|
||||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
|
||||
String codeId = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent().getDetails().get(Details.CODE_ID);
|
||||
Event loginEvent = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
events.expectCodeToToken(codeId).user(userId).assertEvent();
|
||||
events.expectCodeToToken(codeId, loginEvent.getSessionId()).user(userId).assertEvent();
|
||||
|
||||
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||
|
|
Loading…
Reference in a new issue