Skip to content

Commit

Permalink
KEYCLOAK-5490
Browse files Browse the repository at this point in the history
  • Loading branch information
patriot1burke committed Sep 15, 2017
1 parent b7af96a commit affeadf
Show file tree
Hide file tree
Showing 24 changed files with 430 additions and 413 deletions.
Expand Up @@ -94,7 +94,11 @@ public Response exchangeNotSupported() {
}

public Response exchangeNotLinked(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token) {
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "invalid_target");
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "identity provider is not linked");
}

public Response exchangeNotLinkedNoStore(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token) {
return exchangeErrorResponse(uriInfo, authorizedClient, tokenUserSession, token, "identity provider is not linked, can only link to current user session");
}

protected Response exchangeErrorResponse(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, AccessToken token, String reason) {
Expand Down
Expand Up @@ -29,7 +29,7 @@
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface TokenExchangeTo {
public interface ExchangeTokenToIdentityProviderToken {
/**
*
* @param authorizedClient client requesting exchange
Expand All @@ -39,5 +39,5 @@ public interface TokenExchangeTo {
* @param params form parameters received for requested exchange
* @return
*/
Response exchangeTo(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params);
Response exchangeFromToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params);
}
Expand Up @@ -24,7 +24,7 @@
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.TokenExchangeTo;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.Details;
Expand Down Expand Up @@ -62,7 +62,7 @@
/**
* @author Pedro Igor
*/
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> implements TokenExchangeTo {
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> implements ExchangeTokenToIdentityProviderToken {
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);

public static final String OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
Expand Down Expand Up @@ -148,15 +148,15 @@ protected String extractTokenFromResponse(String response, String tokenName) {
}

@Override
public Response exchangeTo(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params) {
public Response exchangeFromToken(UriInfo uriInfo, ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params) {
String requestedType = params.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
if (requestedType != null && !requestedType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)) {
return exchangeUnsupportedRequiredType();
}
if (!getConfig().isStoreToken()) {
String brokerId = tokenUserSession.getNote(Details.IDENTITY_PROVIDER);
if (brokerId == null || !brokerId.equals(getConfig().getAlias())) {
return exchangeNotSupported();
return exchangeNotLinkedNoStore(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
}
return exchangeSessionToken(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
} else {
Expand Down Expand Up @@ -317,7 +317,9 @@ public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_P
BrokeredIdentityContext federatedIdentity = getFederatedIdentity(response);

if (getConfig().isStoreToken()) {
federatedIdentity.setToken(response);
// make sure that token wasn't already set by getFederatedIdentity();
// want to be able to allow provider to set the token itself.
if (federatedIdentity.getToken() == null)federatedIdentity.setToken(response);
}

federatedIdentity.setIdpConfig(getConfig());
Expand Down
Expand Up @@ -231,9 +231,10 @@ protected Response exchangeStoredToken(UriInfo uriInfo, ClientModel authorizedCl
return exchangeNotLinked(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
}
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(model.getToken(), AccessTokenResponse.class);
Long exp = (Long)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
if (exp != null && (long)exp < Time.currentTime()) {
String modelTokenString = model.getToken();
AccessTokenResponse tokenResponse = JsonSerialization.readValue(modelTokenString, AccessTokenResponse.class);
Integer exp = (Integer)tokenResponse.getOtherClaims().get(ACCESS_TOKEN_EXPIRATION);
if (exp != null && exp < Time.currentTime()) {
if (tokenResponse.getRefreshToken() == null) {
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
}
Expand All @@ -243,19 +244,20 @@ protected Response exchangeStoredToken(UriInfo uriInfo, ClientModel authorizedCl
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
if (response.contains("error")) {
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
model.setToken(null);
session.users().updateFederatedIdentity(authorizedClient.getRealm(), tokenSubject, model);
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
}
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
if (newResponse.getExpiresIn() > 0) {
long accessTokenExpiration = Time.currentTime() + newResponse.getExpiresIn();
int accessTokenExpiration = Time.currentTime() + (int)newResponse.getExpiresIn();
newResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
response = JsonSerialization.writeValueAsString(newResponse);
}
String oldToken = tokenUserSession.getNote(FEDERATED_ACCESS_TOKEN);
if (oldToken != null && oldToken.equals(tokenResponse.getToken())) {
long accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + newResponse.getExpiresIn() : 0;
int accessTokenExpiration = newResponse.getExpiresIn() > 0 ? Time.currentTime() + (int)newResponse.getExpiresIn() : 0;
tokenUserSession.setNote(FEDERATED_TOKEN_EXPIRATION, Long.toString(accessTokenExpiration));
tokenUserSession.setNote(FEDERATED_REFRESH_TOKEN, newResponse.getRefreshToken());
tokenUserSession.setNote(FEDERATED_ACCESS_TOKEN, newResponse.getToken());
Expand Down Expand Up @@ -302,6 +304,7 @@ protected Response exchangeSessionToken(UriInfo uriInfo, ClientModel authorizedC
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret()).asString();
if (response.contains("error")) {
logger.debugv("Error refreshing token, refresh token expiration?: {0}", response);
return exchangeTokenExpired(uriInfo, authorizedClient, tokenUserSession, tokenSubject, token);
}
AccessTokenResponse newResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
Expand Down Expand Up @@ -341,13 +344,11 @@ public BrokeredIdentityContext getFederatedIdentity(String response) {
BrokeredIdentityContext identity = extractIdentity(tokenResponse, accessToken, idToken);

if (getConfig().isStoreToken()) {
String response1 = response;
if (tokenResponse.getExpiresIn() > 0) {
long accessTokenExpiration = Time.currentTime() + tokenResponse.getExpiresIn();
tokenResponse.getOtherClaims().put(ACCESS_TOKEN_EXPIRATION, accessTokenExpiration);
response1 = JsonSerialization.writeValueAsString(tokenResponse);
response = JsonSerialization.writeValueAsString(tokenResponse);
}
response = response1;
identity.setToken(response);
}

Expand Down
Expand Up @@ -24,7 +24,7 @@
import org.keycloak.OAuthErrorException;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.TokenExchangeTo;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.common.util.Base64Url;
Expand All @@ -39,7 +39,6 @@
import org.keycloak.models.IdentityProviderModel;
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.protocol.oidc.OIDCLoginProtocol;
Expand Down Expand Up @@ -69,7 +68,6 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -587,7 +585,7 @@ public Response buildTokenExchange() {
String requestedIssuer = formParams.getFirst(OAuth2Constants.REQUESTED_ISSUER);

if (requestedIssuer == null) {
return exchangeClientToClient(authResult);
return exchangeClientToClient(authResult.getUser(), authResult.getSession());
} else {
return exchangeToIdentityProvider(authResult, requestedIssuer);
}
Expand All @@ -601,7 +599,7 @@ public Response exchangeToIdentityProvider(AuthenticationManager.AuthResult auth
}

IdentityProvider provider = IdentityBrokerService.getIdentityProvider(session, realm, requestedIssuer);
if (!(provider instanceof TokenExchangeTo)) {
if (!(provider instanceof ExchangeTokenToIdentityProviderToken)) {
event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Issuer does not support token exchange", Response.Status.BAD_REQUEST);
}
Expand All @@ -610,12 +608,12 @@ public Response exchangeToIdentityProvider(AuthenticationManager.AuthResult auth
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
}
Response response = ((TokenExchangeTo)provider).exchangeTo(uriInfo, client, authResult.getSession(), authResult.getUser(), authResult.getToken(), formParams);
Response response = ((ExchangeTokenToIdentityProviderToken)provider).exchangeFromToken(uriInfo, client, authResult.getSession(), authResult.getUser(), authResult.getToken(), formParams);
return Cors.add(request, Response.fromResponse(response)).auth().allowedOrigins(uriInfo, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();

}

public Response exchangeClientToClient(AuthenticationManager.AuthResult subject) {
protected Response exchangeClientToClient(UserModel targetUser, UserSessionModel targetUserSession) {
String requestedTokenType = formParams.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
if (requestedTokenType == null) {
requestedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
Expand Down Expand Up @@ -653,25 +651,19 @@ public Response exchangeClientToClient(AuthenticationManager.AuthResult subject)
String scope = formParams.getFirst(OAuth2Constants.SCOPE);

AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
authSession.setAuthenticatedUser(subject.getUser());
authSession.setAuthenticatedUser(targetUser);
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);

UserSessionModel userSession = subject.getSession();
event.session(userSession);
event.session(targetUserSession);

AuthenticationManager.setRolesAndMappersInSession(authSession);
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(session, userSession, authSession);

// Notes about client details
userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, clientConnection.getRemoteAddr());
AuthenticatedClientSessionModel clientSession = TokenManager.attachAuthenticationSession(this.session, targetUserSession, authSession);

updateUserSessionFromClientAuth(userSession);
updateUserSessionFromClientAuth(targetUserSession);

TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, this.session, targetUserSession, clientSession)
.generateAccessToken();
responseBuilder.getAccessToken().issuedFor(client.getClientId());

Expand Down
Expand Up @@ -27,7 +27,7 @@
public interface AdminPermissionManagement {
public static final String MANAGE_SCOPE = "manage";
public static final String VIEW_SCOPE = "view";
public static final String EXCHANGE_TO_SCOPE="exchange-to";
public static final String TOKEN_EXCHANGE ="token-exchange";

ClientModel getRealmManagementClient();

Expand Down
Expand Up @@ -40,7 +40,7 @@
import java.util.Map;
import java.util.Set;

import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.EXCHANGE_TO_SCOPE;
import static org.keycloak.services.resources.admin.permissions.AdminPermissionManagement.TOKEN_EXCHANGE;

/**
* Manages default policies for all users.
Expand Down Expand Up @@ -87,7 +87,7 @@ private String getMapRolesCompositePermissionName(ClientModel client) {
}

private String getExchangeToPermissionName(ClientModel client) {
return EXCHANGE_TO_SCOPE + ".permission.client." + client.getId();
return TOKEN_EXCHANGE + ".permission.client." + client.getId();
}

private void initialize(ClientModel client) {
Expand All @@ -107,7 +107,7 @@ private void initialize(ClientModel client) {
Scope mapRoleClientScope = root.initializeScope(MAP_ROLES_CLIENT_SCOPE, server);
Scope mapRoleCompositeScope = root.initializeScope(MAP_ROLES_COMPOSITE_SCOPE, server);
Scope configureScope = root.initializeScope(CONFIGURE_SCOPE, server);
Scope exchangeToScope = root.initializeScope(EXCHANGE_TO_SCOPE, server);
Scope exchangeToScope = root.initializeScope(TOKEN_EXCHANGE, server);

String resourceName = getResourceName(client);
Resource resource = authz.getStoreFactory().getResourceStore().findByName(resourceName, server.getId());
Expand Down Expand Up @@ -207,7 +207,7 @@ private Scope manageScope(ResourceServer server) {
}

private Scope exchangeToScope(ResourceServer server) {
return authz.getStoreFactory().getScopeStore().findByName(EXCHANGE_TO_SCOPE, server.getId());
return authz.getStoreFactory().getScopeStore().findByName(TOKEN_EXCHANGE, server.getId());
}

private Scope configureScope(ResourceServer server) {
Expand Down Expand Up @@ -293,7 +293,7 @@ public Map<String, String> getPermissions(ClientModel client) {
scopes.put(MAP_ROLES_SCOPE, mapRolesPermission(client).getId());
scopes.put(MAP_ROLES_CLIENT_SCOPE, mapRolesClientScopePermission(client).getId());
scopes.put(MAP_ROLES_COMPOSITE_SCOPE, mapRolesCompositePermission(client).getId());
scopes.put(EXCHANGE_TO_SCOPE, exchangeToPermission(client).getId());
scopes.put(TOKEN_EXCHANGE, exchangeToPermission(client).getId());
return scopes;
}

Expand Down Expand Up @@ -328,7 +328,7 @@ public boolean canExchangeTo(ClientModel authorizedClient, ClientModel to) {

Scope scope = exchangeToScope(server);
if (scope == null) {
logger.debug(EXCHANGE_TO_SCOPE + " not initialized");
logger.debug(TOKEN_EXCHANGE + " not initialized");
return false;
}
ClientModelIdentity identity = new ClientModelIdentity(session, authorizedClient);
Expand Down

0 comments on commit affeadf

Please sign in to comment.