done 1st iteration

This commit is contained in:
Bill Burke 2018-01-27 09:47:16 -05:00
parent ddad1cb8af
commit 6b84b9b4b6
22 changed files with 1157 additions and 171 deletions

View file

@ -67,6 +67,7 @@ public class ClientRepresentation {
private Boolean useTemplateMappers;
private ResourceServerRepresentation authorizationSettings;
private Map<String, Boolean> access;
protected String origin;
public String getId() {
@ -384,4 +385,19 @@ public class ClientRepresentation {
public void setAccess(Map<String, Boolean> access) {
this.access = access;
}
/**
* Returns id of ClientStorageProvider that loaded this user
*
* @return NULL if user stored locally
*/
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
}

View file

@ -479,13 +479,13 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public ClientModel addClient(RealmModel realm, String clientId) {
ClientModel client = getClientDelegate().addClient(realm, clientId);
ClientModel client = getRealmDelegate().addClient(realm, clientId);
return addedClient(realm, client);
}
@Override
public ClientModel addClient(RealmModel realm, String id, String clientId) {
ClientModel client = getClientDelegate().addClient(realm, id, clientId);
ClientModel client = getRealmDelegate().addClient(realm, id, clientId);
return addedClient(realm, client);
}
@ -550,7 +550,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (client == null) {
// TODO: Handle with cluster invalidations too
invalidations.add(cacheKey);
return getClientDelegate().getClients(realm);
return getRealmDelegate().getClients(realm);
}
list.add(client);
}
@ -573,7 +573,7 @@ public class RealmCacheSession implements CacheRealmProvider {
for (RoleModel role : client.getRoles()) {
roleRemovalInvalidations(role.getId(), role.getName(), client.getId());
}
return getClientDelegate().removeClient(id, realm);
return getRealmDelegate().removeClient(id, realm);
}
@ -636,7 +636,7 @@ public class RealmCacheSession implements CacheRealmProvider {
String cacheKey = getRolesCacheKey(client.getId());
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId());
if (queryDB) {
return getClientDelegate().getClientRoles(realm, client);
return getRealmDelegate().getClientRoles(realm, client);
}
RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
@ -646,7 +646,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
Set<RoleModel> model = getClientDelegate().getClientRoles(realm, client);
Set<RoleModel> model = getRealmDelegate().getClientRoles(realm, client);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (RoleModel role : model) ids.add(role.getId());
@ -660,7 +660,7 @@ public class RealmCacheSession implements CacheRealmProvider {
RoleModel role = session.realms().getRoleById(id, realm);
if (role == null) {
invalidations.add(cacheKey);
return getClientDelegate().getClientRoles(realm, client);
return getRealmDelegate().getClientRoles(realm, client);
}
list.add(role);
}
@ -674,7 +674,7 @@ public class RealmCacheSession implements CacheRealmProvider {
@Override
public RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name) {
RoleModel role = getClientDelegate().addClientRole(realm, client, id, name);
RoleModel role = getRealmDelegate().addClientRole(realm, client, id, name);
addedRole(role.getId(), client.getId());
return role;
}
@ -714,7 +714,7 @@ public class RealmCacheSession implements CacheRealmProvider {
String cacheKey = getRoleByNameCacheKey(client.getId(), name);
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(client.getId());
if (queryDB) {
return getClientDelegate().getClientRole(realm, client, name);
return getRealmDelegate().getClientRole(realm, client, name);
}
RoleListQuery query = cache.get(cacheKey, RoleListQuery.class);
@ -724,7 +724,7 @@ public class RealmCacheSession implements CacheRealmProvider {
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
RoleModel model = getClientDelegate().getClientRole(realm, client, name);
RoleModel model = getRealmDelegate().getClientRole(realm, client, name);
if (model == null) return null;
query = new RoleListQuery(loaded, cacheKey, realm, model.getId(), client.getClientId());
logger.tracev("adding client role cache miss: client {0} key {1}", client.getClientId(), cacheKey);
@ -734,7 +734,7 @@ public class RealmCacheSession implements CacheRealmProvider {
RoleModel role = getRoleById(query.getRoles().iterator().next(), realm);
if (role == null) {
invalidations.add(cacheKey);
return getClientDelegate().getClientRole(realm, client, name);
return getRealmDelegate().getClientRole(realm, client, name);
}
return role;
}
@ -1025,7 +1025,7 @@ public class RealmCacheSession implements CacheRealmProvider {
logger.tracev("adding client by id cache miss: {0}", cached.getClientId());
cache.addRevisioned(cached, startupRevision);
} else if (invalidations.contains(id)) {
return getClientDelegate().getClientById(id, realm);
return getRealmDelegate().getClientById(id, realm);
} else if (managedApplications.containsKey(id)) {
return managedApplications.get(id);
}

View file

@ -41,6 +41,7 @@ import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.ModelException;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource;
@ -64,6 +65,7 @@ import org.keycloak.models.cache.infinispan.authorization.events.ScopeRemovedEve
import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEvent;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.storage.StorageId;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -348,6 +350,9 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
protected class ResourceServerCache implements ResourceServerStore {
@Override
public ResourceServer create(String clientId) {
if (!StorageId.isLocalStorage(clientId)) {
throw new ModelException("Creating resource server from federated ClientModel not supported");
}
ResourceServer server = getResourceServerStoreDelegate().create(clientId);
registerResourceServerInvalidation(server.getId());
return server;

View file

@ -25,6 +25,8 @@ import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.models.ModelException;
import org.keycloak.storage.StorageId;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@ -46,6 +48,9 @@ public class JPAResourceServerStore implements ResourceServerStore {
@Override
public ResourceServer create(String clientId) {
if (!StorageId.isLocalStorage(clientId)) {
throw new ModelException("Creating resource server from federated ClientModel not supported");
}
ResourceServerEntity entity = new ResourceServerEntity();
entity.setId(clientId);

View file

@ -125,9 +125,6 @@ public class ClientEntity {
@CollectionTable(name="CLIENT_AUTH_FLOW_BINDINGS", joinColumns={ @JoinColumn(name="CLIENT_ID") })
protected Map<String, String> authFlowBindings = new HashMap<String, String>();
@OneToMany(fetch = FetchType.LAZY, mappedBy = "client", cascade = CascadeType.REMOVE)
Collection<ClientIdentityProviderMappingEntity> identityProviders = new ArrayList<ClientIdentityProviderMappingEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
Collection<ProtocolMapperEntity> protocolMappers = new ArrayList<ProtocolMapperEntity>();
@ -322,14 +319,6 @@ public class ClientEntity {
this.frontchannelLogout = frontchannelLogout;
}
public Collection<ClientIdentityProviderMappingEntity> getIdentityProviders() {
return this.identityProviders;
}
public void setIdentityProviders(Collection<ClientIdentityProviderMappingEntity> identityProviders) {
this.identityProviders = identityProviders;
}
public Collection<ProtocolMapperEntity> getProtocolMappers() {
return protocolMappers;
}

View file

@ -1,141 +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.models.jpa.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
/**
* @author pedroigor
*/
@Table(name="CLIENT_IDENTITY_PROV_MAPPING")
@Entity
@IdClass(ClientIdentityProviderMappingEntity.Key.class)
public class ClientIdentityProviderMappingEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CLIENT_ID")
private ClientEntity client;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "IDENTITY_PROVIDER_ID")
private IdentityProviderEntity identityProvider;
@Column(name = "RETRIEVE_TOKEN")
private boolean retrieveToken;
public ClientEntity getClient() {
return this.client;
}
public void setClient(ClientEntity client) {
this.client = client;
}
public IdentityProviderEntity getIdentityProvider() {
return this.identityProvider;
}
public void setIdentityProvider(IdentityProviderEntity identityProvider) {
this.identityProvider = identityProvider;
}
public void setRetrieveToken(boolean retrieveToken) {
this.retrieveToken = retrieveToken;
}
public boolean isRetrieveToken() {
return retrieveToken;
}
public static class Key implements Serializable {
private ClientEntity client;
private IdentityProviderEntity identityProvider;
public Key() {
}
public Key(ClientEntity client, IdentityProviderEntity identityProvider) {
this.client = client;
this.identityProvider = identityProvider;
}
public ClientEntity getUser() {
return client;
}
public IdentityProviderEntity getIdentityProvider() {
return identityProvider;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (identityProvider != null ? !identityProvider.getAlias().equals(key.identityProvider.getAlias()) : key.identityProvider != null)
return false;
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
return true;
}
@Override
public int hashCode() {
int result = client != null ? client.getId().hashCode() : 0;
result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
return result;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof ClientIdentityProviderMappingEntity)) return false;
ClientIdentityProviderMappingEntity key = (ClientIdentityProviderMappingEntity) o;
if (identityProvider != null ? !identityProvider.getAlias().equals(key.identityProvider.getAlias()) : key.identityProvider != null)
return false;
if (client != null ? !client.getId().equals(key.client != null ? key.client.getId() : null) : key.client != null) return false;
return true;
}
@Override
public int hashCode() {
int result = client != null ? client.getId().hashCode() : 0;
result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0);
return result;
}
}

View file

@ -29,4 +29,36 @@
</createTable>
<addPrimaryKey columnNames="CLIENT_ID, BINDING_NAME" constraintName="C_CLI_FLOW_BIND" tableName="CLIENT_AUTH_FLOW_BINDINGS"/>
</changeSet>
<changeSet author="bburke@redhat.com" id="4.0.0-CLEANUP-UNUSED-TABLE">
<dropIndex tableName="CLIENT_IDENTITY_PROV_MAPPING" indexName="IDX_CLIENT_ID_PROV_MAP_CLIENT"/>
<dropPrimaryKey tableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="CONSTR_CLIENT_IDEN_PROV_MAP"/>
<dropUniqueConstraint tableName="CLIENT_IDENTITY_PROV_MAPPING" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12"/>
<dropTable tableName="CLIENT_IDENTITY_PROV_MAPPING"/>
</changeSet>
<changeSet author="bburke@redhat.com" id="4.0.0-KEYCLOAK-6228">
<!-- Modifying some columns so that CLIENT_ID is 255. Drop foreign key constraints too that referenced CLIENT tablename.
This is needed for client storage SPI but only needed for tables that might reference a federated client -->
<!-- Modify USER_CONSENT -->
<dropUniqueConstraint constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT" tableName="USER_CONSENT"/>
<modifyDataType tableName="USER_CONSENT" columnName="CLIENT_ID" newDataType="VARCHAR(255)"/>
<addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT" tableName="USER_CONSENT"/>
<!-- Modify CLIENT_NODE_REGISTRATIONS -->
<dropForeignKeyConstraint constraintName="FK4129723BA992F594" baseTableName="CLIENT"/>
<modifyDataType tableName="CLIENT_NODE_REGISTRATIONS" columnName="CLIENT_ID" newDataType="VARCHAR(255)"/>
<!-- Modify OFFLINE_CLIENT_SESSION -->
<dropPrimaryKey tableName="OFFLINE_CLIENT_SESSION" constraintName="CONSTRAINT_OFFL_CL_SES_PK3"/>
<modifyDataType tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_ID" newDataType="VARCHAR(255)"/>
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
<!-- FED_USER_CONSENT -->
<dropIndex tableName="FED_USER_CONSENT" indexName="IDX_FU_CONSENT"/>
<modifyDataType tableName="FED_USER_CONSENT" columnName="CLIENT_ID" newDataType="VARCHAR(255)"/>
<createIndex tableName="FED_USER_CONSENT" indexName="IDX_FU_CONSENT">
<column name="USER_ID" type="VARCHAR(255)" />
<column name="CLIENT_ID" type="VARCHAR(36)" />
</createIndex>
</changeSet>
</databaseChangeLog>

View file

@ -39,7 +39,6 @@
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
<class>org.keycloak.models.jpa.entities.UserConsentEntity</class>
<class>org.keycloak.models.jpa.entities.UserConsentRoleEntity</class>

View file

@ -485,6 +485,8 @@ public class ModelToRepresentation {
public static ClientRepresentation toRepresentation(ClientModel clientModel) {
ClientRepresentation rep = new ClientRepresentation();
rep.setId(clientModel.getId());
String providerId = StorageId.resolveProviderId(clientModel);
rep.setOrigin(providerId);
rep.setClientId(clientModel.getClientId());
rep.setName(clientModel.getName());
rep.setDescription(clientModel.getDescription());

View file

@ -0,0 +1,139 @@
/*
* 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.client;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.storage.StorageId;
import java.util.Collections;
import java.util.Map;
/**
* Helper base class for ClientModel implementations for ClientStorageProvider implementations.
*
* Contains default implementations of some methods
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class AbstractClientStorageAdapter extends UnsupportedOperationsClientStorageAdapter {
protected KeycloakSession session;
protected RealmModel realm;
protected ClientStorageProviderModel component;
private StorageId storageId;
public AbstractClientStorageAdapter(KeycloakSession session, RealmModel realm, ClientStorageProviderModel component) {
this.session = session;
this.realm = realm;
this.component = component;
}
/**
* Creates federated id based on getClientId() method
*
* @return
*/
@Override
public String getId() {
if (storageId == null) {
storageId = new StorageId(component.getId(), getClientId());
}
return storageId.getId();
}
@Override
public final RealmModel getRealm() {
return realm;
}
/**
* This method really isn't used by anybody anywhere. Legacy feature never supported.
*
* @return
*/
@Override
public boolean isSurrogateAuthRequired() {
return false;
}
/**
* This method really isn't used by anybody anywhere. Legacy feature never supported.
*
* @return
*/
@Override
public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
// do nothing, we don't do anything with this.
}
/**
* This is for logout. Empty implementation for now. Can override if you can store this information somewhere.
*
* @return
*/
@Override
public Map<String, Integer> getRegisteredNodes() {
return Collections.EMPTY_MAP;
}
/**
* This is for logout. Empty implementation for now. Can override if you can store this information somewhere.
*
* @return
*/
@Override
public void registerNode(String nodeHost, int registrationTime) {
// do nothing
}
/**
* This is for logout. Empty implementation for now. Can override if you can store this information somewhere.
*
* @return
*/
@Override
public void unregisterNode(String nodeHost) {
// do nothing
}
/**
* Overriding implementations should call super.updateClient() as this fires off an update event.
*
*/
@Override
public void updateClient() {
session.getKeycloakSessionFactory().publish(new RealmModel.ClientUpdatedEvent() {
@Override
public ClientModel getUpdatedClient() {
return AbstractClientStorageAdapter.this;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
}
}

View file

@ -0,0 +1,280 @@
/*
* 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.client;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.ReadOnlyException;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class AbstractReadOnlyClientStorageAdapter extends AbstractClientStorageAdapter {
public AbstractReadOnlyClientStorageAdapter(KeycloakSession session, RealmModel realm, ClientStorageProviderModel component) {
super(session, realm, component);
}
@Override
public void setClientId(String clientId) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setName(String name) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setDescription(String description) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setEnabled(boolean enabled) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setWebOrigins(Set<String> webOrigins) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void addWebOrigin(String webOrigin) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void removeWebOrigin(String webOrigin) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void addRedirectUri(String redirectUri) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void removeRedirectUri(String redirectUri) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setManagementUrl(String url) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setRootUrl(String url) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setBaseUrl(String url) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setBearerOnly(boolean only) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setNodeReRegistrationTimeout(int timeout) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setClientAuthenticatorType(String clientAuthenticatorType) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setSecret(String secret) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setRegistrationToken(String registrationToken) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setProtocol(String protocol) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setAttribute(String name, String value) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void removeAttribute(String name) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void removeAuthenticationFlowBindingOverride(String binding) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setFrontchannelLogout(boolean flag) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setPublicClient(boolean flag) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setConsentRequired(boolean consentRequired) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setStandardFlowEnabled(boolean standardFlowEnabled) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setClientTemplate(ClientTemplateModel template) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setUseTemplateScope(boolean flag) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setUseTemplateMappers(boolean flag) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setUseTemplateConfig(boolean flag) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setNotBefore(int notBefore) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void removeProtocolMapper(ProtocolMapperModel mapping) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void updateProtocolMapper(ProtocolMapperModel mapping) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void setFullScopeAllowed(boolean value) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void addScopeMapping(RoleModel role) {
throw new ReadOnlyException("client is read only for this update");
}
@Override
public void deleteScopeMapping(RoleModel role) {
throw new ReadOnlyException("client is read only for this update");
}
}

View file

@ -33,7 +33,7 @@ public class ClientStorageProviderSpi implements Spi {
@Override
public boolean isInternal() {
return false;
return true;
}
@Override

View file

@ -0,0 +1,82 @@
/*
* 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.client;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RoleModel;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Base helper class. Unsupported operations are implemented here that throw exception on invocation.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class UnsupportedOperationsClientStorageAdapter implements ClientModel {
@Override
public final RoleModel getRole(String name) {
return null;
}
@Override
public final RoleModel addRole(String name) {
throw new ModelException("Unsupported operation");
}
@Override
public final RoleModel addRole(String id, String name) {
throw new ModelException("Unsupported operation");
}
@Override
public final boolean removeRole(RoleModel role) {
throw new ModelException("Unsupported operation");
}
@Override
public final Set<RoleModel> getRoles() {
return Collections.EMPTY_SET;
}
@Override
public final List<String> getDefaultRoles() {
return Collections.EMPTY_LIST;
}
@Override
public final void addDefaultRole(String name) {
throw new ModelException("Unsupported operation");
}
@Override
public final void updateDefaultRoles(String... defaultRoles) {
throw new ModelException("Unsupported operation");
}
@Override
public final void removeDefaultRoles(String... defaultRoles) {
throw new ModelException("Unsupported operation");
}
}

View file

@ -71,3 +71,4 @@ org.keycloak.credential.hash.PasswordHashSpi
org.keycloak.credential.CredentialSpi
org.keycloak.keys.PublicKeyStorageSpi
org.keycloak.keys.KeySpi
org.keycloak.storage.client.ClientStorageProviderSpi

View file

@ -137,7 +137,7 @@ public class CacheableStorageProviderModel extends PrioritizedComponentModel {
getConfig().putSingle(CACHE_INVALID_BEFORE, Long.toString(cacheInvalidBefore));
}
public static enum CachePolicy {
public enum CachePolicy {
NO_CACHE,
DEFAULT,
EVICT_DAILY,

View file

@ -32,6 +32,7 @@ import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.storage.user.UserLookupProvider;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@ -186,7 +187,8 @@ public class ClientStorageManager implements ClientProvider {
@Override
public RoleModel getClientRole(RealmModel realm, ClientModel client, String name) {
if (!StorageId.isLocalStorage(client.getId())) {
throw new RuntimeException("Federated clients do not support this operation");
//throw new RuntimeException("Federated clients do not support this operation");
return null;
}
return session.clientLocalStorage().getClientRole(realm, client, name);
}
@ -194,11 +196,17 @@ public class ClientStorageManager implements ClientProvider {
@Override
public Set<RoleModel> getClientRoles(RealmModel realm, ClientModel client) {
if (!StorageId.isLocalStorage(client.getId())) {
throw new RuntimeException("Federated clients do not support this operation");
//throw new RuntimeException("Federated clients do not support this operation");
return Collections.EMPTY_SET;
}
return session.clientLocalStorage().getClientRoles(realm, client);
}
@Override
public void close() {
}
@Override
public boolean removeClient(String id, RealmModel realm) {
if (!StorageId.isLocalStorage(id)) {
@ -207,4 +215,6 @@ public class ClientStorageManager implements ClientProvider {
return session.clientLocalStorage().removeClient(id, realm);
}
}

View file

@ -0,0 +1,278 @@
/*
* 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;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.client.AbstractClientStorageAdapter;
import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
import org.keycloak.storage.client.ClientLookupProvider;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedClientStorageProvider implements ClientStorageProvider, ClientLookupProvider {
protected KeycloakSession session;
protected ClientStorageProviderModel component;
protected String clientId;
protected String redirectUri;
public HardcodedClientStorageProvider(KeycloakSession session, ClientStorageProviderModel component) {
this.session = session;
this.component = component;
this.clientId = component.getConfig().getFirst(HardcodedClientStorageProviderFactory.CLIENT_ID);
this.redirectUri = component.getConfig().getFirst(HardcodedClientStorageProviderFactory.REDIRECT_URI);
}
@Override
public ClientModel getClientById(String id, RealmModel realm) {
StorageId storageId = new StorageId(id);
final String clientId = storageId.getExternalId();
if (clientId.equals(clientId)) return new ClientAdapter(realm);
return null;
}
@Override
public ClientModel getClientByClientId(String clientId, RealmModel realm) {
if (clientId.equals(clientId)) return new ClientAdapter(realm);
return null;
}
@Override
public void close() {
}
public class ClientAdapter extends AbstractReadOnlyClientStorageAdapter {
public ClientAdapter(RealmModel realm) {
super(HardcodedClientStorageProvider.this.session, realm, HardcodedClientStorageProvider.this.component);
}
@Override
public String getClientId() {
return clientId;
}
@Override
public String getName() {
return "Federated Client";
}
@Override
public String getDescription() {
return "Pulled in from client storage provider";
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Set<String> getWebOrigins() {
return Collections.EMPTY_SET;
}
@Override
public Set<String> getRedirectUris() {
HashSet<String> set = new HashSet<>();
set.add(redirectUri);
return set;
}
@Override
public String getManagementUrl() {
return null;
}
@Override
public String getRootUrl() {
return null;
}
@Override
public String getBaseUrl() {
return null;
}
@Override
public boolean isBearerOnly() {
return false;
}
@Override
public int getNodeReRegistrationTimeout() {
return 0;
}
@Override
public String getClientAuthenticatorType() {
return null;
}
@Override
public boolean validateSecret(String secret) {
return "password".equals(secret);
}
@Override
public String getSecret() {
return "password";
}
@Override
public String getRegistrationToken() {
return null;
}
@Override
public String getProtocol() {
return null;
}
@Override
public String getAttribute(String name) {
return null;
}
@Override
public Map<String, String> getAttributes() {
return Collections.EMPTY_MAP;
}
@Override
public String getAuthenticationFlowBindingOverride(String binding) {
return null;
}
@Override
public Map<String, String> getAuthenticationFlowBindingOverrides() {
return Collections.EMPTY_MAP;
}
@Override
public boolean isFrontchannelLogout() {
return false;
}
@Override
public boolean isPublicClient() {
return false;
}
@Override
public boolean isConsentRequired() {
return false;
}
@Override
public boolean isStandardFlowEnabled() {
return true;
}
@Override
public boolean isImplicitFlowEnabled() {
return true;
}
@Override
public boolean isDirectAccessGrantsEnabled() {
return true;
}
@Override
public boolean isServiceAccountsEnabled() {
return false;
}
@Override
public ClientTemplateModel getClientTemplate() {
return null;
}
@Override
public boolean useTemplateScope() {
return false;
}
@Override
public boolean useTemplateMappers() {
return false;
}
@Override
public boolean useTemplateConfig() {
return false;
}
@Override
public int getNotBefore() {
return 0;
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
return Collections.EMPTY_SET;
}
@Override
public ProtocolMapperModel getProtocolMapperById(String id) {
return null;
}
@Override
public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) {
return null;
}
@Override
public boolean isFullScopeAllowed() {
return false;
}
@Override
public Set<RoleModel> getScopeMappings() {
return Collections.EMPTY_SET;
}
@Override
public Set<RoleModel> getRealmScopeMappings() {
return Collections.EMPTY_SET;
}
@Override
public boolean hasScope(RoleModel role) {
return false;
}
}
}

View file

@ -0,0 +1,74 @@
/*
* 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;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedClientStorageProviderFactory implements ClientStorageProviderFactory<HardcodedClientStorageProvider> {
@Override
public HardcodedClientStorageProvider create(KeycloakSession session, ComponentModel model) {
return new HardcodedClientStorageProvider(session, new ClientStorageProviderModel(model));
}
public static final String PROVIDER_ID = "hardcoded-client";
@Override
public String getId() {
return PROVIDER_ID;
}
protected static final List<ProviderConfigProperty> CONFIG_PROPERTIES;
public static final String CLIENT_ID = "client_id";
public static final String REDIRECT_URI = "redirect_uri";
static {
CONFIG_PROPERTIES = ProviderConfigurationBuilder.create()
.property().name(CLIENT_ID)
.type(ProviderConfigProperty.STRING_TYPE)
.label("Hardcoded Client Id")
.helpText("Only this client id is available for lookup")
.defaultValue("hardcoded-client")
.add()
.property().name(REDIRECT_URI)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.label("Redirect Uri")
.helpText("Valid redirect uri. Only one allowed")
.defaultValue("http://localhost:8180/*")
.add()
.build();
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
}

View file

@ -0,0 +1,214 @@
/*
* Copyright 2017 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.apache.commons.io.FileUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.events.Details;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowBindings;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
import org.keycloak.testsuite.federation.HardcodedClientStorageProviderFactory;
import org.keycloak.testsuite.federation.UserMapStorageFactory;
import org.keycloak.testsuite.federation.UserPropertyFileStorageFactory;
import org.keycloak.testsuite.forms.UsernameOnlyAuthenticator;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.BasicAuthHelper;
import org.openqa.selenium.By;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* Test that clients can override auth flows
*
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/
public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Page
protected ErrorPage errorPage;
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@Deployment
public static WebArchive deploy() {
return RunOnServerDeployment.create(UserResource.class)
.addPackages(true, "org.keycloak.testsuite");
}
protected String addComponent(ComponentRepresentation component) {
Response resp = adminClient.realm("test").components().add(component);
resp.close();
String id = ApiUtil.getCreatedId(resp);
getCleanup().addComponentId(id);
return id;
}
@Before
public void addProvidersBeforeTest() throws URISyntaxException, IOException {
ComponentRepresentation provider = new ComponentRepresentation();
provider.setName("client-storage-hardcoded");
provider.setProviderId(HardcodedClientStorageProviderFactory.PROVIDER_ID);
provider.setProviderType(ClientStorageProvider.class.getName());
provider.setConfig(new MultivaluedHashMap<>());
provider.getConfig().putSingle(HardcodedClientStorageProviderFactory.CLIENT_ID, "hardcoded-client");
provider.getConfig().putSingle(HardcodedClientStorageProviderFactory.REDIRECT_URI, oauth.getRedirectUri());
String providerId = addComponent(provider);
}
//@Test
public void testRunConsole() throws Exception {
Thread.sleep(10000000);
}
@Test
public void testBrowser() throws Exception {
String clientId = "hardcoded-client";
testBrowser(clientId);
}
private void testBrowser(String clientId) {
oauth.clientId(clientId);
String loginFormUrl = oauth.getLoginFormUrl();
log.info("loginFormUrl: " + loginFormUrl);
//Thread.sleep(10000000);
driver.navigate().to(loginFormUrl);
loginPage.assertCurrent();
// Fill username+password. I am successfully authenticated
oauth.fillLoginForm("test-user@localhost", "password");
appPage.assertCurrent();
events.expectLogin().client(clientId).detail(Details.USERNAME, "test-user@localhost").assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
Assert.assertNotNull(tokenResponse.getAccessToken());
Assert.assertNotNull(tokenResponse.getRefreshToken());
events.clear();
}
@Test
public void testGrantAccessTokenNoOverride() throws Exception {
testDirectGrant("hardcoded-client");
}
private void testDirectGrant(String clientId) {
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
String grantUri = oauth.getResourceOwnerPasswordCredentialGrantUrl();
WebTarget grantTarget = httpClient.target(grantUri);
{ // test no password
String header = BasicAuthHelper.createHeader(clientId, "password");
Form form = new Form();
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
form.param("username", "test-user@localhost");
Response response = grantTarget.request()
.header(HttpHeaders.AUTHORIZATION, header)
.post(Entity.form(form));
assertEquals(401, response.getStatus());
response.close();
}
{ // test invalid password
String header = BasicAuthHelper.createHeader(clientId, "password");
Form form = new Form();
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
form.param("username", "test-user@localhost");
form.param("password", "invalid");
Response response = grantTarget.request()
.header(HttpHeaders.AUTHORIZATION, header)
.post(Entity.form(form));
assertEquals(401, response.getStatus());
response.close();
}
{ // test valid password
String header = BasicAuthHelper.createHeader(clientId, "password");
Form form = new Form();
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
form.param("username", "test-user@localhost");
form.param("password", "password");
Response response = grantTarget.request()
.header(HttpHeaders.AUTHORIZATION, header)
.post(Entity.form(form));
assertEquals(200, response.getStatus());
response.close();
}
httpClient.close();
events.clear();
}
}

View file

@ -41,7 +41,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.UserStorageProvider;
import static org.keycloak.storage.UserStorageProviderModel.CACHE_POLICY;
import org.keycloak.storage.UserStorageProviderModel.CachePolicy;
import org.keycloak.storage.CacheableStorageProviderModel.CachePolicy;
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_DAY;
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_HOUR;
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_MINUTE;