Giter Site home page Giter Site logo

Comments (9)

daniel-frak avatar daniel-frak commented on September 10, 2024

Hi!
The plugin only cares about the legacy system being able to tell it if a user exists and if the password is correct.
What you implement under those interfaces is up to you :)

With that said, Identity Providers usually use a workflow where the user is redirected to their login form, in which case it's probably best to use Keycloak's Identity Brokering features as much as possible, e.g. if you want to provide support for logging in with Google, GitHub, Facebook or Twitter, Keycloak has that support built-in:
https://www.keycloak.org/docs/latest/server_admin/#_identity_broker

from keycloak-user-migration.

slepic avatar slepic commented on September 10, 2024

I have the very same problem. Our legacy system already has users linked to google and apple.
When trying to login from android/ios app we exchange external token for internal, but when the user does not exist in keycloak, but exists in our legacy system, the response is "User already exists".
The rest migration is invoked and a new user is created in keycloak. However this user is not linked with any identity provider, although in our legacy system we know his user id for that provider and we could have sent it in the response to the POST request made by this plugin.

from keycloak-user-migration.

daniel-frak avatar daniel-frak commented on September 10, 2024

@slepic I'm trying to wrap my head around this - could the issue be with the fact that the legacy system is not providing the password needed for user migration? Is there anything in the logs, perhaps?

from keycloak-user-migration.

slepic avatar slepic commented on September 10, 2024

@daniel-frak I'm sorry, I'm just revisiting this issue and I realized I meant the GET endpoint response.

As stated in docs:

The current limitation on external token exchanges is that if the external token maps to an existing user an exchange will not be allowed unless the existing user already has an account link to the external identity provider.

https://www.keycloak.org/docs/latest/securing_apps/#external-token-to-internal-token-exchange

I have already made a proof of concept that if i make token exchange request (external google token to internal keycloak token)
keycloak calls the GET endpoint and creates a user model based on the response
However, the token exchange operation itself then fails because "User already exists".

(in normal flow, the user would choose to link accounts and verify the choice via a link that he receivs via email, but token exchange operation cannot wait for this and so it must choose to deny the request)

But if I then manualy link the created keycloak user to google provider in keycloak admin console using the google userId that I have in legacy application, I can then retry the token exchange and it works.

And so it should also work if the link was imported from the legacy application rather than manually added.

if the GET endpoint could return something like

{
  ...,
  "federatedIdentities": [{
    "identityProvider": "google",
    "userId": "*****",
  }, ...]
}

And then the user model in keycloak would be created including the link of google userId to google identity provider that is configured in keycloak.

from keycloak-user-migration.

daniel-frak avatar daniel-frak commented on September 10, 2024

Unfortunately, I still don't think I understand. However, I can say this:

  1. The way the migration plugin is set up, there should be one GET request for user data and one POST request for validating passwords. There should be no need for exchanging tokens, as the plugin is not meant to actually log the user in (that is handled by Keycloak later).
  2. Since you mentioned Google, bear in mind that Keycloak supports Google authentication (and many others) out of the box:
    https://keycloakthemes.com/blog/how-to-setup-sign-in-with-google-using-keycloak

from keycloak-user-migration.

daniel-frak avatar daniel-frak commented on September 10, 2024

Since this has been inactive for a while, I'm closing this issue.
If you think I should reopen it, let me know. If you do, however, I would appreciate clearer steps to reproduce (ideally a failing Cypress test) so that I can understand the issue better - I think I almost get what the problem is.

from keycloak-user-migration.

slepic avatar slepic commented on September 10, 2024

Hello, thanks, i eventually made a fork of your repo and implemented import of legacy fedarated identities. As it turned out, although it imported the identity with links, it was already too late in the federated identity login process (as it calls a "getByFederatedUserId" first before importing from legacy system). Therefore this has proven to be a no go (unless maybe kc itself could be modified to allow this).

For context, i wanted this for nonupdated mobile app that would only be aware of legacy auth system. I wanted it to still call the same backend endpoint while this endpoint would newly delagate to keycloak via token exchange.

Eventually we ended up implementing a forward compatible version of the mobile app for which we could switch behaviour between legacy auth and keycloak auth via a remote config resource.

This way we completely bypassed what i wanted to achieve by this feature request...

from keycloak-user-migration.

abdurrahmanekr avatar abdurrahmanekr commented on September 10, 2024

@slepic would you mind can you share your fork?

from keycloak-user-migration.

slepic avatar slepic commented on September 10, 2024

@abdurrahmanekr I think I dumped it...
found it on my local tho, here's the patch

diff --git a/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyFederatedIdentity.java b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyFederatedIdentity.java
new file mode 100644
index 0000000..01faf08
--- /dev/null
+++ b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyFederatedIdentity.java
@@ -0,0 +1,72 @@
+package com.danielfrak.code.keycloak.providers.rest.remote;
+
+import java.util.Objects;
+
+/**
+ * A user federated identity in the old authentication system
+ */
+public class LegacyFederatedIdentity {
+
+    private String identityProvider;
+    private String userId;
+    private String userName;
+    private String token;
+
+    public String getIdentityProvider() {
+        return this.identityProvider;
+    }
+
+    public void setIdentityProvider(String identityProvider) {
+        this.identityProvider = identityProvider;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        return userName;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        LegacyFederatedIdentity legacyFederatedIdentity = (LegacyFederatedIdentity) o;
+
+        return Objects.equals(identityProvider, legacyFederatedIdentity.identityProvider)
+            && Objects.equals(userId, legacyFederatedIdentity.userId)
+            && Objects.equals(userName, legacyFederatedIdentity.userName)
+            && Objects.equals(token, legacyFederatedIdentity.token)
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+            identityProvider,
+            userId,
+            userName,
+            token
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyUser.java b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyUser.java
index 3d4f9f8..99ba28b 100644
--- a/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyUser.java
+++ b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/LegacyUser.java
@@ -20,6 +20,7 @@ public class LegacyUser {
     private List<String> roles;
     private List<String> groups;
     private List<String> requiredActions;
+    private List<LegacyFederatedIdentity> federatedIdentities;
 
     public String getId() {
         return id;
@@ -109,6 +110,14 @@ public class LegacyUser {
         this.requiredActions = requiredActions;
     }
 
+    public List<LegacyFederatedIdentity> getFederatedIdentities() {
+        return federatedIdentities;
+    }
+
+    public void setFederatedIdentities(List<LegacyFederatedIdentity> federatedIdentities) {
+        this.federatedIdentities = federatedIdentities;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -129,12 +138,13 @@ public class LegacyUser {
                 Objects.equals(attributes, legacyUser.attributes) &&
                 Objects.equals(roles, legacyUser.roles) &&
                 Objects.equals(groups, legacyUser.groups) &&
-                Objects.equals(requiredActions, legacyUser.requiredActions);
+                Objects.equals(requiredActions, legacyUser.requiredActions)
+                Objects.equals(federatedIdentities, legacyUser.federatedIdentities);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(id, username, email, firstName, lastName, isEnabled, isEmailVerified, attributes,
-                roles, groups, requiredActions);
+                roles, groups, requiredActions, federatedIdentities);
     }
 }
diff --git a/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactory.java b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactory.java
index 1023f3b..5b2986c 100644
--- a/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactory.java
+++ b/src/main/java/com/danielfrak/code/keycloak/providers/rest/remote/UserModelFactory.java
@@ -55,11 +55,12 @@ public class UserModelFactory {
     public UserModel create(LegacyUser legacyUser, RealmModel realm) {
         LOG.infof("Creating user model for: %s", legacyUser.getUsername());
 
+        UserProvider userProvider = session.users();
         UserModel userModel;
         if (isEmpty(legacyUser.getId())) {
-            userModel = session.users().addUser(realm, legacyUser.getUsername());
+            userModel = userProvider.addUser(realm, legacyUser.getUsername());
         } else {
-            userModel = session.users().addUser(
+            userModel = userProvider.addUser(
                     realm,
                     legacyUser.getId(),
                     legacyUser.getUsername(),
@@ -93,6 +94,11 @@ public class UserModelFactory {
                 .forEach(userModel::addRequiredAction);
         }
 
+        if (legacyUser.getFederatedIdentities() != null) {
+            legacyUser.getFederatedIdentities()
+                .forEach(fi -> userProvider.addFederatedIdentity(realm, userModel, mapFederatedIdentity(fi)))
+        }
+
         return userModel;
     }
 
@@ -187,4 +193,13 @@ public class UserModelFactory {
 
         return Optional.of(realmGroup);
     }
+
+    private FederatedIdentityModel mapFederatedIdentity(LegacyFederatedIdentity legacyFederatedIdentity) {
+        return new FederatedIdentityModel(
+            legacyFederatedIdentity.getIdentityProvider(),
+            legacyFederatedIdentity.getUserId(),
+            legacyFederatedIdentity.getUserName(),
+            legacyFederatedIdentity.getToken(),
+        );
+    }
 }

from keycloak-user-migration.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.