Support for transient brokering in admin console

Part-of: Add support for not importing brokered user into Keycloak database

Closes: #11334
This commit is contained in:
Hynek Mlnarik 2023-10-11 13:52:42 +02:00 committed by Hynek Mlnařík
parent 26328a7c1e
commit a668c2cb2b
4 changed files with 82 additions and 36 deletions

View file

@ -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.",

View file

@ -96,6 +96,7 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
const {
control,
register,
setValue,
formState: { errors },
} = useFormContext<IdentityProviderRepresentation>();
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,6 +238,29 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
defaultValue=""
/>
<FormGroupField label="doNotStoreUsers">
<Controller
name="config.doNotStoreUsers"
defaultValue="false"
control={control}
render={({ field }) => (
<Switch
id="doNotStoreUsers"
label={t("on")}
labelOff={t("off")}
isChecked={field.value === "true"}
onChange={(value) => {
field.onChange(value.toString());
// if field is checked, set sync mode to import
if (value) {
setValue("config.syncMode", "IMPORT");
}
}}
/>
)}
/>
</FormGroupField>
{syncModeAvailable && (
<FormGroup
className="pf-u-pb-3xl"
label={t("syncMode")}
@ -271,6 +301,7 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
)}
/>
</FormGroup>
)}
</>
);
};

View file

@ -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);
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.

View file

@ -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");