keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js 71(+71 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js 70(+70 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html 24(+24 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html 24(+24 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html 7(+7 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html 7(+7 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html 7(+7 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html 1(+1 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html 1(+1 -0)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java 20(+17 -3)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java 15(+14 -1)
forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java 8(+7 -1)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java 19(+19 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 17(+16 -1)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java 1(+0 -1)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 13(+7 -6)
services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java 25(+25 -0)
services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java 131(+113 -18)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java 169(+148 -21)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java 6(+4 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java 19(+19 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java 8(+8 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java 8(+8 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java 28(+24 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java 28(+24 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java 22(+20 -2)
Details
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
index ac853f7..8f81a6f 100644
--- a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
@@ -30,6 +30,7 @@ public class FederatedIdentity {
private String firstName;
private String lastName;
private String email;
+ private String token;
public FederatedIdentity(String id) {
if (id == null) {
@@ -84,4 +85,11 @@ public class FederatedIdentity {
}
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return this.token;
+ }
}
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
index cfe6f41..f84765a 100644
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java
@@ -17,9 +17,12 @@
*/
package org.keycloak.broker.provider;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.provider.Provider;
+import javax.ws.rs.core.Response;
+
/**
* @author Pedro Igor
*/
@@ -64,4 +67,6 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
* @return
*/
AuthenticationResponse handleResponse(AuthenticationRequest request);
+
+ Response retrieveToken(FederatedIdentityModel identity);
}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index 3118cfe..14bc767 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -25,7 +25,9 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.models.FederatedIdentityModel;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
@@ -100,7 +102,13 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
.param(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri())
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE).asString();
- return doHandleResponse(response);
+ FederatedIdentity federatedIdentity = getFederatedIdentity(response);
+
+ if (getConfig().isStoreToken()) {
+ federatedIdentity.setToken(response);
+ }
+
+ return AuthenticationResponse.end(federatedIdentity);
}
throw new RuntimeException("No authorization code from identity provider.");
@@ -110,23 +118,22 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
}
@Override
- public C getConfig() {
- return super.getConfig();
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return Response.ok(identity.getToken()).build();
}
- protected AuthenticationResponse doHandleResponse(String response) throws IOException {
- String token = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
-
- if (token == null) {
- throw new RuntimeException("No access token from server.");
- }
-
- return AuthenticationResponse.end(getFederatedIdentity(token));
+ @Override
+ public C getConfig() {
+ return super.getConfig();
}
- protected String extractTokenFromResponse(String response, String tokenName) throws IOException {
+ protected String extractTokenFromResponse(String response, String tokenName) {
if (response.startsWith("{")) {
- return mapper.readTree(response).get(tokenName).getTextValue();
+ try {
+ return mapper.readTree(response).get(tokenName).getTextValue();
+ } catch (IOException e) {
+ throw new RuntimeException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
+ }
} else {
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
@@ -138,9 +145,21 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
return null;
}
- protected FederatedIdentity getFederatedIdentity(String accessToken) {
- throw new RuntimeException("Not implemented.");
- };
+ protected FederatedIdentity getFederatedIdentity(String response) {
+ String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
+
+ if (accessToken == null) {
+ throw new RuntimeException("No access token from server.");
+ }
+
+ return doGetFederatedIdentity(accessToken);
+ }
+
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
+ return null;
+ }
+
+ ;
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
return UriBuilder.fromPath(getConfig().getAuthorizationUrl())
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index c886b06..50a5f03 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -20,7 +20,6 @@ package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AuthenticationRequest;
-import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.jose.jws.JWSInput;
@@ -59,7 +58,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
@Override
- protected AuthenticationResponse doHandleResponse(String response) throws IOException {
+ protected FederatedIdentity getFederatedIdentity(String response) {
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
if (accessToken == null) {
@@ -96,7 +95,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
identity.setUsername(preferredUsername);
- return AuthenticationResponse.end(identity);
+ if (getConfig().isStoreToken()) {
+ identity.setToken(response);
+ }
+
+ return identity;
} catch (Exception e) {
throw new RuntimeException("Could not fetch attributes from userinfo endpoint.", e);
}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 8dc36a1..8e458bc 100644
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
import org.picketlink.common.constants.JBossSAMLConstants;
@@ -54,6 +55,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
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 javax.xml.namespace.QName;
@@ -140,8 +142,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
@Override
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
+ String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
+
+ if (samlResponse == null) {
+ throw new RuntimeException("No response from SAML identity provider.");
+ }
+
try {
- AssertionType assertion = getAssertion(request);
+ AssertionType assertion = getAssertion(samlResponse, request);
SubjectType subject = assertion.getSubject();
STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
@@ -153,19 +161,22 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
identity.setEmail(subjectNameID.getValue());
}
+ if (getConfig().isStoreToken()) {
+ identity.setToken(samlResponse);
+ }
+
return AuthenticationResponse.end(identity);
} catch (Exception e) {
throw new RuntimeException("Could not process response from SAML identity provider.", e);
}
}
- private AssertionType getAssertion(AuthenticationRequest request) throws Exception {
- String samlResponse = getRequestParameter(request, SAML_RESPONSE_PARAMETER);
-
- if (samlResponse == null) {
- throw new RuntimeException("No response from SAML identity provider.");
- }
+ @Override
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return Response.ok(identity.getToken()).build();
+ }
+ private AssertionType getAssertion(String samlResponse, AuthenticationRequest request) throws Exception {
SAML2Request saml2Request = new SAML2Request();
ResponseType responseType;
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index c203b4e..69a7678 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -8,6 +8,7 @@
<column name="REALM_ID" type="VARCHAR(255)"/>
<column name="FEDERATED_USER_ID" type="VARCHAR(255)"/>
<column name="FEDERATED_USERNAME" type="VARCHAR(255)"/>
+ <column name="TOKEN" type="TEXT"/>
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
@@ -21,6 +22,7 @@
<column name="PROVIDER_NAME" type="VARCHAR(255)"/>
<column name="PROVIDER_ID" type="VARCHAR(255)"/>
<column name="UPDATE_PROFILE_FIRST_LOGIN" type="BOOLEAN(1)"/>
+ <column name="STORE_TOKEN" type="BOOLEAN(1)"/>
<column name="REALM_ID" type="VARCHAR(36)"/>
</createTable>
<createTable tableName="IDENTITY_PROVIDER_CONFIG">
@@ -32,6 +34,14 @@
<constraints nullable="false"/>
</column>
</createTable>
+ <createTable tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER">
+ <column name="CLIENT_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="INTERNAL_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
<addColumn tableName="CLIENT">
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
</addColumn>
@@ -41,5 +51,8 @@
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER" constraintName="FK2B4EBC52AE5C3B34" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="FEDERATED_IDENTITY" constraintName="FK404288B92EF007A6" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="IDENTITY_PROVIDER_CONFIG" constraintName="FKDC4897CF864C4E43" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
+ <addForeignKeyConstraint baseColumnNames="INTERNAL_ID" baseTableName="CLIENT_ALLOWED_IDENTITY_PROVIDER" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
+ <addUniqueConstraint columnNames="INTERNAL_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_ALLOWED_IDENTITY_PROVIDER"/>
+ <addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
</changeSet>
</databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index 82c2ebc..3369893 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -28,6 +28,7 @@ public class ApplicationRepresentation {
protected Boolean fullScopeAllowed;
protected Integer nodeReRegistrationTimeout;
protected Map<String, Integer> registeredNodes;
+ protected List<String> allowedIdentityProviders;
public String getId() {
return id;
@@ -188,4 +189,12 @@ public class ApplicationRepresentation {
public void setFrontchannelLogout(Boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
+
+ public List<String> getAllowedIdentityProviders() {
+ return this.allowedIdentityProviders;
+ }
+
+ public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+ this.allowedIdentityProviders = allowedIdentityProviders;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
index 023bd01..cc4b3e6 100644
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -30,6 +30,7 @@ public class IdentityProviderRepresentation {
protected String name;
protected boolean enabled = true;
protected boolean updateProfileFirstLogin = true;
+ protected boolean storeToken;
protected String groupName;
protected Map<String, String> config = new HashMap<String, String>();
@@ -65,14 +66,6 @@ public class IdentityProviderRepresentation {
this.config = config;
}
- public String getGroupName() {
- return this.groupName;
- }
-
- public void setGroupName(String groupName) {
- this.groupName = groupName;
- }
-
public boolean isEnabled() {
return this.enabled;
}
@@ -88,4 +81,20 @@ public class IdentityProviderRepresentation {
public void setUpdateProfileFirstLogin(boolean updateProfileFirstLogin) {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
+
+ public boolean isStoreToken() {
+ return this.storeToken;
+ }
+
+ public void setStoreToken(boolean storeToken) {
+ this.storeToken = storeToken;
+ }
+
+ public String getGroupName() {
+ return this.groupName;
+ }
+
+ public void setGroupName(String groupName) {
+ this.groupName = groupName;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
index de65ae6..12ce2f4 100755
--- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java
@@ -22,6 +22,7 @@ public class OAuthClientRepresentation {
protected Boolean directGrantsOnly;
protected Boolean fullScopeAllowed;
protected Boolean frontchannelLogout;
+ protected List<String> allowedIdentityProviders;
public String getId() {
@@ -135,4 +136,12 @@ public class OAuthClientRepresentation {
public void setFrontchannelLogout(Boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
+
+ public List<String> getAllowedIdentityProviders() {
+ return this.allowedIdentityProviders;
+ }
+
+ public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+ this.allowedIdentityProviders = allowedIdentityProviders;
+ }
}
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index d3cfbaf..13db021 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -464,6 +464,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ApplicationCredentialsCtrl'
})
+ .when('/realms/:realm/applications/:application/identity-provider', {
+ templateUrl : 'partials/application-identity-provider.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ application : function(ApplicationLoader) {
+ return ApplicationLoader();
+ }
+ },
+ controller : 'ApplicationIdentityProviderCtrl'
+ })
.when('/realms/:realm/applications/:application/clustering', {
templateUrl : 'partials/application-clustering.html',
resolve : {
@@ -750,6 +762,18 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'OAuthClientDetailCtrl'
})
+ .when('/realms/:realm/oauth-clients/:oauth/identity-provider', {
+ templateUrl : 'partials/oauth-client-identity-provider.html',
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ oauth : function(OAuthClientLoader) {
+ return OAuthClientLoader();
+ }
+ },
+ controller : 'OAuthClientIdentityProviderCtrl'
+ })
.when('/realms/:realm/oauth-clients', {
templateUrl : 'partials/oauth-client-list.html',
resolve : {
@@ -1050,6 +1074,48 @@ module.directive('onoffswitch', function() {
}
});
+/**
+ * Directive for presenting an ON-OFF switch for checkbox.
+ * This directive provides some additional capabilities to the default onoffswitch such as:
+ *
+ * - Dynamic values for id and name attributes. Useful if you need to use this directive inside a ng-repeat
+ * - Specific scope to specify the value. Instead of just true or false.
+ *
+ * Usage: <input ng-model="mmm" name="nnn" id="iii" kc-onoffswitch-model [on-text="ooo" off-text="fff"] />
+ */
+module.directive('kc-onoffswitch-model', function() {
+ return {
+ restrict: "EA",
+ replace: true,
+ scope: {
+ name: '=',
+ id: '=',
+ value: '=',
+ ngModel: '=',
+ ngDisabled: '=',
+ kcOnText: '@onText',
+ kcOffText: '@offText'
+ },
+ // TODO - The same code acts differently when put into the templateURL. Find why and move the code there.
+ //templateUrl: "templates/kc-switch.html",
+ template: "<span><div class='onoffswitch' tabindex='0'><input type='checkbox' ng-true-value='{{value}}' ng-model='ngModel' ng-disabled='ngDisabled' class='onoffswitch-checkbox' name='kc{{name}}' id='kc{{id}}'><label for='kc{{id}}' class='onoffswitch-label'><span class='onoffswitch-inner'><span class='onoffswitch-active'>{{kcOnText}}</span><span class='onoffswitch-inactive'>{{kcOffText}}</span></span><span class='onoffswitch-switch'></span></label></div></span>",
+ compile: function(element, attrs) {
+
+ if (!attrs.onText) { attrs.onText = "ON"; }
+ if (!attrs.offText) { attrs.offText = "OFF"; }
+
+ element.bind('keydown click', function(e){
+ var code = e.keyCode || e.which;
+ if (code === 32 || code === 13) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ $(e.target).find('input').click();
+ }
+ });
+ }
+ }
+});
+
module.directive('kcInput', function() {
var d = {
scope : true,
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index 784f1b8..3f3a6cb 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -43,6 +43,77 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
});
});
+module.controller('ApplicationIdentityProviderCtrl', function($scope, $location, realm, application, Application, $location, Notifications) {
+ $scope.realm = realm;
+ $scope.application = angular.copy(application);
+
+ $scope.identityProviders = [];
+
+ if (!$scope.application.allowedIdentityProviders) {
+ $scope.application.allowedIdentityProviders = [];
+ }
+
+ for (j = 0; j < realm.identityProviders.length; j++) {
+ var identityProvider = realm.identityProviders[j];
+ var match = false;
+
+ for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
+ var appProvider = $scope.application.allowedIdentityProviders[i];
+
+ if (appProvider == identityProvider.id) {
+ $scope.identityProviders[i] = identityProvider;
+ match = true;
+ }
+ }
+
+ if (!match) {
+ var length = $scope.identityProviders.length;
+
+ length = length + $scope.application.allowedIdentityProviders.length;
+
+ $scope.identityProviders[length] = identityProvider;
+ }
+ }
+
+ $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
+
+ $scope.save = function() {
+ var selectedProviders = [];
+
+ for (i = 0; i < $scope.application.allowedIdentityProviders.length; i++) {
+ var appProvider = $scope.application.allowedIdentityProviders[i];
+
+ if (appProvider) {
+ selectedProviders[selectedProviders.length] = appProvider;
+ }
+ }
+
+ $scope.allowedIdentityProviders = $scope.application.allowedIdentityProviders;
+ $scope.application.allowedIdentityProviders = selectedProviders;
+
+ Application.update({
+ realm : realm.realm,
+ application : application.id
+ }, $scope.application, function() {
+ $scope.changed = false;
+ $scope.application.allowedIdentityProviders = $scope.allowedIdentityProviders;
+ $location.url("/realms/" + realm.realm + "/applications/" + application.id + "/identity-provider");
+ Notifications.success("Your changes have been saved to the application.");
+ });
+ };
+
+ $scope.reset = function() {
+ $scope.application = angular.copy(application);
+ $scope.changed = false;
+ };
+
+ $scope.$watch(function() {
+ return $location.path();
+ }, function() {
+ $scope.path = $location.path().substring(1).split("/");
+ });
+});
+
module.controller('ApplicationSamlKeyCtrl', function($scope, $location, $http, $upload, realm, application,
ApplicationCertificate, ApplicationCertificateGenerate,
ApplicationCertificateDownload, Notifications) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
index 5fe70a7..dec42f5 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
@@ -324,4 +324,74 @@ module.controller('OAuthClientRevocationCtrl', function($scope, realm, oauth, OA
}
});
+module.controller('OAuthClientIdentityProviderCtrl', function($scope, realm, oauth, OAuthClient, $location, Notifications) {
+ $scope.realm = realm;
+ $scope.oauth = angular.copy(oauth);
+
+ $scope.identityProviders = [];
+
+ if (!$scope.oauth.allowedIdentityProviders) {
+ $scope.oauth.allowedIdentityProviders = [];
+ }
+
+ for (j = 0; j < realm.identityProviders.length; j++) {
+ var identityProvider = realm.identityProviders[j];
+ var match = false;
+
+ for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) {
+ var appProvider = $scope.oauth.allowedIdentityProviders[i];
+
+ if (appProvider == identityProvider.id) {
+ $scope.identityProviders[i] = identityProvider;
+ match = true;
+ }
+ }
+
+ if (!match) {
+ var length = $scope.identityProviders.length;
+
+ length = length + $scope.oauth.allowedIdentityProviders.length;
+
+ $scope.identityProviders[length] = identityProvider;
+ }
+ }
+
+ $scope.identityProviders = $scope.identityProviders.filter(function(n){ return n != undefined });
+
+ $scope.save = function() {
+ var selectedProviders = [];
+
+ for (i = 0; i < $scope.oauth.allowedIdentityProviders.length; i++) {
+ var appProvider = $scope.oauth.allowedIdentityProviders[i];
+
+ if (appProvider) {
+ selectedProviders[selectedProviders.length] = appProvider;
+ }
+ }
+
+ $scope.allowedIdentityProviders = $scope.oauth.allowedIdentityProviders;
+ $scope.oauth.allowedIdentityProviders = selectedProviders;
+
+ OAuthClient.update({
+ realm : realm.realm,
+ oauth : oauth.id
+ }, $scope.oauth, function() {
+ $scope.changed = false;
+ $scope.oauth.allowedIdentityProviders = $scope.allowedIdentityProviders;
+ $location.url("/realms/" + realm.realm + "/oauth-clients/" + oauth.id + "/identity-provider");
+ Notifications.success("Your changes have been saved to the application.");
+ });
+ };
+
+ $scope.reset = function() {
+ $scope.oauth = angular.copy(oauth);
+ $scope.changed = false;
+ };
+
+ $scope.$watch(function() {
+ return $location.path();
+ }, function() {
+ $scope.path = $location.path().substring(1).split("/");
+ });
+});
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
new file mode 100755
index 0000000..6c90ef2
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-identity-provider.html
@@ -0,0 +1,24 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+ <kc-navigation-application></kc-navigation-application>
+ <div id="content">
+ <ol class="breadcrumb" data-ng-hide="create">
+ <li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
+ <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">{{application.name}}</a></li>
+ <li class="active">Identity Provider</li>
+ </ol>
+ <h2 data-ng-hide="create"><span>{{application.name}}</span> Identity Provider Settings</h2>
+ <form class="form-horizontal" name="identityProviderForm" novalidate>
+ <div class="form-group" ng-repeat="identityProvider in identityProviders">
+ <legend><span class="text">{{identityProvider.name}}</span></legend>
+ <label class="col-sm-2 control-label" for="{{identityProvider.id}}">Enable</label>
+ <div class="col-sm-4">
+ <input ng-model="application.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" kc-onoffswitch-model />
+ </div>
+ </div>
+ <div class="pull-right form-actions">
+ <button kc-save>Save</button>
+ </div>
+ </form>
+ </div>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
new file mode 100755
index 0000000..1a14620
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-identity-provider.html
@@ -0,0 +1,24 @@
+<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-md-9" role="main">
+ <kc-navigation-oauth-client></kc-navigation-oauth-client>
+ <div id="content">
+ <ol class="breadcrumb" data-ng-hide="create">
+ <li><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
+ <li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">{{oauth.name}}</a></li>
+ <li class="active">Identity Provider</li>
+ </ol>
+ <h2 data-ng-hide="create"><span>{{oauth.name}}</span> Identity Provider Settings</h2>
+ <form class="form-horizontal" name="identityProviderForm" novalidate>
+ <div class="form-group" ng-repeat="identityProvider in identityProviders">
+ <legend><span class="text">{{identityProvider.name}}</span></legend>
+ <label class="col-sm-2 control-label" for="{{identityProvider.id}}">Enable</label>
+ <div class="col-sm-4">
+ <input ng-model="oauth.allowedIdentityProviders[$index]" name="identityProvider.id" id="identityProvider.id" value="identityProvider.id" kc-onoffswitch-model />
+ </div>
+ </div>
+ <div class="pull-right form-actions">
+ <button kc-save>Save</button>
+ </div>
+ </form>
+ </div>
+</div>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
index 135c392..1e4de59 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-oidc.html
@@ -93,6 +93,13 @@
<span tooltip-placement="right" tooltip="Specifies whether the Authorization Server prompts the End-User for reauthentication and consent." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
+ <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+ </div>
+ <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
index e5ba81f..91cf006 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-saml.html
@@ -94,6 +94,13 @@
<span tooltip-placement="right" tooltip="Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
+ <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+ </div>
+ <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
index 0cd6816..e5a28e1 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-social.html
@@ -50,6 +50,13 @@
<span tooltip-placement="right" tooltip="The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'." class="fa fa-info-circle"></span>
</div>
<div class="form-group">
+ <label class="col-sm-2 control-label" for="enabled">Store Tokens</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch />
+ </div>
+ <span tooltip-placement="right" tooltip="Enable/disable if tokens must be stored when authenticating users." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
index cb4d44a..e84e149 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
@@ -6,6 +6,7 @@
<li ng-class="{active: path[4] == 'claims'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/claims">Claims</a></li>
<li ng-class="{active: path[4] == 'scope-mappings'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
<li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/revocation">Revocation</a></li>
+ <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/identity-provider">Identity Provider</a></li>
<li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/sessions">Sessions</a></li>
<li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!application.publicClient"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/clustering">Clustering</a></li>
<li ng-class="{active: path[4] == 'installation'}" data-ng-show="application.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/installation">Installation</a></li>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
index ffca271..db40ee5 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-oauth-client.html
@@ -4,5 +4,6 @@
<li ng-class="{active: path[4] == 'claims'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/claims">Claims</a></li>
<li ng-class="{active: path[4] == 'scope-mappings'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
<li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/revocation">Revocation</a></li>
+ <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/identity-provider">Identity Provider</a></li>
<li ng-class="{active: path[4] == 'installation'}"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
</ul>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
index e8cfbf3..c881e9c 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
@@ -14,25 +14,35 @@
<span>
Personal Info:
<#list oauth.claimsRequested as claim>
- ${claim}
+ ${claim}
</#list>
</span>
</li>
</#if>
- <#list oauth.realmRolesRequested as role>
+ <#if oauth.accessRequestMessage??>
<li>
- <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
+ <span>
+ ${oauth.accessRequestMessage}
+ </span>
</li>
- </#list>
-
- <#list oauth.resourceRolesRequested?keys as resource>
- <#list oauth.resourceRolesRequested[resource] as role>
+ </#if>
+ <#if oauth.realmRolesRequested??>
+ <#list oauth.realmRolesRequested as role>
<li>
- <span class="kc-role"><#if role.description??>${role.description}<#else>${role.name}</#if></span>
- <span class="kc-resource">in <strong>${resource}</strong></span>
+ <span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
</li>
</#list>
- </#list>
+ </#if>
+ <#if oauth.resourceRolesRequested??>
+ <#list oauth.resourceRolesRequested?keys as resource>
+ <#list oauth.resourceRolesRequested[resource] as role>
+ <li>
+ <span class="kc-role"><#if role.description??>${role.description}<#else>${role.name}</#if></span>
+ <span class="kc-resource">in <strong>${resource}</strong></span>
+ </li>
+ </#list>
+ </#list>
+ </#if>
</ul>
<form class="form-actions" action="${url.oauthAction}" method="POST">
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 4fd2de9..0d1f931 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -9,6 +9,7 @@ import org.keycloak.provider.Provider;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import java.net.URI;
import java.util.List;
/**
@@ -39,6 +40,7 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setClientSessionCode(String accessCode);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
+ public LoginFormsProvider setAccessRequest(String message);
public LoginFormsProvider setError(String message);
@@ -56,4 +58,5 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setStatus(Response.Status status);
+ LoginFormsProvider setActionUri(URI requestUri);
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index 704f8a7..f897a5a 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -57,6 +57,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private List<RoleModel> realmRolesRequested;
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private MultivaluedMap<String, String> queryParams;
+ private String accessRequestMessage;
+ private URI actionUri;
public static enum MessageType {SUCCESS, WARNING, ERROR}
@@ -188,8 +190,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (realm != null) {
attributes.put("realm", new RealmBean(realm));
- attributes.put("social", new IdentityProviderBean(realm, baseUri));
- attributes.put("url", new UrlBean(realm, theme, baseUri));
+ attributes.put("social", new IdentityProviderBean(realm, baseUri, this.uriInfo));
+ attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
}
if (client != null) {
@@ -209,7 +211,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
attributes.put("register", new RegisterBean(formData));
break;
case OAUTH_GRANT:
- attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested));
+ attributes.put("oauth", new OAuthGrantBean(accessCode, client, realmRolesRequested, resourceRolesRequested, this.accessRequestMessage));
break;
case CODE:
attributes.put(OAuth2Constants.CODE, new CodeBean(accessCode, messageType == MessageType.ERROR ? message : null));
@@ -304,6 +306,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
@Override
+ public LoginFormsProvider setAccessRequest(String accessRequestMessage) {
+ this.accessRequestMessage = accessRequestMessage;
+ return this;
+ }
+
+ @Override
public LoginFormsProvider setStatus(Response.Status status) {
this.status = status;
return this;
@@ -316,6 +324,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
@Override
+ public LoginFormsProvider setActionUri(URI actionUri) {
+ this.actionUri = actionUri;
+ return this;
+ }
+
+ @Override
public void close() {
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
index 80f29bb..5a77a1a 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/IdentityProviderBean.java
@@ -21,10 +21,13 @@
*/
package org.keycloak.login.freemarker.model;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.flows.Urls;
+import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
@@ -39,7 +42,7 @@ public class IdentityProviderBean {
private List<IdentityProvider> providers;
private RealmModel realm;
- public IdentityProviderBean(RealmModel realm, URI baseURI) {
+ public IdentityProviderBean(RealmModel realm, URI baseURI, UriInfo uriInfo) {
this.realm = realm;
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
@@ -48,6 +51,16 @@ public class IdentityProviderBean {
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.isEnabled()) {
+ String clientId = uriInfo.getQueryParameters().getFirst(OAuth2Constants.CLIENT_ID);
+
+ if (clientId != null) {
+ ClientModel clientModel = realm.findClient(clientId);
+
+ if (clientModel != null && !clientModel.hasIdentityProvider(identityProvider.getId())) {
+ continue;
+ }
+ }
+
String loginUrl = Urls.identityProviderAuthnRequest(baseURI, identityProvider, realm).toString();
providers.add(new IdentityProvider(identityProvider.getId(), identityProvider.getName(), loginUrl));
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
index f358d3d..6d515a1 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/OAuthGrantBean.java
@@ -34,17 +34,19 @@ import java.util.List;
*/
public class OAuthGrantBean {
+ private final String accessRequestMessage;
private List<RoleModel> realmRolesRequested;
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private String code;
private ClientModel client;
private List<String> claimsRequested;
- public OAuthGrantBean(String code, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
+ public OAuthGrantBean(String code, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, String accessRequestMessage) {
this.code = code;
this.client = client;
this.realmRolesRequested = realmRolesRequested;
this.resourceRolesRequested = resourceRolesRequested;
+ this.accessRequestMessage = accessRequestMessage;
// todo support locale
List<String> claims = new LinkedList<String>();
@@ -101,4 +103,8 @@ public class OAuthGrantBean {
public List<String> getClaimsRequested() {
return claimsRequested;
}
+
+ public String getAccessRequestMessage() {
+ return this.accessRequestMessage;
+ }
}
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index bb8df0e..26d67a3 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -32,16 +32,18 @@ import java.net.URI;
*/
public class UrlBean {
+ private final URI actionuri;
private URI baseURI;
private Theme theme;
private String realm;
- public UrlBean(RealmModel realm, Theme theme, URI baseURI) {
+ public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI actionUri) {
this.realm = realm.getName();
this.theme = theme;
this.baseURI = baseURI;
+ this.actionuri = actionUri;
}
public String getLoginAction() {
@@ -85,6 +87,10 @@ public class UrlBean {
}
public String getOauthAction() {
+ if (this.actionuri != null) {
+ return this.actionuri.getPath();
+ }
+
return Urls.realmOauthAction(baseURI, realm).toString();
}
@@ -92,5 +98,4 @@ public class UrlBean {
URI uri = Urls.themeRoot(baseURI);
return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
}
-
}
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
index 14cd081..09f2a55 100755
--- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java
@@ -53,5 +53,4 @@ public interface ApplicationModel extends RoleContainerModel, ClientModel {
void registerNode(String nodeHost, int registrationTime);
void unregisterNode(String nodeHost);
-
}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index ecbb489..0acd9c5 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -1,5 +1,6 @@
package org.keycloak.models;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -97,4 +98,9 @@ public interface ClientModel {
void setNotBefore(int notBefore);
+ void updateAllowedIdentityProviders(List<String> identityProviders);
+
+ List<String> getAllowedIdentityProviders();
+
+ boolean hasIdentityProvider(String providerId);
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index 5b37c34..9cfcead 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -27,6 +27,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private List<String> webOrigins = new ArrayList<String>();
private List<String> redirectUris = new ArrayList<String>();
private List<String> scopeIds = new ArrayList<String>();
+ private List<String> allowedIdentityProviders;
public String getName() {
return name;
@@ -139,4 +140,12 @@ public class ClientEntity extends AbstractIdentifiableEntity {
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
+
+ public List<String> getAllowedIdentityProviders() {
+ return this.allowedIdentityProviders;
+ }
+
+ public void setAllowedIdentityProviders(List<String> allowedIdentityProviders) {
+ this.allowedIdentityProviders = allowedIdentityProviders;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java b/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
index a8fa647..02cdfac 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/FederatedIdentityEntity.java
@@ -8,6 +8,7 @@ public class FederatedIdentityEntity {
private String userId;
private String userName;
private String identityProvider;
+ private String token;
public String getUserId() {
return userId;
@@ -59,4 +60,12 @@ public class FederatedIdentityEntity {
}
return code;
}
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
index 964a12c..4fb33f2 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/IdentityProviderEntity.java
@@ -26,11 +26,13 @@ import java.util.Map;
public class IdentityProviderEntity {
private String internalId;
+ private String id;
+ private String providerId;
private String name;
private boolean enabled;
private boolean updateProfileFirstLogin;
- private String providerId;
- private String id;
+ private boolean storeToken;
+
private Map<String, String> config = new HashMap<String, String>();
public String getInternalId() {
@@ -65,6 +67,14 @@ public class IdentityProviderEntity {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
+ public boolean isStoreToken() {
+ return this.storeToken;
+ }
+
+ public void setStoreToken(boolean storeToken) {
+ this.storeToken = storeToken;
+ }
+
public String getProviderId() {
return providerId;
}
diff --git a/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java b/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
index 77db720..e720c40 100755
--- a/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
+++ b/model/api/src/main/java/org/keycloak/models/FederatedIdentityModel.java
@@ -5,16 +5,20 @@ package org.keycloak.models;
*/
public class FederatedIdentityModel {
- private String userId;
- private String identityProvider;
- private String userName;
-
- public FederatedIdentityModel() {};
+ private final String token;
+ private final String userId;
+ private final String identityProvider;
+ private final String userName;
public FederatedIdentityModel(String identityProvider, String userId, String userName) {
+ this(identityProvider, userId, userName, null);
+ }
+
+ public FederatedIdentityModel(String providerId, String userId, String userName, String token) {
+ this.identityProvider = providerId;
this.userId = userId;
- this.identityProvider = identityProvider;
this.userName = userName;
+ this.token = token;
}
public String getUserId() {
@@ -28,4 +32,8 @@ public class FederatedIdentityModel {
public String getUserName() {
return userName;
}
+
+ public String getToken() {
+ return this.token;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index 11a95a2..76ca2d8 100644
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -50,6 +50,8 @@ public class IdentityProviderModel {
private boolean updateProfileFirstLogin = true;
+ private boolean storeToken;
+
/**
* <p>A map containing the configuration and properties for a specific identity provider instance and implementation. The items
* in the map are understood by the identity provider implementation.</p>
@@ -67,6 +69,7 @@ public class IdentityProviderModel {
this.config = new HashMap<String, String>(model.getConfig());
this.enabled = model.isEnabled();
this.updateProfileFirstLogin = model.isUpdateProfileFirstLogin();
+ this.storeToken = model.isStoreToken();
}
public String getInternalId() {
@@ -117,6 +120,14 @@ public class IdentityProviderModel {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
+ public boolean isStoreToken() {
+ return this.storeToken;
+ }
+
+ public void setStoreToken(boolean storeToken) {
+ this.storeToken = storeToken;
+ }
+
public Map<String, String> getConfig() {
return this.config;
}
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 42c4423..8fe9688 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
@@ -258,6 +258,10 @@ public class ModelToRepresentation {
rep.setRegisteredNodes(new HashMap<String, Integer>(applicationModel.getRegisteredNodes()));
}
+ if (!applicationModel.getAllowedIdentityProviders().isEmpty()) {
+ rep.setAllowedIdentityProviders(applicationModel.getAllowedIdentityProviders());
+ }
+
return rep;
}
@@ -282,6 +286,11 @@ public class ModelToRepresentation {
rep.setWebOrigins(new LinkedList<String>(webOrigins));
}
rep.setNotBefore(model.getNotBefore());
+
+ if (!model.getAllowedIdentityProviders().isEmpty()) {
+ rep.setAllowedIdentityProviders(model.getAllowedIdentityProviders());
+ }
+
return rep;
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index aede6a2..0c7835c 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -112,6 +112,8 @@ public class RepresentationToModel {
if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+ importIdentityProviders(rep, newRealm);
+
if (rep.getApplications() != null) {
Map<String, ApplicationModel> appMap = createApplications(rep, newRealm);
}
@@ -231,21 +233,6 @@ public class RepresentationToModel {
UserModel user = createUser(session, newRealm, userRep, appMap);
}
}
-
- if (rep.getIdentityProviders() != null) {
- for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) {
- IdentityProviderModel identityProviderModel = new IdentityProviderModel();
-
- identityProviderModel.setId(identityProviderRepresentation.getId());
- identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId());
- identityProviderModel.setName(identityProviderRepresentation.getName());
- identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled());
- identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin());
- identityProviderModel.setConfig(identityProviderRepresentation.getConfig());
-
- newRealm.addIdentityProvider(identityProviderModel);
- }
- }
}
public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
@@ -517,6 +504,10 @@ public class RepresentationToModel {
if (rep.getClaims() != null) {
setClaims(resource, rep.getClaims());
}
+
+ if (rep.getAllowedIdentityProviders() != null) {
+ resource.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
+ }
}
public static void setClaims(ClientModel model, ClaimRepresentation rep) {
@@ -632,6 +623,9 @@ public class RepresentationToModel {
}
}
+ if (rep.getAllowedIdentityProviders() != null) {
+ model.updateAllowedIdentityProviders(rep.getAllowedIdentityProviders());
+ }
}
// Scope mappings
@@ -747,4 +741,22 @@ public class RepresentationToModel {
}
}
+
+ private static void importIdentityProviders(RealmRepresentation rep, RealmModel newRealm) {
+ if (rep.getIdentityProviders() != null) {
+ for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) {
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+ identityProviderModel.setId(identityProviderRepresentation.getId());
+ identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId());
+ identityProviderModel.setName(identityProviderRepresentation.getName());
+ identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled());
+ identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin());
+ identityProviderModel.setStoreToken(identityProviderRepresentation.isStoreToken());
+ identityProviderModel.setConfig(identityProviderRepresentation.getConfig());
+
+ newRealm.addIdentityProvider(identityProviderModel);
+ }
+ }
+ }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
index dff08a6..11696c5 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ClientAdapter.java
@@ -8,6 +8,7 @@ import org.keycloak.models.cache.entities.CachedClient;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -259,4 +260,22 @@ public abstract class ClientAdapter implements ClientModel {
copy.putAll(cachedClient.getAttributes());
return copy;
}
+
+ @Override
+ public void updateAllowedIdentityProviders(List<String> identityProviders) {
+ getDelegateForUpdate();
+ updatedClient.updateAllowedIdentityProviders(identityProviders);
+ }
+
+ @Override
+ public List<String> getAllowedIdentityProviders() {
+ if (updatedClient != null) return updatedClient.getAllowedIdentityProviders();
+ return cachedClient.getAllowedIdentityProviders();
+ }
+
+ @Override
+ public boolean hasIdentityProvider(String providerId) {
+ if (updatedClient != null) return updatedClient.hasIdentityProvider(providerId);
+ return cachedClient.hasIdentityProvider(providerId);
+ }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index b4b605e..ac7341d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -6,8 +6,10 @@ import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.RealmCache;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -32,6 +34,7 @@ public class CachedClient {
protected int notBefore;
protected Set<String> scope = new HashSet<String>();
protected Set<String> webOrigins = new HashSet<String>();
+ private List<String> allowedIdentityProviders = new ArrayList<String>();
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
id = model.getId();
@@ -52,7 +55,7 @@ public class CachedClient {
for (RoleModel role : model.getScopeMappings()) {
scope.add(role.getId());
}
-
+ this.allowedIdentityProviders = model.getAllowedIdentityProviders();
}
public String getId() {
@@ -122,4 +125,16 @@ public class CachedClient {
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
+
+ public List<String> getAllowedIdentityProviders() {
+ return this.allowedIdentityProviders;
+ }
+
+ public boolean hasIdentityProvider(String providerId) {
+ if (this.allowedIdentityProviders.isEmpty()) {
+ return true;
+ }
+
+ return this.allowedIdentityProviders.contains(providerId);
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 9eab924..dd1603c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -7,9 +7,9 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ApplicationEntity;
+import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.util.Time;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@@ -231,13 +231,6 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
em.flush();
}
- public static boolean contains(String str, String[] array) {
- for (String s : array) {
- if (str.equals(s)) return true;
- }
- return false;
- }
-
@Override
public void updateDefaultRoles(String[] defaultRoles) {
Collection<RoleEntity> entities = applicationEntity.getDefaultRoles();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index e71ba14..e8fb943 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -5,11 +5,14 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ClientEntity;
+import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.jpa.entities.ScopeMappingEntity;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -293,4 +296,60 @@ public abstract class ClientAdapter implements ClientModel {
copy.putAll(entity.getAttributes());
return copy;
}
+
+ @Override
+ public void updateAllowedIdentityProviders(List<String> identityProviders) {
+ Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
+ Set<String> already = new HashSet<String>();
+ List<IdentityProviderEntity> remove = new ArrayList<IdentityProviderEntity>();
+ for (IdentityProviderEntity rel : entities) {
+ if (!contains(rel.getId(), identityProviders.toArray(new String[identityProviders.size()]))) {
+ remove.add(rel);
+ } else {
+ already.add(rel.getId());
+ }
+ }
+ for (IdentityProviderEntity entity : remove) {
+ entities.remove(entity);
+ }
+ em.flush();
+ for (String providerId : identityProviders) {
+ if (!already.contains(providerId)) {
+ TypedQuery<IdentityProviderEntity> query = em.createNamedQuery("findIdentityProviderById", IdentityProviderEntity.class).setParameter("id", providerId);
+ IdentityProviderEntity providerEntity = query.getSingleResult();
+ entities.add(providerEntity);
+ }
+ }
+ em.flush();
+ }
+
+ @Override
+ public List<String> getAllowedIdentityProviders() {
+ Collection<IdentityProviderEntity> entities = entity.getAllowedIdentityProviders();
+ List<String> providers = new ArrayList<String>();
+
+ for (IdentityProviderEntity entity : entities) {
+ providers.add(entity.getId());
+ }
+
+ return providers;
+ }
+
+ @Override
+ public boolean hasIdentityProvider(String providerId) {
+ List<String> allowedIdentityProviders = getAllowedIdentityProviders();
+
+ if (allowedIdentityProviders.isEmpty()) {
+ return true;
+ }
+
+ return allowedIdentityProviders.contains(providerId);
+ }
+
+ public static boolean contains(String str, String[] array) {
+ for (String s : array) {
+ if (str.equals(s)) return true;
+ }
+ return false;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 8b32096..ac43656 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -9,10 +9,14 @@ import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
+import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -68,6 +72,10 @@ public abstract class ClientEntity {
@CollectionTable(name="CLIENT_ATTRIBUTES", joinColumns={ @JoinColumn(name="CLIENT_ID") })
protected Map<String, String> attributes = new HashMap<String, String>();
+ @OneToMany(fetch = FetchType.LAZY)
+ @JoinTable(name="CLIENT_ALLOWED_IDENTITY_PROVIDER", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="INTERNAL_ID")})
+ Collection<IdentityProviderEntity> allowedIdentityProviders = new ArrayList<IdentityProviderEntity>();
+
public RealmEntity getRealm() {
return realm;
}
@@ -179,4 +187,12 @@ public abstract class ClientEntity {
public void setFrontchannelLogout(boolean frontchannelLogout) {
this.frontchannelLogout = frontchannelLogout;
}
+
+ public Collection<IdentityProviderEntity> getAllowedIdentityProviders() {
+ return this.allowedIdentityProviders;
+ }
+
+ public void setAllowedIdentityProviders(Collection<IdentityProviderEntity> allowedIdentityProviders) {
+ this.allowedIdentityProviders = allowedIdentityProviders;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
index 1a91888..ccb1465 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederatedIdentityEntity.java
@@ -45,6 +45,9 @@ public class FederatedIdentityEntity {
@Column(name = "FEDERATED_USERNAME")
protected String userName;
+ @Column(name = "TOKEN")
+ protected String token;
+
public UserEntity getUser() {
return user;
}
@@ -85,6 +88,14 @@ public class FederatedIdentityEntity {
this.realmId = realmId;
}
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
public static class Key implements Serializable {
protected UserEntity user;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index 96d9c50..408d706 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -9,6 +9,8 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
import javax.persistence.Table;
import java.util.Map;
@@ -17,6 +19,9 @@ import java.util.Map;
*/
@Entity
@Table(name="IDENTITY_PROVIDER")
+@NamedQueries({
+ @NamedQuery(name="findIdentityProviderById", query="select identityProvider from IdentityProviderEntity identityProvider where identityProvider.id = :id")
+})
public class IdentityProviderEntity {
@Id
@@ -42,6 +47,9 @@ public class IdentityProviderEntity {
@Column(name="UPDATE_PROFILE_FIRST_LOGIN")
private boolean updateProfileFirstLogin;
+ @Column(name="STORE_TOKEN")
+ private boolean storeToken;
+
@ElementCollection
@MapKeyColumn(name="name")
@Column(name="value", columnDefinition = "TEXT")
@@ -104,6 +112,14 @@ public class IdentityProviderEntity {
this.updateProfileFirstLogin = updateProfileFirstLogin;
}
+ public boolean isStoreToken() {
+ return this.storeToken;
+ }
+
+ public void setStoreToken(boolean storeToken) {
+ this.storeToken = storeToken;
+ }
+
public Map<String, String> getConfig() {
return this.config;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 60a79e6..098421a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -96,6 +96,7 @@ public class JpaUserProvider implements UserProvider {
entity.setIdentityProvider(identity.getIdentityProvider());
entity.setUserId(identity.getUserId());
entity.setUserName(identity.getUserName());
+ entity.setToken(identity.getToken());
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
entity.setUser(userEntity);
em.persist(entity);
@@ -344,7 +345,7 @@ public class JpaUserProvider implements UserProvider {
List<FederatedIdentityEntity> results = query.getResultList();
Set<FederatedIdentityModel> set = new HashSet<FederatedIdentityModel>();
for (FederatedIdentityEntity entity : results) {
- set.add(new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()));
+ set.add(new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()));
}
return set;
}
@@ -352,7 +353,7 @@ public class JpaUserProvider implements UserProvider {
@Override
public FederatedIdentityModel getFederatedIdentity(UserModel user, String identityProvider, RealmModel realm) {
FederatedIdentityEntity entity = findFederatedIdentity(user, identityProvider);
- return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName()) : null;
+ return (entity != null) ? new FederatedIdentityModel(entity.getIdentityProvider(), entity.getUserId(), entity.getUserName(), entity.getToken()) : null;
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 6cd1d0a..9f2b93b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1120,6 +1120,7 @@ public class RealmAdapter implements RealmModel {
identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
+ identityProviderModel.setStoreToken(entity.isStoreToken());
identityProviders.add(identityProviderModel);
}
@@ -1174,6 +1175,7 @@ public class RealmAdapter implements RealmModel {
entity.setName(identityProvider.getName());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+ entity.setStoreToken(identityProvider.isStoreToken());
entity.setConfig(identityProvider.getConfig());
}
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 3f84c2b..02e512a 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -11,7 +11,6 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.mongo.keycloak.entities.MongoApplicationEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
-import org.keycloak.util.Time;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index ad56f84..d3f93db 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -291,5 +291,30 @@ public abstract class ClientAdapter<T extends MongoIdentifiableEntity> extends A
return copy;
}
+ @Override
+ public void updateAllowedIdentityProviders(List<String> identityProviders) {
+ List<String> providerIds = new ArrayList<String>();
+ for (String providerId : identityProviders) {
+ providerIds.add(providerId);
+ }
+ getMongoEntityAsClient().setAllowedIdentityProviders(identityProviders);
+ updateMongoEntity();
+ }
+
+ @Override
+ public List<String> getAllowedIdentityProviders() {
+ return getMongoEntityAsClient().getAllowedIdentityProviders();
+ }
+
+ @Override
+ public boolean hasIdentityProvider(String providerId) {
+ List<String> allowedIdentityProviders = getMongoEntityAsClient().getAllowedIdentityProviders();
+
+ if (allowedIdentityProviders.isEmpty()) {
+ return true;
+ }
+
+ return allowedIdentityProviders.contains(providerId);
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index a542c22..b51fce3 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -6,10 +6,10 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
-import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@@ -225,7 +225,7 @@ public class MongoUserProvider implements UserProvider {
Set<FederatedIdentityModel> result = new HashSet<FederatedIdentityModel>();
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
FederatedIdentityModel model = new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(),
- federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName());
+ federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName(), federatedIdentityEntity.getToken());
result.add(model);
}
return result;
@@ -296,13 +296,14 @@ public class MongoUserProvider implements UserProvider {
@Override
- public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
+ public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
user = getUserById(user.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
- federatedIdentityEntity.setIdentityProvider(socialLink.getIdentityProvider());
- federatedIdentityEntity.setUserId(socialLink.getUserId());
- federatedIdentityEntity.setUserName(socialLink.getUserName());
+ federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider());
+ federatedIdentityEntity.setUserId(identity.getUserId());
+ federatedIdentityEntity.setUserName(identity.getUserName());
+ federatedIdentityEntity.setToken(identity.getToken());
getMongoStore().pushItemToList(userEntity, "federatedIdentities", federatedIdentityEntity, true, invocationContext);
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index de0a0cc..44058be 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -796,6 +796,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
+ identityProviderModel.setStoreToken(entity.isStoreToken());
identityProviders.add(identityProviderModel);
}
@@ -850,6 +851,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setName(identityProvider.getName());
entity.setEnabled(identityProvider.isEnabled());
entity.setUpdateProfileFirstLogin(identityProvider.isUpdateProfileFirstLogin());
+ entity.setStoreToken(identityProvider.isStoreToken());
entity.setConfig(identityProvider.getConfig());
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index fc0e6ca..1917844 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -5,6 +5,7 @@ import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -83,6 +84,7 @@ public class IdentityProviderResource {
String providerId = formDataMap.get("providerId").get(0).getBodyAsString();
String enabled = formDataMap.get("enabled").get(0).getBodyAsString();
String updateProfileFirstLogin = formDataMap.get("updateProfileFirstLogin").get(0).getBodyAsString();
+ String storeToken = formDataMap.get("storeToken").get(0).getBodyAsString();
InputPart file = formDataMap.get("file").get(0);
InputStream inputStream = file.getBody(InputStream.class, null);
IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
@@ -94,6 +96,7 @@ public class IdentityProviderResource {
providerModel.setProviderId(providerId);
providerModel.setEnabled(Boolean.valueOf(enabled));
providerModel.setUpdateProfileFirstLogin(Boolean.valueOf(updateProfileFirstLogin));
+ providerModel.setStoreToken(Boolean.valueOf(storeToken));
providerModel.setConfig(config);
return create(uriInfo, providerModel);
@@ -117,10 +120,32 @@ public class IdentityProviderResource {
@DELETE
@NoCache
public Response delete(@PathParam("id") String providerId) {
+ for (ClientModel applicationModel : getClientModels()) {
+ List<String> allowedIdentityProviders = applicationModel.getAllowedIdentityProviders();
+
+ for (String appProvider : new ArrayList<String>(allowedIdentityProviders)) {
+ if (appProvider.equals(providerId)) {
+ allowedIdentityProviders.remove(appProvider);
+ }
+ }
+
+ applicationModel.updateAllowedIdentityProviders(allowedIdentityProviders);
+ }
+
this.realm.removeIdentityProviderById(providerId);
+
return Response.noContent().build();
}
+ private List<ClientModel> getClientModels() {
+ List<ClientModel> clients = new ArrayList<ClientModel>();
+
+ clients.addAll(this.realm.getOAuthClients());
+ clients.addAll(this.realm.getApplications());
+
+ return clients;
+ }
+
@PUT
@Consumes("application/json")
public Response update(IdentityProviderModel model) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
index 0f14b30..17c05e5 100644
--- a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
@@ -28,28 +28,35 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.provider.ProviderFactory;
+import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationManager.AuthResult;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.EventsManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.social.SocialIdentityProvider;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+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;
@@ -100,6 +107,13 @@ public class AuthenticationBrokerResource {
try {
ClientSessionModel clientSession = clientCode.getClientSession();
+ ClientModel clientModel = clientSession.getClient();
+ Response response = checkClientPermissions(clientModel, providerId);
+
+ if (response != null) {
+ return response;
+ }
+
IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
if (identityProvider == null) {
@@ -109,7 +123,8 @@ public class AuthenticationBrokerResource {
AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, code, realm,
clientSession));
- Response response = authenticationResponse.getResponse();
+
+ response = authenticationResponse.getResponse();
if (response != null) {
event.success();
@@ -142,6 +157,67 @@ public class AuthenticationBrokerResource {
return handleResponse(realmName, providerId);
}
+ @GET
+ @Path("{realm}/{provider_id}/token")
+ public Response retrieveToken(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId) {
+ return getToken(realmName, providerId, false);
+ }
+
+ private Response getToken(String realmName, String providerId, boolean forceRetrieval) {
+ RealmManager realmManager = new RealmManager(session);
+ RealmModel realm = realmManager.getRealmByName(realmName);
+ AppAuthManager authManager = new AppAuthManager();
+ AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, request.getHttpHeaders());
+
+ if (authResult != null) {
+ String audience = authResult.getToken().getAudience();
+ ClientModel clientModel = realm.findClient(audience);
+
+ if (clientModel == null) {
+ return Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN);
+ }
+
+ if (!clientModel.hasIdentityProvider(providerId)) {
+ return Flows.errors().error("Client [" + audience + "] not authorized.", Response.Status.FORBIDDEN);
+ }
+
+ if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
+ return Flows.forms(session, realm, clientModel, uriInfo)
+ .setClientSessionCode(authManager.extractAuthorizationHeaderToken(request.getHttpHeaders()))
+ .setAccessRequest("Your information from " + providerId + " identity provider.")
+ .setClient(clientModel)
+ .setUriInfo(this.uriInfo)
+ .setActionUri(this.uriInfo.getRequestUri())
+ .createOAuthGrant();
+ }
+
+ IdentityProvider identityProvider = getIdentityProvider(realm, providerId);
+ IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
+
+ if (identityProviderConfig.isStoreToken()) {
+ FederatedIdentityModel identity = this.session.users().getFederatedIdentity(authResult.getUser(), providerId, realm);
+
+ return identityProvider.retrieveToken(identity);
+ }
+
+ return Flows.errors().error("Identity Provider [" + providerId + "] does not support this operation.", Response.Status.FORBIDDEN);
+ }
+
+ return Flows.errors().error("Invalid code.", Response.Status.FORBIDDEN);
+ }
+
+ @POST
+ @Path("{realm}/{provider_id}/token")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response consentTokenRetrieval(@PathParam("realm") final String realmName, @PathParam("provider_id") String providerId,
+ final MultivaluedMap<String, String> formData) {
+ if (formData.containsKey("cancel")) {
+ return Flows.errors().error("Permission not approved.", Response.Status.FORBIDDEN);
+ }
+
+ return getToken(realmName, providerId, true);
+ }
+
private Response handleResponse(String realmName, String providerId) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName);
@@ -166,17 +242,24 @@ public class AuthenticationBrokerResource {
}
ClientSessionModel clientSession = clientCode.getClientSession();
+ ClientModel clientModel = clientSession.getClient();
+ Response response = checkClientPermissions(clientModel, providerId);
+
+ if (response != null) {
+ return response;
+ }
AuthenticationResponse authenticationResponse = provider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession));
- Response response = authenticationResponse.getResponse();
+
+ response = authenticationResponse.getResponse();
if (response != null) {
return response;
}
- FederatedIdentity socialUser = authenticationResponse.getUser();
+ FederatedIdentity identity = authenticationResponse.getUser();
- return performLocalAuthentication(realm, providerId, socialUser, clientCode);
+ return performLocalAuthentication(realm, providerId, identity, clientCode);
} catch (Exception e) {
if (session.getTransaction().isActive()) {
session.getTransaction().rollback();
@@ -192,14 +275,14 @@ public class AuthenticationBrokerResource {
}
}
- private Response performLocalAuthentication(RealmModel realm, String providerId, FederatedIdentity socialUser, ClientSessionCode clientCode) {
+ private Response performLocalAuthentication(RealmModel realm, String providerId, FederatedIdentity identity, ClientSessionCode clientCode) {
ClientSessionModel clientSession = clientCode.getClientSession();
- FederatedIdentityModel socialLink = new FederatedIdentityModel(providerId, socialUser.getId(),
- socialUser.getUsername());
- UserModel federatedUser = session.users().getUserByFederatedIdentity(socialLink, realm);
+ FederatedIdentityModel identityModel = new FederatedIdentityModel(providerId, identity.getId(),
+ identity.getUsername(), identity.getToken());
+ UserModel federatedUser = session.users().getUserByFederatedIdentity(identityModel, realm);
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(realm, providerId);
- String authMethod = socialLink.getUserId() + "@" + identityProviderConfig.getId();
+ String authMethod = identityModel.getUserId() + "@" + identityProviderConfig.getId();
EventBuilder event = new EventsManager(realm, session, clientConnection).createEventBuilder()
.event(EventType.LOGIN)
.client(clientSession.getClient())
@@ -228,29 +311,29 @@ public class AuthenticationBrokerResource {
return redirectToErrorPage(realm, "Insufficient permissions to link identity");
}
- session.users().addFederatedIdentity(realm, authenticatedUser, socialLink);
+ session.users().addFederatedIdentity(realm, authenticatedUser, identityModel);
event.success();
return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
}
- UserModel user = session.users().getUserByEmail(socialUser.getEmail(), realm);
+ UserModel user = session.users().getUserByEmail(identity.getEmail(), realm);
String errorMessage = "federatedIdentityEmailExists";
if (user == null) {
- user = session.users().getUserByUsername(socialUser.getUsername(), realm);
+ user = session.users().getUserByUsername(identity.getUsername(), realm);
errorMessage = "federatedIdentityUsernameExists";
}
if (user == null) {
- federatedUser = session.users().addUser(realm, socialUser.getUsername());
+ federatedUser = session.users().addUser(realm, identity.getUsername());
federatedUser.setEnabled(true);
- federatedUser.setFirstName(socialUser.getFirstName());
- federatedUser.setLastName(socialUser.getLastName());
- federatedUser.setEmail(socialUser.getEmail());
+ federatedUser.setFirstName(identity.getFirstName());
+ federatedUser.setLastName(identity.getLastName());
+ federatedUser.setEmail(identity.getEmail());
- session.users().addFederatedIdentity(realm, federatedUser, socialLink);
+ session.users().addFederatedIdentity(realm, federatedUser, identityModel);
event.clone().user(federatedUser).event(EventType.REGISTER)
.detail(Details.REGISTER_METHOD, authMethod)
@@ -272,7 +355,7 @@ public class AuthenticationBrokerResource {
event.user(federatedUser);
- String username = socialLink.getUserId() + "@" + identityProviderConfig.getName();
+ String username = identityModel.getUserId() + "@" + identityProviderConfig.getName();
UserSessionModel userSession = session.sessions()
.createUserSession(realm, federatedUser, username, clientConnection.getRemoteAddr(), "broker", false);
@@ -353,4 +436,16 @@ public class AuthenticationBrokerResource {
return null;
}
+ private Response checkClientPermissions(ClientModel clientModel, String providerId) {
+ if (clientModel == null) {
+ return Flows.errors().error("Invalid client.", Response.Status.FORBIDDEN);
+ }
+
+ if (!clientModel.hasIdentityProvider(providerId)) {
+ return Flows.errors().error("Client [" + clientModel.getClientId() + "] not authorized.", Response.Status.FORBIDDEN);
+ }
+
+ return null;
+ }
+
}
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
index 8c070be..5266c68 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -24,8 +24,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp
config.setUserInfoUrl(PROFILE_URL);
}
- @Override
- protected FederatedIdentity getFederatedIdentity(String accessToken) {
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
try {
JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
index 5069591..7be16dc 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -25,7 +25,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
}
@Override
- protected FederatedIdentity getFederatedIdentity(String accessToken) {
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
try {
JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 0e7673c..1b7a563 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -27,12 +27,14 @@ import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.social.SocialIdentityProvider;
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.auth.RequestToken;
import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
@@ -109,4 +111,9 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
throw new RuntimeException(e);
}
}
+
+ @Override
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return Response.noContent().build();
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index a3329d8..29c6aab 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -25,7 +25,6 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.events.Details;
@@ -220,7 +219,7 @@ public class AccountTest {
changePasswordPage.logout();
- events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AccountPasswordPage.PATH).assertEvent();
+ events.expectLogout(sessionId).detail(Details.REDIRECT_URI, changePasswordPage.getPath()).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index da5d014..4d3c4f4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -23,28 +23,43 @@ import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.IDToken;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testsuite.rule.WebRule.HtmlUnitDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import static com.thoughtworks.selenium.SeleneseTestBase.fail;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -68,6 +83,15 @@ public abstract class AbstractIdentityProviderTest {
@WebResource
private LoginUpdateProfilePage updateProfilePage;
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected OAuthGrantPage grantPage;
+
+ @WebResource
+ protected AccountPasswordPage changePasswordPage;
+
private KeycloakSession session;
@Before
@@ -76,6 +100,7 @@ public abstract class AbstractIdentityProviderTest {
removeTestUsers();
brokerServerRule.stopSession(this.session, true);
this.session = brokerServerRule.startSession();
+ assertNotNull(getIdentityProviderModel());
}
@After
@@ -87,8 +112,6 @@ public abstract class AbstractIdentityProviderTest {
public void testSuccessfulAuthentication() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- identityProviderModel.setUpdateProfileFirstLogin(true);
-
assertSuccessfulAuthentication(identityProviderModel);
}
@@ -96,8 +119,6 @@ public abstract class AbstractIdentityProviderTest {
public void testSuccessfulAuthenticationWithoutUpdateProfile() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- identityProviderModel.setUpdateProfileFirstLogin(false);
-
assertSuccessfulAuthentication(identityProviderModel);
}
@@ -121,10 +142,6 @@ public abstract class AbstractIdentityProviderTest {
@Test
public void testUserAlreadyExistsWhenUpdatingProfile() {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
- identityProviderModel.setUpdateProfileFirstLogin(true);
-
this.driver.navigate().to("http://localhost:8081/test-app/");
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
@@ -185,10 +202,66 @@ public abstract class AbstractIdentityProviderTest {
assertEquals("User with email already exists. Please login to account management to link the account.", element.getText());
}
- private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) {
+ @Test(expected = NoSuchElementException.class)
+ public void testIdentityProviderNotAllowed() {
+ this.driver.navigate().to("http://localhost:8081/test-app/");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+
+ driver.findElement(By.className("model-oidc-idp"));
+ }
+
+ @Test
+ public void testTokenStorageAndRetrievalByApplication() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+ identityProviderModel.setStoreToken(true);
+
+ authenticateWithIdentityProvider(identityProviderModel);
+
+ UserModel federatedUser = getFederatedUser();
+ RealmModel realm = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+
+ assertFalse(federatedIdentities.isEmpty());
+ assertEquals(1, federatedIdentities.size());
+
+ FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
+
+ assertNotNull(identityModel.getToken());
+
+ UserSessionStatus userSessionStatus = retrieveSessionStatus();
+ String accessToken = userSessionStatus.getAccessTokenString();
+ String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
+ final String authHeader = "Bearer " + accessToken;
+ ClientRequestFilter authFilter = new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+ }
+ };
+ Client client = ClientBuilder.newBuilder().register(authFilter).build();
+ UriBuilder authBase = UriBuilder.fromUri(tokenEndpointUrl);
+ WebTarget tokenEndpoint = client.target(authBase);
+ Response response = tokenEndpoint.request().get();
+
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertNotNull(response.readEntity(String.class));
+
+ driver.navigate().to("http://localhost:8081/test-app/logout");
driver.navigate().to("http://localhost:8081/test-app");
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+ }
+
+ @Test
+ public void testTokenStorageAndRetrievalByOAuthClient() {
+ IdentityProviderModel identityProviderModel = getIdentityProviderModel();
+
+ identityProviderModel.setStoreToken(true);
+ identityProviderModel.setUpdateProfileFirstLogin(false);
+
+ driver.navigate().to("http://localhost:8081/test-app");
// choose the identity provider
this.loginPage.clickSocial(getProviderId());
@@ -200,15 +273,42 @@ public abstract class AbstractIdentityProviderTest {
doAfterProviderAuthentication();
- if (identityProviderModel.isUpdateProfileFirstLogin()) {
- String userEmail = "new@email.com";
- String userFirstName = "New first";
- String userLastName = "New last";
+ changePasswordPage.realm("realm-with-broker");
+ changePasswordPage.open();
+ changePasswordPage.changePassword("password", "password");
- // update profile
- this.updateProfilePage.assertCurrent();
- this.updateProfilePage.update(userFirstName, userLastName, userEmail);
- }
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+
+ oauth.realm("realm-with-broker");
+ oauth.redirectUri("http://localhost:8081/third-party");
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
+
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
+ AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
+ String tokenEndpointUrl = "http://localhost:8081/auth/broker/realm-with-broker/" + getProviderId() + "/token";
+ String authHeader = "Bearer " + accessToken.getAccessToken();
+ HtmlUnitDriver htmlUnitDriver = (WebRule.HtmlUnitDriver) this.driver;
+
+ htmlUnitDriver.getWebClient().addRequestHeader(HttpHeaders.AUTHORIZATION, authHeader);
+
+ htmlUnitDriver.navigate().to(tokenEndpointUrl);
+
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ assertNotNull(driver.getPageSource());
+
+ doAssertTokenRetrieval(driver.getPageSource());
+ }
+
+ protected abstract void doAssertTokenRetrieval(String pageSource);
+
+ private void assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel) {
+ authenticateWithIdentityProvider(identityProviderModel);
// authenticated and redirected to app
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
@@ -217,7 +317,7 @@ public abstract class AbstractIdentityProviderTest {
assertNotNull(federatedUser);
- doAssertFederatedUser(federatedUser);
+ doAssertFederatedUser(federatedUser, identityProviderModel);
RealmModel realm = getRealm();
@@ -236,6 +336,32 @@ public abstract class AbstractIdentityProviderTest {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
}
+ private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel) {
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login"));
+
+ // choose the identity provider
+ this.loginPage.clickSocial(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+
+ // log in to identity provider
+ this.loginPage.login("test-user", "password");
+
+ doAfterProviderAuthentication();
+
+ if (identityProviderModel.isUpdateProfileFirstLogin()) {
+ String userEmail = "new@email.com";
+ String userFirstName = "New first";
+ String userLastName = "New last";
+
+ // update profile
+ this.updateProfilePage.assertCurrent();
+ this.updateProfilePage.update(userFirstName, userLastName, userEmail);
+ }
+ }
+
protected UserModel getFederatedUser() {
UserSessionStatus userSessionStatus = retrieveSessionStatus();
IDToken idToken = userSessionStatus.getIdToken();
@@ -256,6 +382,9 @@ public abstract class AbstractIdentityProviderTest {
assertNotNull(identityProviderModel);
+ identityProviderModel.setUpdateProfileFirstLogin(true);
+ identityProviderModel.setEnabled(true);
+
return identityProviderModel;
}
@@ -263,9 +392,7 @@ public abstract class AbstractIdentityProviderTest {
return this.session.realms().getRealm("realm-with-broker");
}
- protected void doAssertFederatedUser(UserModel federatedUser) {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+ protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
String userEmail = "new@email.com";
String userFirstName = "New first";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
index be9503d..deb2611 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/BrokerKeyCloakRule.java
@@ -35,6 +35,7 @@ public class BrokerKeyCloakRule extends AbstractKeycloakRule {
server.importRealm(getClass().getResourceAsStream("/broker-test/test-realm-with-broker.json"));
URL url = getClass().getResource("/broker-test/test-app-keycloak.json");
deployApplication("test-app", "/test-app", UserSessionStatusServlet.class, url.getPath(), "manager");
+ deployApplication("test-app-allowed-providers", "/test-app-allowed-providers", UserSessionStatusServlet.class, url.getPath(), "manager");
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index d8e7594..9a646cb 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -76,6 +76,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
identityProviderModel.getConfig().put("config-added", "value-added");
identityProviderModel.setEnabled(false);
identityProviderModel.setUpdateProfileFirstLogin(false);
+ identityProviderModel.setStoreToken(true);
realm.updateIdentityProvider(identityProviderModel);
@@ -87,8 +88,9 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertEquals("Changed Name", identityProviderModel.getName());
assertEquals("value-added", identityProviderModel.getConfig().get("config-added"));
- assertEquals(false, identityProviderModel.isEnabled());
- assertEquals(false, identityProviderModel.isUpdateProfileFirstLogin());
+ assertFalse(identityProviderModel.isEnabled());
+ assertFalse(identityProviderModel.isUpdateProfileFirstLogin());
+ assertTrue(identityProviderModel.isStoreToken());
identityProviderModel.setName("Changed Name Again");
identityProviderModel.getConfig().remove("config-added");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 011af1b..66e4b71 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -3,11 +3,18 @@ package org.keycloak.testsuite.broker;
import org.junit.ClassRule;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testutils.KeycloakServer;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
/**
* @author pedroigor
@@ -39,6 +46,18 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
}
@Override
+ protected void doAssertTokenRetrieval(String pageSource) {
+ try {
+ AccessTokenResponse accessTokenResponse = JsonSerialization.readValue(pageSource, AccessTokenResponse.class);
+
+ assertNotNull(accessTokenResponse.getToken());
+ assertNotNull(accessTokenResponse.getIdToken());
+ } catch (IOException e) {
+ fail("Could not parse token.");
+ }
+ }
+
+ @Override
protected String getProviderId() {
return "kc-oidc-idp";
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
index 4ef9241..0c0e369 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
@@ -20,8 +20,11 @@ package org.keycloak.testsuite.broker.provider;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
+import javax.ws.rs.core.Response;
+
/**
* @author pedroigor
*/
@@ -45,4 +48,9 @@ public class CustomIdentityProvider extends AbstractIdentityProvider<IdentityPro
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
return null;
}
+
+ @Override
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return null;
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
index 86aa6a3..e3a2bd2 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/social/CustomSocialProvider.java
@@ -20,9 +20,12 @@ package org.keycloak.testsuite.broker.provider.social;
import org.keycloak.broker.provider.AbstractIdentityProvider;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.social.SocialIdentityProvider;
+import javax.ws.rs.core.Response;
+
/**
* @author pedroigor
*/
@@ -46,4 +49,9 @@ public class CustomSocialProvider extends AbstractIdentityProvider<IdentityProvi
public AuthenticationResponse handleResponse(AuthenticationRequest request) {
return null;
}
+
+ @Override
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return null;
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
index b14328a..7783665 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java
@@ -8,9 +8,17 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testutils.KeycloakServer;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+
+import java.net.URLDecoder;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
/**
* @author pedroigor
@@ -37,15 +45,27 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
}
@Override
- protected void doAssertFederatedUser(UserModel federatedUser) {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+ protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
- super.doAssertFederatedUser(federatedUser);
+ super.doAssertFederatedUser(federatedUser, identityProviderModel);
} else {
assertEquals("test-user@localhost", federatedUser.getEmail());
assertNull(federatedUser.getFirstName());
assertNull(federatedUser.getLastName());
}
}
+
+ @Override
+ protected void doAssertTokenRetrieval(String pageSource) {
+ try {
+ SAML2Request saml2Request = new SAML2Request();
+ ResponseType responseType = (ResponseType) saml2Request
+ .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+
+ assertNotNull(responseType);
+ assertFalse(responseType.getAssertions().isEmpty());
+ } catch (Exception e) {
+ fail("Could not parse token.");
+ }
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
index 47ddb14..e387097 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java
@@ -8,9 +8,17 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testutils.KeycloakServer;
+import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
+import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
+import org.picketlink.identity.federation.web.util.PostBindingUtil;
+
+import java.net.URLDecoder;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
/**
* @author pedroigor
@@ -37,15 +45,27 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
}
@Override
- protected void doAssertFederatedUser(UserModel federatedUser) {
- IdentityProviderModel identityProviderModel = getIdentityProviderModel();
-
+ protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel) {
if (identityProviderModel.isUpdateProfileFirstLogin()) {
- super.doAssertFederatedUser(federatedUser);
+ super.doAssertFederatedUser(federatedUser, identityProviderModel);
} else {
assertEquals("test-user@localhost", federatedUser.getEmail());
assertNull(federatedUser.getFirstName());
assertNull(federatedUser.getLastName());
}
}
+
+ @Override
+ protected void doAssertTokenRetrieval(String pageSource) {
+ try {
+ SAML2Request saml2Request = new SAML2Request();
+ ResponseType responseType = (ResponseType) saml2Request
+ .getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
+
+ assertNotNull(responseType);
+ assertFalse(responseType.getAssertions().isEmpty());
+ } catch (Exception e) {
+ fail("Could not parse token.");
+ }
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
index c2a6fdd..b135473 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/util/UserSessionStatusServlet.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.broker.util;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import javax.servlet.ServletException;
@@ -49,7 +50,8 @@ public class UserSessionStatusServlet extends HttpServlet {
private void writeSessionStatus(HttpServletRequest req, HttpServletResponse resp) throws IOException {
KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
IDToken idToken = context.getIdToken();
- JsonNode jsonNode = new ObjectMapper().valueToTree(new UserSessionStatus(idToken));
+ AccessToken accessToken = context.getToken();
+ JsonNode jsonNode = new ObjectMapper().valueToTree(new UserSessionStatus(idToken, accessToken, context.getTokenString()));
PrintWriter writer = resp.getWriter();
writer.println(jsonNode.toString());
@@ -59,14 +61,18 @@ public class UserSessionStatusServlet extends HttpServlet {
public static class UserSessionStatus implements Serializable {
+ private String accessTokenString;
+ private AccessToken accessToken;
private IDToken idToken;
public UserSessionStatus() {
}
- public UserSessionStatus(IDToken idToken) {
+ public UserSessionStatus(IDToken idToken, AccessToken accessToken, String tokenString) {
this.idToken = idToken;
+ this.accessToken = accessToken;
+ this.accessTokenString = tokenString;
}
public IDToken getIdToken() {
@@ -76,5 +82,17 @@ public class UserSessionStatusServlet extends HttpServlet {
public void setIdToken(IDToken idToken) {
this.idToken = idToken;
}
+
+ public AccessToken getAccessToken() {
+ return this.accessToken;
+ }
+
+ public void setAccessToken(AccessToken accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ public String getAccessTokenString() {
+ return this.accessTokenString;
+ }
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 0dc027b..975806e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -33,8 +33,6 @@ import javax.ws.rs.core.UriBuilder;
*/
public class AccountPasswordPage extends AbstractAccountPage {
- public static String PATH = AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
-
@FindBy(id = "password")
private WebElement passwordInput;
@@ -47,6 +45,8 @@ public class AccountPasswordPage extends AbstractAccountPage {
@FindBy(className = "btn-primary")
private WebElement submitButton;
+ private String realmName = "test";
+
public void changePassword(String password, String newPassword, String passwordConfirm) {
passwordInput.sendKeys(password);
newPasswordInput.sendKeys(newPassword);
@@ -55,12 +55,26 @@ public class AccountPasswordPage extends AbstractAccountPage {
submitButton.click();
}
+ public void changePassword(String newPassword, String passwordConfirm) {
+ newPasswordInput.sendKeys(newPassword);
+ passwordConfirmInput.sendKeys(passwordConfirm);
+
+ submitButton.click();
+ }
+
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/password");
}
public void open() {
- driver.navigate().to(PATH);
+ driver.navigate().to(getPath());
}
+ public void realm(String realmName) {
+ this.realmName = realmName;
+ }
+
+ public String getPath() {
+ return AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
+ }
}
diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
index d5865c1..2c98048 100755
--- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
+++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json
@@ -3,6 +3,7 @@
"realm": "realm-with-broker",
"enabled": true,
"requiredCredentials": [ "password" ],
+ "resetPasswordAllowed": true,
"defaultRoles": [ "manager" ],
"privateKey": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCCPyvTTb14vSMkpe/pds2P5Cqxk7bkeFnQiNMS1vyZ+HS2O79fxzp1eAguHnBTs4XTRT7SZJhIT/6utgqZjmDigKV5N7X5ptq8BM/W1qa1cYBRip261pc+tWf3IywJYQ9yFI9mUQarmIEl0D7GH16NSZklheaWfbodRVarvX+ML0amNtGYVDft/RftYmgbKKrK218qQp9R4GZFtf/Q/RmboNXN7weMINU8GWVkTRrccKBIXSunT6zXGfuj3Wp1YpVq20BWwY2OMM/P+yDAc7LKEO1LJqPBdT4r9BRn2lXiaga3AL24gTKZPKU/tu7uqfFciF+i4Rr58SMDNOzQcnklAgMBAAECggEAc0eibJYEO5d8QXW1kPgcHV2gBChv2mxDYnWYDLbIQSdNdfYP/qABt/MTmm5KkWr16fcCEYoD1w0mqFBrtVn1msSusUmEAYGTXJMNumOmjjX1kzaTQMmqeFBrwqwYz/xehWR5P+A7fSmwNV3KEeW19GvN5w5K96w0TLAQdFV3TQVPSytusDunwuR1yltMe1voaEDZ9z0Pi08YiEk2f6xhj5CMkoiw3mNImzfruphHullxU4FD05fH6tDeJ381527ILpAzDsgYZh4aFLKjUHem96bX4EL7FIzBJ6okgN78AZnUC/EaVfgFTw0qfhoWvZV4ruVXXiMhCg4CMMRDq/k9iQKBgQDBNWsJMT84OnnWmQoJmZogkFV+tsGrSK6Re+aJxLWpishh7dwAnT2OcagZvVdUb0FwNWu1D0B9/SKDDMRnnHBhOGDpH57m/eQdRU0oX1BD27xvffk0lLcfD4BTxnR5e9jss8K4twc9jf0P1rxC/loGJ2NtCH0BrPHgz54Ea+96ewKBgQCsk3JDaaPnFwzVYm2BXlhxOxLPsF4wvD2rIRAswZV4C5xebjand8nwiMmVpNd0PRLkEnkI+waURGv2EY/P3JsssoiY8Xqe8f/1G+SQKre7lbqOas8rFoALepC0BYDiZDFy0Z9ZnRAFzRI5sgIt7jpoMRD4xDNlmiV8X+yBxc3Y3wKBgQChDQsU1YUyNKQ8+sLAL9anEEkD4Ald4q8JPHN2IY+gLLxNzT0XEfsu0pTiJ8805axxgUYv3e/PVYNAJBNPnrqaf6lgiegl+jr9Hzhqz9CTUAYqFaL2boSakoxQyNtsLI0s+cb1vDN/3uy0GDZDzcty18BsMagqDmRtFgNNAj/UIwKBgQCahbeFBv0cOPZjxisY8Bou4N8aGehsqNBq/0LVYExuXa8YmoTTdJ3bgw9Er4G/ccQNdUDsuqAMeCtW/CiRzQ0ge4d1sprB4Rv3I4+HSsiS7SFKzfZLtWzXWlpg5qCdlWr1TR7qhYjIOPO9t1beO3YOvwhcRoliyyAPenBxTmTfbwKBgDtm2WJ5VlQgNpIdOs1CCiqd0DFmWOmvBPspPC1kySiy+Ndr9jNohRZkR7pEjgqA5E8rdzc88LirUN7bY5HFHRWN9KXrs5/o3O1K3GFCp64N6nvnPEYZ2zSJalcMC2fjSsJg26z8Dg1H+gfTIDUMoGiEAAnJXuqk+WayPU+fZMLn",
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQAB",
@@ -163,7 +164,20 @@
"redirectUris": [
"/test-app/*"
],
- "webOrigins": []
+ "webOrigins": [],
+ "allowedIdentityProviders" : [
+ "model-oidc-idp"
+ ]
+ }
+ ],
+ "oauthClients" : [
+ {
+ "name" : "third-party",
+ "enabled": true,
+ "redirectUris": [
+ "http://localhost:8081/third-party/*"
+ ],
+ "secret": "password"
}
],
"roles" : {