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

This commit is contained in:
pedroigor committed 2015-02-06 22:23:34 -02:00
1 parent 9b6254399d
commit 4b1ba704ec
64 files changed
+1193 -170

No files matched your search

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