diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index e8438a545c..a4ce685e5d 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -2871,6 +2871,7 @@ "disableUserInfo": "Disable user info", "isAccessTokenJWT": "Access Token is JWT", "userInfoUrl": "User Info URL", + "doNotStoreUsers": "Do not store users", "issuer": "Issuer", "prompt": "Prompt", "prompts": { @@ -2954,6 +2955,7 @@ "disableUserInfoHelp": "Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service.", "isAccessTokenJWTHelp": "The Access Token received from the Identity Provider is a JWT and its claims will be accessible for mappers.", "userInfoUrlHelp": "The User Info Url. This is optional.", + "doNotStoreUsersHelp": "When enabled, users from this broker are not persisted in internal database.", "issuerHelp": "The issuer identifier for the issuer of the response. If not provided, no validation will be performed.", "promptHelp": "Specifies whether the Authorization Server prompts the End-User for re-authentication and consent.", "acceptsPromptNoneHelp": "This is just used together with Identity Provider Authenticator or when kc_idp_hint points to this identity provider. In case that client sends a request with prompt=none and user is not yet authenticated, the error will not be directly returned to client, but the request with prompt=none will be forwarded to this identity provider.", diff --git a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx index dec7270b6f..01427c3dad 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx @@ -96,6 +96,7 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => { const { control, register, + setValue, formState: { errors }, } = useFormContext(); const [syncModeOpen, setSyncModeOpen] = useState(false); @@ -105,6 +106,12 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => { defaultValue: "false", }); const claimFilterRequired = filteredByClaim === "true"; + const transientSessions = useWatch({ + control, + name: "config.doNotStoreUsers", + defaultValue: "false", + }); + const syncModeAvailable = transientSessions === "false"; return ( <> {!isOIDC && !isSAML && ( @@ -231,46 +238,70 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => { defaultValue="" /> - - } - fieldId="syncMode" - > + ( - + /> )} /> - + + {syncModeAvailable && ( + + } + fieldId="syncMode" + > + ( + + )} + /> + + )} ); }; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index 64edfe885f..89e53d933e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -53,6 +53,7 @@ import org.keycloak.models.UserLoginFailureModel; import org.keycloak.models.UserManager; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.light.LightweightUserAdapter; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RoleUtils; @@ -615,7 +616,9 @@ public class UserResource { public void logout() { auth.users().requireManage(user); - session.users().setNotBeforeForUser(realm, user, Time.currentTime()); + if (! LightweightUserAdapter.isLightweightUser(user)) { + session.users().setNotBeforeForUser(realm, user, Time.currentTime()); + } session.sessions().getUserSessionsStream(realm, user) .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions. diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 5cc70d2421..f1dbb6acde 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -34,6 +34,8 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.models.light.LightweightUserAdapter; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.policy.PasswordPolicyNotMetException; @@ -68,6 +70,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.keycloak.models.Constants.SESSION_NOTE_LIGHTWEIGHT_USER; import static org.keycloak.models.utils.KeycloakModelUtils.findGroupByPath; import static org.keycloak.userprofile.UserProfileContext.USER_API; @@ -225,7 +228,14 @@ public class UsersResource { */ @Path("{id}") public UserResource user(final @PathParam("id") String id) { - UserModel user = session.users().getUserById(realm, id); + UserModel user = null; + if (LightweightUserAdapter.isLightweightUser(id)) { + UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, LightweightUserAdapter.getLightweightUserId(id)); + user = userSession.getUser(); + } else { + user = session.users().getUserById(realm, id); + } + if (user == null) { // we do this to make sure somebody can't phish ids if (auth.users().canQuery()) throw new NotFoundException("User not found");