keycloak-aplcache

Merge pull request #1182 from patriot1burke/master more

4/26/2015 12:20:15 PM

Details

diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedRoleMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedRoleMapper.java
new file mode 100755
index 0000000..31684a0
--- /dev/null
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedRoleMapper.java
@@ -0,0 +1,110 @@
+package org.keycloak.broker.provider;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.IdentityProviderMapperModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
+    public static final String ROLE = "role";
+    protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+    static {
+        ProviderConfigProperty property;
+        property = new ProviderConfigProperty();
+        property.setName(ROLE);
+        property.setLabel("Role");
+        property.setHelpText("Role to grant to user.  To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        configProperties.add(property);
+    }
+
+
+
+    public static String[] parseRole(String role) {
+        int scopeIndex = role.indexOf('.');
+        if (scopeIndex > -1) {
+            String appName = role.substring(0, scopeIndex);
+            role = role.substring(scopeIndex + 1);
+            String[] rtn = {appName, role};
+            return rtn;
+        } else {
+            String[] rtn = {null, role};
+            return rtn;
+
+        }
+    }
+
+    public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
+        String[] parsedRole = parseRole(roleName);
+        RoleModel role = null;
+        if (parsedRole[0] == null) {
+            role = realm.getRole(parsedRole[1]);
+        } else {
+            ClientModel client = realm.getClientByClientId(parsedRole[0]);
+            role = client.getRole(parsedRole[1]);
+        }
+        return role;
+    }
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return "Role Importer";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "Hardcoded Role";
+    }
+
+    public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
+
+
+    public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String[] getCompatibleProviders() {
+        return COMPATIBLE_PROVIDERS;
+    }
+
+    @Override
+    public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        String roleName = mapperModel.getConfig().get(ROLE);
+        RoleModel role = getRoleFromString(realm, roleName);
+        if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
+        user.grantRole(role);
+    }
+
+    @Override
+    public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+
+    }
+
+    @Override
+    public String getHelpText() {
+        return "When user is imported from provider, hardcode a role mapping for it.";
+    }
+}
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
index 990c6de..4017499 100755
--- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java
@@ -13,6 +13,8 @@ import org.keycloak.provider.ProviderFactory;
  * @version $Revision: 1 $
  */
 public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
+    public static final String ANY_PROVIDER = "*";
+
     String[] getCompatibleProviders();
     String getDisplayCategory();
     String getDisplayType();
diff --git a/broker/core/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/core/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
new file mode 100755
index 0000000..a3bf053
--- /dev/null
+++ b/broker/core/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
@@ -0,0 +1 @@
+org.keycloak.broker.provider.HardcodedRoleMapper
\ No newline at end of file
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/ExternalKeycloakRoleToRoleMapper.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/ExternalKeycloakRoleToRoleMapper.java
new file mode 100755
index 0000000..f5bf539
--- /dev/null
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/ExternalKeycloakRoleToRoleMapper.java
@@ -0,0 +1,118 @@
+package org.keycloak.broker.oidc.mappers;
+
+import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
+import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
+import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.HardcodedRoleMapper;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.models.IdentityProviderMapperModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.JsonWebToken;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
+
+    public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
+
+    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+    private static final String EXTERNAL_ROLE = "external.role";
+
+    static {
+        ProviderConfigProperty property;
+        ProviderConfigProperty property1;
+        property1 = new ProviderConfigProperty();
+        property1.setName(EXTERNAL_ROLE);
+        property1.setLabel("External role");
+        property1.setHelpText("External role to check for.  To reference an application role the syntax is appname.approle, i.e. myapp.myrole.");
+        property1.setType(ProviderConfigProperty.STRING_TYPE);
+        configProperties.add(property1);
+        property = new ProviderConfigProperty();
+        property.setName(HardcodedRoleMapper.ROLE);
+        property.setLabel("Role");
+        property.setHelpText("Role to grant to user if external role is present.  To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
+        property.setType(ProviderConfigProperty.STRING_TYPE);
+        configProperties.add(property);
+    }
+
+    public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
+
+
+    @Override
+    public List<ProviderConfigProperty> getConfigProperties() {
+        return configProperties;
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String[] getCompatibleProviders() {
+        return COMPATIBLE_PROVIDERS;
+    }
+
+    @Override
+    public String getDisplayCategory() {
+        return "Role Importer";
+    }
+
+    @Override
+    public String getDisplayType() {
+        return "External Role to Role";
+    }
+
+    @Override
+    public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        RoleModel role = hasRole(realm, mapperModel, context);
+        if (role != null) {
+            user.grantRole(role);
+        }
+    }
+
+    private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
+        //if (token == null) return;
+        String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
+        String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
+        String externalRoleName = parseRole[1];
+        String claimName = null;
+        if (parseRole[0] == null) {
+            claimName = "realm_access.roles";
+        } else {
+            claimName = "resource_access." + parseRole[0] + ".roles";
+        }
+        Object claim = getClaimValue(token, claimName);
+        if (valueEquals(externalRoleName, claim)) {
+            RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
+            if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
+            return role;
+        }
+        return null;
+    }
+
+    @Override
+    public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
+        RoleModel role = hasRole(realm, mapperModel, context);
+        if (role == null) {
+            user.deleteRoleMapping(role);
+        }
+    }
+
+    @Override
+    public String getHelpText() {
+        return "Looks for an external role in a keycloak access token.  If external role exists, grant the user the specified realm or application role.";
+    }
+
+}
diff --git a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
index c2245b6..de86af2 100755
--- a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
+++ b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
@@ -1,2 +1,3 @@
-org.keycloak.broker.oidc.mappers.RoleMapper
+org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
+org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
 org.keycloak.broker.oidc.mappers.UserAttributeMapper
\ No newline at end of file
diff --git a/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
index 5051d1d..9de7273 100755
--- a/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
+++ b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
@@ -1,2 +1,2 @@
-org.keycloak.broker.saml.mappers.RoleMapper
+org.keycloak.broker.saml.mappers.AttributeToRoleMapper
 org.keycloak.broker.saml.mappers.UserAttributeMapper
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/identity-broker.xml b/docbook/reference/en/en-US/modules/identity-broker.xml
index 4c1a3c2..8d31ae4 100755
--- a/docbook/reference/en/en-US/modules/identity-broker.xml
+++ b/docbook/reference/en/en-US/modules/identity-broker.xml
@@ -72,7 +72,9 @@
         <para>
             When using Keycloak as an identity broker, users are not forced to provide their credentials in order to
             authenticate in a specific realm. Instead of that, they are presented with a list of identity providers from
-            where they can pick one and authenticate. The following diagram demonstrates the steps involved when using
+            where they can pick one and authenticate. You can also configure a hard-coded default broker.  In this case
+            the user will not be given a choice, but instead be redirected directly the the parent broker.
+            The following diagram demonstrates the steps involved when using
             Keycloak to broker an external identity provider:
         </para>
 
@@ -272,6 +274,25 @@
                             be used by any other means.
                         </entry>
                     </row>
+                   <row>
+                        <entry>
+                            <literal>Store Tokens</literal>
+                        </entry>
+                        <entry>
+                            Any external tokens provided by the parent IDP will be stored.
+                            This options is useful if you are using social authentication and need to access the token in order to invoke the
+                            API of a social provider on behalf of the user.
+                        </entry>
+                    </row>
+                    <row>
+                        <entry>
+                            <literal>Stored Tokens Readable</literal>
+                        </entry>
+                        <entry>
+                            Automatically assigns a <literal>broker.READ_TOKEN</literal> role that allows the user
+                            to access any stored external tokens via the broker service.
+                        </entry>
+                    </row>
                     <row>
                         <entry>
                             <literal>Update Profile on First Login</literal>
@@ -293,17 +314,6 @@
                             You can put number into this field, providers with lower numbers are shown first.
                         </entry>
                     </row>
-                    <!--<row>-->
-                        <!--<entry>-->
-                            <!--<literal>Store Tokens</literal>-->
-                        <!--</entry>-->
-                        <!--<entry>-->
-                            <!--Allows you to store tokens issued by an identity provider during the authentication of a specific user.-->
-                            <!--Tokens are stored and can be retrieved later.-->
-                            <!--This options is useful if you are using social authentication and need to access the token in order to invoke the-->
-                            <!--API of a social provider on behalf of the user.-->
-                        <!--</entry>-->
-                    <!--</row>-->
                 </tbody>
             </tgroup>
         </table>
@@ -1003,6 +1013,14 @@
                 </tbody>
             </tgroup>
         </table>
+        <para>
+            You can also import all this configuration data by providing a URL or XML file that points to the entity descriptor of the external
+            SAML IDP you want to connect to.
+        </para>
+        <para>
+            Once you create a SAML provider, there is an <literal>EXPORT</literal> button that appears when viewing that provider.
+            Clicking this button will export a SAML entity descriptor which you can use to
+        </para>
     </section>
 
     <section>
@@ -1104,63 +1122,45 @@
                 </tbody>
             </tgroup>
         </table>
+        <para>
+            You can also import all this configuration data by providing a URL or file that points to OpenID Provider Metadata (see OIDC Discovery specification)
+        </para>
     </section>
 
-    <!--<section>-->
-        <!--<title>Retrieving Tokens from Identity Providers</title>-->
-        <!--<para>-->
-            <!--Keycloak allows you to store tokens and responses from identity providers during the authentication process.-->
-            <!--For that, you can use the <literal>Store Token</literal> configuration option, as mentioned before.-->
-        <!--</para>-->
-        <!--<para>-->
-            <!--It also allows you to retrieve these tokens and responses once the user is authenticated in order to use their-->
-            <!--information or use them to invoke external resources protected by these tokens.-->
-            <!--The latter case is usually related with social providers,-->
-            <!--where you usually need to use their tokens to invoke methods on their APIs.-->
-        <!--</para>-->
-        <!--<para>-->
-            <!--To retrieve a token for a particular identity provider you need to send a request as follows:-->
-        <!--</para>-->
-        <!--<programlisting language="JAVA"><![CDATA[GET /auth/realms/{realm}/broker/{provider_alias}/token HTTP/1.1-->
-<!--Host: localhost:8080-->
-<!--Authorization: Bearer {keycloak_access_token}]]></programlisting>-->
-        <!--<para>-->
-            <!--In this case, given that you are accessing an protected service in Keycloak, you need to send the access token-->
-            <!--issued by Keycloak during the user authentication.-->
-        <!--</para>-->
-        <!--<para>-->
-            <!--By default, the Keycloak access token issued for the application can't be automatically used for retrieve thirdparty token. You will-->
-            <!--need to enable this in admin console first:-->
-            <!--<orderedlist>-->
-                <!--<listitem>-->
-                    <!--<para>-->
-                        <!--Click 'Applications' on the left side menu.-->
-                    <!--</para>-->
-                <!--</listitem>-->
-                <!--<listitem>-->
-                    <!--<para>-->
-                        <!--Select an application from the list.-->
-                    <!--</para>-->
-                <!--</listitem>-->
-                <!--<listitem>-->
-                    <!--<para>-->
-                        <!--Click the 'Identity Provider' tab.-->
-                    <!--</para>-->
-                <!--</listitem>-->
-                <!--<listitem>-->
-                    <!--<para>-->
-                        <!--From this page you can configure if an application is allowed to retrieve tokens from an specific identity provider. For that,-->
-                        <!--just click on the <emphasis>Can Retrieve Token</emphasis> button.-->
-                    <!--</para>-->
-                <!--</listitem>-->
-            <!--</orderedlist>-->
-        <!--</para>-->
-        <!--<note>-->
-            <!--<para>-->
-                <!--If your application is not at the same origin as the authentication server, make sure you have properly configured CORS.-->
-            <!--</para>-->
-        <!--</note>-->
-    <!--</section>-->
+    <section>
+        <title>Retrieving Tokens from Identity Providers</title>
+        <para>
+            Keycloak allows you to store tokens and responses from identity providers during the authentication process.
+            For that, you can use the <literal>Store Token</literal> configuration option, as mentioned before.
+        </para>
+        <para>
+            It also allows you to retrieve these tokens and responses once the user is authenticated in order to use their
+            information or use them to invoke external resources protected by these tokens.
+            The latter case is usually related with social providers,
+            where you usually need to use their tokens to invoke methods on their APIs.
+        </para>
+        <para>
+            To retrieve a token for a particular identity provider you need to send a request as follows:
+        </para>
+        <programlisting language="JAVA"><![CDATA[GET /auth/realms/{realm}/broker/{provider_alias}/token HTTP/1.1
+Host: localhost:8080
+Authorization: Bearer {keycloak_access_token}]]></programlisting>
+        <para>
+            In this case, given that you are accessing an protected service in Keycloak, you need to send the access token
+            issued by Keycloak during the user authentication.
+        </para>
+        <para>
+            By default, the Keycloak access token issued for the application can't be automatically used for retrieve thirdparty token.
+            A user will have to have the <literal>broker.READ_TOKEN</literal> role.  The client will also have to have that role
+            in its scope.  In the broker configuration page you can automatically assign this role to newly imported users by
+            turning on the <literal>Stored Tokens Readable</literal> switch.
+        </para>
+        <note>
+            <para>
+                If your application is not at the same origin as the authentication server, make sure you have properly configured CORS.
+            </para>
+        </note>
+    </section>
 
     <section>
         <title>Automatically Select and Identity Provider</title>
@@ -1189,6 +1189,19 @@ keycloak.createLoginUrl({
     </section>
 
     <section>
+        <title>Mapping/Importing SAML and OIDC Metadata</title>
+        <para>
+            You can import SAML assertion data, OpenID Connect ID Token claims, and Keycloak access token claims
+            into new users that are imported from a brokered IDP.  After you configure a broker, you'll see a <literal>Mappers</literal>
+            button appear.  Click on that and you'll get to the list of mappers that are assigned to this broker.  There is a
+            <literal>Create</literal> button on this page.  Clicking on this create button allows you to create a broker mapper.
+            Broker mappers can import SAML attributes or OIDC ID/Access token claims into user attributes.  You can assign
+            a role mapping to a user if a claim or external role exists.  There's a bunch of options here so just mouse over
+            the tool tips to see what each mapper can do for you.
+        </para>
+    </section>
+
+    <section>
         <title>Examples</title>
         <para>
             Keycloak provides some useful examples about how to use it as an identity broker.
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
index 114aa06..539c410 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProviderResource.java
@@ -181,7 +181,7 @@ public class IdentityProviderResource {
         for (ProviderFactory factory : factories) {
             IdentityProviderMapper mapper = (IdentityProviderMapper)factory;
             for (String type : mapper.getCompatibleProviders()) {
-                if (type.equals(identityProviderModel.getProviderId())) {
+                if (IdentityProviderMapper.ANY_PROVIDER.equals(type) || type.equals(identityProviderModel.getProviderId())) {
                     IdentityProviderMapperTypeRepresentation rep = new IdentityProviderMapperTypeRepresentation();
                     rep.setId(mapper.getId());
                     rep.setCategory(mapper.getDisplayCategory());
@@ -198,7 +198,7 @@ public class IdentityProviderResource {
                         rep.getProperties().add(propRep);
                     }
                     types.put(rep.getId(), rep);
-
+                    break;
                 }
             }
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index e51c209..c10ef1e 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -283,9 +283,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             } catch (Exception e) {
                 return redirectToLoginPage(e, clientCode);
             }
+        } else {
+            updateFederatedIdentity(context, federatedUser);
         }
 
-        updateFederatedIdentity(context, federatedUser);
 
         UserSessionModel userSession = this.session.sessions()
                 .createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false, context.getBrokerSessionId(), context.getBrokerUserId());
@@ -335,26 +336,26 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build();
     }
 
-    private void updateFederatedIdentity(BrokeredIdentityContext updatedIdentity, UserModel federatedUser) {
-        FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, updatedIdentity.getIdpConfig().getAlias(), this.realmModel);
+    private void updateFederatedIdentity(BrokeredIdentityContext context, UserModel federatedUser) {
+        FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel);
 
         // Skip DB write if tokens are null or equal
-        if (!ObjectUtil.isEqualOrNull(updatedIdentity.getToken(), federatedIdentityModel.getToken())) {
-            federatedIdentityModel.setToken(updatedIdentity.getToken());
+        if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrNull(context.getToken(), federatedIdentityModel.getToken())) {
+            federatedIdentityModel.setToken(context.getToken());
 
             this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
 
             if (isDebugEnabled()) {
-                LOGGER.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, updatedIdentity.getIdpConfig().getAlias());
+                LOGGER.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias());
             }
         }
-        updatedIdentity.getIdp().updateBrokeredUser(session, realmModel, federatedUser, updatedIdentity);
-        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(updatedIdentity.getIdpConfig().getAlias());
+        context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context);
+        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
         if (mappers != null) {
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
             for (IdentityProviderMapperModel mapper : mappers) {
                 IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-                target.updateBrokeredUser(session, realmModel, federatedUser, mapper, updatedIdentity);
+                target.updateBrokeredUser(session, realmModel, federatedUser, mapper, context);
             }
         }
 
@@ -484,14 +485,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         throw new IdentityBrokerException("Configuration for identity provider [" + providerId + "] not found.");
     }
 
-    private UserModel createUser(BrokeredIdentityContext updatedIdentity) {
-        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(updatedIdentity.getIdpConfig().getAlias(), updatedIdentity.getId(),
-                updatedIdentity.getUsername(), updatedIdentity.getToken());
+    private UserModel createUser(BrokeredIdentityContext context) {
+        FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
+                context.getUsername(), context.getToken());
         // Check if no user already exists with this username or email
         UserModel existingUser = null;
 
-        if (updatedIdentity.getEmail() != null) {
-            existingUser = this.session.users().getUserByEmail(updatedIdentity.getEmail(), this.realmModel);
+        if (context.getEmail() != null) {
+            existingUser = this.session.users().getUserByEmail(context.getEmail(), this.realmModel);
         }
 
         if (existingUser != null) {
@@ -499,13 +500,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
             throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS);
         }
 
-        String username = updatedIdentity.getUsername();
-        if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isEmpty(updatedIdentity.getEmail())) {
-            username = updatedIdentity.getEmail();
+        String username = context.getUsername();
+        if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isEmpty(context.getEmail())) {
+            username = context.getEmail();
         } else if (username == null) {
-            username = updatedIdentity.getIdpConfig().getAlias() + "." + updatedIdentity.getId();
+            username = context.getIdpConfig().getAlias() + "." + context.getId();
         } else {
-            username = updatedIdentity.getIdpConfig().getAlias() + "." + updatedIdentity.getUsername();
+            username = context.getIdpConfig().getAlias() + "." + context.getUsername();
         }
         if (username != null) {
             username = username.trim();
@@ -529,33 +530,36 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
         }
 
         federatedUser.setEnabled(true);
-        federatedUser.setEmail(updatedIdentity.getEmail());
-        federatedUser.setFirstName(updatedIdentity.getFirstName());
-        federatedUser.setLastName(updatedIdentity.getLastName());
+        federatedUser.setEmail(context.getEmail());
+        federatedUser.setFirstName(context.getFirstName());
+        federatedUser.setLastName(context.getLastName());
 
 
-        if (updatedIdentity.getIdpConfig().isAddReadTokenRoleOnCreate()) {
+        if (context.getIdpConfig().isAddReadTokenRoleOnCreate()) {
             RoleModel readTokenRole = realmModel.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(READ_TOKEN_ROLE);
             federatedUser.grantRole(readTokenRole);
         }
 
+        if (context.getIdpConfig().isStoreToken()) {
+            federatedIdentityModel.setToken(context.getToken());
+        }
 
         this.session.users().addFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel);
 
-        updatedIdentity.getIdp().importNewUser(session, realmModel, federatedUser, updatedIdentity);
-        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(updatedIdentity.getIdpConfig().getAlias());
+        context.getIdp().importNewUser(session, realmModel, federatedUser, context);
+        Set<IdentityProviderMapperModel> mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias());
         if (mappers != null) {
             KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
             for (IdentityProviderMapperModel mapper : mappers) {
                 IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper());
-                target.importNewUser(session, realmModel, federatedUser, mapper, updatedIdentity);
+                target.importNewUser(session, realmModel, federatedUser, mapper, context);
             }
         }
 
 
         this.event.clone().user(federatedUser).event(EventType.REGISTER)
                 .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider())
-                .detail(Details.IDENTITY_PROVIDER_USERNAME, updatedIdentity.getUsername())
+                .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername())
                 .removeDetail("auth_method")
                 .success();
 
diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml
index 8f79b62..d6b0d53 100755
--- a/testsuite/tomcat7/pom.xml
+++ b/testsuite/tomcat7/pom.xml
@@ -13,11 +13,26 @@
     <name>Keycloak Tomcat 7 Integration TestSuite</name>
     <properties>
         <!--<tomcat.version>8.0.14</tomcat.version>-->
-        <tomcat.version>7.0.54</tomcat.version>
+        <tomcat.version>7.0.59</tomcat.version>
     </properties>
     <description />
 
    <dependencies>
+       <dependency>
+           <groupId>org.apache.tomcat</groupId>
+           <artifactId>tomcat-catalina</artifactId>
+           <version>7.0.59</version>
+       </dependency>
+       <dependency>
+           <groupId>org.apache.tomcat</groupId>
+           <artifactId>tomcat-util</artifactId>
+           <version>7.0.59</version>
+       </dependency>
+       <dependency>
+           <groupId>org.apache.tomcat.embed</groupId>
+           <artifactId>tomcat-embed-core</artifactId>
+           <version>7.0.59</version>
+       </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-dependencies-server-all</artifactId>
@@ -199,21 +214,6 @@
            <scope>test</scope>
        </dependency>
 
-       <dependency>
-           <groupId>org.apache.tomcat</groupId>
-           <artifactId>tomcat-catalina</artifactId>
-           <version>${tomcat.version}</version>
-       </dependency>
-       <dependency>
-           <groupId>org.apache.tomcat</groupId>
-           <artifactId>tomcat-util</artifactId>
-           <version>${tomcat.version}</version>
-       </dependency>
-       <dependency>
-           <groupId>org.apache.tomcat.embed</groupId>
-           <artifactId>tomcat-embed-core</artifactId>
-           <version>${tomcat.version}</version>
-       </dependency>
 
     </dependencies>
     <build>
diff --git a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
index 7a38655..3f20d16 100755
--- a/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
+++ b/testsuite/tomcat7/src/test/java/org/keycloak/testsuite/Tomcat7Test.java
@@ -22,6 +22,7 @@
 package org.keycloak.testsuite;
 
 import org.apache.catalina.startup.Tomcat;
+import org.apache.tomcat.util.http.mapper.Mapper;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;