diff --git a/docbook/reference/en/en-US/modules/saml.xml b/docbook/reference/en/en-US/modules/saml.xml index 1de1dc6d7b..912fc63b14 100755 --- a/docbook/reference/en/en-US/modules/saml.xml +++ b/docbook/reference/en/en-US/modules/saml.xml @@ -96,12 +96,78 @@ + + Force Name ID Format + + + If the request has a name ID policy, ignore it and used the value configured in the admin console + under Name ID Format + + + + + Name ID Format + + + Name ID Format for the subject. If no name ID policy is specified in the request or if the + Force Name ID Format attribute is true, this value is used. + + + + + Master SAML Processing URL + + + This URL will be used for all SAML requests and responsed directed to the SP. It will be used + as the Assertion Consumer Service URL and the Single Logout Service URL. If a login request + contains the Assertion Consumer Service URL, that will take precedence, but this URL must be valided + by a registered Valid Redirect URI pattern + + + + + Assertion Consumer Service POST Binding URL + + + POST Binding URL for the Assertion Consumer Service. + + + + + Assertion Consumer Service Redirect Binding URL + + + Redirect Binding URL for the Assertion Consumer Service. + + + + + Logout Service POST Binding URL + + + POST Binding URL for the Logout Service. + + + + + Logout Service Redirect Binding URL + + + Redirect Binding URL for the Logout Service. + + + - You have to specify an admin URL if you want logout to work. This should be a URL that will except single logout - requests from the Keycloak server. You should also specify a default redirect url. Keycloak will redirect to this - url after single logout is complete. + For login to work, Keycloak needs to be able to resolve the URL for the Assertion Consumer Service of the SP. If + you are relying on the SP to provide this URL in the login request, then you must register valid redirect uri patterns + so that this URL can be validated. You can set the Master SAML Processing URL as well, or alternatively, you can + specify the Assertion Consumer Service URL per binding. + + + For logout to work, you must specify a Master SAML Processing URL, or the Loging Service URL for the binding + you want Keycloak to use. One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js index 8e9adf50a0..617d5abbd1 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js @@ -553,6 +553,12 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, "RSA_SHA512", "DSA_SHA1" ]; + $scope.nameIdFormats = [ + "username", + "email", + "transient", + "persistent" + ]; $scope.realm = realm; $scope.create = !application.name; @@ -563,6 +569,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.samlClientSignature = false; $scope.samlEncrypt = false; $scope.samlForcePostBinding = false; + $scope.samlForceNameIdFormat = false; if (!$scope.create) { if (!application.attributes) { application.attributes = {}; @@ -588,13 +595,25 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, } else if (application.attributes['saml.signature.algorithm'] == 'DSA_SHA1') { $scope.signatureAlgorithm = $scope.signatureAlgorithms[3]; } + if (application.attributes['saml_name_id_format'] == 'unspecified') { + $scope.nameIdFormat = $scope.nameIdFormats[0]; + } else if (application.attributes['saml_name_id_format'] == 'email') { + $scope.nameIdFormat = $scope.nameIdFormats[1]; + } else if (application.attributes['saml_name_id_format'] == 'transient') { + $scope.nameIdFormat = $scope.nameIdFormats[2]; + } else if (application.attributes['saml_name_id_format'] == 'persistent') { + $scope.nameIdFormat = $scope.nameIdFormats[3]; + } + } else { $scope.application = { enabled: true, attributes: {}}; $scope.application.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; } if ($scope.application.attributes["saml.server.signature"]) { @@ -633,6 +652,13 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.samlAuthnStatement = false; } } + if ($scope.application.attributes["saml_force_name_id_format"]) { + if ($scope.application.attributes["saml_force_name_id_format"] == "true") { + $scope.samlForceNameIdFormat = true; + } else { + $scope.samlForceNameIdFormat = false; + } + } if ($scope.application.attributes["saml.multivalued.roles"]) { if ($scope.application.attributes["saml.multivalued.roles"] == "true") { $scope.samlMultiValuedRoles = true; @@ -677,6 +703,10 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.application.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm; }; + $scope.changeNameIdFormat = function() { + $scope.application.attributes['saml_name_id_format'] = $scope.nameIdFormat; + }; + $scope.$watch(function() { return $location.path(); }, function() { @@ -733,6 +763,12 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, } else { $scope.application.attributes["saml.authnstatement"] = "false"; + } + if ($scope.samlForceNameIdFormat == true) { + $scope.application.attributes["saml_force_name_id_format"] = "true"; + } else { + $scope.application.attributes["saml_force_name_id_format"] = "false"; + } if ($scope.samlMultiValuedRoles == true) { $scope.application.attributes["saml.multivalued.roles"] = "true"; @@ -749,6 +785,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, $scope.application.protocol = $scope.protocol; $scope.application.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm; + $scope.application.attributes['saml_name_id_format'] = $scope.nameIdFormat; if ($scope.application.protocol != 'saml' && !$scope.application.bearerOnly && (!$scope.application.redirectUris || $scope.application.redirectUris.length == 0)) { Notifications.error("You must specify at least one redirect uri"); diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html index 2271ef74b7..1ce6d80c64 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html @@ -56,20 +56,6 @@ -
- -
- -
- -
-
- -
- -
- -
@@ -99,7 +85,7 @@
- +
+
- +
-
- +
+
- +
+ +
- +
- +
- +
@@ -183,7 +175,7 @@
- +
@@ -193,6 +185,14 @@
+
+ +
+ +
+ +
@@ -218,6 +218,37 @@
+
+ Fine Grain SAML Endpoint Configuration +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
diff --git a/pom.xml b/pom.xml index 66314cfd18..4aada1a49c 100755 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 0.33.12 2.3.8 1.50 - 1.46 + 1.50 1.9.9 4.2.1 2.3.7.Final @@ -143,7 +143,7 @@ org.bouncycastle - bcmail-jdk16 + bcmail-jdk15on ${bouncycastle.mail.version} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java index 833fa2de3f..39d6f37305 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorImporterService.java @@ -89,15 +89,20 @@ public class EntityDescriptorImporterService { if (spDescriptorType.isWantAssertionsSigned()) { app.setAttribute(SamlProtocol.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); } - String adminUrl = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); - if (adminUrl != null) app.setManagementUrl(adminUrl); + String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); + if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost); + String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); + if (logoutPost != null) app.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect); - String urlPattern = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); - if (urlPattern == null) { - urlPattern = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); + String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()); + if (assertionConsumerServicePostBinding != null) { + app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding); + app.addRedirectUri(assertionConsumerServicePostBinding); } - if (urlPattern != null) { - app.addRedirectUri(urlPattern); + String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()); + if (assertionConsumerServiceRedirectBinding != null) { + app.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding); + app.addRedirectUri(assertionConsumerServiceRedirectBinding); } for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) { diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index a83bfd8b64..ffdb39d83b 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -51,6 +51,8 @@ public class SamlProtocol implements LoginProtocol { public static final String SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE = "saml_assertion_consumer_url_redirect"; public static final String SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE = "saml_single_logout_service_url_post"; public static final String SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE = "saml_single_logout_service_url_redirect"; + public static final String SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE = "saml_force_name_id_format"; + public static final String SAML_NAME_ID_FORMAT_ATTRIBUTE = "saml_name_id_format"; public static final String LOGIN_PROTOCOL = "saml"; public static final String SAML_BINDING = "saml_binding"; public static final String SAML_POST_BINDING = "post"; @@ -175,10 +177,30 @@ public class SamlProtocol implements LoginProtocol { protected String getNameIdFormat(ClientSessionModel clientSession) { String nameIdFormat = clientSession.getNote(GeneralConstants.NAMEID_FORMAT); + ClientModel client = clientSession.getClient(); + boolean forceFormat = forceNameIdFormat(client); + String configuredNameIdFormat = client.getAttribute(SAML_NAME_ID_FORMAT_ATTRIBUTE); + if ((nameIdFormat == null || forceFormat) && configuredNameIdFormat != null) { + if (configuredNameIdFormat.equals("email")) { + nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get(); + } else if (configuredNameIdFormat.equals("persistent")) { + nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get(); + } else if (configuredNameIdFormat.equals("transient")) { + nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get(); + } else if (configuredNameIdFormat.equals("username")) { + nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get(); + } else { + nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get(); + } + } if(nameIdFormat == null) return SAML_DEFAULT_NAMEID_FORMAT; return nameIdFormat; } + public static boolean forceNameIdFormat(ClientModel client) { + return "true".equals(client.getAttribute(SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE)); + } + protected String getNameId(String nameIdFormat, ClientSessionModel clientSession, UserSessionModel userSession) { if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) { return userSession.getUser().getEmail(); diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index 44808d6683..b21af787c7 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -245,7 +245,7 @@ public class SamlService { // Handle NameIDPolicy from SP NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy(); - if(nameIdPolicy != null) { + if(nameIdPolicy != null && !SamlProtocol.forceNameIdFormat(client)) { String nameIdFormat = nameIdPolicy.getFormat().toString(); // TODO: Handle AllowCreate too, relevant for persistent NameID. if(isSupportedNameIdFormat(nameIdFormat)) { @@ -254,8 +254,6 @@ public class SamlService { event.error(Errors.INVALID_TOKEN); return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat."); } - } else { - clientSession.setNote(GeneralConstants.NAMEID_FORMAT, JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get()); } Response response = authManager.checkNonFormAuthentication(session, clientSession, realm, uriInfo, request, clientConnection, headers, event); diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index cae67d1a7d..5800d98df6 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -24,6 +24,14 @@ + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + org.keycloak keycloak-dependencies-server-all diff --git a/testsuite/integration/src/test/resources/saml/signed-post-email/WEB-INF/picketlink.xml b/testsuite/integration/src/test/resources/saml/signed-post-email/WEB-INF/picketlink.xml index d036b07b42..b5cd01c63b 100755 --- a/testsuite/integration/src/test/resources/saml/signed-post-email/WEB-INF/picketlink.xml +++ b/testsuite/integration/src/test/resources/saml/signed-post-email/WEB-INF/picketlink.xml @@ -21,7 +21,7 @@ class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler"/> - diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json index 3ebeb7cfd1..9635c0c73d 100755 --- a/testsuite/integration/src/test/resources/saml/testsaml.json +++ b/testsuite/integration/src/test/resources/saml/testsaml.json @@ -123,6 +123,8 @@ "http://localhost:8081/sales-post-sig-email/*" ], "attributes": { + "saml_force_name_id_format": "true", + "saml_name_id_format": "email", "saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email", "saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email", "saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email", @@ -205,11 +207,8 @@ "redirectUris": [ "http://localhost:8081/employee-sig/*" ], + "adminUrl": "http://localhost:8081/employee-sig", "attributes": { - "saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig", - "saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig", - "saml_single_logout_service_url_post": "http://localhost:8081/employee-sig", - "saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig", "saml.server.signature": "true", "saml.client.signature": "true", "saml.signature.algorithm": "RSA_SHA1", diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml index 7a86e40c32..f407cd3c2a 100755 --- a/testsuite/performance/pom.xml +++ b/testsuite/performance/pom.xml @@ -201,8 +201,8 @@ org.bouncycastle - bcprov-jdk16 - ${bouncycastle.version} + >bcprov-jdk15on + ${bouncycastle.crypto.version}