Create serializable lightweight user adapter

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:48:11 +02:00 committed by Hynek Mlnařík
parent 35a226f928
commit 1ec2a97f92
2 changed files with 358 additions and 0 deletions

View file

@ -0,0 +1,121 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.light;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.SubjectCredentialManager;
import java.util.List;
import java.util.stream.Stream;
/**
*
* @author hmlnarik
*/
class EmptyCredentialManager implements SubjectCredentialManager {
public static final EmptyCredentialManager INSTANCE = new EmptyCredentialManager();
@Override
public boolean isValid(List<CredentialInput> inputs) {
return false;
}
@Override
public boolean updateCredential(CredentialInput input) {
// no-op
return false;
}
@Override
public void updateStoredCredential(CredentialModel cred) {
// no-op
}
@Override
public CredentialModel createStoredCredential(CredentialModel cred) {
// no-op
return null;
}
@Override
public boolean removeStoredCredentialById(String id) {
// no-op
return false;
}
@Override
public CredentialModel getStoredCredentialById(String id) {
return null;
}
@Override
public Stream<CredentialModel> getStoredCredentialsStream() {
return Stream.empty();
}
@Override
public Stream<CredentialModel> getStoredCredentialsByTypeStream(String type) {
return Stream.empty();
}
@Override
public CredentialModel getStoredCredentialByNameAndType(String name, String type) {
return null;
}
@Override
public boolean moveStoredCredentialTo(String id, String newPreviousCredentialId) {
return false;
}
@Override
public void updateCredentialLabel(String credentialId, String credentialLabel) {
// no-op
}
@Override
public void disableCredentialType(String credentialType) {
// no-op
}
@Override
public Stream<String> getDisableableCredentialTypesStream() {
return Stream.empty();
}
@Override
public boolean isConfiguredFor(String type) {
return false;
}
@Override
public boolean isConfiguredLocally(String type) {
return false;
}
@Override
public Stream<String> getConfiguredUserStorageCredentialTypesStream() {
return Stream.empty();
}
@Override
public CredentialModel createCredentialThroughProvider(CredentialModel model) {
return null;
}
}

View file

@ -0,0 +1,237 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.light;
import org.keycloak.common.util.Base64;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SubjectCredentialManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.storage.adapter.AbstractInMemoryUserAdapter;
import org.keycloak.util.JsonSerialization;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author hmlnarik
*/
@JsonIncludeProperties({
"id",
"createdTimestamp",
"emailVerified",
"enabled",
"roleIds",
"groupIds",
"attributes",
"requiredActions",
"federationLink",
"serviceAccountClientLink",
"readonly"
})
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class LightweightUserAdapter extends AbstractInMemoryUserAdapter {
private Consumer<LightweightUserAdapter> updateHandler = a -> {};
public static final String ID_PREFIX = "lightweight-";
public static boolean isLightweightUser(UserModel user) {
return user instanceof LightweightUserAdapter;
}
public static boolean isLightweightUser(String id) {
return id != null && id.startsWith(ID_PREFIX);
}
public static String getLightweightUserId(String id) {
try {
return id == null || id.length() < ID_PREFIX.length()
? null
: new String(Base64.decode(id.substring(ID_PREFIX.length())), StandardCharsets.UTF_8);
} catch (IOException ex) {
return null;
}
}
public LightweightUserAdapter(KeycloakSession session, String id) {
super(session, null, ID_PREFIX + Base64.encodeBytes(id.getBytes(StandardCharsets.UTF_8)));
}
protected LightweightUserAdapter() {
}
public static LightweightUserAdapter fromString(KeycloakSession session, RealmModel realm, String serializedForm) {
if (serializedForm == null) {
return null;
}
try {
LightweightUserAdapter res = JsonSerialization.readValue(serializedForm, LightweightUserAdapter.class);
res.session = session;
res.realm = realm;
return res;
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public SubjectCredentialManager credentialManager() {
return EmptyCredentialManager.INSTANCE;
}
public String serialize() {
try {
return JsonSerialization.writeValueAsString(this);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void deleteRoleMapping(RoleModel role) {
super.deleteRoleMapping(role); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void grantRole(RoleModel role) {
super.grantRole(role); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setServiceAccountClientLink(String clientInternalId) {
super.setServiceAccountClientLink(clientInternalId); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setFederationLink(String link) {
super.setFederationLink(link); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void leaveGroup(GroupModel group) {
super.leaveGroup(group); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void joinGroup(GroupModel group) {
super.joinGroup(group); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setEmailVerified(boolean verified) {
super.setEmailVerified(verified); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void removeRequiredAction(RequiredAction action) {
super.removeRequiredAction(action); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void addRequiredAction(RequiredAction action) {
super.addRequiredAction(action); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void removeRequiredAction(String action) {
super.removeRequiredAction(action); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void addRequiredAction(String action) {
super.addRequiredAction(action); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void removeAttribute(String name) {
super.removeAttribute(name); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setAttribute(String name, List<String> values) {
super.setAttribute(name, values); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setSingleAttribute(String name, String value) {
super.setSingleAttribute(name, value); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setCreatedTimestamp(Long timestamp) {
super.setCreatedTimestamp(timestamp); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setReadonly(boolean flag) {
super.setReadonly(flag); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void addDefaults() {
super.addDefaults(); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
@Override
public void setUsername(String username) {
super.setUsername(username); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/OverriddenMethodBody
update();
}
public void setUpdateHandler(Consumer<LightweightUserAdapter> updateHandler) {
this.updateHandler = updateHandler == null ? lua -> {} : updateHandler;
}
private void update() {
updateHandler.accept(this);
}
}