keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 152(+115 -37)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html 31(+6 -25)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-client.html 83(+83 -0)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClientRepresentation.java 60(+60 -0)
Details
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 5a2c747..e5e1bb9 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -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',
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index ec28ef4..1424307 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -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,29 +1041,121 @@ 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.");
+ });
+ }
+ };
+
+ $scope.reset = function() {
+ $route.reload();
+ };
+
+ $scope.cancel = function() {
+ $location.url("/realms/" + realm.realm + "/clients");
+ };
+});
+
+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() {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 6a2bd48..f697c04 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -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>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-client.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
new file mode 100755
index 0000000..dd2a962
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
@@ -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>
\ No newline at end of file
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
new file mode 100755
index 0000000..241cc26
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -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);
+
+ }
+
+
+
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClientRepresentation.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClientRepresentation.java
new file mode 100755
index 0000000..4151d62
--- /dev/null
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlClientRepresentation.java
@@ -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);
+
+ }
+}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
index 7dcc866..f7b4d1e 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
@@ -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);
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
index 5742381..a876b19 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocolFactory.java
@@ -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);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index b9211ad..8cd0d8f 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -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);
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
index 0666fab..d011089 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -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());
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index 358860f..e823ac2 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -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);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index 155bdf1..f4ef632 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -271,6 +271,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
}
}, 10, 500);
+ Thread.sleep(100);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}