Merge pull request #3051 from patriot1burke/master

user fed spi fixes and simple test
This commit is contained in:
Bill Burke 2016-07-20 12:51:42 -04:00 committed by GitHub
commit 76c4fbb241
20 changed files with 1096 additions and 82 deletions

View file

@ -27,7 +27,6 @@ import org.keycloak.models.ModelException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
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.UserEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.DefaultRoles;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.StorageProviderModel;
@ -88,15 +88,7 @@ public class JpaUserProvider implements UserProvider {
UserAdapter userModel = new UserAdapter(session, realm, em, entity);
if (addDefaultRoles) {
for (String r : realm.getDefaultRoles()) {
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
}
}
DefaultRoles.addDefaultRoles(realm, userModel);
for (GroupModel g : realm.getDefaultGroups()) {
userModel.joinGroupImpl(g); // No need to check if user has group as it's new user

View file

@ -19,6 +19,10 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
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.Set;
@ -27,7 +31,11 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink);

View file

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

View file

@ -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) {
return new StorageId(user.getId()).getProviderId();
}

View file

@ -29,7 +29,6 @@ import org.keycloak.provider.Provider;
* @version $Revision: 1 $
*/
public interface StorageProvider extends Provider {
StorageProviderModel getModel();
void preRemove(RealmModel realm);
void preRemove(RealmModel realm, GroupModel group);

View file

@ -28,16 +28,16 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
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.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.models.UserCredentialValueModel;
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.UserProvider;
import org.keycloak.models.UserQueryProvider;
import org.keycloak.models.UserUpdateProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserUpdateProvider;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.storage.federated.UserFederatedStorageProvider;

View file

@ -16,138 +16,211 @@
*/
package org.keycloak.storage.adapter;
import org.keycloak.common.util.MultivaluedHashMap;
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.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.Map;
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>
* @version $Revision: 1 $
*/
public abstract class AbstractUserAdapter implements UserModel {
public static class ReadOnlyException extends RuntimeException {
public ReadOnlyException(String message) {
super(message);
}
}
protected KeycloakSession session;
protected RealmModel realm;
protected StorageProviderModel storageProviderModel;
public UserFederatedStorageProvider getFederatedStorage() {
return null;
public AbstractUserAdapter(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) {
this.session = session;
this.realm = realm;
this.storageProviderModel = storageProviderModel;
}
@Override
public Set<String> getRequiredActions() {
return getFederatedStorage().getRequiredActions(realm, this);
return Collections.EMPTY_SET;
}
@Override
public void addRequiredAction(String action) {
getFederatedStorage().addRequiredAction(realm, this, action);
throw new ReadOnlyException("user is read only for this update");
}
@Override
public void removeRequiredAction(String action) {
getFederatedStorage().removeRequiredAction(realm, this, action);
throw new ReadOnlyException("user is read only for this update");
}
@Override
public void addRequiredAction(RequiredAction action) {
getFederatedStorage().addRequiredAction(realm, this, action.name());
throw new ReadOnlyException("user is read only for this update");
}
@Override
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
public Set<GroupModel> getGroups() {
return null;
Set<GroupModel> set = new HashSet<>();
if (appendDefaultGroups()) set.addAll(realm.getDefaultGroups());
set.addAll(getGroupsInternal());
return set;
}
@Override
public void joinGroup(GroupModel group) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public void leaveGroup(GroupModel group) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public boolean isMemberOf(GroupModel group) {
return false;
}
@Override
public String getFederationLink() {
return null;
}
@Override
public void setFederationLink(String link) {
}
@Override
public String getServiceAccountClientLink() {
return null;
}
@Override
public void setServiceAccountClientLink(String clientInternalId) {
Set<GroupModel> roles = getGroups();
return KeycloakModelUtils.isMember(roles, group);
}
@Override
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
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
public boolean hasRole(RoleModel role) {
return false;
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
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
public Set<RoleModel> getRoleMappings() {
return null;
Set<RoleModel> set = new HashSet<>();
if (appendDefaultRolesToRoleMappings()) set.addAll(DefaultRoles.getDefaultRoles(realm));
set.addAll(getRoleMappingsInternal());
return set;
}
@Override
public void deleteRoleMapping(RoleModel role) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
public boolean isEnabled() {
return false;
return true;
}
@Override
public void setEnabled(boolean enabled) {
throw new ReadOnlyException("user is read only for this update");
}
@Override
@ -157,6 +230,176 @@ public abstract class AbstractUserAdapter implements UserModel {
@Override
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");
}
}

View file

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

View file

@ -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 {
}

View file

@ -14,7 +14,12 @@
* See the License for the specific language governing permissions and
* 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;

View file

@ -14,7 +14,12 @@
* See the License for the specific language governing permissions and
* 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.Set;

View file

@ -14,7 +14,10 @@
* See the License for the specific language governing permissions and
* 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>

View file

@ -14,7 +14,11 @@
* See the License for the specific language governing permissions and
* 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.Map;

View file

@ -14,7 +14,11 @@
* See the License for the specific language governing permissions and
* 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>

View file

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

View file

@ -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() {
}
}

View file

@ -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() {
}
}

View file

@ -0,0 +1 @@
org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory

View file

@ -23,6 +23,7 @@ log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%
log4j.logger.org.keycloak=info
# Enable to view events
# log4j.logger.org.keycloak.events=debug

View file

@ -0,0 +1 @@
tbrady=goat