/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.picketlink.idm.token.internal;
import org.picketlink.common.properties.Property;
import org.picketlink.common.properties.query.AnnotatedPropertyCriteria;
import org.picketlink.common.properties.query.NamedPropertyCriteria;
import org.picketlink.common.properties.query.PropertyQueries;
import org.picketlink.common.reflection.Reflections;
import org.picketlink.idm.IdentityManagementException;
import org.picketlink.idm.config.TokenStoreConfiguration;
import org.picketlink.idm.credential.Token;
import org.picketlink.idm.credential.TokenCredential;
import org.picketlink.idm.credential.handler.TokenCredentialHandler;
import org.picketlink.idm.credential.handler.annotations.CredentialHandlers;
import org.picketlink.idm.credential.storage.CredentialStorage;
import org.picketlink.idm.internal.AbstractIdentityStore;
import org.picketlink.idm.model.Account;
import org.picketlink.idm.model.AttributedType;
import org.picketlink.idm.model.IdentityType;
import org.picketlink.idm.model.Partition;
import org.picketlink.idm.model.Relationship;
import org.picketlink.idm.model.annotation.IdentityStereotype;
import org.picketlink.idm.model.annotation.RelationshipStereotype;
import org.picketlink.idm.model.annotation.StereotypeProperty;
import org.picketlink.idm.model.basic.Realm;
import org.picketlink.idm.query.AttributeParameter;
import org.picketlink.idm.query.Condition;
import org.picketlink.idm.query.IdentityQuery;
import org.picketlink.idm.query.QueryParameter;
import org.picketlink.idm.query.RelationshipQuery;
import org.picketlink.idm.query.RelationshipQueryParameter;
import org.picketlink.idm.query.internal.EqualCondition;
import org.picketlink.idm.spi.CredentialStore;
import org.picketlink.idm.spi.IdentityContext;
import org.picketlink.idm.spi.PartitionStore;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static org.picketlink.idm.IDMMessages.MESSAGES;
import static org.picketlink.idm.credential.Token.Consumer;
import static org.picketlink.idm.spi.IdentityContext.CREDENTIALS;
/**
* @author Pedro Igor
*/
@CredentialHandlers({
TokenCredentialHandler.class})
public class TokenIdentityStore extends AbstractIdentityStore<TokenStoreConfiguration>
implements CredentialStore<TokenStoreConfiguration>,
PartitionStore<TokenStoreConfiguration> {
private List<Consumer> tokenConsumers;
@Override
public void setup(TokenStoreConfiguration config) {
super.setup(config);
this.tokenConsumers = config.getTokenConsumer();
}
@Override
protected void removeFromRelationships(IdentityContext context, IdentityType identityType) {
}
@Override
protected void removeCredentials(IdentityContext context, Account account) {
}
@Override
protected void updateAttributedType(IdentityContext context, AttributedType attributedType) {
}
@Override
protected void removeAttributedType(IdentityContext context, AttributedType attributedType) {
}
@Override
public <V extends IdentityType> List<V> fetchQueryResults(IdentityContext context, IdentityQuery<V> query) {
List<V> identityTypes = new ArrayList<V>();
Class<V> identityTypeType = query.getIdentityType();
IdentityStereotype stereotype = identityTypeType.getAnnotation(IdentityStereotype.class);
if (stereotype == null) {
throw new IdentityManagementException("Type [" + identityTypeType + "] does not define a " + IdentityStereotype.class + ".");
}
Token currentToken = getCurrentToken(context);
IdentityType identityType = null;
for (Condition condition : query.getConditions()) {
QueryParameter queryParameter = condition.getParameter();
String queryParameterName = ((AttributeParameter) queryParameter).getName();
if (IdentityType.PARTITION.equals(queryParameter)) {
continue;
}
if (EqualCondition.class.isInstance(condition)) {
EqualCondition equalCondition = (EqualCondition) condition;
Object queryParameterValue = equalCondition.getValue();
if (queryParameterValue == null) {
throw new IdentityManagementException("Query parameter [" + queryParameterName + "] does not have any value.");
}
if (IdentityType.ID.equals(queryParameter)) {
identityType = getTokenConsumer(currentToken)
.extractIdentity(currentToken, identityTypeType, StereotypeProperty.Property.IDENTITY_ID, queryParameterValue);
} else {
Property<Object> mappedProperty = PropertyQueries
.createQuery(identityTypeType)
.addCriteria(new NamedPropertyCriteria(queryParameterName))
.getFirstResult();
if (mappedProperty == null) {
throw new IdentityManagementException("IdentityType [" + identityTypeType + "] does not have a property with name [" + queryParameterName + "].");
}
StereotypeProperty stereotypeProperty = mappedProperty.getAnnotatedElement()
.getAnnotation(StereotypeProperty.class);
if (stereotypeProperty == null) {
throw new IdentityManagementException("Query parameter [" + queryParameterName + "] does not maps to a " + StereotypeProperty.Property.class + ".");
}
identityType = getTokenConsumer(currentToken)
.extractIdentity(currentToken, identityTypeType, stereotypeProperty
.value(), queryParameterValue);
}
} else {
throw new IdentityManagementException("Unsupported query condition. Token store only understands equality condition.");
}
}
if (identityType != null) {
identityTypes.add((V) identityType);
}
return unmodifiableList(identityTypes);
}
@Override
public <V extends Relationship> List<V> fetchQueryResults(IdentityContext context, RelationshipQuery<V> query) {
ArrayList<V> relationships = new ArrayList<V>();
Class<V> relationshipType = query.getRelationshipClass();
RelationshipStereotype stereotype = relationshipType.getAnnotation(RelationshipStereotype.class);
if (stereotype == null) {
throw new IdentityManagementException("Type [" + relationshipType + "] does not define a " + RelationshipStereotype.class + ".");
}
V relationshipInstance = null;
for (QueryParameter queryParameter : query.getParameters().keySet()) {
String queryParameterName = ((RelationshipQueryParameter) queryParameter).getName();
Property<Object> nameProperty = PropertyQueries
.createQuery(relationshipType)
.addCriteria(new NamedPropertyCriteria(queryParameterName))
.getFirstResult();
if (nameProperty == null) {
throw new IdentityManagementException("Type [" + relationshipType + "] does not have a property with name [" + queryParameterName + "].");
}
StereotypeProperty stereotypeProperty = nameProperty.getAnnotatedElement().getAnnotation(StereotypeProperty.class);
if (stereotypeProperty == null) {
throw new IdentityManagementException("Query parameter [" + queryParameterName + "] does not maps to a " + StereotypeProperty.Property.class + " for type [" + relationshipType + ".");
}
Object[] queryParameterValues = query.getParameter(queryParameter);
if (queryParameterValues == null || queryParameterValues.length == 0) {
throw new IdentityManagementException("Query parameter [" + queryParameterName + "] does not have any value.");
} else if (queryParameterValues.length > 1) {
throw new IdentityManagementException("Query parameter [" + queryParameterName + "] value must be single-valued.");
}
Token currentToken = getCurrentToken(context);
if (currentToken != null) {
IdentityType identityType = resolveIdentityTypeFromToken(currentToken, queryParameterValues, stereotypeProperty);
if (identityType == null) {
return emptyList();
}
if (relationshipInstance == null) {
try {
relationshipInstance = Reflections.newInstance(relationshipType);
} catch (Exception e) {
throw new IdentityManagementException("Could not create Relationship type [" + relationshipType + "].");
}
}
Property property = resolveProperty(relationshipType, stereotypeProperty.value());
property.setValue(relationshipInstance, identityType);
}
}
if (relationshipInstance != null) {
relationships.add(relationshipInstance);
}
return unmodifiableList(relationships);
}
@Override
public void storeCredential(IdentityContext context, Account account, CredentialStorage storage) {
}
@Override
public <T extends CredentialStorage> T retrieveCurrentCredential(IdentityContext context, Account account, Class<T> storageClass) {
return null;
}
@Override
public <T extends CredentialStorage> List<T> retrieveCredentials(IdentityContext context, Account account, Class<T> storageClass) {
return emptyList();
}
@Override
public void removeCredential(IdentityContext identityContext, Account account, Class<? extends CredentialStorage> aClass) {
}
private IdentityType resolveIdentityTypeFromToken(Token currentToken, Object[] queryParameterValues, StereotypeProperty stereotypeProperty) {
IdentityType identityType;
try {
identityType = (IdentityType) queryParameterValues[0];
} catch (ClassCastException cce) {
throw new IdentityManagementException("Query parameter value is not an IdentityType instance.", cce);
}
if (identityType == null) {
throw new IdentityManagementException("Query parameter value can not be null.");
}
if (StereotypeProperty.Property.RELATIONSHIP_GRANT_ROLE.equals(stereotypeProperty.value())) {
return extractIdentityTypeFromToken(currentToken, identityType, StereotypeProperty.Property.IDENTITY_ROLE_NAME);
} else if (StereotypeProperty.Property.RELATIONSHIP_GRANT_ASSIGNEE.equals(stereotypeProperty.value())
|| StereotypeProperty.Property.RELATIONSHIP_GROUP_MEMBERSHIP_MEMBER.equals(stereotypeProperty.value())) {
return extractIdentityTypeFromToken(currentToken, identityType, StereotypeProperty.Property.IDENTITY_USER_NAME);
} else if (StereotypeProperty.Property.RELATIONSHIP_GROUP_MEMBERSHIP_GROUP.equals(stereotypeProperty.value())) {
return extractIdentityTypeFromToken(currentToken, identityType, StereotypeProperty.Property.IDENTITY_GROUP_NAME);
}
throw new IdentityManagementException("Could not resolve any IdentityType [" + identityType + "] from Token [" + currentToken + ".");
}
private IdentityType extractIdentityTypeFromToken(Token token, IdentityType identityType, StereotypeProperty.Property stereotypeProperty) {
Property mappedProperty = resolveProperty(identityType.getClass(), stereotypeProperty);
Object identifier = mappedProperty.getValue(identityType);
if (identifier == null) {
throw new IdentityManagementException("The IdentityType [" + identityType + "] does not have a value for property [" + mappedProperty
.getName() + "].");
}
return getTokenConsumer(token).extractIdentity(token, identityType.getClass(), stereotypeProperty, identifier);
}
private Consumer getTokenConsumer(Token token) {
for (Consumer consumer : this.tokenConsumers) {
if (consumer.getTokenType().isAssignableFrom(token.getClass())) {
return consumer;
}
}
throw MESSAGES.credentialNoConsumerForToken(token);
}
private Token getCurrentToken(IdentityContext context) {
TokenCredential tokenCredential = getAuthenticatedAccountCredentials(context);
return tokenCredential.getToken();
}
private TokenCredential getAuthenticatedAccountCredentials(IdentityContext context) {
TokenCredential tokenCredential;
try {
tokenCredential = context.getParameter(CREDENTIALS);
} catch (ClassCastException cce) {
throw new IdentityManagementException("ContextParameter [" + CREDENTIALS + " does not reference a TokenCredential type instance.");
}
if (tokenCredential == null) {
throw new IdentityManagementException("No TokenCredential found in the invocation context. Make sure you have a ContextInitializer which sets it.");
}
return tokenCredential;
}
/**
* <p>Resolves a {@link org.picketlink.common.properties.Property} from the given <code>type</code> mapped with a certain {@link
* org.picketlink.idm.model.annotation.StereotypeProperty.Property}.</p>
*
* @param type The type.
* @param stereotypeProperty The stereotype property to look for.
*
* @return
*
* @throws org.picketlink.idm.IdentityManagementException If no property exists in the given type for the given stereotype
* property.
*/
private Property resolveProperty(Class<?> type, StereotypeProperty.Property stereotypeProperty) throws IdentityManagementException {
List<Property<Object>> properties = PropertyQueries
.createQuery(type)
.addCriteria(new AnnotatedPropertyCriteria(StereotypeProperty.class))
.getResultList();
if (properties.isEmpty()) {
throw new IdentityManagementException("IdentityType [" + type + "] does not have any property mapped with " + StereotypeProperty.class + ".");
}
for (Property property : properties) {
StereotypeProperty propertyStereotypeProperty = property.getAnnotatedElement().getAnnotation(StereotypeProperty.class);
if (stereotypeProperty.equals(propertyStereotypeProperty.value())) {
return property;
}
}
throw new IdentityManagementException("Could not resolve property in type [" + type + " for StereotypeProperty [" + stereotypeProperty + ".");
}
@Override
public String getConfigurationName(IdentityContext identityContext, Partition partition) {
return null;
}
@Override
public <P extends Partition> P get(IdentityContext identityContext, Class<P> partitionClass, String name) {
return (P) new Realm(Realm.DEFAULT_REALM);
}
@Override
public <P extends Partition> List<P> get(IdentityContext identityContext, Class<P> partitionClass) {
ArrayList<P> partitions = new ArrayList<P>();
partitions.add((P) get(identityContext, Realm.class, Realm.DEFAULT_REALM));
return partitions;
}
@Override
public <P extends Partition> P lookupById(IdentityContext context, Class<P> partitionClass, String id) {
return null;
}
@Override
public void add(IdentityContext identityContext, Partition partition, String configurationName) {
}
@Override
public void update(IdentityContext identityContext, Partition partition) {
}
@Override
public void remove(IdentityContext identityContext, Partition partition) {
}
}