Merge pull request #3051 from patriot1burke/master
user fed spi fixes and simple test
This commit is contained in:
commit
76c4fbb241
20 changed files with 1096 additions and 82 deletions
|
@ -27,7 +27,6 @@ import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -41,6 +40,7 @@ import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserEntity;
|
import org.keycloak.models.jpa.entities.UserEntity;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
|
import org.keycloak.models.utils.DefaultRoles;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.storage.StorageProviderModel;
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
|
||||||
|
@ -88,15 +88,7 @@ public class JpaUserProvider implements UserProvider {
|
||||||
UserAdapter userModel = new UserAdapter(session, realm, em, entity);
|
UserAdapter userModel = new UserAdapter(session, realm, em, entity);
|
||||||
|
|
||||||
if (addDefaultRoles) {
|
if (addDefaultRoles) {
|
||||||
for (String r : realm.getDefaultRoles()) {
|
DefaultRoles.addDefaultRoles(realm, userModel);
|
||||||
userModel.grantRoleImpl(realm.getRole(r)); // No need to check if user has role as it's new user
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ClientModel application : realm.getClients()) {
|
|
||||||
for (String r : application.getDefaultRoles()) {
|
|
||||||
userModel.grantRoleImpl(application.getRole(r)); // No need to check if user has role as it's new user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (GroupModel g : realm.getDefaultGroups()) {
|
for (GroupModel g : realm.getDefaultGroups()) {
|
||||||
userModel.joinGroupImpl(g); // No need to check if user has group as it's new user
|
userModel.joinGroupImpl(g); // No need to check if user has group as it's new user
|
||||||
|
|
|
@ -19,6 +19,10 @@ package org.keycloak.models;
|
||||||
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.storage.StorageProviderModel;
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
|
import org.keycloak.storage.user.UserUpdateProvider;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -27,7 +31,11 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface UserProvider extends Provider, UserLookupProvider, UserQueryProvider, UserCredentialValidatorProvider, UserUpdateProvider {
|
public interface UserProvider extends Provider,
|
||||||
|
UserLookupProvider,
|
||||||
|
UserQueryProvider,
|
||||||
|
UserCredentialValidatorProvider,
|
||||||
|
UserUpdateProvider {
|
||||||
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
// Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession
|
||||||
|
|
||||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
|
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.utils;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class DefaultRoles {
|
||||||
|
public static Set<RoleModel> getDefaultRoles(RealmModel realm) {
|
||||||
|
Set<RoleModel> set = new HashSet<>();
|
||||||
|
for (String r : realm.getDefaultRoles()) {
|
||||||
|
set.add(realm.getRole(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ClientModel application : realm.getClients()) {
|
||||||
|
for (String r : application.getDefaultRoles()) {
|
||||||
|
set.add(application.getRole(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
|
||||||
|
}
|
||||||
|
public static void addDefaultRoles(RealmModel realm, UserModel userModel) {
|
||||||
|
for (RoleModel role : getDefaultRoles(realm)) {
|
||||||
|
userModel.grantRole(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,12 @@ public class StorageId implements Serializable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StorageId(String providerId, String storageId) {
|
||||||
|
this.id = "f:" + providerId + ":" + storageId;
|
||||||
|
this.providerId = providerId;
|
||||||
|
this.storageId = storageId;
|
||||||
|
}
|
||||||
|
|
||||||
public static String resolveProviderId(UserModel user) {
|
public static String resolveProviderId(UserModel user) {
|
||||||
return new StorageId(user.getId()).getProviderId();
|
return new StorageId(user.getId()).getProviderId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.keycloak.provider.Provider;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface StorageProvider extends Provider {
|
public interface StorageProvider extends Provider {
|
||||||
StorageProviderModel getModel();
|
|
||||||
|
|
||||||
void preRemove(RealmModel realm);
|
void preRemove(RealmModel realm);
|
||||||
void preRemove(RealmModel realm, GroupModel group);
|
void preRemove(RealmModel realm, GroupModel group);
|
||||||
|
|
|
@ -28,16 +28,16 @@ import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserCredentialAuthenticationProvider;
|
import org.keycloak.storage.user.UserCredentialAuthenticationProvider;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValidatorProvider;
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserLookupProvider;
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserProvider;
|
import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.UserQueryProvider;
|
import org.keycloak.storage.user.UserQueryProvider;
|
||||||
import org.keycloak.models.UserUpdateProvider;
|
import org.keycloak.storage.user.UserUpdateProvider;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
|
|
|
@ -16,138 +16,211 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.storage.adapter;
|
package org.keycloak.storage.adapter;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.models.utils.DefaultRoles;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This abstract class provides implementations for everything but getUsername(). getId() returns a default value
|
||||||
|
* of "f:" + providerId + ":" + getUsername(). isEnabled() returns true. getRoleMappings() will return default roles.
|
||||||
|
* getGroups() will return default groups.
|
||||||
|
*
|
||||||
|
* All other read methods return null, an empty collection, or false depending
|
||||||
|
* on the type. All update methods throw a ReadOnlyException.
|
||||||
|
*
|
||||||
|
* Provider implementors should override the methods for attributes, properties, and mappings they support.
|
||||||
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractUserAdapter implements UserModel {
|
public abstract class AbstractUserAdapter implements UserModel {
|
||||||
|
public static class ReadOnlyException extends RuntimeException {
|
||||||
|
public ReadOnlyException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
protected RealmModel realm;
|
protected RealmModel realm;
|
||||||
|
protected StorageProviderModel storageProviderModel;
|
||||||
|
|
||||||
public UserFederatedStorageProvider getFederatedStorage() {
|
public AbstractUserAdapter(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) {
|
||||||
return null;
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.storageProviderModel = storageProviderModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getRequiredActions() {
|
public Set<String> getRequiredActions() {
|
||||||
return getFederatedStorage().getRequiredActions(realm, this);
|
return Collections.EMPTY_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(String action) {
|
public void addRequiredAction(String action) {
|
||||||
getFederatedStorage().addRequiredAction(realm, this, action);
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRequiredAction(String action) {
|
public void removeRequiredAction(String action) {
|
||||||
getFederatedStorage().removeRequiredAction(realm, this, action);
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRequiredAction(RequiredAction action) {
|
public void addRequiredAction(RequiredAction action) {
|
||||||
getFederatedStorage().addRequiredAction(realm, this, action.name());
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeRequiredAction(RequiredAction action) {
|
public void removeRequiredAction(RequiredAction action) {
|
||||||
getFederatedStorage().removeRequiredAction(realm, this, action.name());
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get group membership mappings that are managed by this storage provider
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected Set<GroupModel> getGroupsInternal() {
|
||||||
|
return Collections.EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the realm's default groups be appended to getGroups() call?
|
||||||
|
* If your storage provider is not managing group mappings then it is recommended that
|
||||||
|
* this method return true
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean appendDefaultGroups() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<GroupModel> getGroups() {
|
public Set<GroupModel> getGroups() {
|
||||||
return null;
|
Set<GroupModel> set = new HashSet<>();
|
||||||
|
if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups());
|
||||||
|
set.addAll(getGroupsInternal());
|
||||||
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void joinGroup(GroupModel group) {
|
public void joinGroup(GroupModel group) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void leaveGroup(GroupModel group) {
|
public void leaveGroup(GroupModel group) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMemberOf(GroupModel group) {
|
public boolean isMemberOf(GroupModel group) {
|
||||||
return false;
|
Set<GroupModel> roles = getGroups();
|
||||||
}
|
return KeycloakModelUtils.isMember(roles, group);
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFederationLink() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFederationLink(String link) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServiceAccountClientLink() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setServiceAccountClientLink(String clientInternalId) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RoleModel> getRealmRoleMappings() {
|
public Set<RoleModel> getRealmRoleMappings() {
|
||||||
return null;
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
|
|
||||||
|
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
|
||||||
|
for (RoleModel role : roleMappings) {
|
||||||
|
RoleContainerModel container = role.getContainer();
|
||||||
|
if (container instanceof RealmModel) {
|
||||||
|
realmRoles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return realmRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||||
return null;
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
|
|
||||||
|
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||||
|
for (RoleModel role : roleMappings) {
|
||||||
|
RoleContainerModel container = role.getContainer();
|
||||||
|
if (container instanceof ClientModel) {
|
||||||
|
ClientModel appModel = (ClientModel) container;
|
||||||
|
if (appModel.getId().equals(app.getId())) {
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasRole(RoleModel role) {
|
public boolean hasRole(RoleModel role) {
|
||||||
return false;
|
Set<RoleModel> roles = getRoleMappings();
|
||||||
|
return KeycloakModelUtils.hasRole(roles, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void grantRole(RoleModel role) {
|
public void grantRole(RoleModel role) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the realm's default roles be appended to getRoleMappings() call?
|
||||||
|
* If your storage provider is not managing all role mappings then it is recommended that
|
||||||
|
* this method return true
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean appendDefaultRolesToRoleMappings() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<RoleModel> getRoleMappingsInternal() {
|
||||||
|
return Collections.EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RoleModel> getRoleMappings() {
|
public Set<RoleModel> getRoleMappings() {
|
||||||
return null;
|
Set<RoleModel> set = new HashSet<>();
|
||||||
|
if (appendDefaultRolesToRoleMappings()) set.addAll(DefaultRoles.getDefaultRoles(realm));
|
||||||
|
set.addAll(getRoleMappingsInternal());
|
||||||
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteRoleMapping(RoleModel role) {
|
public void deleteRoleMapping(RoleModel role) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -157,6 +230,176 @@ public abstract class AbstractUserAdapter implements UserModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOtpEnabled(boolean totp) {
|
public void setOtpEnabled(boolean totp) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getFederationLink() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFederationLink(String link) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getServiceAccountClientLink() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setServiceAccountClientLink(String clientInternalId) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StorageId storageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults to 'f:' + storageProvider.getId() + ':' + getUsername()
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
if (storageId == null) {
|
||||||
|
storageId = new StorageId(storageProviderModel.getId(), getUsername());
|
||||||
|
}
|
||||||
|
return storageId.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUsername(String username) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long created = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getCreatedTimestamp() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreatedTimestamp(Long timestamp) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSingleAttribute(String name, String value) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstAttribute(String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return new MultivaluedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttribute(String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLastName() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEmail() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmail(String email) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmailVerified() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmailVerified(boolean verified) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredentialDirectly(UserCredentialValueModel cred) {
|
||||||
|
throw new ReadOnlyException("user is read only for this update");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.storage.adapter;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleContainerModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.DefaultRoles;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assumes everything is managed by federated storage except for username. getId() returns a default value
|
||||||
|
* of "f:" + providerId + ":" + getUsername(). UserModel properties like enabled, firstName, lastName, email, etc. are all
|
||||||
|
* stored as attributes in federated storage.
|
||||||
|
*
|
||||||
|
* isEnabled() defaults to true if the ENABLED_ATTRIBUTE isn't set in federated
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public abstract class AbstractUserAdapterFederatedStorage implements UserModel {
|
||||||
|
public static String FIRST_NAME_ATTRIBUTE = "FIRST_NAME";
|
||||||
|
public static String LAST_NAME_ATTRIBUTE = "LAST_NAME";
|
||||||
|
public static String EMAIL_ATTRIBUTE = "EMAIL";
|
||||||
|
public static String EMAIL_VERIFIED_ATTRIBUTE = "EMAIL_VERIFIED";
|
||||||
|
public static String CREATED_TIMESTAMP_ATTRIBUTE = "CREATED_TIMESTAMP";
|
||||||
|
public static String ENABLED_ATTRIBUTE = "ENABLED";
|
||||||
|
public static String OTP_ENABLED_ATTRIBUTE = "OTP_ENABLED";
|
||||||
|
|
||||||
|
|
||||||
|
protected KeycloakSession session;
|
||||||
|
protected RealmModel realm;
|
||||||
|
protected StorageProviderModel storageProviderModel;
|
||||||
|
|
||||||
|
public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) {
|
||||||
|
this.session = session;
|
||||||
|
this.realm = realm;
|
||||||
|
this.storageProviderModel = storageProviderModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserFederatedStorageProvider getFederatedStorage() {
|
||||||
|
return session.userFederatedStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRequiredActions() {
|
||||||
|
return getFederatedStorage().getRequiredActions(realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredAction(String action) {
|
||||||
|
getFederatedStorage().addRequiredAction(realm, this, action);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRequiredAction(String action) {
|
||||||
|
getFederatedStorage().removeRequiredAction(realm, this, action);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRequiredAction(RequiredAction action) {
|
||||||
|
getFederatedStorage().addRequiredAction(realm, this, action.name());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRequiredAction(RequiredAction action) {
|
||||||
|
getFederatedStorage().removeRequiredAction(realm, this, action.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get group membership mappings that are managed by this storage provider
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected Set<GroupModel> getGroupsInternal() {
|
||||||
|
return Collections.EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the realm's default groups be appended to getGroups() call?
|
||||||
|
* If your storage provider is not managing group mappings then it is recommended that
|
||||||
|
* this method return true
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean appendDefaultGroups() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<GroupModel> getGroups() {
|
||||||
|
Set<GroupModel> set = new HashSet<>();
|
||||||
|
set.addAll(getFederatedStorage().getGroups(realm, this));
|
||||||
|
if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups());
|
||||||
|
set.addAll(getGroupsInternal());
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void joinGroup(GroupModel group) {
|
||||||
|
getFederatedStorage().joinGroup(realm, this, group);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveGroup(GroupModel group) {
|
||||||
|
getFederatedStorage().leaveGroup(realm, this, group);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMemberOf(GroupModel group) {
|
||||||
|
Set<GroupModel> roles = getGroups();
|
||||||
|
return KeycloakModelUtils.isMember(roles, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getRealmRoleMappings() {
|
||||||
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
|
|
||||||
|
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
|
||||||
|
for (RoleModel role : roleMappings) {
|
||||||
|
RoleContainerModel container = role.getContainer();
|
||||||
|
if (container instanceof RealmModel) {
|
||||||
|
realmRoles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return realmRoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||||
|
Set<RoleModel> roleMappings = getRoleMappings();
|
||||||
|
|
||||||
|
Set<RoleModel> roles = new HashSet<RoleModel>();
|
||||||
|
for (RoleModel role : roleMappings) {
|
||||||
|
RoleContainerModel container = role.getContainer();
|
||||||
|
if (container instanceof ClientModel) {
|
||||||
|
ClientModel appModel = (ClientModel) container;
|
||||||
|
if (appModel.getId().equals(app.getId())) {
|
||||||
|
roles.add(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasRole(RoleModel role) {
|
||||||
|
Set<RoleModel> roles = getRoleMappings();
|
||||||
|
return KeycloakModelUtils.hasRole(roles, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void grantRole(RoleModel role) {
|
||||||
|
getFederatedStorage().grantRole(realm, this, role);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the realm's default roles be appended to getRoleMappings() call?
|
||||||
|
* If your storage provider is not managing all role mappings then it is recommended that
|
||||||
|
* this method return true
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean appendDefaultRolesToRoleMappings() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<RoleModel> getRoleMappingsInternal() {
|
||||||
|
return Collections.EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RoleModel> getRoleMappings() {
|
||||||
|
Set<RoleModel> set = new HashSet<>();
|
||||||
|
set.addAll(getFederatedRoleMappings());
|
||||||
|
if (appendDefaultRolesToRoleMappings()) set.addAll(DefaultRoles.getDefaultRoles(realm));
|
||||||
|
set.addAll(getRoleMappingsInternal());
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Set<RoleModel> getFederatedRoleMappings() {
|
||||||
|
return getFederatedStorage().getRoleMappings(realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteRoleMapping(RoleModel role) {
|
||||||
|
getFederatedStorage().deleteRoleMapping(realm, this, role);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
String val = getFirstAttribute(ENABLED_ATTRIBUTE);
|
||||||
|
if (val == null) return true;
|
||||||
|
else return Boolean.valueOf(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
setSingleAttribute(ENABLED_ATTRIBUTE, Boolean.toString(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOtpEnabled() {
|
||||||
|
String val = getFirstAttribute(OTP_ENABLED_ATTRIBUTE);
|
||||||
|
if (val == null) return false;
|
||||||
|
else return Boolean.valueOf(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOtpEnabled(boolean totp) {
|
||||||
|
setSingleAttribute(OTP_ENABLED_ATTRIBUTE, Boolean.toString(totp));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getFederationLink() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setFederationLink(String link) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getServiceAccountClientLink() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should not be overriden
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setServiceAccountClientLink(String clientInternalId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StorageId storageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults to 'f:' + storageProvider.getId() + ':' + getUsername()
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
if (storageId == null) {
|
||||||
|
storageId = new StorageId(storageProviderModel.getId(), getUsername());
|
||||||
|
}
|
||||||
|
return storageId.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getCreatedTimestamp() {
|
||||||
|
String val = getFirstAttribute(CREATED_TIMESTAMP_ATTRIBUTE);
|
||||||
|
if (val == null) return null;
|
||||||
|
else return Long.valueOf(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreatedTimestamp(Long timestamp) {
|
||||||
|
if (timestamp == null) {
|
||||||
|
setSingleAttribute(CREATED_TIMESTAMP_ATTRIBUTE, null);
|
||||||
|
} else {
|
||||||
|
setSingleAttribute(CREATED_TIMESTAMP_ATTRIBUTE, Long.toString(timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSingleAttribute(String name, String value) {
|
||||||
|
getFederatedStorage().setSingleAttribute(realm, this, name, value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
getFederatedStorage().removeAttribute(realm, this, name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, List<String> values) {
|
||||||
|
getFederatedStorage().setAttribute(realm, this, name, values);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstAttribute(String name) {
|
||||||
|
return getFederatedStorage().getAttributes(realm, this).getFirst(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> getAttributes() {
|
||||||
|
return getFederatedStorage().getAttributes(realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAttribute(String name) {
|
||||||
|
return getFederatedStorage().getAttributes(realm, this).get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFirstName() {
|
||||||
|
return getFirstAttribute(FIRST_NAME_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
setSingleAttribute(FIRST_NAME_ATTRIBUTE, firstName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLastName() {
|
||||||
|
return getFirstAttribute(LAST_NAME_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
setSingleAttribute(LAST_NAME_ATTRIBUTE, lastName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEmail() {
|
||||||
|
return getFirstAttribute(EMAIL_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmail(String email) {
|
||||||
|
setSingleAttribute(EMAIL_ATTRIBUTE, email);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmailVerified() {
|
||||||
|
String val = getFirstAttribute(EMAIL_VERIFIED_ATTRIBUTE);
|
||||||
|
if (val == null) return false;
|
||||||
|
else return Boolean.valueOf(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEmailVerified(boolean verified) {
|
||||||
|
setSingleAttribute(EMAIL_VERIFIED_ATTRIBUTE, Boolean.toString(verified));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredential(UserCredentialModel cred) {
|
||||||
|
getFederatedStorage().updateCredential(realm, this, cred);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||||
|
return getFederatedStorage().getCredentials(realm, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateCredentialDirectly(UserCredentialValueModel cred) {
|
||||||
|
getFederatedStorage().updateCredential(realm, this, cred);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 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.storage.federated;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class CredentialModel {
|
|
||||||
}
|
|
|
@ -14,7 +14,12 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
|
@ -14,7 +14,10 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
@ -14,7 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
|
@ -14,7 +14,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.storage.user;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.testsuite.federation.storage;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UserFederationStorageTest {
|
||||||
|
@ClassRule
|
||||||
|
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
StorageProviderModel model = new StorageProviderModel();
|
||||||
|
model.setDisplayName("user-props");
|
||||||
|
model.setPriority(1);
|
||||||
|
model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID);
|
||||||
|
appRealm.addStorageProvider(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@Rule
|
||||||
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
private void loginSuccessAndLogout(String username, String password) {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login(username, password);
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
oauth.openLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loginBadPassword(String username) {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("username", "badpassword");
|
||||||
|
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginSuccess() {
|
||||||
|
loginSuccessAndLogout("tbrady", "goat");
|
||||||
|
loginBadPassword("tbrady");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.testsuite.federation.storage;
|
||||||
|
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.storage.StorageId;
|
||||||
|
import org.keycloak.storage.StorageProvider;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
import org.keycloak.storage.adapter.AbstractUserAdapter;
|
||||||
|
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
|
||||||
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
|
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||||
|
import org.keycloak.storage.user.UserLookupProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider {
|
||||||
|
|
||||||
|
protected Properties userPasswords;
|
||||||
|
protected StorageProviderModel model;
|
||||||
|
protected KeycloakSession session;
|
||||||
|
protected boolean federatedStorageEnabled;
|
||||||
|
|
||||||
|
public UserPropertyFileStorage(KeycloakSession session, StorageProviderModel model, Properties userPasswords) {
|
||||||
|
this.session = session;
|
||||||
|
this.model = model;
|
||||||
|
this.userPasswords = userPasswords;
|
||||||
|
this.federatedStorageEnabled = model.getConfig().containsKey("USER_FEDERATED_STORAGE");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserById(String id, RealmModel realm) {
|
||||||
|
StorageId storageId = new StorageId(id);
|
||||||
|
final String username = storageId.getStorageId();
|
||||||
|
if (!userPasswords.containsKey(username)) return null;
|
||||||
|
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserModel createUser(final RealmModel realm, final String username) {
|
||||||
|
return new AbstractUserAdapter(session, realm, model) {
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||||
|
if (!userPasswords.containsKey(username)) return null;
|
||||||
|
|
||||||
|
return createUser(realm, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByEmail(String email, RealmModel realm) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, GroupModel group) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preRemove(RealmModel realm, StorageProviderModel model) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||||
|
for (UserCredentialModel cred : input) {
|
||||||
|
if (!cred.getType().equals(UserCredentialModel.PASSWORD)) return false;
|
||||||
|
String password = (String)userPasswords.get(user.getUsername());
|
||||||
|
if (password == null) return false;
|
||||||
|
if (!password.equals(cred.getValue())) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 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.testsuite.federation.storage;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.storage.StorageProvider;
|
||||||
|
import org.keycloak.storage.StorageProviderFactory;
|
||||||
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UserPropertyFileStorageFactory implements StorageProviderFactory {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "user-password-props";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> type) {
|
||||||
|
return type.isAssignableFrom(UserPropertyFileStorage.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StorageProvider getInstance(KeycloakSession session, StorageProviderModel model) {
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(getClass().getResourceAsStream("/storage-test/user-password.properties"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return new UserPropertyFileStorage(session, model, props);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getConfigurationOptions() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StorageProvider create(KeycloakSession session) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory
|
|
@ -23,6 +23,7 @@ log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%
|
||||||
|
|
||||||
log4j.logger.org.keycloak=info
|
log4j.logger.org.keycloak=info
|
||||||
|
|
||||||
|
|
||||||
# Enable to view events
|
# Enable to view events
|
||||||
# log4j.logger.org.keycloak.events=debug
|
# log4j.logger.org.keycloak.events=debug
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
tbrady=goat
|
Loading…
Reference in a new issue