keycloak-aplcache
Changes
connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java 1(+1 -0)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java 13(+9 -4)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java 87(+87 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java 127(+127 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionRoleEntity.java 97(+97 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java 97(+0 -97)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java 12(+6 -6)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java 67(+61 -6)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java 34(+8 -26)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java 79(+79 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java 188(+87 -101)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java 20(+15 -5)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionKey.java 36(+0 -36)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java 125(+87 -38)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java 10(+6 -4)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java 30(+12 -18)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java 88(+88 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java 80(+80 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java 11(+6 -5)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java 80(+69 -11)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java 46(+13 -33)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java 163(+149 -14)
Details
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 7511ed2..2463c4c 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -20,7 +20,8 @@
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
<!-- JpaUserSessionProvider -->
- <class>org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index cea49ff..70135fb 100644
--- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -32,6 +32,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.mongo.keycloak.entities.MongoOAuthClientEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity",
"org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity",
+ "org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity",
"org.keycloak.models.entities.FederationProviderEntity"
};
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
index 403512f..de2f6e4 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
@@ -2,6 +2,7 @@ package org.keycloak.account.freemarker.model;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -9,8 +10,10 @@ import org.keycloak.util.Time;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -58,16 +61,18 @@ public class SessionsBean {
return Time.toDate(max);
}
- public List<String> getApplications() {
- List<String> apps = new ArrayList<String>();
- for (ClientModel client : session.getClientAssociations()) {
+ public Set<String> getApplications() {
+ Set<String> apps = new HashSet<String>();
+ for (ClientSessionModel clientSession : session.getClientSessions()) {
+ ClientModel client = clientSession.getClient();
if (client instanceof ApplicationModel) apps.add(client.getClientId());
}
return apps;
}
public List<String> getClients() {
List<String> apps = new ArrayList<String>();
- for (ClientModel client : session.getClientAssociations()) {
+ for (ClientSessionModel clientSession : session.getClientSessions()) {
+ ClientModel client = clientSession.getClient();
if (client instanceof OAuthClientModel) apps.add(client.getClientId());
}
return apps;
diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
new file mode 100644
index 0000000..66f2131
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java
@@ -0,0 +1,39 @@
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ClientSessionModel {
+
+ public String getId();
+
+ public ClientModel getClient();
+
+ public String getState();
+
+ public UserSessionModel getUserSession();
+
+ public String getRedirectUri();
+
+ public int getTimestamp();
+
+ public void setTimestamp(int timestamp);
+
+ public Action getAction();
+
+ public void setAction(Action action);
+
+ public Set<String> getRoles();
+
+ public static enum Action {
+ OAUTH_GRANT,
+ CODE_TO_TOKEN,
+ VERIFY_EMAIL,
+ UPDATE_PROFILE,
+ CONFIGURE_TOTP,
+ UPDATE_PASSWORD
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index e7ce35c..d299a99 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -39,10 +39,6 @@ public interface UserSessionModel {
void setLastSessionRefresh(int seconds);
- void associateClient(ClientModel client);
-
- List<ClientModel> getClientAssociations();
-
- void removeAssociatedClient(ClientModel client);
+ List<ClientSessionModel> getClientSessions();
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index d91061d..d2c66d0 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -3,6 +3,7 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -10,6 +11,9 @@ import java.util.List;
*/
public interface UserSessionProvider extends Provider {
+ ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles);
+ ClientSessionModel getClientSession(RealmModel realm, String id);
+
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe);
UserSessionModel getUserSession(RealmModel realm, String id);
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 13e3a89..b28f71c 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -4,6 +4,7 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.OAuthClientModel;
@@ -209,7 +210,8 @@ public class ModelToRepresentation {
rep.setLastAccess(((long)session.getLastSessionRefresh())* 1000L);
rep.setUser(session.getUser().getUsername());
rep.setIpAddress(session.getIpAddress());
- for (ClientModel client : session.getClientAssociations()) {
+ for (ClientSessionModel clientSession : session.getClientSessions()) {
+ ClientModel client = clientSession.getClient();
if (client instanceof ApplicationModel) {
rep.getApplications().add(client.getClientId());
} else if (client instanceof OAuthClientModel) {
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
new file mode 100644
index 0000000..ebcef17
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java
@@ -0,0 +1,87 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity;
+
+import javax.persistence.EntityManager;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionAdapter implements ClientSessionModel {
+
+ private KeycloakSession session;
+ private ClientSessionEntity entity;
+ private EntityManager em;
+ private RealmModel realm;
+
+ public ClientSessionAdapter(KeycloakSession session, EntityManager em, RealmModel realm, ClientSessionEntity entity) {
+ this.session = session;
+ this.em = em;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public ClientModel getClient() {
+ return realm.findClientById(entity.getClientId());
+ }
+
+ @Override
+ public String getState() {
+ return entity.getState();
+ }
+
+ @Override
+ public UserSessionModel getUserSession() {
+ return new UserSessionAdapter(session, em, realm, entity.getSession());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return entity.getRedirectUri();
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ entity.setTimestamp(timestamp);
+ }
+
+ @Override
+ public Action getAction() {
+ return entity.getAction();
+ }
+
+ @Override
+ public void setAction(Action action) {
+ entity.setAction(action);
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ Set<String> roles = new HashSet<String>();
+ if (entity.getRoles() != null) {
+ for (ClientSessionRoleEntity e : entity.getRoles()) {
+ roles.add(e.getRoleId());
+ }
+ }
+ return roles;
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
new file mode 100755
index 0000000..65df663
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java
@@ -0,0 +1,127 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import org.keycloak.models.ClientSessionModel;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@Table(name = "CLIENT_SESSION")
+@NamedQueries({
+ @NamedQuery(name = "removeClientSessionByRealm", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId)"),
+ @NamedQuery(name = "removeClientSessionByUser", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId)"),
+ @NamedQuery(name = "removeClientSessionByClient", query = "delete from ClientSessionEntity a where a.clientId = :clientId and a.session IN (select s from UserSessionEntity s where s.realmId = :realmId)"),
+ @NamedQuery(name = "removeClientSessionByExpired", query = "delete from ClientSessionEntity a where a.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))")
+})
+public class ClientSessionEntity {
+
+ @Id
+ @Column(name = "ID", length = 36)
+ protected String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "SESSION_ID")
+ protected UserSessionEntity session;
+
+ @Column(name="CLIENT_ID",length = 36)
+ protected String clientId;
+
+ @Column(name="TIMESTAMP")
+ protected int timestamp;
+
+ @Column(name="REDIRECT_URI")
+ protected String redirectUri;
+
+ @Column(name="STATE")
+ protected String state;
+
+ @Column(name="ACTION")
+ protected ClientSessionModel.Action action;
+
+ @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
+ protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public UserSessionEntity getSession() {
+ return session;
+ }
+
+ public void setSession(UserSessionEntity session) {
+ this.session = session;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ public void setRedirectUri(String redirectUri) {
+ this.redirectUri = redirectUri;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public ClientSessionModel.Action getAction() {
+ return action;
+ }
+
+ public void setAction(ClientSessionModel.Action action) {
+ this.action = action;
+ }
+
+ public Collection<ClientSessionRoleEntity> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Collection<ClientSessionRoleEntity> roles) {
+ this.roles = roles;
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionRoleEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionRoleEntity.java
new file mode 100755
index 0000000..e1be9d3
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionRoleEntity.java
@@ -0,0 +1,97 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@NamedQueries({
+ @NamedQuery(name = "removeClientSessionRoleByUser", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"),
+ @NamedQuery(name = "removeClientSessionRoleByClient", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
+ @NamedQuery(name = "removeClientSessionRoleByRealm", query="delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId))"),
+ @NamedQuery(name = "removeClientSessionRoleByExpired", query = "delete from ClientSessionRoleEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))")
+})
+@Table(name="CLIENT_SESSION_ROLE")
+@Entity
+@IdClass(ClientSessionRoleEntity.Key.class)
+public class ClientSessionRoleEntity {
+
+ @Id
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name="CLIENT_SESSION")
+ protected ClientSessionEntity clientSession;
+
+ @Id
+ @Column(name = "ROLE_ID")
+ protected String roleId;
+
+ public ClientSessionEntity getClientSession() {
+ return clientSession;
+ }
+
+ public void setClientSession(ClientSessionEntity clientSession) {
+ this.clientSession = clientSession;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ public void setRoleId(String roleId) {
+ this.roleId = roleId;
+ }
+
+
+ public static class Key implements Serializable {
+
+ protected ClientSessionEntity clientSession;
+
+ protected String roleId;
+
+ public Key() {
+ }
+
+ public Key(ClientSessionEntity clientSession, String roleId) {
+ this.clientSession = clientSession;
+ this.roleId = roleId;
+ }
+
+ public ClientSessionEntity getClientSession() {
+ return clientSession;
+ }
+
+ public String getRoleId() {
+ return roleId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (!roleId.equals(key.roleId)) return false;
+ if (!clientSession.getId().equals(key.clientSession.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = clientSession.getId().hashCode();
+ result = 31 * result + roleId.hashCode();
+ return result;
+ }
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
index 9094a90..3716c3c 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
@@ -20,8 +20,8 @@ import java.util.Collection;
@Table(name = "USER_SESSION")
@NamedQueries({
@NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId order by s.started, s.id"),
- @NamedQuery(name = "getUserSessionByClient", query = "select s from UserSessionEntity s join s.clients c where s.realmId = :realmId and c.clientId = :clientId order by s.started, s.id"),
- @NamedQuery(name = "getActiveUserSessionByClient", query = "select count(s) from UserSessionEntity s join s.clients c where s.realmId = :realmId and c.clientId = :clientId"),
+ @NamedQuery(name = "getUserSessionByClient", query = "select s from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId order by s.started, s.id"),
+ @NamedQuery(name = "getActiveUserSessionByClient", query = "select count(s) from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId"),
@NamedQuery(name = "removeUserSessionByRealm", query = "delete from UserSessionEntity s where s.realmId = :realmId"),
@NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId"),
@NamedQuery(name = "removeUserSessionByExpired", query = "delete from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)")
@@ -56,8 +56,8 @@ public class UserSessionEntity {
@Column(name="LAST_SESSION_REFRESH")
protected int lastSessionRefresh;
- @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="session")
- protected Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
+ @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="session")
+ protected Collection<ClientSessionEntity> clientSessions = new ArrayList<ClientSessionEntity>();
public String getId() {
return id;
@@ -131,8 +131,8 @@ public class UserSessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
- public Collection<ClientUserSessionAssociationEntity> getClients() {
- return clients;
+ public Collection<ClientSessionEntity> getClientSessions() {
+ return clientSessions;
}
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index 640af99..92e416b 100644
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -1,12 +1,15 @@
package org.keycloak.models.sessions.jpa;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -17,6 +20,7 @@ import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -33,6 +37,46 @@ public class JpaUserSessionProvider implements UserSessionProvider {
}
@Override
+ public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
+ UserSessionEntity userSessionEntity = em.find(UserSessionEntity.class, userSession.getId());
+
+ ClientSessionEntity entity = new ClientSessionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setClientId(client.getId());
+ entity.setSession(userSessionEntity);
+ entity.setRedirectUri(redirectUri);
+ entity.setState(state);
+ em.persist(entity);
+
+ if (roles != null) {
+ List<ClientSessionRoleEntity> roleEntities = new LinkedList<ClientSessionRoleEntity>();
+ for (String r : roles) {
+ ClientSessionRoleEntity roleEntity = new ClientSessionRoleEntity();
+ roleEntity.setClientSession(entity);
+ roleEntity.setRoleId(r);
+ em.persist(roleEntity);
+
+ roleEntities.add(roleEntity);
+ }
+ entity.setRoles(roleEntities);
+ }
+
+ userSessionEntity.getClientSessions().add(entity);
+
+ return new ClientSessionAdapter(session, em, realm, entity);
+ }
+
+ @Override
+ public ClientSessionModel getClientSession(RealmModel realm, String id) {
+ ClientSessionEntity clientSession = em.find(ClientSessionEntity.class, id);
+ if (clientSession != null && clientSession.getSession().getRealmId().equals(realm.getId())) {
+ return new ClientSessionAdapter(session, em, realm, clientSession);
+ }
+ return null;
+ }
+
+ @Override
public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
String id = username + "-" + realm;
UsernameLoginFailureEntity entity = em.find(UsernameLoginFailureEntity.class, id);
@@ -109,7 +153,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
List<UserSessionModel> list = new LinkedList<UserSessionModel>();
TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionByClient", UserSessionEntity.class)
.setParameter("realmId", realm.getId())
- .setParameter("clientId", client.getClientId());
+ .setParameter("clientId", client.getId());
if (firstResult != -1) {
query.setFirstResult(firstResult);
}
@@ -126,7 +170,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
Object count = em.createNamedQuery("getActiveUserSessionByClient")
.setParameter("realmId", realm.getId())
- .setParameter("clientId", client.getClientId())
+ .setParameter("clientId", client.getId())
.getSingleResult();
return ((Number)count).intValue();
}
@@ -141,7 +185,11 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm, UserModel user) {
- em.createNamedQuery("removeClientUserSessionByUser")
+ em.createNamedQuery("removeClientSessionRoleByUser")
+ .setParameter("realmId", realm.getId())
+ .setParameter("userId", user.getId())
+ .executeUpdate();
+ em.createNamedQuery("removeClientSessionByUser")
.setParameter("realmId", realm.getId())
.setParameter("userId", user.getId())
.executeUpdate();
@@ -156,7 +204,12 @@ public class JpaUserSessionProvider implements UserSessionProvider {
int maxTime = Time.currentTime() - realm.getSsoSessionMaxLifespan();
int idleTime = Time.currentTime() - realm.getSsoSessionIdleTimeout();
- em.createNamedQuery("removeClientUserSessionByExpired")
+ em.createNamedQuery("removeClientSessionRoleByExpired")
+ .setParameter("realmId", realm.getId())
+ .setParameter("maxTime", maxTime)
+ .setParameter("idleTime", idleTime)
+ .executeUpdate();
+ em.createNamedQuery("removeClientSessionByExpired")
.setParameter("realmId", realm.getId())
.setParameter("maxTime", maxTime)
.setParameter("idleTime", idleTime)
@@ -170,7 +223,8 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSessions(RealmModel realm) {
- em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+ em.createNamedQuery("removeClientSessionRoleByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+ em.createNamedQuery("removeClientSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("removeUserSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
}
@@ -182,7 +236,8 @@ public class JpaUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- em.createNamedQuery("removeClientUserSessionByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getClientId()).executeUpdate();
+ em.createNamedQuery("removeClientSessionRoleByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getId()).executeUpdate();
+ em.createNamedQuery("removeClientSessionByClient").setParameter("realmId", realm.getId()).setParameter("clientId", client.getId()).executeUpdate();
}
@Override
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
index 01bda8e..3261b21 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
@@ -1,15 +1,15 @@
package org.keycloak.models.sessions.jpa;
-import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity;
+import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
import javax.persistence.EntityManager;
-import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -114,30 +114,12 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
- public void associateClient(ClientModel client) {
- for (ClientUserSessionAssociationEntity ass : entity.getClients()) {
- if (ass.getClientId().equals(client.getClientId())) return;
+ public List<ClientSessionModel> getClientSessions() {
+ List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();
+ for (ClientSessionEntity e : entity.getClientSessions()) {
+ clientSessions.add(new ClientSessionAdapter(session, em, realm, e));
}
-
- ClientUserSessionAssociationEntity association = new ClientUserSessionAssociationEntity();
- association.setClientId(client.getClientId());
- association.setSession(entity);
- em.persist(association);
- entity.getClients().add(association);
- }
-
- @Override
- public void removeAssociatedClient(ClientModel client) {
- em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", client.getClientId()).executeUpdate();
- }
-
- @Override
- public List<ClientModel> getClientAssociations() {
- List<ClientModel> clients = new ArrayList<ClientModel>();
- for (ClientUserSessionAssociationEntity association : entity.getClients()) {
- clients.add(realm.findClient(association.getClientId()));
- }
- return clients;
+ return clientSessions;
}
@Override
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
new file mode 100644
index 0000000..81918b1
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java
@@ -0,0 +1,79 @@
+package org.keycloak.models.sessions.mem;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionAdapter implements ClientSessionModel {
+
+ private KeycloakSession session;
+ private MemUserSessionProvider provider;
+ private RealmModel realm;
+ private ClientSessionEntity entity;
+
+ public ClientSessionAdapter(KeycloakSession session, MemUserSessionProvider provider, RealmModel realm, ClientSessionEntity entity) {
+ this.session = session;
+ this.provider = provider;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public ClientModel getClient() {
+ return realm.findClientById(entity.getClientId());
+ }
+
+ @Override
+ public String getState() {
+ return entity.getState();
+ }
+
+ @Override
+ public UserSessionModel getUserSession() {
+ return new UserSessionAdapter(session, provider, realm, entity.getSession());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return entity.getRedirectUri();
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ entity.setTimestamp(timestamp);
+ }
+
+ @Override
+ public ClientSessionModel.Action getAction() {
+ return entity.getAction();
+ }
+
+ @Override
+ public void setAction(ClientSessionModel.Action action) {
+ entity.setAction(action);
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return entity.getRoles();
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
index 2596137..30cb920 100644
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
@@ -1,5 +1,6 @@
package org.keycloak.models.sessions.mem.entities;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -17,7 +18,7 @@ public class UserSessionEntity {
private boolean rememberMe;
private int started;
private int lastSessionRefresh;
- private List<String> clients = new LinkedList<String>();
+ private List<ClientSessionEntity> clientSessions = Collections.synchronizedList(new LinkedList<ClientSessionEntity>());
public String getId() {
return id;
@@ -91,12 +92,21 @@ public class UserSessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
- public List<String> getClients() {
- return clients;
+ public void addClientSession(ClientSessionEntity clientSession) {
+ if (clientSessions == null) {
+ clientSessions = new LinkedList<ClientSessionEntity>();
+ }
+ clientSessions.add(clientSession);
}
- public void setClients(List<String> clients) {
- this.clients = clients;
+ public void removeClientSession(ClientSessionEntity clientSession) {
+ if (clientSessions != null) {
+ clientSessions.remove(clientSession);
+ }
+ }
+
+ public List<ClientSessionEntity> getClientSessions() {
+ return clientSessions;
}
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index 7021046..8650397 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -1,14 +1,15 @@
package org.keycloak.models.sessions.mem;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
-import org.keycloak.models.sessions.mem.entities.UserSessionKey;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureEntity;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureKey;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -19,6 +20,7 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -27,16 +29,43 @@ import java.util.concurrent.ConcurrentHashMap;
public class MemUserSessionProvider implements UserSessionProvider {
private final KeycloakSession session;
- private final ConcurrentHashMap<UserSessionKey, UserSessionEntity> sessions;
+ private final ConcurrentHashMap<String, UserSessionEntity> userSessions;
+ private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
private final ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures;
- public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<UserSessionKey, UserSessionEntity> sessions, ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures) {
+ public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, ClientSessionEntity> clientSessions, ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures) {
this.session = session;
- this.sessions = sessions;
+ this.userSessions = userSessions;
+ this.clientSessions = clientSessions;
this.loginFailures = loginFailures;
}
@Override
+ public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
+ UserSessionEntity userSessionEntity = getUserSessionEntity(realm, userSession.getId());
+
+ ClientSessionEntity entity = new ClientSessionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setClientId(client.getId());
+ entity.setSession(userSessionEntity);
+ entity.setRedirectUri(redirectUri);
+ entity.setState(state);
+ entity.setRoles(roles);
+
+ userSessionEntity.addClientSession(entity);
+
+ clientSessions.put(entity.getId(), entity);
+ return new ClientSessionAdapter(session, this, realm, entity);
+ }
+
+ @Override
+ public ClientSessionModel getClientSession(RealmModel realm, String id) {
+ ClientSessionEntity entity = clientSessions.get(id);
+ return entity != null ? new ClientSessionAdapter(session, this, realm, entity) : null;
+ }
+
+ @Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
String id = KeycloakModelUtils.generateId();
@@ -54,23 +83,31 @@ public class MemUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
- sessions.put(new UserSessionKey(realm.getId(), id), entity);
+ userSessions.put(id, entity);
- return new UserSessionAdapter(session, realm, entity);
+ return new UserSessionAdapter(session, this, realm, entity);
}
@Override
public UserSessionModel getUserSession(RealmModel realm, String id) {
- UserSessionEntity entity = sessions.get(new UserSessionKey(realm.getId(), id));
- return entity != null ? new UserSessionAdapter(session, realm, entity) : null;
+ UserSessionEntity entity = getUserSessionEntity(realm, id);
+ return entity != null ? new UserSessionAdapter(session, this, realm, entity) : null;
+ }
+
+ UserSessionEntity getUserSessionEntity(RealmModel realm, String id) {
+ UserSessionEntity entity = userSessions.get(id);
+ if (entity != null && entity.getRealm().equals(realm.getId())) {
+ return entity;
+ }
+ return null;
}
@Override
public List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user) {
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
- for (UserSessionEntity s : sessions.values()) {
+ for (UserSessionEntity s : this.userSessions.values()) {
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
- userSessions.add(new UserSessionAdapter(session, realm, s));
+ userSessions.add(new UserSessionAdapter(session, this, realm, s));
}
}
return userSessions;
@@ -78,14 +115,21 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
- List<UserSessionModel> clientSessions = new LinkedList<UserSessionModel>();
- for (UserSessionEntity s : sessions.values()) {
- if (s.getRealm().equals(realm.getId()) && s.getClients().contains(client.getClientId())) {
- clientSessions.add(new UserSessionAdapter(session, realm, s));
+ List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
+ for (ClientSessionEntity s : clientSessions.values()) {
+ if (s.getSession().getRealm().equals(realm.getId()) && s.getClientId().equals(client.getId())) {
+ if (!userSessionEntities.contains(s.getSession())) {
+ userSessionEntities.add(s.getSession());
+ }
}
}
- Collections.sort(clientSessions, new UserSessionSort());
- return clientSessions;
+
+ List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
+ for (UserSessionEntity e : userSessionEntities) {
+ userSessions.add(new UserSessionAdapter(session, this, realm, e));
+ }
+ Collections.sort(userSessions, new UserSessionSort());
+ return userSessions;
}
@Override
@@ -101,49 +145,61 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
- int count = 0;
- for (UserSessionEntity s : sessions.values()) {
- if (s.getRealm().equals(realm.getId()) && s.getClients().contains(client.getClientId())) {
- count++;
- }
- }
- return count;
+ return getUserSessions(realm, client).size();
}
@Override
public void removeUserSession(RealmModel realm, UserSessionModel session) {
- sessions.remove(new UserSessionKey(realm.getId(), session.getId()));
+ UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
+ if (entity != null) {
+ userSessions.remove(entity.getId());
+ for (ClientSessionEntity clientSession : entity.getClientSessions()) {
+ clientSessions.remove(clientSession.getId());
+ }
+ }
}
@Override
public void removeUserSessions(RealmModel realm, UserModel user) {
- Iterator<UserSessionEntity> itr = sessions.values().iterator();
+ Iterator<UserSessionEntity> itr = userSessions.values().iterator();
while (itr.hasNext()) {
UserSessionEntity s = itr.next();
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
itr.remove();
+
+ for (ClientSessionEntity clientSession : s.getClientSessions()) {
+ clientSessions.remove(clientSession.getId());
+ }
}
}
}
@Override
public void removeExpiredUserSessions(RealmModel realm) {
- Iterator<UserSessionEntity> itr = sessions.values().iterator();
+ Iterator<UserSessionEntity> itr = userSessions.values().iterator();
while (itr.hasNext()) {
UserSessionEntity s = itr.next();
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
itr.remove();
+
+ for (ClientSessionEntity clientSession : s.getClientSessions()) {
+ clientSessions.remove(clientSession.getId());
+ }
}
}
}
@Override
public void removeUserSessions(RealmModel realm) {
- Iterator<UserSessionEntity> itr = sessions.values().iterator();
+ Iterator<UserSessionEntity> itr = userSessions.values().iterator();
while (itr.hasNext()) {
UserSessionEntity s = itr.next();
if (s.getRealm().equals(realm.getId())) {
itr.remove();
+
+ for (ClientSessionEntity clientSession : s.getClientSessions()) {
+ clientSessions.remove(clientSession.getId());
+ }
}
}
}
@@ -185,17 +241,10 @@ public class MemUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- Iterator<UserSessionEntity> itr = sessions.values().iterator();
- while (itr.hasNext()) {
- UserSessionEntity s = itr.next();
- if (s.getRealm().equals(realm.getId())) {
- itr.remove();
- }
- }
-
- for (UserSessionEntity s : sessions.values()) {
- if (s.getRealm().equals(realm.getId())) {
- s.getClients().remove(client.getClientId());
+ for (ClientSessionEntity e : clientSessions.values()) {
+ if (e.getSession().getRealm().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
+ clientSessions.remove(e.getId());
+ e.getSession().removeClientSession(e);
}
}
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
index c1ae6a3..27d11e7 100644
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
@@ -4,8 +4,8 @@ import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
-import org.keycloak.models.sessions.mem.entities.UserSessionKey;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureEntity;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureKey;
@@ -18,13 +18,15 @@ public class MemUserSessionProviderFactory implements UserSessionProviderFactory
public static final String ID = "mem";
- private ConcurrentHashMap<UserSessionKey, UserSessionEntity> sessions = new ConcurrentHashMap<UserSessionKey, UserSessionEntity>();
+ private ConcurrentHashMap<String, UserSessionEntity> userSessions = new ConcurrentHashMap<String, UserSessionEntity>();
+
+ private ConcurrentHashMap<String, ClientSessionEntity> clientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
private ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures = new ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity>();
@Override
public UserSessionProvider create(KeycloakSession session) {
- return new MemUserSessionProvider(session, sessions, loginFailures);
+ return new MemUserSessionProvider(session, userSessions, clientSessions, loginFailures);
}
@Override
@@ -33,7 +35,7 @@ public class MemUserSessionProviderFactory implements UserSessionProviderFactory
@Override
public void close() {
- sessions.clear();
+ userSessions.clear();
loginFailures.clear();
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
index a9008cf..2d1b16a 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
@@ -1,10 +1,12 @@
package org.keycloak.models.sessions.mem;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
import java.util.LinkedList;
@@ -17,12 +19,14 @@ public class UserSessionAdapter implements UserSessionModel {
private final KeycloakSession session;
+ private MemUserSessionProvider provider;
private final RealmModel realm;
private final UserSessionEntity entity;
- public UserSessionAdapter(KeycloakSession session, RealmModel realm, UserSessionEntity entity) {
+ public UserSessionAdapter(KeycloakSession session, MemUserSessionProvider provider, RealmModel realm, UserSessionEntity entity) {
this.session = session;
+ this.provider = provider;
this.realm = realm;
this.entity = entity;
}
@@ -98,24 +102,14 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
- public void associateClient(ClientModel client) {
- if (!entity.getClients().contains(client.getClientId())) {
- entity.getClients().add(client.getClientId());
+ public List<ClientSessionModel> getClientSessions() {
+ List<ClientSessionModel> clientSessionModels = new LinkedList<ClientSessionModel>();
+ if (entity.getClientSessions() != null) {
+ for (ClientSessionEntity e : entity.getClientSessions()) {
+ clientSessionModels.add(new ClientSessionAdapter(session, provider, realm, e));
+ }
}
- }
-
- @Override
- public List<ClientModel> getClientAssociations() {
- List<ClientModel> models = new LinkedList<ClientModel>();
- for (String clientId : entity.getClients()) {
- models.add(realm.findClient(clientId));
- }
- return models;
- }
-
- @Override
- public void removeAssociatedClient(ClientModel client) {
- entity.getClients().remove(client.getClientId());
+ return clientSessionModels;
}
@Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
new file mode 100644
index 0000000..c21ae09
--- /dev/null
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java
@@ -0,0 +1,88 @@
+package org.keycloak.models.sessions.mongo;
+
+import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
+import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionAdapter implements ClientSessionModel {
+
+ private KeycloakSession session;
+ private MongoUserSessionProvider provider;
+ private RealmModel realm;
+ private MongoClientSessionEntity entity;
+ private MongoUserSessionEntity userSessionEntity;
+ private MongoStoreInvocationContext invContext;
+
+ public ClientSessionAdapter(KeycloakSession session, MongoUserSessionProvider provider, RealmModel realm, MongoClientSessionEntity entity, MongoUserSessionEntity userSessionEntity, MongoStoreInvocationContext invContext) {
+ this.session = session;
+ this.provider = provider;
+ this.realm = realm;
+ this.entity = entity;
+ this.userSessionEntity = userSessionEntity;
+ this.invContext = invContext;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public ClientModel getClient() {
+ return realm.findClientById(entity.getClientId());
+ }
+
+ @Override
+ public String getState() {
+ return entity.getState();
+ }
+
+ @Override
+ public UserSessionModel getUserSession() {
+ return new UserSessionAdapter(session, provider, userSessionEntity, realm, invContext);
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return entity.getRedirectUri();
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ entity.setTimestamp(timestamp);
+ invContext.getMongoStore().updateEntity(userSessionEntity, invContext);
+ }
+
+ @Override
+ public Action getAction() {
+ return entity.getAction();
+ }
+
+ @Override
+ public void setAction(Action action) {
+ entity.setAction(action);
+ invContext.getMongoStore().updateEntity(userSessionEntity, invContext);
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return entity.getRoles() != null ? new HashSet<String>(entity.getRoles()) : null;
+ }
+
+}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
new file mode 100644
index 0000000..5bd9ac2
--- /dev/null
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java
@@ -0,0 +1,80 @@
+package org.keycloak.models.sessions.mongo.entities;
+
+import org.keycloak.models.ClientSessionModel;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MongoClientSessionEntity {
+
+ private String id;
+ private String clientId;
+
+ private String redirectUri;
+ private String state;
+
+ private int timestamp;
+ private ClientSessionModel.Action action;
+ private List<String> roles;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getRedirectUri() {
+ return redirectUri;
+ }
+
+ public void setRedirectUri(String redirectUri) {
+ this.redirectUri = redirectUri;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public ClientSessionModel.Action getAction() {
+ return action;
+ }
+
+ public void setAction(ClientSessionModel.Action action) {
+ this.action = action;
+ }
+
+ public List<String> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<String> roles) {
+ this.roles = roles;
+ }
+
+}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
index 8d0e946..89679de 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
@@ -6,6 +6,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.entities.AbstractIdentifiableEntity;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -30,7 +31,7 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
private int lastSessionRefresh;
- private List<String> associatedClientIds = new ArrayList<String>();
+ private List<MongoClientSessionEntity> clientSessions;
public String getRealmId() {
return realmId;
@@ -96,12 +97,12 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
this.lastSessionRefresh = lastSessionRefresh;
}
- public List<String> getAssociatedClientIds() {
- return associatedClientIds;
+ public List<MongoClientSessionEntity> getClientSessions() {
+ return clientSessions;
}
- public void setAssociatedClientIds(List<String> associatedClientIds) {
- this.associatedClientIds = associatedClientIds;
+ public void setClientSessions(List<MongoClientSessionEntity> clientSessions) {
+ this.clientSessions = clientSessions;
}
@Override
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index b555407..e4e75c5 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -6,18 +6,22 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.util.Time;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -35,6 +39,47 @@ public class MongoUserSessionProvider implements UserSessionProvider {
}
@Override
+ public ClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession, String redirectUri, String state, Set<String> roles) {
+ MongoUserSessionEntity userSessionEntity = getUserSessionEntity(realm, userSession.getId());
+
+ MongoClientSessionEntity entity = new MongoClientSessionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setTimestamp(Time.currentTime());
+ entity.setClientId(client.getId());
+ entity.setRedirectUri(redirectUri);
+ entity.setState(state);
+ if (roles != null) {
+ entity.setRoles(new LinkedList<String>(roles));
+ }
+
+ mongoStore.pushItemToList(userSessionEntity, "clientSessions", entity, false, invocationContext);
+
+ return new ClientSessionAdapter(session, this, realm, entity, userSessionEntity, invocationContext);
+ }
+
+ @Override
+ public ClientSessionModel getClientSession(RealmModel realm, String id) {
+ DBObject query = new QueryBuilder()
+ .and("realmId").is(realm.getId())
+ .and("clientSessions.id").is(id).get();
+
+ List<MongoUserSessionEntity> entities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
+ if (entities.isEmpty()) {
+ return null;
+ }
+
+ MongoUserSessionEntity userSessionEntity = entities.get(0);
+ List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
+ for (MongoClientSessionEntity s : sessions) {
+ if (s.getId().equals(id)) {
+ return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
MongoUserSessionEntity entity = new MongoUserSessionEntity();
entity.setRealmId(realm.getId());
@@ -50,25 +95,29 @@ public class MongoUserSessionProvider implements UserSessionProvider {
entity.setLastSessionRefresh(currentTime);
mongoStore.insertEntity(entity, invocationContext);
- return new UserSessionAdapter(session, entity, realm, invocationContext);
+ return new UserSessionAdapter(session, this, entity, realm, invocationContext);
}
@Override
public UserSessionModel getUserSession(RealmModel realm, String id) {
- MongoUserSessionEntity entity = mongoStore.loadEntity(MongoUserSessionEntity.class, id, invocationContext);
+ MongoUserSessionEntity entity = getUserSessionEntity(realm, id);
if (entity == null) {
return null;
} else {
- return new UserSessionAdapter(session, entity, realm, invocationContext);
+ return new UserSessionAdapter(session, this, entity, realm, invocationContext);
}
}
+ MongoUserSessionEntity getUserSessionEntity(RealmModel realm, String id) {
+ return mongoStore.loadEntity(MongoUserSessionEntity.class, id, invocationContext);
+ }
+
@Override
public List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user) {
DBObject query = new BasicDBObject("user", user.getId());
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
for (MongoUserSessionEntity e : mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext)) {
- sessions.add(new UserSessionAdapter(session, e, realm, invocationContext));
+ sessions.add(new UserSessionAdapter(session, this, e, realm, invocationContext));
}
return sessions;
}
@@ -80,14 +129,14 @@ public class MongoUserSessionProvider implements UserSessionProvider {
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
DBObject query = new QueryBuilder()
- .and("associatedClientIds").is(client.getId())
+ .and("clientSessions.clientId").is(client.getId())
.get();
DBObject sort = new BasicDBObject("started", 1).append("id", 1);
List<MongoUserSessionEntity> sessions = mongoStore.loadEntities(MongoUserSessionEntity.class, query, sort, firstResult, maxResults, invocationContext);
List<UserSessionModel> result = new LinkedList<UserSessionModel>();
for (MongoUserSessionEntity session : sessions) {
- result.add(new UserSessionAdapter(this.session, session, realm, invocationContext));
+ result.add(new UserSessionAdapter(this.session, this, session, realm, invocationContext));
}
return result;
}
@@ -95,7 +144,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
@Override
public int getActiveUserSessions(RealmModel realm, ClientModel client) {
DBObject query = new QueryBuilder()
- .and("associatedClientIds").is(client.getId())
+ .and("clientSessions.clientId").is(client.getId())
.get();
return mongoStore.countEntities(MongoUserSessionEntity.class, query, invocationContext);
}
@@ -184,13 +233,22 @@ public class MongoUserSessionProvider implements UserSessionProvider {
}
@Override
+ // TODO Not very efficient, should use Mongo $pull to remove directly
public void onClientRemoved(RealmModel realm, ClientModel client) {
DBObject query = new QueryBuilder()
- .and("realmId").is(realm.getId())
+ .and("clientSessions.clientId").is(client.getId())
.get();
- List<MongoUserSessionEntity> sessions = invocationContext.getMongoStore().loadEntities(MongoUserSessionEntity.class, query, invocationContext);
- for (MongoUserSessionEntity session : sessions) {
- invocationContext.getMongoStore().pullItemFromList(session, "associatedClientIds", client.getClientId(), invocationContext);
+ List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
+ for (MongoUserSessionEntity e : userSessionEntities) {
+ List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
+ for (MongoClientSessionEntity c : e.getClientSessions()) {
+ if (c.getClientId().equals(client.getId())) {
+ remove.add(c);
+ }
+ }
+ for (MongoClientSessionEntity c : remove) {
+ mongoStore.pullItemFromList(e, "clientSessions", c, invocationContext);
+ }
}
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
index d7a3223..633e1e6 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
@@ -2,14 +2,15 @@ package org.keycloak.models.sessions.mongo;
import org.jboss.logging.Logger;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
-import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
-import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -19,16 +20,19 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
private static final Logger logger = Logger.getLogger(UserSessionAdapter.class);
+ private final MongoUserSessionProvider provider;
private MongoUserSessionEntity entity;
private RealmModel realm;
private KeycloakSession keycloakSession;
+ private final MongoStoreInvocationContext invContext;
- public UserSessionAdapter(KeycloakSession keycloakSession, MongoUserSessionEntity entity, RealmModel realm, MongoStoreInvocationContext invContext)
- {
+ public UserSessionAdapter(KeycloakSession keycloakSession, MongoUserSessionProvider provider, MongoUserSessionEntity entity, RealmModel realm, MongoStoreInvocationContext invContext) {
super(invContext);
+ this.provider = provider;
this.entity = entity;
this.realm = realm;
this.keycloakSession = keycloakSession;
+ this.invContext = invContext;
}
@Override
@@ -125,36 +129,12 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
}
@Override
- public void associateClient(ClientModel client) {
- getMongoStore().pushItemToList(entity, "associatedClientIds", client.getId(), true, invocationContext);
- }
-
- @Override
- public List<ClientModel> getClientAssociations() {
- List<String> associatedClientIds = getMongoEntity().getAssociatedClientIds();
-
- List<ClientModel> clients = new ArrayList<ClientModel>();
- for (String clientId : associatedClientIds) {
- // Try application first
- ClientModel client = realm.getApplicationById(clientId);
-
- // And then OAuthClient
- if (client == null) {
- client = realm.getOAuthClientById(clientId);
- }
-
- if (client != null) {
- clients.add(client);
- } else {
- logger.warnf("Not found associated client with Id: %s", clientId);
- }
+ public List<ClientSessionModel> getClientSessions() {
+ List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
+ for (MongoClientSessionEntity e : entity.getClientSessions()) {
+ sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
}
- return clients;
- }
-
- @Override
- public void removeAssociatedClient(ClientModel client) {
- getMongoStore().pullItemFromList(entity, "associatedClientIds", client.getId(), invocationContext);
+ return sessions;
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCode.java b/services/src/main/java/org/keycloak/services/managers/AccessCode.java
new file mode 100755
index 0000000..edea255
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCode.java
@@ -0,0 +1,171 @@
+package org.keycloak.services.managers;
+
+import org.keycloak.OAuthErrorException;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.util.Base64Url;
+import org.keycloak.util.Time;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.Signature;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AccessCode {
+
+ private final RealmModel realm;
+ private final ClientSessionModel clientSession;
+
+ public AccessCode(RealmModel realm, ClientSessionModel clientSession) {
+ this.realm = realm;
+ this.clientSession = clientSession;
+ }
+
+ public static AccessCode parse(String code, KeycloakSession session, RealmModel realm) {
+ try {
+ String[] parts = code.split("\\.");
+ String id = new String(Base64Url.decode(parts[1]));
+
+ ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
+ if (clientSession == null) {
+ return null;
+ }
+
+ String hash = createSignatureHash(realm, clientSession);
+ if (!hash.equals(parts[0])) {
+ return null;
+ }
+
+ return new AccessCode(realm, clientSession);
+ } catch (RuntimeException e) {
+ return null;
+ }
+ }
+
+ public String getCodeId() {
+ return clientSession.getId();
+ }
+
+ public UserModel getUser() {
+ return clientSession.getUserSession().getUser();
+ }
+
+ public String getSessionState() {
+ return clientSession.getUserSession().getId();
+ }
+
+ public boolean isValid(RequiredAction requiredAction) {
+ return isValid(convertToAction(requiredAction));
+ }
+
+ public boolean isValid(ClientSessionModel.Action requestedAction) {
+ ClientSessionModel.Action action = clientSession.getAction();
+ if (action == null) {
+ return false;
+ }
+
+ int timestamp = clientSession.getTimestamp();
+
+ if (!action.equals(requestedAction)) {
+ return false;
+ }
+
+ int lifespan = action.equals(ClientSessionModel.Action.CODE_TO_TOKEN) ? realm.getAccessCodeLifespan() : realm.getAccessCodeLifespanUserAction();
+ return timestamp + lifespan > Time.currentTime();
+ }
+
+ public Set<RoleModel> getRequestedRoles() {
+ Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
+ for (String roleId : clientSession.getRoles()) {
+ RoleModel role = realm.getRoleById(roleId);
+ if (role == null) {
+ new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid role " + roleId);
+ }
+ requestedRoles.add(realm.getRoleById(roleId));
+ }
+ return requestedRoles;
+ }
+
+ public ClientModel getClient() {
+ return clientSession.getClient();
+ }
+
+ public String getState() {
+ return clientSession.getState();
+ }
+
+ public String getRedirectUri() {
+ return clientSession.getRedirectUri();
+ }
+
+ public ClientSessionModel.Action getAction() {
+ return clientSession.getAction();
+ }
+
+ public void setAction(ClientSessionModel.Action action) {
+ clientSession.setAction(action);
+ clientSession.setTimestamp(Time.currentTime());
+ }
+
+ public void setRequiredAction(RequiredAction requiredAction) {
+ setAction(convertToAction(requiredAction));
+ }
+
+ private ClientSessionModel.Action convertToAction(RequiredAction requiredAction) {
+ switch (requiredAction) {
+ case CONFIGURE_TOTP:
+ return ClientSessionModel.Action.CONFIGURE_TOTP;
+ case UPDATE_PASSWORD:
+ return ClientSessionModel.Action.UPDATE_PASSWORD;
+ case UPDATE_PROFILE:
+ return ClientSessionModel.Action.UPDATE_PROFILE;
+ case VERIFY_EMAIL:
+ return ClientSessionModel.Action.VERIFY_EMAIL;
+ default:
+ throw new IllegalArgumentException("Unknown required action " + requiredAction);
+ }
+ }
+
+ public String getCode() {
+ String hash = createSignatureHash(realm, clientSession);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(hash);
+ sb.append(".");
+ sb.append(Base64Url.encode(clientSession.getId().getBytes()));
+
+ return sb.toString();
+ }
+
+ private static String createSignatureHash(RealmModel realm, ClientSessionModel clientSession) {
+ try {
+ Signature signature = Signature.getInstance(RSAProvider.getJavaAlgorithm(Algorithm.RS256));
+ signature.initSign(realm.getPrivateKey());
+ signature.update(clientSession.getId().getBytes());
+ signature.update(ByteBuffer.allocate(4).putInt(clientSession.getTimestamp()));
+ if (clientSession.getAction() != null) {
+ signature.update(clientSession.getAction().toString().getBytes());
+ }
+ byte[] sign = signature.sign();
+
+ MessageDigest digest = MessageDigest.getInstance("sha-1");
+ digest.update(sign);
+ return Base64Url.encode(digest.digest());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index 727679e..16a8fca 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -10,13 +10,13 @@ import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
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.AccessCode;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -28,7 +28,6 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.UUID;
/**
* Stateful object that creates tokens and manages oauth access codes
@@ -39,22 +38,6 @@ import java.util.UUID;
public class TokenManager {
protected static final Logger logger = Logger.getLogger(TokenManager.class);
- public AccessCodeEntry parseCode(String code, KeycloakSession session, RealmModel realm) {
- try {
- JWSInput input = new JWSInput(code);
- if (!RSAProvider.verify(input, realm.getPublicKey())) {
- logger.error("Could not verify access code");
- return null;
- }
- AccessCode accessCode = input.readJsonContent(AccessCode.class);
- return new AccessCodeEntry(session, realm, accessCode);
- } catch (Exception e) {
- logger.error("error parsing access code", e);
- return null;
- }
-
- }
-
public static void applyScope(RoleModel role, RoleModel scope, Set<RoleModel> visited, Set<RoleModel> requested) {
if (visited.contains(scope)) return;
visited.add(scope);
@@ -69,30 +52,14 @@ public class TokenManager {
}
}
-
-
- public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, KeycloakSession keycloakSession, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
- return createAccessCodeEntry(scopeParam, state, redirect, keycloakSession, realm, client, user, session);
- }
-
- private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, KeycloakSession keycloakSession, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
- AccessCode code = new AccessCode();
- code.setId(UUID.randomUUID().toString() + System.currentTimeMillis());
- code.setClientId(client.getClientId());
- code.setUserId(user.getId());
- code.setTimestamp(Time.currentTime());
- code.setSessionState(session != null ? session.getId() : null);
- code.setRedirectUri(redirect);
- code.setState(state);
-
+ public AccessCode createAccessCode(String scopeParam, String state, String redirect, KeycloakSession session, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession) {
Set<String> requestedRoles = new HashSet<String>();
for (RoleModel r : getAccess(scopeParam, client, user)) {
requestedRoles.add(r.getId());
}
- code.setRequestedRoles(requestedRoles);
- AccessCodeEntry entry = new AccessCodeEntry(keycloakSession, realm, code);
- return entry;
+ ClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession, redirect, state, requestedRoles);
+ return new AccessCode(realm, clientSession);
}
public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index f3b751e..f0fddfa 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -40,12 +40,14 @@ import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationLinkModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -151,9 +153,20 @@ public class AccountService {
}
}
if (authResult != null) {
- if (authResult.getSession() != null) {
- authResult.getSession().associateClient(application);
+ UserSessionModel userSession = authResult.getSession();
+ if (userSession != null) {
+ boolean associated = false;
+ for (ClientSessionModel c : userSession.getClientSessions()) {
+ if (c.getClient().equals(application)) {
+ associated = true;
+ break;
+ }
+ }
+ if (!associated) {
+ session.sessions().createClientSession(realm, application, userSession, null, null, null);
+ }
}
+
account.setUser(auth.getUser());
AuthenticationLinkModel authLinkModel = auth.getUser().getAuthenticationLink();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 8c1cffa..52f8436 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -27,7 +27,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
-import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.managers.TokenManager;
@@ -820,7 +820,7 @@ public class UsersResource {
return Flows.errors().error("AccountProvider management not enabled", Response.Status.INTERNAL_SERVER_ERROR);
}
- AccessCodeEntry accessCode = tokenManager.createAccessCode(scope, state, redirect, session, realm, client, user, null);
+ AccessCode accessCode = tokenManager.createAccessCode(scope, state, redirect, session, realm, client, user, null);
accessCode.setRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
try {
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index b73c0fc..93cd489 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -30,6 +30,7 @@ import org.keycloak.audit.Details;
import org.keycloak.audit.EventType;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
@@ -37,9 +38,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.representations.AccessCode;
import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
@@ -82,7 +82,7 @@ public class OAuthFlows {
this.tokenManager = tokenManager;
}
- public Response redirectAccessCode(AccessCodeEntry accessCode, UserSessionModel userSession, String state, String redirect) {
+ public Response redirectAccessCode(AccessCode accessCode, UserSessionModel userSession, String state, String redirect) {
String code = accessCode.getCode();
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code);
log.debugv("redirectAccessCode: state: {0}", state);
@@ -122,7 +122,7 @@ public class OAuthFlows {
isEmailVerificationRequired(user);
boolean isResource = client instanceof ApplicationModel;
- AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session);
+ AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session);
log.debugv("processAccessCode: isResource: {0}", isResource);
log.debugv("processAccessCode: go to oauth page?: {0}",
@@ -144,7 +144,7 @@ public class OAuthFlows {
}
if (!isResource) {
- accessCode.setAction(AccessCode.Action.OAUTH_GRANT);
+ accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
@@ -165,6 +165,8 @@ public class OAuthFlows {
if (redirect != null) {
audit.success();
+
+ accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
return redirectAccessCode(accessCode, session, state, redirect);
} else {
return null;
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index 60988ca..9040045 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -34,6 +34,7 @@ import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
@@ -43,7 +44,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.ClientConnection;
-import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
@@ -106,7 +107,7 @@ public class RequiredActionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updateProfile(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
+ AccessCode accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PROFILE);
if (accessCode == null) {
return unauthorized();
}
@@ -144,7 +145,7 @@ public class RequiredActionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updateTotp(final MultivaluedMap<String, String> formData) {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
+ AccessCode accessCode = getAccessCodeEntry(RequiredAction.CONFIGURE_TOTP);
if (accessCode == null) {
return unauthorized();
}
@@ -182,7 +183,7 @@ public class RequiredActionsService {
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updatePassword(final MultivaluedMap<String, String> formData) {
logger.debug("updatePassword");
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
+ AccessCode accessCode = getAccessCodeEntry(RequiredAction.UPDATE_PASSWORD);
if (accessCode == null) {
logger.debug("updatePassword access code is null");
return unauthorized();
@@ -231,8 +232,8 @@ public class RequiredActionsService {
@GET
public Response emailVerification() {
if (uriInfo.getQueryParameters().containsKey("key")) {
- AccessCodeEntry accessCode = tokenManager.parseCode(uriInfo.getQueryParameters().getFirst("key"), session, realm);
- if (accessCode == null || accessCode.isExpired() || !RequiredAction.VERIFY_EMAIL.equals(accessCode.getRequiredAction())) {
+ AccessCode accessCode = AccessCode.parse(uriInfo.getQueryParameters().getFirst("key"), session, realm);
+ if (accessCode == null || !accessCode.isValid(RequiredAction.VERIFY_EMAIL)) {
return unauthorized();
}
@@ -248,13 +249,12 @@ public class RequiredActionsService {
return redirectOauth(user, accessCode);
} else {
- AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
+ AccessCode accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
if (accessCode == null) {
return unauthorized();
}
initAudit(accessCode);
- //audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
return Flows.forms(session, realm, uriInfo).setAccessCode(accessCode.getCode()).setUser(accessCode.getUser())
.createResponse(RequiredAction.VERIFY_EMAIL);
@@ -265,8 +265,8 @@ public class RequiredActionsService {
@GET
public Response passwordReset() {
if (uriInfo.getQueryParameters().containsKey("key")) {
- AccessCodeEntry accessCode = tokenManager.parseCode(uriInfo.getQueryParameters().getFirst("key"), session, realm);
- if (accessCode == null || accessCode.isExpired() || !RequiredAction.UPDATE_PASSWORD.equals(accessCode.getRequiredAction())) {
+ AccessCode accessCode = AccessCode.parse(uriInfo.getQueryParameters().getFirst("key"), session, realm);
+ if (accessCode == null || !accessCode.isValid(RequiredAction.UPDATE_PASSWORD)) {
return unauthorized();
}
@@ -317,7 +317,7 @@ public class RequiredActionsService {
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
audit.session(userSession);
- AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, session, realm, client, user, userSession);
+ AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, session, realm, client, user, userSession);
accessCode.setRequiredAction(RequiredAction.UPDATE_PASSWORD);
try {
@@ -339,38 +339,33 @@ public class RequiredActionsService {
return Flows.forms(session, realm, uriInfo).setSuccess("emailSent").createPasswordReset();
}
- private AccessCodeEntry getAccessCodeEntry(RequiredAction requiredAction) {
+ private AccessCode getAccessCodeEntry(RequiredAction requiredAction) {
String code = uriInfo.getQueryParameters().getFirst(OAuth2Constants.CODE);
if (code == null) {
logger.debug("getAccessCodeEntry code as not in query param");
return null;
}
- AccessCodeEntry accessCodeEntry = tokenManager.parseCode(code, session, realm);
- if (accessCodeEntry == null) {
+ AccessCode accessCode = AccessCode.parse(code, session, realm);
+ if (accessCode == null) {
logger.debug("getAccessCodeEntry access code entry null");
return null;
}
- if (accessCodeEntry.isExpired()) {
- logger.debugv("getAccessCodeEntry: access code id: {0}", accessCodeEntry.getCodeId());
- logger.debugv("getAccessCodeEntry access code entry expired");
- return null;
- }
-
- if (!requiredAction.equals(accessCodeEntry.getRequiredAction())) {
- logger.debugv("Invalid access code action: {0}", requiredAction);
+ if (!accessCode.isValid(requiredAction)) {
+ logger.debugv("getAccessCodeEntry: access code id: {0}", accessCode.getCodeId());
+ logger.debugv("getAccessCodeEntry access code not valid");
return null;
}
- return accessCodeEntry;
+ return accessCode;
}
- private UserModel getUser(AccessCodeEntry accessCode) {
+ private UserModel getUser(AccessCode accessCode) {
return session.users().getUserByUsername(accessCode.getUser().getUsername(), realm);
}
- private Response redirectOauth(UserModel user, AccessCodeEntry accessCode) {
+ private Response redirectOauth(UserModel user, AccessCode accessCode) {
if (accessCode == null) {
return null;
}
@@ -382,7 +377,7 @@ public class RequiredActionsService {
.createResponse(requiredActions.iterator().next());
} else {
logger.debugv("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
- accessCode.setAction(null);
+ accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
AuthenticationManager authManager = new AuthenticationManager();
@@ -400,7 +395,7 @@ public class RequiredActionsService {
}
}
- private void initAudit(AccessCodeEntry accessCode) {
+ private void initAudit(AccessCode accessCode) {
audit.event(EventType.LOGIN).client(accessCode.getClient())
.user(accessCode.getUser())
.session(accessCode.getSessionState())
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 917dd2d..926cb39 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -19,21 +19,20 @@ import org.keycloak.authentication.AuthenticationProviderManager;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.representations.AccessCode;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.ClientConnection;
-import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -43,6 +42,7 @@ import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
+import org.keycloak.util.Base64Url;
import org.keycloak.util.BasicAuthHelper;
import javax.ws.rs.Consumes;
@@ -281,7 +281,6 @@ public class TokenService {
String scope = form.getFirst(OAuth2Constants.SCOPE);
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false);
- userSession.associateClient(client);
audit.session(userSession);
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
@@ -623,10 +622,15 @@ public class TokenService {
throw new BadRequestException("Code not specified", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
}
-
-
- AccessCodeEntry accessCode = tokenManager.parseCode(code, session, realm);
+ AccessCode accessCode = AccessCode.parse(code, session, realm);
if (accessCode == null) {
+ String[] parts = code.split("\\.");
+ if (parts.length == 2) {
+ try {
+ audit.detail(Details.CODE_ID, new String(Base64Url.decode(parts[1])));
+ } catch (Throwable t) {
+ }
+ }
Map<String, String> res = new HashMap<String, String>();
res.put(OAuth2Constants.ERROR, "invalid_grant");
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code not found");
@@ -635,7 +639,7 @@ public class TokenService {
.build();
}
audit.detail(Details.CODE_ID, accessCode.getCodeId());
- if (accessCode.isExpired()) {
+ if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) {
Map<String, String> res = new HashMap<String, String>();
res.put(OAuth2Constants.ERROR, "invalid_grant");
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is expired");
@@ -643,14 +647,8 @@ public class TokenService {
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
.build();
}
- if (accessCode.getAction() != null) {
- Map<String, String> res = new HashMap<String, String>();
- res.put(OAuth2Constants.ERROR, "invalid_grant");
- res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is not active");
- audit.error(Errors.INVALID_CODE);
- return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
- .build();
- }
+
+ accessCode.setAction(null);
audit.user(accessCode.getUser());
audit.session(accessCode.getSessionState());
@@ -698,8 +696,6 @@ public class TokenService {
logger.debug("accessRequest SUCCESS");
- userSession.associateClient(client);
-
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
try {
@@ -982,22 +978,22 @@ public class TokenService {
String code = formData.getFirst(OAuth2Constants.CODE);
- AccessCodeEntry accessCodeEntry = tokenManager.parseCode(code, session, realm);
- if (accessCodeEntry == null || !AccessCode.Action.OAUTH_GRANT.equals(accessCodeEntry.getAction())) {
+ AccessCode accessCode = AccessCode.parse(code, session, realm);
+ if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
audit.error(Errors.INVALID_CODE);
- return oauth.forwardToSecurityFailure("Unknown access code.");
+ return oauth.forwardToSecurityFailure("Invalid access code.");
}
- audit.detail(Details.CODE_ID, accessCodeEntry.getCodeId());
+ audit.detail(Details.CODE_ID, accessCode.getCodeId());
- String redirect = accessCodeEntry.getRedirectUri();
- String state = accessCodeEntry.getState();
+ String redirect = accessCode.getRedirectUri();
+ String state = accessCode.getState();
- audit.client(accessCodeEntry.getClient())
- .user(accessCodeEntry.getUser())
+ audit.client(accessCode.getClient())
+ .user(accessCode.getUser())
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.REDIRECT_URI, redirect);
- UserSessionModel userSession = session.sessions().getUserSession(realm, accessCodeEntry.getSessionState());
+ UserSessionModel userSession = session.sessions().getUserSession(realm, accessCode.getSessionState());
if (userSession != null) {
audit.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
audit.detail(Details.USERNAME, userSession.getLoginUsername());
@@ -1020,8 +1016,8 @@ public class TokenService {
audit.success();
- accessCodeEntry.setAction(null);
- return oauth.redirectAccessCode(accessCodeEntry, userSession, state, redirect);
+ accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
+ return oauth.redirectAccessCode(accessCode, userSession, state, redirect);
}
@Path("oauth/oob")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index c51e89e..282823d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -22,6 +22,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.util.Time;
import java.util.HashMap;
import java.util.HashSet;
@@ -328,17 +329,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
}
public static Matcher<String> isCodeId() {
- return new TypeSafeMatcher<String>() {
- @Override
- protected boolean matchesSafely(String item) {
- return (UUID.randomUUID().toString() + System.currentTimeMillis()).length() == item.length();
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Not an Code ID");
- }
- };
+ return isUUID();
}
public static Matcher<String> isUUID() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index c089781..d7a29be 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -5,6 +5,7 @@ import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -13,7 +14,11 @@ import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.util.Time;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -59,7 +64,47 @@ public class UserSessionProviderTest {
assertSession(session.sessions().getUserSession(realm, sessions[0].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
assertSession(session.sessions().getUserSession(realm, sessions[1].getId()), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
- assertSession(session.sessions().getUserSession(realm, sessions[2].getId()), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started);
+ assertSession(session.sessions().getUserSession(realm, sessions[2].getId()), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+ }
+
+ @Test
+ public void testCreateClientSession() {
+ UserSessionModel[] sessions = createSessions();
+
+ List<ClientSessionModel> clientSessions = sessions[0].getClientSessions();
+ assertEquals(2, clientSessions.size());
+ ClientSessionModel session = clientSessions.get(0);
+
+ assertEquals(null, session.getAction());
+ assertEquals(realm.findClient("test-app").getClientId(), session.getClient().getClientId());
+ assertEquals(sessions[0].getId(), session.getUserSession().getId());
+ assertEquals("http://redirect", session.getRedirectUri());
+ assertEquals("state", session.getState());
+ assertEquals(2, session.getRoles().size());
+ assertTrue(session.getRoles().contains("one"));
+ assertTrue(session.getRoles().contains("two"));
+ }
+
+ @Test
+ public void testUpdateClientSession() {
+ UserSessionModel[] sessions = createSessions();
+
+ String id = sessions[0].getClientSessions().get(0).getId();
+
+ ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
+
+ int time = clientSession.getTimestamp();
+ assertEquals(null, clientSession.getAction());
+
+ clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN);
+ clientSession.setTimestamp(time + 10);
+
+ kc.stopSession(session, true);
+ session = kc.startSession();
+
+ ClientSessionModel updated = session.sessions().getClientSession(realm, id);
+ assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN, updated.getAction());
+ assertEquals(time + 10, updated.getTimestamp());
}
@Test
@@ -72,22 +117,108 @@ public class UserSessionProviderTest {
@Test
public void testRemoveUserSessionsByUser() {
- createSessions();
+ UserSessionModel[] sessions = createSessions();
+
+ List<String> clientSessionsRemoved = new LinkedList<String>();
+ List<String> clientSessionsKept = new LinkedList<String>();
+ for (UserSessionModel s : sessions) {
+ for (ClientSessionModel c : s.getClientSessions()) {
+ if (c.getUserSession().getUser().getUsername().equals("user1")) {
+ clientSessionsRemoved.add(c.getId());
+ } else {
+ clientSessionsKept.add(c.getId());
+ }
+ }
+ }
+
session.sessions().removeUserSessions(realm, session.users().getUserByUsername("user1", realm));
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+
+ for (String c : clientSessionsRemoved) {
+ assertNull(session.sessions().getClientSession(realm, c));
+ }
+ for (String c : clientSessionsKept) {
+ assertNotNull(session.sessions().getClientSession(realm, c));
+ }
+ }
+
+ @Test
+ public void testRemoveUserSession() {
+ UserSessionModel userSession = createSessions()[0];
+
+ List<String> clientSessionsRemoved = new LinkedList<String>();
+ for (ClientSessionModel c : userSession.getClientSessions()) {
+ clientSessionsRemoved.add(c.getId());
+ }
+
+ session.sessions().removeUserSession(realm, userSession);
+ resetSession();
+
+ assertNull(session.sessions().getUserSession(realm, userSession.getId()));
+ for (String c : clientSessionsRemoved) {
+ assertNull(session.sessions().getClientSession(realm, c));
+ }
}
@Test
public void testRemoveUserSessionsByRealm() {
- createSessions();
+ UserSessionModel[] sessions = createSessions();
+
+ List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();
+ for (UserSessionModel s : sessions) {
+ clientSessions.addAll(s.getClientSessions());
+ }
+
session.sessions().removeUserSessions(realm);
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+
+ for (ClientSessionModel c : clientSessions) {
+ assertNull(session.sessions().getClientSession(realm, c.getId()));
+ }
+ }
+
+ @Test
+ public void testOnClientRemoved() {
+ UserSessionModel[] sessions = createSessions();
+
+ List<String> clientSessionsRemoved = new LinkedList<String>();
+ List<String> clientSessionsKept = new LinkedList<String>();
+ for (UserSessionModel s : sessions) {
+ s = session.sessions().getUserSession(realm, s.getId());
+ for (ClientSessionModel c : s.getClientSessions()) {
+ if (c.getClient().getClientId().equals("third-party")) {
+ clientSessionsRemoved.add(c.getId());
+ } else {
+ clientSessionsKept.add(c.getId());
+ }
+ }
+ }
+
+ session.sessions().onClientRemoved(realm, realm.findClient("third-party"));
+ resetSession();
+
+ for (String c : clientSessionsRemoved) {
+ assertNull(session.sessions().getClientSession(realm, c));
+ }
+ for (String c : clientSessionsKept) {
+ assertNotNull(session.sessions().getClientSession(realm, c));
+ }
+
+ session.sessions().onClientRemoved(realm, realm.findClient("test-app"));
+ resetSession();
+
+ for (String c : clientSessionsRemoved) {
+ assertNull(session.sessions().getClientSession(realm, c));
+ }
+ for (String c : clientSessionsKept) {
+ assertNull(session.sessions().getClientSession(realm, c));
+ }
}
@Test
@@ -111,7 +242,7 @@ public class UserSessionProviderTest {
public void testGetByClient() {
UserSessionModel[] sessions = createSessions();
- assertSessions(session.sessions().getUserSessions(realm, realm.findClient("test-app")), sessions[0], sessions[1]);
+ assertSessions(session.sessions().getUserSessions(realm, realm.findClient("test-app")), sessions[0], sessions[1], sessions[2]);
assertSessions(session.sessions().getUserSessions(realm, realm.findClient("third-party")), sessions[0]);
}
@@ -120,7 +251,7 @@ public class UserSessionProviderTest {
for (int i = 0; i < 25; i++) {
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false);
userSession.setStarted(Time.currentTime() + i);
- userSession.associateClient(realm.findClient("test-app"));
+ session.sessions().createClientSession(realm, realm.findClient("test-app"), userSession, "http://redirect", "state", new HashSet<String>());
}
resetSession();
@@ -147,26 +278,30 @@ public class UserSessionProviderTest {
assertArrayEquals(expectedIps, actualIps);
}
-
-
@Test
public void testGetCountByClient() {
createSessions();
- assertEquals(2, session.sessions().getActiveUserSessions(realm, realm.findClient("test-app")));
+ assertEquals(3, session.sessions().getActiveUserSessions(realm, realm.findClient("test-app")));
assertEquals(1, session.sessions().getActiveUserSessions(realm, realm.findClient("third-party")));
}
private UserSessionModel[] createSessions() {
- UserSessionModel[] sessions = new UserSessionModel[4];
+ UserSessionModel[] sessions = new UserSessionModel[3];
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true);
- sessions[0].associateClient(realm.findClient("test-app"));
- sessions[0].associateClient(realm.findClient("third-party"));
+
+ Set<String> roles = new HashSet<String>();
+ roles.add("one");
+ roles.add("two");
+
+ session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
+ session.sessions().createClientSession(realm, realm.findClient("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>());
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true);
- sessions[1].associateClient(realm.findClient("test-app"));
+ session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>());
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true);
+ session.sessions().createClientSession(realm, realm.findClient("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>());
resetSession();
@@ -205,9 +340,9 @@ public class UserSessionProviderTest {
assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);
- String[] actualClients = new String[session.getClientAssociations().size()];
+ String[] actualClients = new String[session.getClientSessions().size()];
for (int i = 0; i < actualClients.length; i++) {
- actualClients[i] = session.getClientAssociations().get(i).getClientId();
+ actualClients[i] = session.getClientSessions().get(i).getClient().getClientId();
}
Arrays.sort(clients);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index b47a84b..ba7c41d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -29,7 +29,9 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Details;
import org.keycloak.audit.Errors;
import org.keycloak.audit.Event;
+import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
@@ -45,6 +47,7 @@ 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.assertNotNull;
import static org.junit.Assert.assertNull;
/**
@@ -72,13 +75,6 @@ public class AccessTokenTest {
@Test
public void accessTokenRequest() throws Exception {
- keycloakRule.update(new KeycloakRule.KeycloakSetup() {
- @Override
- public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- appRealm.setAccessCodeLifespan(1);
- }
- });
-
oauth.doLogin("test-user@localhost", "password");
Event loginEvent = events.expectLogin().assertEvent();
@@ -112,22 +108,6 @@ public class AccessTokenTest {
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());
-
- Thread.sleep(2000);
- response = oauth.doAccessTokenRequest(code, "password");
- Assert.assertEquals(400, response.getStatusCode());
-
- AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null);
- expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null);
- expectedEvent.assertEvent();
-
- keycloakRule.update(new KeycloakRule.KeycloakSetup() {
- @Override
- public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- appRealm.setAccessCodeLifespan(60);
- }
- });
-
}
@Test
@@ -162,10 +142,109 @@ public class AccessTokenTest {
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.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).client((String) null).user((String) null).session((String) null).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent();
events.clear();
}
+ @Test
+ public void accessTokenCodeExpired() {
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAccessCodeLifespan(1);
+ }
+ });
+
+ oauth.doLogin("test-user@localhost", "password");
+
+ Event loginEvent = events.expectLogin().assertEvent();
+
+ String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+ loginEvent.getSessionId();
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+ Assert.assertEquals(400, response.getStatusCode());
+
+ AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null);
+ expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null);
+ expectedEvent.assertEvent();
+
+ events.clear();
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAccessCodeLifespan(60);
+ }
+ });
+ }
+
+ @Test
+ public void accessTokenCodeUsed() {
+ oauth.doLogin("test-user@localhost", "password");
+
+ Event loginEvent = events.expectLogin().assertEvent();
+
+ String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+ loginEvent.getSessionId();
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+ Assert.assertEquals(200, response.getStatusCode());
+
+ events.clear();
+
+ response = oauth.doAccessTokenRequest(code, "password");
+ Assert.assertEquals(400, response.getStatusCode());
+
+ AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null);
+ expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null);
+ expectedEvent.assertEvent();
+
+ events.clear();
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAccessCodeLifespan(60);
+ }
+ });
+ }
+
+ @Test
+ public void accessTokenCodeHasRequiredAction() {
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
+ UserModel user = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
+ user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
+ }
+ });
+
+ oauth.doLogin("test-user@localhost", "password");
+
+ String code = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
+
+ OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+ Assert.assertEquals(400, response.getStatusCode());
+
+ Event event = events.poll();
+ assertNotNull(event.getDetails().get(Details.CODE_ID));
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ manager.getSession().users().getUserByUsername("test-user@localhost", appRealm).removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
+ }
+ });
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 41f11f0..98c2270 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -23,6 +23,7 @@ package org.keycloak.testsuite.oauth;
import org.junit.Assert;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
@@ -30,7 +31,7 @@ import org.keycloak.audit.Details;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
-import org.keycloak.representations.AccessCode;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
@@ -44,6 +45,8 @@ import org.openqa.selenium.WebDriver;
import java.io.IOException;
+import static org.junit.Assert.assertEquals;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -75,14 +78,13 @@ public class AuthorizationCodeTest {
Assert.assertTrue(response.isRedirected());
Assert.assertNotNull(response.getCode());
- Assert.assertEquals("mystate", response.getState());
+ assertEquals("mystate", response.getState());
Assert.assertNull(response.getError());
- oauth.verifyCode(response.getCode());
+ keycloakRule.verifyCode(response.getCode());
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
- AccessCode accessCode = new JWSInput(response.getCode()).readJsonContent(AccessCode.class);
- Assert.assertEquals(codeId,accessCode.getId());
+ assertCode(codeId, response.getCode());
}
@Test
@@ -101,11 +103,10 @@ public class AuthorizationCodeTest {
Assert.assertTrue(title.startsWith("Success code="));
String code = driver.findElement(By.id(OAuth2Constants.CODE)).getText();
- oauth.verifyCode(code);
+ keycloakRule.verifyCode(code);
String codeId = events.expectLogin().detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
- AccessCode accessCode = new JWSInput(code).readJsonContent(AccessCode.class);
- Assert.assertEquals(codeId,accessCode.getId());
+ assertCode(codeId, code);
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
@@ -132,7 +133,7 @@ public class AuthorizationCodeTest {
Assert.assertTrue(title.equals("Error error=access_denied"));
String error = driver.findElement(By.id(OAuth2Constants.ERROR)).getText();
- Assert.assertEquals("access_denied", error);
+ assertEquals("access_denied", error);
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
@@ -160,11 +161,10 @@ public class AuthorizationCodeTest {
Assert.assertTrue(response.isRedirected());
Assert.assertNotNull(response.getCode());
- oauth.verifyCode(response.getCode());
+ keycloakRule.verifyCode(response.getCode());
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
- AccessCode accessCode = new JWSInput(response.getCode()).readJsonContent(AccessCode.class);
- Assert.assertEquals(codeId,accessCode.getId());
+ assertCode(codeId, response.getCode());
}
@Test
@@ -176,11 +176,15 @@ public class AuthorizationCodeTest {
Assert.assertNull(response.getState());
Assert.assertNull(response.getError());
- oauth.verifyCode(response.getCode());
+ keycloakRule.verifyCode(response.getCode());
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
- AccessCode accessCode = new JWSInput(response.getCode()).readJsonContent(AccessCode.class);
- Assert.assertEquals(codeId,accessCode.getId());
+ assertCode(codeId, response.getCode());
+ }
+
+ private void assertCode(String expectedCodeId, String actualCode) {
+ AccessCode code = keycloakRule.verifyCode(actualCode);
+ assertEquals(expectedCodeId, code.getCodeId());
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 4d7c211..3a3f6c8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -38,8 +38,11 @@ import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.resources.TokenService;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.PemUtils;
@@ -217,12 +220,6 @@ public class OAuthClient {
}
}
- public void verifyCode(String code) {
- if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
- throw new RuntimeException("Failed to verify code");
- }
- }
-
public RefreshToken verifyRefreshToken(String refreshToken) {
try {
JWSInput jws = new JWSInput(refreshToken);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index 1e30a4b..f108a63 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -21,10 +21,12 @@
*/
package org.keycloak.testsuite.rule;
+import org.junit.Assert;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.services.managers.AccessCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.ApplicationServlet;
@@ -107,6 +109,24 @@ public class KeycloakRule extends AbstractKeycloakRule {
stopSession(session, true);
}
+ public AccessCode verifyCode(String code) {
+ KeycloakSession session = startSession();
+ try {
+ RealmModel realm = session.realms().getRealm("test");
+ try {
+ AccessCode accessCode = AccessCode.parse(code, session, realm);
+ if (accessCode == null) {
+ Assert.fail("Invalid code");
+ }
+ return accessCode;
+ } catch (Throwable t) {
+ throw new AssertionError("Failed to parse code", t);
+ }
+ } finally {
+ stopSession(session, false);
+ }
+ }
+
public abstract static class KeycloakSetup {
protected KeycloakSession session;