keycloak-aplcache

Merge pull request #2180 from cainj13/persistentWildcard add

2/9/2016 12:43:30 PM

Details

diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml b/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
index ded8f64..d6c93fa 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/saml.xml
@@ -18,16 +18,20 @@
 <chapter id="saml">
     <title>SAML SSO</title>
     <para>
-        Keycloak supports SAML 2.0 for registered applications.  Both POST and Redirect bindings are supported.  You can choose
-        to require client signature validation and can have the server sign and/or encrypt responses as well.  We do not
-        yet support logout via redirects.  All logouts happen via a background POST binding request to the application
-        that will be logged out.  We do not support SAML 1.1 either.  If you want support for either of those, please
+        Keycloak supports SAML 2.0 for registered applications. Both POST and Redirect bindings are supported. You can
+        choose
+        to require client signature validation and can have the server sign and/or encrypt responses as well. We do not
+        yet support logout via redirects. All logouts happen via a background POST binding request to the application
+        that will be logged out. We do not support SAML 1.1 either. If you want support for either of those, please
         log a JIRA request and we'll schedule it.
     </para>
     <para>
-        When you create an application in the admin console, you can choose which protocol the application will log in with.
-        In the application create screen, choose <literal>saml</literal> from the protocol list.  After that there
-        are a bunch of configuration options.  Here is a description of each item:
+        When you create an application in the admin console, you can choose which protocol the application will log in
+        with.
+        In the application create screen, choose
+        <literal>saml</literal>
+        from the protocol list. After that there
+        are a bunch of configuration options. Here is a description of each item:
     </para>
     <para>
         <variablelist>
@@ -35,7 +39,7 @@
                 <term>Client ID</term>
                 <listitem>
                     <para>
-                        This value must match the issuer value sent with AuthNRequests.  Keycloak will pull the issuer
+                        This value must match the issuer value sent with AuthNRequests. Keycloak will pull the issuer
                         from the Authn SAML request and match it to a client by this value.
                     </para>
                 </listitem>
@@ -45,7 +49,8 @@
                 <listitem>
                     <para>
                         SAML login responses may specify the authentication method used (password, etc.) as well as
-                        a timestamp of the login.  Setting this to on will include that statement in the response document.
+                        a timestamp of the login. Setting this to on will include that statement in the response
+                        document.
                     </para>
                 </listitem>
             </varlistentry>
@@ -53,7 +58,8 @@
                 <term>Multi-valued Roles</term>
                 <listitem>
                     <para>
-                        If this switch is off, any user role mappings will have a corresponding attribute created for it.
+                        If this switch is off, any user role mappings will have a corresponding attribute created for
+                        it.
                         If this switch is turn on, only one role attribute will be created, but it will have
                         multiple values within in.
                     </para>
@@ -71,7 +77,9 @@
                 <term>Sign Assertions</term>
                 <listitem>
                     <para>
-                        With the <literal>Sign Documents</literal> switch signs the whole document.  With this setting
+                        With the
+                        <literal>Sign Documents</literal>
+                        switch signs the whole document. With this setting
                         you just assign the assertions of the document.
                     </para>
                 </listitem>
@@ -88,7 +96,7 @@
                 <term>Encrypt Assertions</term>
                 <listitem>
                     <para>
-                        Encrypt assertions in SAML documents with the realm's private key.  The AES algorithm is used
+                        Encrypt assertions in SAML documents with the realm's private key. The AES algorithm is used
                         with a key size of 128 bits.
                     </para>
                 </listitem>
@@ -97,8 +105,10 @@
                 <term>Client Signature Required</term>
                 <listitem>
                     <para>
-                        Expect that documents coming from a client are signed.  Keycloak will validate this signature
-                        using the client keys set up in the <literal>Application Keys</literal> submenu item.
+                        Expect that documents coming from a client are signed. Keycloak will validate this signature
+                        using the client keys set up in the
+                        <literal>Application Keys</literal>
+                        submenu item.
                     </para>
                 </listitem>
             </varlistentry>
@@ -106,8 +116,10 @@
                 <term>Force POST Binding</term>
                 <listitem>
                     <para>
-                        By default, Keycloak will respond using the initial SAML binding of the original request.  By turning
-                        on this switch, you will force Keycloak to always respond using the SAML POST Binding even if the
+                        By default, Keycloak will respond using the initial SAML binding of the original request. By
+                        turning
+                        on this switch, you will force Keycloak to always respond using the SAML POST Binding even if
+                        the
                         original request was the Redirect binding.
                     </para>
                 </listitem>
@@ -116,9 +128,12 @@
                 <term>Front Channel Logout</term>
                 <listitem>
                     <para>
-                        If true, this application requires a browser redirect to be able to perform a logout.  For example,
-                        the application may require a cookie to be reset which could only be done by a done via a redirect.
-                        If this switch is false, then Keycloak will invoke a background SAML request to logout the application.
+                        If true, this application requires a browser redirect to be able to perform a logout. For
+                        example,
+                        the application may require a cookie to be reset which could only be done by a done via a
+                        redirect.
+                        If this switch is false, then Keycloak will invoke a background SAML request to logout the
+                        application.
                     </para>
                 </listitem>
             </varlistentry>
@@ -126,7 +141,8 @@
                 <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
+                        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>
@@ -135,18 +151,118 @@
                 <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.
+                        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. Properties used for each of the
+                        respective formats are defined below.
                     </para>
+                    <variablelist>
+                        <varlistentry>
+                            <term>username</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</literal>
+                                </para>
+                                <para>
+                                    Source:
+                                    <literal>UserModel.userName</literal>
+                                    property
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>email</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</literal>
+                                </para>
+                                <para>
+                                    Source:
+                                    <literal>UserModel.email</literal>
+                                    property
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>transient</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</literal>
+                                </para>
+                                <para>
+                                    Source:<literal>G-$randomUuid</literal>, I.E.
+                                    <literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>
+                                </para>
+                            </listitem>
+                        </varlistentry>
+                        <varlistentry>
+                            <term>persistent</term>
+                            <listitem>
+                                <para>
+                                    Format:
+                                    <literal>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</literal>
+                                </para>
+                                <para>
+                                    The persistent identifier will be evaluated in the following order. If one step
+                                    does not yield a value, processing will continue on to the next until a value is
+                                    found.
+                                </para>
+                                <variablelist>
+                                    <varlistentry>
+                                        <term>
+                                            1) saml.persistent.name.id.for.$clientId
+                                            <literal>UserModel</literal> attribute
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                identifier unique to this client/user pair.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                    <varlistentry>
+                                        <term>
+                                            2) saml.persistent.name.id.for.*
+                                            <literal>UserModel</literal> attribute
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                user identifier for all clients, unless otherwise overridden by a
+                                                $clientId attribute.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                    <varlistentry>
+                                        <term>
+                                            3) G-$randomUuid
+                                        </term>
+                                        <listitem>
+                                            <para>
+                                                I.E.<literal>G-5ef5b38f-864f-41ad-82a0-04ade9139500</literal>. If
+                                                neither a $clientId or wildcard user attribute is found, a persistent
+                                                identifier will be generated for the given client. Note that once this
+                                                identifier has been generated, a user attribute with the key
+                                                <emphasis>saml.persistent.name.id.for.$clientId</emphasis>
+                                                will be persisted and used on all subsequent requests against the given
+                                                client.
+                                            </para>
+                                        </listitem>
+                                    </varlistentry>
+                                </variablelist>
+                            </listitem>
+                        </varlistentry>
+                    </variablelist>
                 </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
+                        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>
@@ -186,9 +302,10 @@
         </variablelist>
     </para>
     <para>
-        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
+        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>
@@ -196,32 +313,43 @@
         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
-        to the role attributes in the SAML document using their basic name.  So, if you have multiple application roles
-        you might have name collisions.  You can use the Scope Mapping menu item to control which role mappings are set
+        One thing to note is that roles are not treated as a hierarchy. So, any role mappings will just be added
+        to the role attributes in the SAML document using their basic name. So, if you have multiple application roles
+        you might have name collisions. You can use the Scope Mapping menu item to control which role mappings are set
         in the response.
     </para>
     <section>
         <title>SAML Entity Descriptor</title>
         <para>
-            If you go into the admin console in the application list menu page you will see an <literal>Import</literal>
-            button.  If you click on that you can import SAML Service Provider definitions using the <ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor</ulink>
-            format described in SAML 2.0.  You should review all the information there to make sure everything is set up correctly.
+            If you go into the admin console in the application list menu page you will see an
+            <literal>Import</literal>
+            button. If you click on that you can import SAML Service Provider definitions using the
+            <ulink url="http://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf">Entity Descriptor
+            </ulink>
+            format described in SAML 2.0. You should review all the information there to make sure everything is set up
+            correctly.
         </para>
         <para>
-            Each realm has a URL where you can view the XML entity descriptor for the IDP.  <literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
+            Each realm has a URL where you can view the XML entity descriptor for the IDP.
+            <literal>root/auth/realms/{realm}/protocol/saml/descriptor</literal>
         </para>
     </section>
     <section>
         <title>IDP Initiated Login</title>
         <para>
-            IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you into a specific application/client.  To set this up
-            go to the client page in the admin console of the client you want to set this up for.  Specify the <literal>IDP Initiated SSO URL Name</literal>.  This is a simple string
-            with no whitespace in it.  After this you can reference your client at the following URL:  <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
+            IDP Initiated Login is a feature that where you can set up a URL on the Keycloak server that will log you
+            into a specific application/client. To set this up
+            go to the client page in the admin console of the client you want to set this up for. Specify the<literal>
+            IDP Initiated SSO URL Name</literal>. This is a simple string
+            with no whitespace in it. After this you can reference your client at the following URL:
+            <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}</literal>
         </para>
         <para>
-            If your client requires a special relay state, you can also configure this in the admin console.  Alternatively, you can specify the relay state in a
-            <literal>RelayState</literal> query parameter, i.e. :  <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
+            If your client requires a special relay state, you can also configure this in the admin console.
+            Alternatively, you can specify the relay state in a
+            <literal>RelayState</literal>
+            query parameter, i.e. :
+            <literal>root/auth/realms/{realm}/protocol/saml/clients/{url-name}?RelayState=thestate</literal>
         </para>
     </section>
 </chapter>
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 2d8f756..042779f 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -281,16 +281,7 @@ public class SamlProtocol implements LoginProtocol {
             // "G-" stands for "generated" Add this for the slight possibility of collisions.
             return "G-" + UUID.randomUUID().toString();
         } else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())) {
-            // generate a persistent user id specifically for each client.
-            UserModel user = userSession.getUser();
-            String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
-            String samlPersistentId = user.getFirstAttribute(name);
-            if (samlPersistentId != null)
-                return samlPersistentId;
-            // "G-" stands for "generated"
-            samlPersistentId = "G-" + UUID.randomUUID().toString();
-            user.setSingleAttribute(name, samlPersistentId);
-            return samlPersistentId;
+            return getPersistentNameId(clientSession, userSession);
         } else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
             // TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
             return userSession.getUser().getUsername();
@@ -299,6 +290,43 @@ public class SamlProtocol implements LoginProtocol {
         }
     }
 
+    /**
+     * Attempts to retrieve the persistent type NameId as follows:
+     *
+     * <ol>
+     *     <li>saml.persistent.name.id.for.$clientId user attribute</li>
+     *     <li>saml.persistent.name.id.for.* user attribute</li>
+     *     <li>G-$randomUuid</li>
+     * </ol>
+     *
+     * If a randomUuid is generated, an attribute for the given saml.persistent.name.id.for.$clientId will be generated,
+     * otherwise no state change will occur with respect to the user's attributes.
+     *
+     * @return the user's persistent NameId
+     */
+    protected String getPersistentNameId(final ClientSessionModel clientSession, final UserSessionModel userSession) {
+        // attempt to retrieve the UserID for the client-specific attribute
+        final UserModel user = userSession.getUser();
+        final String clientNameId = String.format("%s.%s", SAML_PERSISTENT_NAME_ID_FOR,
+                clientSession.getClient().getClientId());
+        String samlPersistentNameId = user.getFirstAttribute(clientNameId);
+        if (samlPersistentNameId != null) {
+            return samlPersistentNameId;
+        }
+
+        // check for a wildcard attribute
+        final String wildcardNameId = String.format("%s.*", SAML_PERSISTENT_NAME_ID_FOR);
+        samlPersistentNameId = user.getFirstAttribute(wildcardNameId);
+        if (samlPersistentNameId != null) {
+            return samlPersistentNameId;
+        }
+
+        // default to generated.  "G-" stands for "generated"
+        samlPersistentNameId = "G-" + UUID.randomUUID().toString();
+        user.setSingleAttribute(clientNameId, samlPersistentNameId);
+        return samlPersistentNameId;
+    }
+
     @Override
     public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
         ClientSessionModel clientSession = accessCode.getClientSession();