Remove access token from access code, and create token when code is exchanged for token

This commit is contained in:
Stian Thorgersen 2014-07-18 09:48:19 +01:00
parent 3514d5cd78
commit b196d0dded
9 changed files with 150 additions and 129 deletions

View file

@ -1,6 +1,5 @@
package org.keycloak.representations; package org.keycloak.representations;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
/** /**
@ -10,15 +9,18 @@ import java.util.Set;
*/ */
public class AccessCode { public class AccessCode {
protected String id; protected String id;
protected String clientId;
protected String userId;
protected String usernameUsed; protected String usernameUsed;
protected String state; protected String state;
protected String sessionState;
protected String redirectUri; protected String redirectUri;
protected boolean rememberMe; protected boolean rememberMe;
protected String authMethod; protected String authMethod;
protected int timestamp; protected int timestamp;
protected int expiration; protected int expiration;
protected AccessToken accessToken;
protected Set<String> requiredActions; protected Set<String> requiredActions;
protected Set<String> requestedRoles;
public String getId() { public String getId() {
return id; return id;
@ -28,6 +30,22 @@ public class AccessCode {
this.id = id; this.id = id;
} }
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getState() { public String getState() {
return state; return state;
} }
@ -36,6 +54,14 @@ public class AccessCode {
this.state = state; this.state = state;
} }
public String getSessionState() {
return sessionState;
}
public void setSessionState(String sessionState) {
this.sessionState = sessionState;
}
public String getRedirectUri() { public String getRedirectUri() {
return redirectUri; return redirectUri;
} }
@ -68,14 +94,6 @@ public class AccessCode {
this.expiration = expiration; this.expiration = expiration;
} }
public AccessToken getAccessToken() {
return accessToken;
}
public void setAccessToken(AccessToken accessToken) {
this.accessToken = accessToken;
}
public int getTimestamp() { public int getTimestamp() {
return timestamp; return timestamp;
} }
@ -99,4 +117,12 @@ public class AccessCode {
public void setUsernameUsed(String usernameUsed) { public void setUsernameUsed(String usernameUsed) {
this.usernameUsed = usernameUsed; this.usernameUsed = usernameUsed;
} }
public Set<String> getRequestedRoles() {
return requestedRoles;
}
public void setRequestedRoles(Set<String> requestedRoles) {
this.requestedRoles = requestedRoles;
}
} }

View file

@ -1,9 +1,11 @@
package org.keycloak.services.managers; package org.keycloak.services.managers;
import org.keycloak.OAuthErrorException;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.representations.AccessCode; import org.keycloak.representations.AccessCode;
@ -33,29 +35,45 @@ public class AccessCodeEntry {
} }
public UserModel getUser() { public UserModel getUser() {
return keycloakSession.users().getUserById(accessCode.getAccessToken().getSubject(), realm); return keycloakSession.users().getUserById(accessCode.getUserId(), realm);
} }
public String getSessionState() { public String getSessionState() {
return accessCode.getAccessToken().getSessionState(); return accessCode.getSessionState();
}
public void setSessionState(String state) {
accessCode.setSessionState(state);
} }
public boolean isExpired() { public boolean isExpired() {
return accessCode.getExpiration() != 0 && Time.currentTime() > accessCode.getExpiration(); return accessCode.getExpiration() != 0 && Time.currentTime() > accessCode.getExpiration();
} }
public AccessToken getToken() { public Set<RoleModel> getRequestedRoles() {
return accessCode.getAccessToken(); Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
for (String roleId : accessCode.getRequestedRoles()) {
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() { public ClientModel getClient() {
return realm.findClient(accessCode.getAccessToken().getIssuedFor()); return realm.findClient(accessCode.getClientId());
} }
public String getState() { public String getState() {
return accessCode.getState(); return accessCode.getState();
} }
public void setState(String state) {
accessCode.setState(state);
}
public String getRedirectUri() { public String getRedirectUri() {
return accessCode.getRedirectUri(); return accessCode.getRedirectUri();
} }

View file

@ -1,7 +1,6 @@
package org.keycloak.services.managers; package org.keycloak.services.managers;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.audit.Audit; import org.keycloak.audit.Audit;
import org.keycloak.audit.Details; import org.keycloak.audit.Details;
@ -24,12 +23,9 @@ import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -80,21 +76,24 @@ public class TokenManager {
} }
private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, KeycloakSession keycloakSession, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) { private AccessCodeEntry createAccessCodeEntry(String scopeParam, String state, String redirect, KeycloakSession keycloakSession, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
List<RoleModel> realmRolesRequested = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRolesRequested = new MultivaluedMapImpl<String, RoleModel>();
AccessToken token = createClientAccessToken(scopeParam, realm, client, user, session, realmRolesRequested, resourceRolesRequested);
if (session != null) token.setSessionState(session.getId());
AccessCode code = new AccessCode(); AccessCode code = new AccessCode();
code.setId(UUID.randomUUID().toString() + System.currentTimeMillis()); code.setId(UUID.randomUUID().toString() + System.currentTimeMillis());
code.setAccessToken(token); code.setClientId(client.getClientId());
code.setUserId(user.getId());
code.setTimestamp(Time.currentTime()); code.setTimestamp(Time.currentTime());
code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan()); code.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
code.setState(state); code.setSessionState(session != null ? session.getId() : null);
code.setRedirectUri(redirect); code.setRedirectUri(redirect);
code.setState(state);
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); AccessCodeEntry entry = new AccessCodeEntry(keycloakSession, realm, code);
return entry; return entry;
} }
public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException { public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
@ -142,11 +141,54 @@ public class TokenManager {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
} }
verifyAccess(refreshToken, realm, client, user);
AccessToken accessToken = initToken(realm, client, user, userSession);
accessToken.setRealmAccess(refreshToken.getRealmAccess());
accessToken.setResourceAccess(refreshToken.getResourceAccess());
// only refresh session if next token refresh will be after idle timeout
if (currentTime + realm.getAccessTokenLifespan() > userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout()) {
userSession.setLastSessionRefresh(currentTime);
}
return accessToken;
}
public AccessToken createClientAccessToken(Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
AccessToken token = initToken(realm, client, user, session);
for (RoleModel role : requestedRoles) {
addComposites(token, role);
}
return token;
}
public Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
Set<RoleModel> roleMappings = user.getRoleMappings();
Set<RoleModel> scopeMappings = client.getScopeMappings();
if (client instanceof ApplicationModel) {
scopeMappings.addAll(((ApplicationModel) client).getRoles());
}
for (RoleModel role : roleMappings) {
for (RoleModel desiredRole : scopeMappings) {
Set<RoleModel> visited = new HashSet<RoleModel>();
applyScope(role, desiredRole, visited, requestedRoles);
}
}
return requestedRoles;
}
public void verifyAccess(AccessToken token, RealmModel realm, ClientModel client, UserModel user) throws OAuthErrorException {
ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null; ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
if (refreshToken.getRealmAccess() != null) { if (token.getRealmAccess() != null) {
for (String roleName : refreshToken.getRealmAccess().getRoles()) { for (String roleName : token.getRealmAccess().getRoles()) {
RoleModel role = realm.getRole(roleName); RoleModel role = realm.getRole(roleName);
if (role == null) { if (role == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid realm role " + roleName);
@ -159,8 +201,8 @@ public class TokenManager {
} }
} }
} }
if (refreshToken.getResourceAccess() != null) { if (token.getResourceAccess() != null) {
for (Map.Entry<String, AccessToken.Access> entry : refreshToken.getResourceAccess().entrySet()) { for (Map.Entry<String, AccessToken.Access> entry : token.getResourceAccess().entrySet()) {
ApplicationModel app = realm.getApplicationByName(entry.getKey()); ApplicationModel app = realm.getApplicationByName(entry.getKey());
if (app == null) { if (app == null) {
throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName()); throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "Application no longer exists", "Application no longer exists: " + app.getName());
@ -180,67 +222,6 @@ public class TokenManager {
} }
} }
AccessToken accessToken = initToken(realm, client, user, userSession);
accessToken.setRealmAccess(refreshToken.getRealmAccess());
accessToken.setResourceAccess(refreshToken.getResourceAccess());
// only refresh session if next token refresh will be after idle timeout
if (currentTime + realm.getAccessTokenLifespan() > userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout()) {
userSession.setLastSessionRefresh(currentTime);
}
return accessToken;
}
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) {
return createClientAccessToken(scopeParam, realm, client, user, session, new LinkedList<RoleModel>(), new MultivaluedMapImpl<String, RoleModel>());
}
public AccessToken createClientAccessToken(String scopeParam, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
Set<RoleModel> roleMappings = user.getRoleMappings();
Set<RoleModel> scopeMappings = client.getScopeMappings();
ApplicationModel clientApp = (client instanceof ApplicationModel) ? (ApplicationModel)client : null;
Set<RoleModel> clientAppRoles = clientApp == null ? null : clientApp.getRoles();
if (clientAppRoles != null) scopeMappings.addAll(clientAppRoles);
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
for (RoleModel role : roleMappings) {
if (clientApp != null && role.getContainer().equals(clientApp)) requestedRoles.add(role);
for (RoleModel desiredRole : scopeMappings) {
Set<RoleModel> visited = new HashSet<RoleModel>();
applyScope(role, desiredRole, visited, requestedRoles);
}
}
for (RoleModel role : requestedRoles) {
if (role.getContainer() instanceof RealmModel) {
realmRolesRequested.add(role);
} else if (role.getContainer() instanceof ApplicationModel) {
ApplicationModel app = (ApplicationModel)role.getContainer();
resourceRolesRequested.add(app.getName(), role);
}
}
AccessToken token = initToken(realm, client, user, session);
if (realmRolesRequested.size() > 0) {
for (RoleModel role : realmRolesRequested) {
addComposites(token, role);
}
}
if (resourceRolesRequested.size() > 0) {
for (List<RoleModel> roles : resourceRolesRequested.values()) {
for (RoleModel role : roles) {
addComposites(token, role);
}
}
}
return token;
} }
public void initClaims(IDToken token, ClientModel model, UserModel user) { public void initClaims(IDToken token, ClientModel model, UserModel user) {
@ -363,7 +344,8 @@ public class TokenManager {
} }
public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user, UserSessionModel session) { public AccessTokenResponseBuilder generateAccessToken(String scopeParam, ClientModel client, UserModel user, UserSessionModel session) {
accessToken = createClientAccessToken(scopeParam, realm, client, user, session); Set<RoleModel> requestedRoles = getAccess(scopeParam, client, user);
accessToken = createClientAccessToken(requestedRoles, realm, client, user, session);
return this; return this;
} }

View file

@ -227,7 +227,7 @@ public class RequiredActionsService {
// Password reset through email won't have an associated session // Password reset through email won't have an associated session
if (accessCode.getSessionState() == null) { if (accessCode.getSessionState() == null) {
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserById(accessCode.getUser().getId(), realm), clientConnection.getRemoteAddr()); UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserById(accessCode.getUser().getId(), realm), clientConnection.getRemoteAddr());
accessCode.getToken().setSessionState(userSession.getId()); accessCode.setSessionState(userSession.getId());
audit.session(userSession); audit.session(userSession);
} }

View file

@ -23,6 +23,7 @@ import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
@ -641,14 +642,6 @@ public class TokenService {
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res) return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
.build(); .build();
} }
if (!accessCode.getToken().isActive()) {
Map<String, String> res = new HashMap<String, String>();
res.put(OAuth2Constants.ERROR, "invalid_grant");
res.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
audit.error(Errors.INVALID_CODE);
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
.build();
}
audit.user(accessCode.getUser()); audit.user(accessCode.getUser());
audit.session(accessCode.getSessionState()); audit.session(accessCode.getSessionState());
@ -698,8 +691,20 @@ public class TokenService {
userSession.associateClient(client); userSession.associateClient(client);
AccessToken token = tokenManager.createClientAccessToken(accessCode.getRequestedRoles(), realm, client, user, userSession);
try {
tokenManager.verifyAccess(token, realm, client, user);
} catch (OAuthErrorException e) {
Map<String, String> error = new HashMap<String, String>();
error.put(OAuth2Constants.ERROR, e.getError());
if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
audit.error(Errors.INVALID_CODE);
return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
}
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit) AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
.accessToken(accessCode.getToken()) .accessToken(token)
.generateIDToken() .generateIDToken()
.generateRefreshToken().build(); .generateRefreshToken().build();

View file

@ -157,32 +157,22 @@ public class OAuthFlows {
if (!isResource) { if (!isResource) {
accessCode.resetExpiration(); accessCode.resetExpiration();
List<RoleModel> realmRolesRequested = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> appRolesRequested = new MultivaluedMapImpl<String, RoleModel>();
if (accessCode.getToken().getRealmAccess() != null) {
if (accessCode.getToken().getRealmAccess().getRoles() != null) {
for (String role : accessCode.getToken().getRealmAccess().getRoles()) {
RoleModel roleModel = realm.getRole(role);
if (roleModel != null) realmRolesRequested.add(roleModel);
}
}
}
if (accessCode.getToken().getResourceAccess().size() > 0) {
for (Map.Entry<String, AccessToken.Access> entry : accessCode.getToken().getResourceAccess().entrySet()) {
ApplicationModel app = realm.getApplicationByName(entry.getKey());
if (app == null) continue;
if (entry.getValue().getRoles() != null) {
for (String role : entry.getValue().getRoles()) {
RoleModel roleModel = app.getRole(role);
if (roleModel != null) appRolesRequested.add(entry.getKey(), roleModel);
}
} List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
for (RoleModel r : accessCode.getRequestedRoles()) {
if (r.getContainer() instanceof RealmModel) {
realmRoles.add(r);
} else {
resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r);
} }
} }
return Flows.forms(this.session, realm, uriInfo).setAccessCode(accessCode.getCode()).
setAccessRequest(realmRolesRequested, appRolesRequested). return Flows.forms(this.session, realm, uriInfo)
setClient(client).createOAuthGrant(); .setAccessCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles)
.setClient(client)
.createOAuthGrant();
} }
if (redirect != null) { if (redirect != null) {

View file

@ -101,7 +101,7 @@ public class AdapterTest {
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm); UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null); UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession); AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
return tm.encodeToken(adminRealm, token); return tm.encodeToken(adminRealm, token);
} finally { } finally {
keycloakRule.stopSession(session, true); keycloakRule.stopSession(session, true);

View file

@ -88,7 +88,7 @@ public class RelativeUriAdapterTest {
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm); UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null); UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession); AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
adminToken = tm.encodeToken(adminRealm, token); adminToken = tm.encodeToken(adminRealm, token);
} }

View file

@ -80,7 +80,7 @@ public class AdminAPITest {
TokenManager tm = new TokenManager(); TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm); UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null); UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, null);
AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin, userSession); AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
return tm.encodeToken(adminRealm, token); return tm.encodeToken(adminRealm, token);
} finally { } finally {
keycloakRule.stopSession(session, true); keycloakRule.stopSession(session, true);