Merge pull request #1958 from patriot1burke/master
refactor client create
This commit is contained in:
commit
4782780857
12 changed files with 519 additions and 67 deletions
|
@ -1132,7 +1132,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
controller : 'UserRoleMappingCtrl'
|
||||
})
|
||||
.when('/create/client/:realm', {
|
||||
templateUrl : resourceUrl + '/partials/client-detail.html',
|
||||
templateUrl : resourceUrl + '/partials/create-client.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
|
@ -1150,7 +1150,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ClientDetailCtrl'
|
||||
controller : 'CreateClientCtrl'
|
||||
})
|
||||
.when('/realms/:realm/clients/:client', {
|
||||
templateUrl : resourceUrl + '/partials/client-detail.html',
|
||||
|
|
|
@ -736,7 +736,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
"bearer-only"
|
||||
];
|
||||
|
||||
$scope.protocols = Object.keys(serverInfo.providers['login-protocol'].providers).sort();
|
||||
$scope.protocols = ['openid-connect',
|
||||
'saml'];//Object.keys(serverInfo.providers['login-protocol'].providers).sort();
|
||||
|
||||
$scope.templates = [ {name:'NONE'}];
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
|
@ -765,7 +766,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
];
|
||||
|
||||
$scope.realm = realm;
|
||||
$scope.create = !client.clientId;
|
||||
$scope.samlAuthnStatement = false;
|
||||
$scope.samlMultiValuedRoles = false;
|
||||
$scope.samlServerSignature = false;
|
||||
|
@ -870,20 +870,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
if (!$scope.create) {
|
||||
$scope.client = angular.copy(client);
|
||||
updateProperties();
|
||||
} else {
|
||||
$scope.client = {
|
||||
enabled: true,
|
||||
standardFlowEnabled: true,
|
||||
attributes: {}
|
||||
};
|
||||
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
||||
$scope.client.redirectUris = [];
|
||||
$scope.accessType = $scope.accessTypes[0];
|
||||
$scope.protocol = $scope.protocols[0];
|
||||
$scope.signatureAlgorithm = $scope.signatureAlgorithms[1];
|
||||
$scope.nameIdFormat = $scope.nameIdFormats[0];
|
||||
$scope.samlAuthnStatement = true;
|
||||
$scope.samlForceNameIdFormat = false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1055,28 +1041,15 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && ($scope.client.standardFlowEnabled || $scope.client.implicitFlowEnabled) && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
|
||||
Notifications.error("You must specify at least one redirect uri");
|
||||
} else {
|
||||
if ($scope.create) {
|
||||
Client.save({
|
||||
realm: realm.realm,
|
||||
client: ''
|
||||
}, $scope.client, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + id);
|
||||
Notifications.success("The client has been created.");
|
||||
});
|
||||
} else {
|
||||
Client.update({
|
||||
realm : realm.realm,
|
||||
client : client.id
|
||||
}, $scope.client, function() {
|
||||
$scope.changed = false;
|
||||
client = angular.copy($scope.client);
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id);
|
||||
Notifications.success("Your changes have been saved to the client.");
|
||||
});
|
||||
}
|
||||
Client.update({
|
||||
realm : realm.realm,
|
||||
client : client.id
|
||||
}, $scope.client, function() {
|
||||
$scope.changed = false;
|
||||
client = angular.copy($scope.client);
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + client.id);
|
||||
Notifications.success("Your changes have been saved to the client.");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1089,6 +1062,111 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('CreateClientCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
|
||||
$scope.protocols = ['openid-connect',
|
||||
'saml'];//Object.keys(serverInfo.providers['login-protocol'].providers).sort();
|
||||
|
||||
$scope.templates = [ {name:'NONE'}];
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
var template = templates[i];
|
||||
$scope.templates.push(template);
|
||||
}
|
||||
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.client = {
|
||||
enabled: true,
|
||||
attributes: {}
|
||||
};
|
||||
$scope.client.redirectUris = [];
|
||||
$scope.protocol = $scope.protocols[0];
|
||||
|
||||
|
||||
$scope.importFile = function(fileContent){
|
||||
console.debug(fileContent);
|
||||
ClientDescriptionConverter.save({
|
||||
realm: realm.realm
|
||||
}, fileContent, function (data) {
|
||||
$scope.client = data;
|
||||
$scope.importing = true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.viewImportDetails = function() {
|
||||
$modal.open({
|
||||
templateUrl: resourceUrl + '/partials/modal/view-object.html',
|
||||
controller: 'ObjectModalCtrl',
|
||||
resolve: {
|
||||
object: function () {
|
||||
return $scope.client;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
$scope.switchChange = function() {
|
||||
$scope.changed = true;
|
||||
}
|
||||
|
||||
$scope.changeProtocol = function() {
|
||||
if ($scope.protocol == "openid-connect") {
|
||||
$scope.client.protocol = "openid-connect";
|
||||
} else if ($scope.protocol == "saml") {
|
||||
$scope.client.protocol = "saml";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
|
||||
function isChanged() {
|
||||
if (!angular.equals($scope.client, client)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$scope.$watch('client', function() {
|
||||
$scope.changed = isChanged();
|
||||
}, true);
|
||||
|
||||
|
||||
$scope.save = function() {
|
||||
|
||||
$scope.client.protocol = $scope.protocol;
|
||||
|
||||
if ($scope.client.protocol == 'openid-connect' && !$scope.client.rootUrl) {
|
||||
Notifications.error("You must specify the root URL of application");
|
||||
}
|
||||
|
||||
if ($scope.client.protocol == 'saml' && !$scope.client.adminUrl) {
|
||||
Notifications.error("You must specify the SAML Endpoint URL");
|
||||
}
|
||||
|
||||
Client.save({
|
||||
realm: realm.realm,
|
||||
client: ''
|
||||
}, $scope.client, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/clients/" + id);
|
||||
Notifications.success("The client has been created.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$route.reload();
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/clients");
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('ClientScopeMappingCtrl', function($scope, $http, realm, client, clients, templates, Notifications,
|
||||
Client, ClientTemplate,
|
||||
ClientRealmScopeMapping, ClientClientScopeMapping, ClientRole,
|
||||
|
|
|
@ -2,30 +2,15 @@
|
|||
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
|
||||
<li data-ng-show="create">{{:: 'add-client' | translate}}</li>
|
||||
<li data-ng-hide="create">{{client.clientId}}</li>
|
||||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group" data-ng-show="create">
|
||||
<label for="name" class="col-sm-2 control-label">{{:: 'import' | translate}}</label>
|
||||
|
||||
<div class="col-md-6" data-ng-hide="importing">
|
||||
<label for="import-file" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
|
||||
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" data-ng-show="importing">
|
||||
<button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
|
||||
<button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="clientId">{{:: 'client-id' | translate}} <span class="required" data-ng-show="create">*</span></label>
|
||||
<label class="col-md-2 control-label" for="clientId">{{:: 'client-id' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" id="clientId" name="clientId" data-ng-model="client.clientId" autofocus required>
|
||||
</div>
|
||||
|
@ -250,14 +235,14 @@
|
|||
<kc-tooltip>{{:: 'valid-redirect-uris.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly && !create">
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly">
|
||||
<label class="col-md-2 control-label" for="baseUrl">{{:: 'base-url' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" name="baseUrl" id="baseUrl" data-ng-model="client.baseUrl">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'base-url.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-hide="create || protocol == 'saml'">
|
||||
<div class="form-group" data-ng-hide="protocol == 'saml'">
|
||||
<label class="col-md-2 control-label" for="adminUrl">{{:: 'admin-url' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" name="adminUrl" id="adminUrl"
|
||||
|
@ -287,7 +272,7 @@
|
|||
</div>
|
||||
<kc-tooltip>{{:: 'idp-sso-relay-state.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly && !create && protocol == 'openid-connect' && (client.standardFlowEnabled || client.implicitFlowEnabled)">
|
||||
<div class="form-group" data-ng-show="!client.bearerOnly && protocol == 'openid-connect' && (client.standardFlowEnabled || client.implicitFlowEnabled)">
|
||||
<label class="col-md-2 control-label" for="newWebOrigin">{{:: 'web-origins' | translate}}</label>
|
||||
|
||||
<div class="col-sm-6">
|
||||
|
@ -342,11 +327,7 @@
|
|||
</fieldset>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageClients">
|
||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageClients">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
|
||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/clients">{{:: 'clients' | translate}}</a></li>
|
||||
<li>{{:: 'add-client' | translate}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{{:: 'import' | translate}}</label>
|
||||
|
||||
<div class="col-md-6" data-ng-hide="importing">
|
||||
<label for="import-file" class="btn btn-default">{{:: 'select-file' | translate}} <i class="pficon pficon-import"></i></label>
|
||||
<input id="import-file" type="file" class="hidden" kc-on-read-file="importFile($fileContent)">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" data-ng-show="importing">
|
||||
<button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
|
||||
<button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="clientId">{{:: 'client-id' | translate}} <span class="required">*</span></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" id="clientId" name="clientId" data-ng-model="client.clientId" autofocus required>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-id.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="protocol">{{:: 'client-protocol' | translate}}</label>
|
||||
<div class="col-sm-6">
|
||||
<div>
|
||||
<select class="form-control" id="protocol"
|
||||
ng-change="changeProtocol()"
|
||||
ng-model="protocol"
|
||||
ng-options="aProtocol for aProtocol in protocols">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'client-protocol.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="protocol">Client Template</label>
|
||||
<div class="col-sm-6">
|
||||
<div>
|
||||
<select class="form-control" id="template"
|
||||
ng-model="client.clientTemplate"
|
||||
ng-options="template.name as template.name for template in templates">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<kc-tooltip>Client template this client inherits configuration from</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-hide="protocol == 'saml'">
|
||||
<label class="col-md-2 control-label" for="rootUrl">{{:: 'root-url' | translate}} <span class="required">*</span></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" name="rootUrl" id="rootUrl" data-ng-model="client.rootUrl">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="protocol == 'saml'">
|
||||
<label class="col-md-2 control-label" for="masterSamlUrl">Client SAML Endpoint <span class="required">*</span></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="text" name="masterSamlUrl" id="masterSamlUrl"
|
||||
data-ng-model="client.adminUrl">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'master-saml-processing-url.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
|
||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
127
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
Executable file
127
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
Executable file
|
@ -0,0 +1,127 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlClient {
|
||||
public static final String SAML_SIGNING_PRIVATE_KEY = "saml.signing.private.key";
|
||||
protected ClientModel client;
|
||||
|
||||
public SamlClient(ClientModel client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getCanonicalizationMethod() {
|
||||
return client.getAttribute(SamlProtocol.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public void setCanonicalizationMethod(String value) {
|
||||
client.setAttribute(SamlProtocol.SAML_CANONICALIZATION_METHOD_ATTRIBUTE, value);
|
||||
}
|
||||
|
||||
public SignatureAlgorithm getSignatureAlgorithm() {
|
||||
String alg = client.getAttribute(SamlProtocol.SAML_SIGNATURE_ALGORITHM);
|
||||
if (alg != null) {
|
||||
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
|
||||
if (algorithm != null)
|
||||
return algorithm;
|
||||
}
|
||||
return SignatureAlgorithm.RSA_SHA256;
|
||||
}
|
||||
|
||||
public void setSignatureAlgorithm(SignatureAlgorithm algorithm) {
|
||||
client.setAttribute(SamlProtocol.SAML_SIGNATURE_ALGORITHM, algorithm.name());
|
||||
}
|
||||
|
||||
public String getNameIDFormat() {
|
||||
return client.getAttributes().get(SamlProtocol.SAML_NAME_ID_FORMAT_ATTRIBUTE);
|
||||
}
|
||||
public void setNameIDFormat(String format) {
|
||||
client.setAttribute(SamlProtocol.SAML_NAME_ID_FORMAT_ATTRIBUTE, format);
|
||||
}
|
||||
|
||||
public boolean includeAuthnStatement() {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_AUTHNSTATEMENT));
|
||||
}
|
||||
|
||||
public void setIncludeAuthnStatement(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_AUTHNSTATEMENT, Boolean.toString(val));
|
||||
}
|
||||
|
||||
public boolean forceNameIDFormat() {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE));
|
||||
|
||||
}
|
||||
public void setForceNameIDFormat(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE, Boolean.toString(val));
|
||||
}
|
||||
|
||||
public boolean requiresRealmSignature(ClientModel client) {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_SERVER_SIGNATURE));
|
||||
}
|
||||
|
||||
public void setRequiresRealmSignature(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_SERVER_SIGNATURE, Boolean.toString(val));
|
||||
|
||||
}
|
||||
|
||||
public boolean forcePostBinding(ClientModel client) {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING));
|
||||
}
|
||||
|
||||
public void setForcePostBinding(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_FORCE_POST_BINDING, Boolean.toString(val));
|
||||
|
||||
}
|
||||
public boolean samlAssertionSignature(ClientModel client) {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_ASSERTION_SIGNATURE));
|
||||
}
|
||||
|
||||
public void setAssertionSignature(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_ASSERTION_SIGNATURE , Boolean.toString(val));
|
||||
|
||||
}
|
||||
public boolean requiresEncryption(ClientModel client) {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_ENCRYPT));
|
||||
}
|
||||
|
||||
|
||||
public void setRequiresEncryption(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_ENCRYPT, Boolean.toString(val));
|
||||
|
||||
}
|
||||
|
||||
public boolean requiresClientSignature(ClientModel client) {
|
||||
return "true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE));
|
||||
}
|
||||
|
||||
public void setRequiresClientSignature(boolean val) {
|
||||
client.setAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE , Boolean.toString(val));
|
||||
|
||||
}
|
||||
|
||||
public String getClientSigningCertificate() {
|
||||
return client.getAttribute(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public void setClientSigningCertificate(String val) {
|
||||
client.setAttribute(SamlProtocol.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, val);
|
||||
|
||||
}
|
||||
|
||||
public String getClientSigningPrivateKey() {
|
||||
return client.getAttribute(SAML_SIGNING_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
public void setClientSigningPrivateKey(String val) {
|
||||
client.setAttribute(SAML_SIGNING_PRIVATE_KEY, val);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlClientRepresentation {
|
||||
protected ClientRepresentation rep;
|
||||
|
||||
public SamlClientRepresentation(ClientRepresentation rep) {
|
||||
this.rep = rep;
|
||||
}
|
||||
|
||||
public String getCanonicalizationMethod() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public String getSignatureAlgorithm() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_SIGNATURE_ALGORITHM);
|
||||
}
|
||||
|
||||
public String getNameIDFormat() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_NAME_ID_FORMAT_ATTRIBUTE);
|
||||
|
||||
}
|
||||
|
||||
public String getIncludeAuthnStatement() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_AUTHNSTATEMENT);
|
||||
|
||||
}
|
||||
|
||||
public String getForceNameIDFormat() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
public String getSamlServerSignature() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_SERVER_SIGNATURE);
|
||||
|
||||
}
|
||||
|
||||
public String getForcePostBinding() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_FORCE_POST_BINDING);
|
||||
|
||||
}
|
||||
public String getClientSignature() {
|
||||
if (rep.getAttributes() == null) return null;
|
||||
return rep.getAttributes().get(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE);
|
||||
|
||||
}
|
||||
}
|
|
@ -6,15 +6,20 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.AbstractLoginProtocolFactory;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
|
||||
import org.keycloak.protocol.saml.mappers.RoleListMapper;
|
||||
import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.saml.SignatureAlgorithm;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||
|
||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -95,4 +100,47 @@ public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
client.addProtocolMapper(model);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupClientDefaults(ClientRepresentation clientRep, ClientModel newClient) {
|
||||
SamlClientRepresentation rep = new SamlClientRepresentation(clientRep);
|
||||
SamlClient client = new SamlClient(newClient);
|
||||
if (clientRep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
|
||||
if (rep.getCanonicalizationMethod() == null) {
|
||||
client.setCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE);
|
||||
}
|
||||
if (rep.getSignatureAlgorithm() == null) {
|
||||
client.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
|
||||
}
|
||||
|
||||
if (rep.getNameIDFormat() == null) {
|
||||
client.setNameIDFormat("username");
|
||||
}
|
||||
|
||||
if (rep.getIncludeAuthnStatement() == null) {
|
||||
client.setIncludeAuthnStatement(true);
|
||||
}
|
||||
|
||||
if (rep.getForceNameIDFormat() == null) {
|
||||
client.setForceNameIDFormat(false);
|
||||
}
|
||||
|
||||
if (rep.getSamlServerSignature() == null) {
|
||||
client.setRequiresRealmSignature(true);
|
||||
}
|
||||
if (rep.getForcePostBinding() == null) {
|
||||
client.setForcePostBinding(true);
|
||||
}
|
||||
|
||||
if (rep.getClientSignature() == null) {
|
||||
client.setRequiresClientSignature(true);
|
||||
CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(newClient.getClientId());
|
||||
client.setClientSigningCertificate(info.getCertificate());
|
||||
client.setClientSigningPrivateKey(info.getPrivateKey());
|
||||
}
|
||||
|
||||
if (clientRep.isFrontchannelLogout() == null) {
|
||||
newClient.setFrontchannelLogout(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package org.keycloak.protocol;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -27,4 +29,12 @@ public interface LoginProtocolFactory extends ProviderFactory<LoginProtocol> {
|
|||
List<ProtocolMapperModel> getDefaultBuiltinMappers();
|
||||
|
||||
Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager);
|
||||
|
||||
/**
|
||||
* Setup default values for new clients.
|
||||
*
|
||||
* @param rep
|
||||
* @param newClient
|
||||
*/
|
||||
void setupClientDefaults(ClientRepresentation rep, ClientModel newClient);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
*/
|
||||
package org.keycloak.protocol.oidc;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -29,12 +31,16 @@ import org.keycloak.protocol.oidc.mappers.FullNameMapper;
|
|||
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
||||
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
|
||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
||||
|
||||
/**
|
||||
|
@ -42,6 +48,7 @@ import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
||||
private static Logger logger = Logger.getLogger(OIDCLoginProtocolFactory.class);
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
public static final String EMAIL = "email";
|
||||
|
@ -159,4 +166,44 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
|
|||
public String getId() {
|
||||
return "openid-connect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupClientDefaults(ClientRepresentation rep, ClientModel newClient) {
|
||||
if (rep.getRootUrl() != null && (rep.getRedirectUris() == null || rep.getRedirectUris().isEmpty())) {
|
||||
String root = rep.getRootUrl();
|
||||
if (root.endsWith("/")) root = root + "*";
|
||||
else root = root + "/*";
|
||||
newClient.addRedirectUri(root);
|
||||
|
||||
Set<String> origins = new HashSet<String>();
|
||||
String origin = UriUtils.getOrigin(root);
|
||||
logger.debugv("adding default client origin: {0}" , origin);
|
||||
origins.add(origin);
|
||||
newClient.setWebOrigins(origins);
|
||||
}
|
||||
if (rep.isBearerOnly() == null
|
||||
&& rep.isPublicClient() == null) {
|
||||
newClient.setPublicClient(true);
|
||||
}
|
||||
if (rep.isBearerOnly() == null) newClient.setBearerOnly(false);
|
||||
if (rep.getAdminUrl() == null && rep.getRootUrl() != null) {
|
||||
newClient.setManagementUrl(rep.getRootUrl());
|
||||
}
|
||||
|
||||
|
||||
// Backwards compatibility only
|
||||
if (rep.isDirectGrantsOnly() != null) {
|
||||
logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
|
||||
newClient.setStandardFlowEnabled(!rep.isDirectGrantsOnly());
|
||||
newClient.setDirectAccessGrantsEnabled(rep.isDirectGrantsOnly());
|
||||
} else {
|
||||
if (rep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
|
||||
if (rep.isDirectAccessGrantsEnabled() == null) newClient.setDirectAccessGrantsEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
if (rep.isImplicitFlowEnabled() == null) newClient.setImplicitFlowEnabled(false);
|
||||
if (rep.isPublicClient() == null) newClient.setPublicClient(true);
|
||||
if (rep.isFrontchannelLogout() == null) newClient.setFrontchannelLogout(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
auth.requireCreate();
|
||||
|
||||
try {
|
||||
ClientModel clientModel = ClientManager.createClient(session, session.getContext().getRealm(), client, true);
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
|
||||
if (client.getClientId() == null) {
|
||||
clientModel.setClientId(clientModel.getId());
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import org.keycloak.models.UserSessionProvider;
|
|||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
||||
import org.keycloak.representations.adapters.config.BaseRealmConfig;
|
||||
|
@ -45,10 +47,25 @@ public class ClientManager {
|
|||
public ClientManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not be called from an import. This really expects that the client is created from the admin console.
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
* @param rep
|
||||
* @param addDefaultRoles
|
||||
* @return
|
||||
*/
|
||||
public static ClientModel createClient(KeycloakSession session, RealmModel realm, ClientRepresentation rep, boolean addDefaultRoles) {
|
||||
ClientModel client = RepresentationToModel.createClient(session, realm, rep, addDefaultRoles);
|
||||
|
||||
// remove default mappers
|
||||
if (rep.getProtocol() != null) {
|
||||
LoginProtocolFactory providerFactory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, rep.getProtocol());
|
||||
providerFactory.setupClientDefaults(rep, client);
|
||||
}
|
||||
|
||||
|
||||
// remove default mappers if there is a template
|
||||
if (rep.getProtocolMappers() == null && rep.getClientTemplate() != null) {
|
||||
Set<ProtocolMapperModel> mappers = client.getProtocolMappers();
|
||||
for (ProtocolMapperModel mapper : mappers) client.removeProtocolMapper(mapper);
|
||||
|
|
|
@ -271,6 +271,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
}
|
||||
|
||||
}, 10, 500);
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue