diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index 2e585c3..bedef8d 100644
--- a/core/src/main/java/org/keycloak/OAuth2Constants.java
+++ b/core/src/main/java/org/keycloak/OAuth2Constants.java
@@ -97,6 +97,8 @@ public interface OAuth2Constants {
String AUDIENCE="audience";
String SUBJECT_TOKEN="subject_token";
String SUBJECT_TOKEN_TYPE="subject_token_type";
+ String REQUESTED_TOKEN_TYPE="requested_token_type";
+ String REQUESTED_ISSUER="requested_issuer";
String ACCESS_TOKEN_TYPE="urn:ietf:params:oauth:token-type:access_token";
String REFRESH_TOKEN_TYPE="urn:ietf:params:oauth:token-type:refresh_token";
String JWT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt";
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/TokenExchangeTo.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/TokenExchangeTo.java
new file mode 100644
index 0000000..36755d4
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/TokenExchangeTo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.broker.provider;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.AccessToken;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface TokenExchangeTo {
+ /**
+ *
+ * @param authorizedClient client requesting exchange
+ * @param tokenUserSession UserSessionModel of token exchanging from
+ * @param tokenSubject UserModel of token exchanging from
+ * @param token access token representation of token exchanging from
+ * @param params form parameters received for requested exchange
+ * @return
+ */
+ Response exchangeTo(ClientModel authorizedClient, UserSessionModel tokenUserSession, UserModel tokenSubject, AccessToken token, MultivaluedMap<String, String> params);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index fef17c6..b0b3e19 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -564,19 +564,43 @@ public class TokenEndpoint {
public Response buildTokenExchange() {
event.detail(Details.AUTH_METHOD, "oauth_credentials");
- String scope = formParams.getFirst(OAuth2Constants.SCOPE);
String subjectToken = formParams.getFirst(OAuth2Constants.SUBJECT_TOKEN);
String subjectTokenType = formParams.getFirst(OAuth2Constants.SUBJECT_TOKEN_TYPE);
+ if (subjectTokenType != null && !subjectTokenType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE)) {
+ event.error(Errors.INVALID_TOKEN);
+ throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token type, must be access token", Response.Status.BAD_REQUEST);
+
+ }
+
+
AuthenticationManager.AuthResult authResult = AuthenticationManager.verifyIdentityToken(session, realm, uriInfo, clientConnection, true, true, false, subjectToken, headers);
if (authResult == null) {
event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.BAD_REQUEST);
}
+ String requestedIssuer = formParams.getFirst(OAuth2Constants.REQUESTED_ISSUER);
+
+ if (requestedIssuer == null) {
+
+ }
+
+ return exchangeClientToClient(authResult);
+ }
+
+ public Response exchangeClientToClient(AuthenticationManager.AuthResult subject) {
+ String requestedTokenType = formParams.getFirst(OAuth2Constants.REQUESTED_TOKEN_TYPE);
+ if (requestedTokenType == null) {
+ requestedTokenType = OAuth2Constants.REFRESH_TOKEN_TYPE;
+ } else if (!requestedTokenType.equals(OAuth2Constants.ACCESS_TOKEN_TYPE) && !requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE)) {
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorResponseException("unsupported_requested_token_type", "Unsupported requested token type", Response.Status.BAD_REQUEST);
+
+ }
String audience = formParams.getFirst(OAuth2Constants.AUDIENCE);
if (audience == null) {
event.error(Errors.INVALID_REQUEST);
- throw new ErrorResponseException("invalid_audience", "No audience specified", Response.Status.BAD_REQUEST);
+ throw new ErrorResponseException("invalid_audience", "Audience parameter required", Response.Status.BAD_REQUEST);
}
ClientModel targetClient = null;
@@ -594,7 +618,7 @@ public class TokenEndpoint {
}
boolean exchangeFromAllowed = false;
- for (String aud : authResult.getToken().getAudience()) {
+ for (String aud : subject.getToken().getAudience()) {
ClientModel audClient = realm.getClientByClientId(aud);
if (audClient == null) continue;
if (audClient.equals(client)) {
@@ -617,13 +641,15 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.ACCESS_DENIED, "Client not allowed to exchange", Response.Status.FORBIDDEN);
}
+ String scope = formParams.getFirst(OAuth2Constants.SCOPE);
+
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
- authSession.setAuthenticatedUser(authResult.getUser());
+ authSession.setAuthenticatedUser(subject.getUser());
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
- UserSessionModel userSession = authResult.getSession();
+ UserSessionModel userSession = subject.getSession();
event.session(userSession);
AuthenticationManager.setRolesAndMappersInSession(authSession);
@@ -637,10 +663,13 @@ public class TokenEndpoint {
updateUserSessionFromClientAuth(userSession);
TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, targetClient, event, session, userSession, clientSession)
- .generateAccessToken()
- .generateRefreshToken();
+ .generateAccessToken();
responseBuilder.getAccessToken().issuedFor(client.getClientId());
- responseBuilder.getRefreshToken().issuedFor(client.getClientId());
+
+ if (requestedTokenType.equals(OAuth2Constants.REFRESH_TOKEN_TYPE)) {
+ responseBuilder.generateRefreshToken();
+ responseBuilder.getRefreshToken().issuedFor(client.getClientId());
+ }
String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
if (TokenUtil.isOIDCRequest(scopeParam)) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
index b20d462..46b15d0 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
@@ -185,7 +185,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
authz.getStoreFactory().getPolicyStore().delete(manageMembersPermission.getId());
}
Policy viewMembersPermission = viewMembersPermission(group);
- if (viewMembersPermission == null) {
+ if (viewMembersPermission != null) {
authz.getStoreFactory().getPolicyStore().delete(viewMembersPermission.getId());
}
Policy manageMembershipPermission = manageMembershipPermission(group);