[KEYCLOAK-992] - Token retrieval from brokered idps.

This commit is contained in:
pedroigor 2015-02-06 22:23:34 -02:00
parent 9b6254399d
commit 4b1ba704ec
64 changed files with 1193 additions and 170 deletions

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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.");
@ -109,24 +117,23 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
}
}
@Override
public Response retrieveToken(FederatedIdentityModel identity) {
return Response.ok(identity.getToken()).build();
}
@Override
public C getConfig() {
return super.getConfig();
}
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));
}
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())

View file

@ -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);
}

View file

@ -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;

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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) {

View file

@ -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("/");
});
});

View file

@ -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>

View file

@ -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>

View file

@ -92,6 +92,13 @@
</div>
<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">

View file

@ -93,6 +93,13 @@
</div>
<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">

View file

@ -49,6 +49,13 @@
</div>
<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">

View file

@ -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>

View file

@ -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>

View file

@ -14,25 +14,35 @@
<span>
Personal Info:&nbsp;
<#list oauth.claimsRequested as claim>
${claim}&nbsp;
${claim}&nbsp;
</#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">

View file

@ -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);
}

View file

@ -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));
@ -303,6 +305,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return this;
}
@Override
public LoginFormsProvider setAccessRequest(String accessRequestMessage) {
this.accessRequestMessage = accessRequestMessage;
return this;
}
@Override
public LoginFormsProvider setStatus(Response.Status status) {
this.status = status;
@ -315,6 +323,12 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return this;
}
@Override
public LoginFormsProvider setActionUri(URI actionUri) {
this.actionUri = actionUri;
return this;
}
@Override
public void close() {
}

View file

@ -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));
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -53,5 +53,4 @@ public interface ApplicationModel extends RoleContainerModel, ClientModel {
void registerNode(String nodeHost, int registrationTime);
void unregisterNode(String nodeHost);
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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());
}
}

View file

@ -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();
AuthenticationResponse authenticationResponse = provider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession));
Response response = authenticationResponse.getResponse();
ClientModel clientModel = clientSession.getClient();
Response response = checkClientPermissions(clientModel, providerId);
if (response != null) {
return response;
}
FederatedIdentity socialUser = authenticationResponse.getUser();
AuthenticationResponse authenticationResponse = provider.handleResponse(createAuthenticationRequest(providerId, null, realm, clientSession));
return performLocalAuthentication(realm, providerId, socialUser, clientCode);
response = authenticationResponse.getResponse();
if (response != null) {
return response;
}
FederatedIdentity identity = authenticationResponse.getUser();
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;
}
}

View file

@ -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) {

View file

@ -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();

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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");

View file

@ -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,7 +202,141 @@ public abstract class AbstractIdentityProviderTest {
assertEquals("User with email already exists. Please login to account management to link the account.", element.getText());
}
@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());
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
// log in to identity provider
this.loginPage.login("test-user", "password");
doAfterProviderAuthentication();
changePasswordPage.realm("realm-with-broker");
changePasswordPage.open();
changePasswordPage.changePassword("password", "password");
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"));
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
doAssertFederatedUser(federatedUser, identityProviderModel);
RealmModel realm = getRealm();
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
assertEquals(1, federatedIdentities.size());
FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
assertEquals(federatedUser.getUsername(), federatedIdentityModel.getUserName());
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"));
}
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"));
@ -209,31 +360,6 @@ public abstract class AbstractIdentityProviderTest {
this.updateProfilePage.assertCurrent();
this.updateProfilePage.update(userFirstName, userLastName, userEmail);
}
// authenticated and redirected to app
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
doAssertFederatedUser(federatedUser);
RealmModel realm = getRealm();
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
assertEquals(1, federatedIdentities.size());
FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next();
assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider());
assertEquals(federatedUser.getUsername(), federatedIdentityModel.getUserName());
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"));
}
protected UserModel getFederatedUser() {
@ -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";

View file

@ -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

View file

@ -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");

View file

@ -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
@ -38,6 +45,18 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
grantPage.accept();
}
@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";

View file

@ -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.");
}
}
}

View file

@ -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.");
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}

View file

@ -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" : {