keycloak-aplcache

more saml config

2/12/2015 12:56:16 AM

Details

diff --git a/docbook/reference/en/en-US/modules/saml.xml b/docbook/reference/en/en-US/modules/saml.xml
index 1de1dc6..912fc63 100755
--- a/docbook/reference/en/en-US/modules/saml.xml
+++ b/docbook/reference/en/en-US/modules/saml.xml
@@ -96,12 +96,78 @@
                     </para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>Force Name ID Format</term>
+                <listitem>
+                    <para>
+                        If the request has a name ID policy, ignore it and used the value configured in the admin console
+                        under Name ID Format
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Name ID Format</term>
+                <listitem>
+                    <para>
+                        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.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Master SAML Processing URL</term>
+                <listitem>
+                    <para>
+                        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
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Assertion Consumer Service POST Binding URL</term>
+                <listitem>
+                    <para>
+                        POST Binding URL for the Assertion Consumer Service.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Assertion Consumer Service Redirect Binding URL</term>
+                <listitem>
+                    <para>
+                        Redirect Binding URL for the Assertion Consumer Service.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Logout Service POST Binding URL</term>
+                <listitem>
+                    <para>
+                        POST Binding URL for the Logout Service.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Logout Service Redirect Binding URL</term>
+                <listitem>
+                    <para>
+                        Redirect Binding URL for the Logout Service.
+                    </para>
+                </listitem>
+            </varlistentry>
         </variablelist>
     </para>
     <para>
-        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.
+    </para>
+    <para>
+        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.
     </para>
     <para>
         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/partials/application-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-detail.html
index 1ad2bc3..1ce6d80 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
@@ -221,16 +221,16 @@
             <fieldset>
                 <legend collapsed><span class="text">Fine Grain SAML Endpoint Configuration</span>  <span tooltip-placement="right" tooltip="Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service." class="fa fa-info-circle"></span></legend>
                 <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                    <label class="col-sm-2 control-label" for="logoutPostBinding">Assertion Consumer Service POST Binding URL</label>
+                    <label class="col-sm-2 control-label" for="consumerServicePost">Assertion Consumer Service POST Binding URL</label>
                     <div class="col-sm-6">
-                        <input ng-model="application.attributes.saml_assertion_consumer_service_url_post" class="form-control" type="text" name="logoutPostBinding" id="logoutPostBinding" />
+                        <input ng-model="application.attributes.saml_assertion_consumer_url_post" class="form-control" type="text" name="consumerServicePost" id="consumerServicePost" />
                     </div>
                     <span tooltip-placement="right" tooltip="SAML POST Binding URL for the application's assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding." class="fa fa-info-circle"></span>
                 </div>
                 <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                    <label class="col-sm-2 control-label" for="logoutPostBinding">Assertion Consumer Service Redirect Binding URL</label>
+                    <label class="col-sm-2 control-label" for="consumerServiceRedirect">Assertion Consumer Service Redirect Binding URL</label>
                     <div class="col-sm-6">
-                        <input ng-model="application.attributes.saml_assertion_consumer_service_url_redirect" class="form-control" type="text" name="logoutRedirectBinding" id="logoutRedirectBinding" />
+                        <input ng-model="application.attributes.saml_assertion_consumer_url_redirect" class="form-control" type="text" name="consumerServiceRedirect" id="consumerServiceRedirect" />
                     </div>
                     <span tooltip-placement="right" tooltip="SAML Redirect Binding URL for the application's assertion consumer service (login responses).  You can leave this blank if you do not have a URL for this binding." class="fa fa-info-circle"></span>
                 </div>
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 833fa2d..39d6f37 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 urlPattern = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
-            if (urlPattern == null) {
-                urlPattern = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
+            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 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 a83bfd8..ffdb39d 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 44808d6..b21af78 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 cae67d1..5800d98 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -25,6 +25,14 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk15on</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-dependencies-server-all</artifactId>
             <version>${project.version}</version>
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 d036b07..b5cd01c 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"/>
         <Handler
                 class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
-            <Option Key="NAMEID_FORMAT" Value="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
+            <!-- <Option Key="NAMEID_FORMAT" Value="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/> -->
         </Handler>
         <Handler
                 class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler"/>
diff --git a/testsuite/integration/src/test/resources/saml/testsaml.json b/testsuite/integration/src/test/resources/saml/testsaml.json
index 3b9931a..9635c0c 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",