/**
* PODD is an OWL ontology database used for scientific project management
*
* Copyright (C) 2009-2013 The University Of Queensland
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.podd.restlet;
import info.aduna.iteration.Iterations;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.openrdf.OpenRDFException;
import org.openrdf.OpenRDFUtil;
import org.openrdf.model.Literal;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.impl.DatasetImpl;
import org.openrdf.query.resultio.helpers.QueryResultCollector;
import org.openrdf.queryrender.RenderUtils;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ntriples.NTriplesUtil;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ClientInfo;
import org.restlet.data.Status;
import org.restlet.engine.security.RoleMapping;
import org.restlet.resource.ResourceException;
import org.restlet.security.Enroler;
import org.restlet.security.Group;
import org.restlet.security.LocalVerifier;
import org.restlet.security.Realm;
import org.restlet.security.Role;
import org.restlet.security.User;
import org.restlet.security.Verifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.ansell.restletutils.RestletUtilRole;
import com.github.ansell.restletutils.SesameRealmConstants;
import com.github.podd.exception.PoddRuntimeException;
import com.github.podd.utils.PODD;
import com.github.podd.utils.PasswordHash;
import com.github.podd.utils.PoddRoles;
import com.github.podd.utils.PoddUser;
import com.github.podd.utils.PoddUserStatus;
import com.github.podd.utils.RdfUtility;
public class PoddSesameRealm extends Realm
{
// ======================= begin inner classes ==========================
/**
* Enroler class based on the default security model.
*/
private class DefaultPoddSesameRealmEnroler implements Enroler
{
@Override
public void enrole(final ClientInfo clientInfo)
{
RepositoryConnection conn = null;
try
{
conn = PoddSesameRealm.this.repository.getConnection();
final PoddUser user = PoddSesameRealm.this.findUser(clientInfo.getUser().getIdentifier(), conn);
if(user != null)
{
// Add roles specific to this user
final Set<Role> userRoles = PoddSesameRealm.this.findRoles(user, conn);
for(final Role role : userRoles)
{
clientInfo.getRoles().add(role);
}
// FIXME: When we support groups, reenable this section
// Find all the inherited groups of this user
// final Set<Group> userGroups = PoddSesameRealm.this.findGroups(user);
// Add roles common to group members
// final Set<Role> groupRoles = PoddSesameRealm.this.findRoles(userGroups);
// for(final Role role : groupRoles)
// {
// clientInfo.getRoles().add(role);
// }
}
}
catch(final OpenRDFException e)
{
PoddSesameRealm.this.log.error("Found exception while finding roles for user", e);
throw new RuntimeException("Found exception while finding roles for user", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
PoddSesameRealm.this.log.error("Found exception closing repository connection", e);
}
}
}
}
}
/**
* Verifier class based on the default security model. It looks up users in the mapped
* organizations.
*/
private class DefaultPoddSesameRealmVerifier extends LocalVerifier
{
@Override
protected User createUser(final String identifier, final Request request, final Response response)
{
final PoddUser checkUser = PoddSesameRealm.this.findUser(identifier);
if(checkUser == null)
{
PoddSesameRealm.this.log.error("Cannot create a user for the given identifier: {}", identifier);
throw new IllegalArgumentException("Cannot create a user for the given identifier");
}
else if(checkUser.getUserStatus() == PoddUserStatus.INACTIVE)
{
throw new PoddRuntimeException("Could not login as user is inactive");
}
final PoddUser result =
new PoddUser(identifier, (char[])null, checkUser.getFirstName(), checkUser.getLastName(),
checkUser.getEmail(), checkUser.getUserStatus(), checkUser.getHomePage(),
checkUser.getOrganization(), checkUser.getOrcid(), checkUser.getTitle(),
checkUser.getPhone(), checkUser.getAddress(), checkUser.getPosition());
return result;
}
@Override
public char[] getLocalSecret(final String identifier)
{
throw new PoddRuntimeException("This method should never be called");
}
/**
* This replaces the default implementation in LocalVerifier with an implementation that
* transparently uses a hash for comparison
*/
@Override
public int verify(final String identifier, final char[] secret)
{
try
{
final PoddUserSecretHash secretHash = PoddSesameRealm.this.getUserSecretHash(identifier);
return secretHash.compare(secret) ? Verifier.RESULT_VALID : Verifier.RESULT_INVALID;
}
catch(OpenRDFException | NoSuchAlgorithmException | InvalidKeySpecException e)
{
throw new PoddRuntimeException("Could not verify user identity", e);
}
}
}
protected static final String PARAM_USER_URI = "userUri";
protected static final String PARAM_USER_SECRET = "userSecret";
protected static final String PARAM_USER_FIRSTNAME = "userFirstName";
protected static final String PARAM_USER_LASTNAME = "userLastName";
protected static final String PARAM_USER_EMAIL = "userEmail";
protected static final String PARAM_USER_STATUS = "userStatus";
protected static final String PARAM_USER_HOMEPAGE = "userHomePage";
protected static final String PARAM_USER_IDENTIFIER = "userIdentifier";
protected static final String PARAM_USER_ORCID = "userOrcid";
protected static final String PARAM_USER_ORGANIZATION = "userOrganization";
protected static final String PARAM_USER_TITLE = "userTitle";
protected static final String PARAM_USER_PHONE = "userPhone";
protected static final String PARAM_USER_ADDRESS = "userAddress";
protected static final String PARAM_USER_POSITION = "userPosition";
protected static final String PARAM_ROLE = "role";
protected static final String PARAM_OBJECT_URI = "objectUri";
protected static final String PARAM_SEARCH_TERM = "searchTerm";
/**
* The Sesame Repository to use to get access to user information.
*/
private Repository repository;
private URI[] userManagerContexts;
protected ValueFactory vf;
/** The currently cached list of root groups. */
private volatile List<Group> cachedRootGroups;
protected final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* Constructor.
*/
public PoddSesameRealm(final Repository repository, final URI... contexts)
{
OpenRDFUtil.verifyContextNotNull(contexts);
this.setRepository(repository);
this.setContexts(contexts);
// set PODD-specific Enroler and Verifier
this.setEnroler(new DefaultPoddSesameRealmEnroler());
this.setVerifier(new DefaultPoddSesameRealmVerifier());
// this.cachedRootGroups = new CopyOnWriteArrayList<Group>();
// this.rootGroups = new CopyOnWriteArrayList<Group>();
// this.roleMappings = new CopyOnWriteArrayList<RoleMapping>();
// this.users = new CopyOnWriteArrayList<User>();
}
public PoddUserSecretHash getUserSecretHash(final String identifier) throws OpenRDFException
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
final PoddUser findUser = this.findUser(identifier, conn);
if(findUser == null)
{
throw new PoddRuntimeException("No user found for the given identifier: " + identifier);
}
final List<Statement> hashList =
Iterations.asList(conn.getStatements(findUser.getUri(), PODD.PODD_USER_SECRET_HASH, null, false,
this.userManagerContexts));
if(hashList.isEmpty() || hashList.size() > 1)
{
throw new PoddRuntimeException("Could not verify user identity: " + identifier);
}
return new PoddUserSecretHash(((Literal)hashList.get(0).getObject()).getLabel(), findUser);
}
finally
{
if(conn != null)
{
conn.close();
}
}
}
/**
* Recursively adds groups where a given user is a member.
*
* @param user
* The member user.
* @param userGroups
* The set of user groups to update.
* @param currentGroup
* The current group to inspect.
* @param stack
* The stack of ancestor groups.
* @param inheritOnly
* Indicates if only the ancestors groups that have their "inheritRoles" property
* enabled should be added.
*/
private void addGroupsForUser(final PoddUser user, final Set<Group> userGroups, final Group currentGroup,
final Set<Group> stack, final boolean inheritOnly)
{
if((currentGroup != null) && !stack.contains(currentGroup))
{
stack.add(currentGroup);
if(currentGroup.getMemberUsers().contains(user))
{
userGroups.add(currentGroup);
// Add the ancestor groups as well
boolean inherit = !inheritOnly || currentGroup.isInheritingRoles();
if(inherit)
{
for(final Group group : stack)
{
userGroups.add(group);
inherit = !inheritOnly || group.isInheritingRoles();
}
}
}
for(final Group group : currentGroup.getMemberGroups())
{
this.addGroupsForUser(user, userGroups, group, stack, inheritOnly);
}
}
}
private void addRoleMapping(final RoleMapping nextMapping) throws RepositoryException
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
conn.begin();
final URI nextRoleMappingUUID = this.vf.createURI("urn:oas:rolemapping:", UUID.randomUUID().toString());
conn.add(this.vf.createStatement(nextRoleMappingUUID, RDF.TYPE, SesameRealmConstants.OAS_ROLEMAPPING),
this.getContexts());
conn.add(this.vf.createStatement(nextRoleMappingUUID, SesameRealmConstants.OAS_ROLEMAPPEDROLE, this
.getRoleByName(nextMapping.getTarget().getName()).getURI()), this.getContexts());
if(nextMapping.getSource() instanceof Group)
{
conn.add(
this.vf.createStatement(nextRoleMappingUUID, SesameRealmConstants.OAS_ROLEMAPPEDGROUP,
this.vf.createLiteral(((Group)nextMapping.getSource()).getName())), this.getContexts());
}
else if(nextMapping.getSource() instanceof User)
{
conn.add(
this.vf.createStatement(nextRoleMappingUUID, SesameRealmConstants.OAS_ROLEMAPPEDUSER,
this.vf.createLiteral(((User)nextMapping.getSource()).getIdentifier())),
this.getContexts());
}
else
{
conn.rollback();
throw new RuntimeException("Could not map role for unknown source type: "
+ nextMapping.getSource().getClass().getName());
}
conn.commit();
}
catch(final RepositoryException e)
{
this.log.error("Found exception while adding role mapping", e);
if(conn != null)
{
conn.rollback();
}
throw e;
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found exception closing repository connection", e);
}
}
}
}
/**
* Adds a fully populated root group to the underlying repository, including a statement
* indicating that this group is a root group.
*
* @param nextRootGroup
*/
public void addRootGroup(final Group nextRootGroup)
{
this.getRootGroups().add(nextRootGroup);
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
conn.begin();
this.storeGroup(nextRootGroup, conn, true);
conn.commit();
}
catch(final OpenRDFException e)
{
this.log.error("Found exception while storing root group", e);
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Found exception while trying to roll back connection", e1);
}
}
throw new RuntimeException("Found exception while storing root group", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found exception closing repository connection", e);
}
}
}
}
public final URI addUser(final PoddUser nextUser)
{
try
{
return this.addUser(nextUser, true);
}
catch(NoSuchAlgorithmException | InvalidKeySpecException | OpenRDFException e)
{
throw new PoddRuntimeException("Could not add user", e);
}
}
protected URI addUser(final PoddUser nextUser, final boolean isNew) throws NoSuchAlgorithmException,
InvalidKeySpecException, OpenRDFException
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
conn.begin();
final PoddUser oldUser = this.findUser(nextUser.getIdentifier(), conn);
if(isNew && oldUser != null)
{
throw new IllegalStateException("User already exists");
}
else if(!isNew && oldUser == null)
{
throw new IllegalStateException("Could not modify User (does not exist)");
}
URI nextUserUUID;
if(oldUser != null)
{
nextUserUUID = oldUser.getUri();
}
else
{
nextUserUUID =
this.vf.createURI("urn:oas:user:", nextUser.getIdentifier() + ":"
+ UUID.randomUUID().toString());
}
// FIXME: Optimise the following to require less queries
final List<Statement> userIdentifierStatements =
Iterations.asList(conn.getStatements(null, SesameRealmConstants.OAS_USERIDENTIFIER,
this.vf.createLiteral(nextUser.getIdentifier()), true, this.getContexts()));
if(!userIdentifierStatements.isEmpty())
{
for(final Statement nextUserIdentifierStatement : userIdentifierStatements)
{
if(nextUserIdentifierStatement.getSubject() instanceof URI)
{
// retrieve the user URI to persist it with the new statements
// does not matter if this is overwritten multiple times if there were
// multiple users with this identifier in the database
nextUserUUID = (URI)nextUserIdentifierStatement.getSubject();
}
}
}
String existingHash = null;
if(conn.hasStatement(nextUserUUID, PODD.PODD_USER_SECRET_HASH, null, false, this.getContexts()))
{
final List<Statement> hashList =
Iterations.asList(conn.getStatements(nextUserUUID, PODD.PODD_USER_SECRET_HASH, null, false,
this.getContexts()));
if(hashList.isEmpty())
{
// Should not happen in normal circumstances as we are wrapping with
// conn.hasStatement
throw new PoddRuntimeException("Inconsistent database state detected");
}
else if(hashList.size() > 1)
{
// Cannot detect a unique hash in database for user, if they are updating, they
// will now need to provide a new hash
throw new PoddRuntimeException("Inconsistent database state detected");
}
existingHash = ((Literal)hashList.get(0).getObject()).getLabel();
}
// remove all of the previously known statements
conn.remove(nextUserUUID, null, null, this.getContexts());
conn.add(nextUserUUID, RDF.TYPE, SesameRealmConstants.OAS_USER, this.getContexts());
conn.add(nextUserUUID, SesameRealmConstants.OAS_USERIDENTIFIER,
this.vf.createLiteral(nextUser.getIdentifier()), this.getContexts());
if(existingHash == null && nextUser.getSecret() == null)
{
throw new PoddRuntimeException("Must provide a password for user");
}
String createHash = null;
if(nextUser.getSecret() != null)
{
createHash = PasswordHash.createHash(nextUser.getSecret());
}
else
{
createHash = existingHash;
}
conn.add(nextUserUUID, PODD.PODD_USER_SECRET_HASH, this.vf.createLiteral(createHash), this.getContexts());
if(nextUser.getFirstName() != null)
{
conn.add(nextUserUUID, SesameRealmConstants.OAS_USERFIRSTNAME,
this.vf.createLiteral(nextUser.getFirstName()), this.getContexts());
}
if(nextUser.getLastName() != null)
{
conn.add(nextUserUUID, SesameRealmConstants.OAS_USERLASTNAME,
this.vf.createLiteral(nextUser.getLastName()), this.getContexts());
}
if(nextUser.getEmail() != null)
{
conn.add(nextUserUUID, SesameRealmConstants.OAS_USEREMAIL, this.vf.createLiteral(nextUser.getEmail()),
this.getContexts());
}
if(nextUser.getOrganization() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_ORGANIZATION, this.vf.createLiteral(nextUser.getOrganization()),
this.getContexts());
}
if(nextUser.getOrcid() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_ORCID, this.vf.createLiteral(nextUser.getOrcid()),
this.getContexts());
}
if(nextUser.getHomePage() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_HOMEPAGE, nextUser.getHomePage(), this.getContexts());
}
if(nextUser.getTitle() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_TITLE, this.vf.createLiteral(nextUser.getTitle()),
this.getContexts());
}
if(nextUser.getPhone() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_PHONE, this.vf.createLiteral(nextUser.getPhone()),
this.getContexts());
}
if(nextUser.getAddress() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_ADDRESS, this.vf.createLiteral(nextUser.getAddress()),
this.getContexts());
}
if(nextUser.getPosition() != null)
{
conn.add(nextUserUUID, PODD.PODD_USER_POSITION, this.vf.createLiteral(nextUser.getPosition()),
this.getContexts());
}
PoddUserStatus status = PoddUserStatus.INACTIVE;
if(nextUser.getUserStatus() != null)
{
status = nextUser.getUserStatus();
}
conn.add(nextUserUUID, PODD.PODD_USER_STATUS, status.getURI(), this.getContexts());
conn.commit();
return nextUserUUID;
}
catch(final Throwable e)
{
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Found unexpected exception while rolling back repository connection after exception");
}
}
throw e;
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found unexpected repository exception", e);
}
}
}
}
protected PoddUser buildRestletUserFromSparqlResult(final String userIdentifier, final BindingSet bindingSet)
{
this.log.debug("Building PoddUser from SPARQL results");
final PoddUser result =
new PoddUser(userIdentifier, null, bindingSet.getValue(PoddSesameRealm.PARAM_USER_FIRSTNAME)
.stringValue(), bindingSet.getValue(PoddSesameRealm.PARAM_USER_LASTNAME).stringValue(),
bindingSet.getValue(PoddSesameRealm.PARAM_USER_EMAIL).stringValue(), PoddUserStatus.INACTIVE);
PoddUserStatus userStatus = PoddUserStatus.INACTIVE;
final Value statusVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_STATUS);
if(statusVal != null && statusVal instanceof URI)
{
userStatus = PoddUserStatus.getUserStatusByUri((URI)statusVal);
}
char[] secret = null;
if(bindingSet.hasBinding(PoddSesameRealm.PARAM_USER_SECRET))
{
secret = bindingSet.getValue(PoddSesameRealm.PARAM_USER_SECRET).stringValue().trim().toCharArray();
}
// Do not allow users without secrets to perform actions
if(secret == null || secret.length == 0)
{
userStatus = PoddUserStatus.INACTIVE;
}
result.setUserStatus(userStatus);
final Value organizationVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_ORGANIZATION);
if(organizationVal != null)
{
result.setOrganization(organizationVal.stringValue());
}
final Value orcidVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_ORCID);
if(orcidVal != null)
{
result.setOrcid(orcidVal.stringValue());
}
final Value homePageVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_HOMEPAGE);
if(homePageVal != null)
{
result.setHomePage((URI)homePageVal);
}
final Value uriVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_URI);
if(uriVal != null)
{
result.setUri((URI)uriVal);
}
final Value titleVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_TITLE);
if(titleVal != null)
{
result.setTitle(titleVal.stringValue());
}
final Value phoneVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_PHONE);
if(phoneVal != null)
{
result.setPhone(phoneVal.stringValue());
}
final Value addressVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_ADDRESS);
if(addressVal != null)
{
result.setAddress(addressVal.stringValue());
}
final Value positionVal = bindingSet.getValue(PoddSesameRealm.PARAM_USER_POSITION);
if(positionVal != null)
{
result.setPosition(positionVal.stringValue());
}
return result;
}
protected Role buildRoleFromSparqlResult(final BindingSet bindingSet)
{
final URI roleUri = (URI)bindingSet.getValue(PoddSesameRealm.PARAM_ROLE);
return PoddRoles.getRoleByUri(roleUri).getRole();
}
protected String buildSparqlQueryForObjectRoles(final String userIdentifier, final URI objectUri)
{
this.log.debug("Building SPARQL query for Roles between User and object URI");
final StringBuilder query = new StringBuilder();
query.append(" SELECT DISTINCT ?");
query.append(PoddSesameRealm.PARAM_ROLE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" WHERE ");
query.append(" { ");
final String roleMappingVar = " ?mapping ";
query.append(roleMappingVar);
query.append(RenderUtils.getSPARQLQueryString(SesameRealmConstants.OAS_ROLEMAPPEDUSER));
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" . ");
query.append(roleMappingVar);
query.append(RenderUtils.getSPARQLQueryString(SesameRealmConstants.OAS_ROLEMAPPEDROLE));
query.append(" ?");
query.append(PoddSesameRealm.PARAM_ROLE);
query.append(" . ");
query.append(roleMappingVar);
query.append(RenderUtils.getSPARQLQueryString(PODD.PODD_ROLEMAPPEDOBJECT));
query.append(" ?object . ");
if(userIdentifier != null)
{
query.append(" FILTER ( ?userIdentifier IN (");
query.append("\"" + RenderUtils.escape(userIdentifier) + "\"");
query.append(") ) ");
}
query.append(" FILTER ( ?object IN (");
query.append(RenderUtils.getSPARQLQueryString(objectUri));
query.append(") ) ");
query.append(" } ");
this.log.debug("roles query: {}", query);
return query.toString();
}
protected String buildSparqlQueryForRolesWithObjects(final String userIdentifier)
{
this.log.debug("Building SPARQL query for Roles and object URIs of a User");
final StringBuilder query = new StringBuilder();
query.append(" SELECT ?");
query.append(PoddSesameRealm.PARAM_ROLE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_OBJECT_URI);
query.append(" WHERE ");
query.append(" { ");
final String roleMappingVar = " ?mapping ";
query.append(roleMappingVar);
query.append(" <" + SesameRealmConstants.OAS_ROLEMAPPEDUSER + "> ");
query.append(" \"");
query.append(RenderUtils.escape(userIdentifier));
query.append("\" . ");
query.append(roleMappingVar);
query.append(" <" + SesameRealmConstants.OAS_ROLEMAPPEDROLE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_ROLE);
query.append(" . ");
query.append(" OPTIONAL{ ");
query.append(roleMappingVar);
query.append(" <" + PODD.PODD_ROLEMAPPEDOBJECT + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_OBJECT_URI);
query.append(" . } ");
query.append(" } ");
this.log.debug("roles query: {}", query);
return query.toString();
}
protected String buildSparqlQueryToFindUser(final String userIdentifier, final boolean findAllUsers)
{
this.log.debug("Building SPARQL query");
final StringBuilder query = new StringBuilder();
query.append(" SELECT ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_SECRET);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_STATUS);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORCID);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_HOMEPAGE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_TITLE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_PHONE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ADDRESS);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_POSITION);
query.append(" WHERE ");
query.append(" { ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" a <" + SesameRealmConstants.OAS_USER + "> . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERIDENTIFIER + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_STATUS + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_STATUS);
query.append(" . ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_SECRET_HASH + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_SECRET);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ORCID + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORCID);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_HOMEPAGE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_HOMEPAGE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ORGANIZATION + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERFIRSTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERLASTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USEREMAIL + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_TITLE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_TITLE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_PHONE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_PHONE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ADDRESS + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ADDRESS);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_POSITION + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_POSITION);
query.append(" . } ");
if(!findAllUsers)
{
query.append(" FILTER(str(?userIdentifier) = \"" + RenderUtils.escape(userIdentifier) + "\") ");
}
query.append(" } ");
final String queryString = query.toString();
this.log.debug("buildSparqlQueryToFindUser: query={}", queryString);
return queryString;
}
protected String buildSparqlQueryToGetUserByStatus(final PoddUserStatus status, final String orderByField,
final boolean isDescending, final int limit, final int offset)
{
this.log.debug("Building SPARQL query");
final StringBuilder query = new StringBuilder();
query.append(" SELECT ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_SECRET);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_STATUS);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORCID);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_HOMEPAGE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_TITLE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_PHONE);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ADDRESS);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_POSITION);
query.append(" WHERE ");
query.append(" { ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" a <" + SesameRealmConstants.OAS_USER + "> . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERIDENTIFIER + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_SECRET_HASH + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_SECRET);
query.append(" . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_STATUS + "> ");
query.append(" <" + status.getURI() + "> ");
query.append(" . ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_HOMEPAGE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_HOMEPAGE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ORGANIZATION + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERFIRSTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERLASTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USEREMAIL + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_TITLE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_TITLE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_PHONE + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_PHONE);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ADDRESS + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ADDRESS);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_POSITION + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_POSITION);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ORCID + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORCID);
query.append(" . } ");
query.append(" } ");
if(isDescending)
{
query.append(" ORDER BY DESC(" + orderByField + ") ");
}
else
{
query.append(" ORDER BY " + orderByField);
}
query.append(" LIMIT " + limit);
query.append(" OFFSET " + offset);
return query.toString();
}
protected String buildSparqlQueryToSearchUsers(final PoddUserStatus status, final String orderByField,
final boolean isDescending, final int limit, final int offset)
{
this.log.debug("Building SPARQL query");
final StringBuilder query = new StringBuilder();
query.append(" SELECT ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_SECRET);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_STATUS);
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" WHERE ");
query.append(" { ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" a <" + SesameRealmConstants.OAS_USER + "> . ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERIDENTIFIER + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" . ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_ORGANIZATION + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_ORGANIZATION);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERFIRSTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USERLASTNAME + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" . } ");
query.append(" OPTIONAL{ ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + SesameRealmConstants.OAS_USEREMAIL + "> ");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_EMAIL);
query.append(" . } ");
// return a dummy password
query.append(" VALUES ?" + PoddSesameRealm.PARAM_USER_SECRET + " { \"not_available\" } . ");
// filter by Status if provided
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_URI);
query.append(" <" + PODD.PODD_USER_STATUS + "> ");
if(status == null)
{
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_STATUS);
}
else
{
query.append(" <" + status.getURI() + "> ");
}
query.append(" . ");
// concatenate identifier, first and last names for searching
query.append(" BIND(CONCAT(");
query.append(" ?");
query.append(PoddSesameRealm.PARAM_USER_FIRSTNAME);
query.append(" , \" \" , ?");
query.append(PoddSesameRealm.PARAM_USER_LASTNAME);
query.append(" , \" \" , ?");
query.append(PoddSesameRealm.PARAM_USER_IDENTIFIER);
query.append(" ) AS ?name)");
// filter for "searchTerm" in ?name
query.append(" FILTER( ");
query.append(" CONTAINS( LCASE(?name) , LCASE(?" + PoddSesameRealm.PARAM_SEARCH_TERM + ") ) ");
query.append(") ");
query.append(" } ");
if(isDescending)
{
query.append(" ORDER BY DESC(" + orderByField + ") ");
}
else
{
query.append(" ORDER BY " + orderByField);
}
if(limit > -1)
{
query.append(" LIMIT " + limit);
}
if(offset > 0)
{
query.append(" OFFSET " + offset);
}
return query.toString();
}
private Group createGroupForStatements(final Iterable<Statement> nextGroupStatements)
{
final Group nextGroup = new Group();
for(final Statement nextStatement : nextGroupStatements)
{
if(nextStatement.getPredicate().equals(SesameRealmConstants.OAS_GROUPNAME))
{
nextGroup.setName(nextStatement.getObject().stringValue());
}
else if(nextStatement.getPredicate().equals(SesameRealmConstants.OAS_GROUPDESCRIPTION))
{
nextGroup.setDescription(nextStatement.getObject().stringValue());
}
else if(nextStatement.getPredicate().equals(SesameRealmConstants.OAS_GROUPINHERITINGROLES))
{
nextGroup.setInheritingRoles(((Literal)nextStatement.getObject()).booleanValue());
}
else if(nextStatement.getPredicate().equals(SesameRealmConstants.OAS_GROUPMEMBERUSER))
{
nextGroup.getMemberUsers().add(this.findUser(nextStatement.getObject().stringValue()));
}
else if(nextStatement.getPredicate().equals(RDF.TYPE)
&& (nextStatement.getObject().equals(SesameRealmConstants.OAS_GROUP) || nextStatement.getObject()
.equals(SesameRealmConstants.OAS_ROOTGROUP)))
{
this.log.trace("Found rdf type statement for group: {}", nextStatement);
}
else
{
this.log.debug("Found unrecognised statement parsing group: {}", nextStatement);
}
}
return nextGroup;
}
private Group createGroupHierarchy(final Group parentGroup, final RepositoryConnection conn, final URI nextGroupUri)
{
try
{
// get the statements for the nextGroupUri
final Model nextRootGroupStatements =
new LinkedHashModel(Iterations.asList(conn.getStatements(nextGroupUri, null, null, true,
this.getContexts())));
// create the group
final Group newGroup = this.createGroupForStatements(nextRootGroupStatements);
if(parentGroup != null)
{
// add the group as a member group for the parent group
parentGroup.getMemberGroups().add(newGroup);
}
// check if there are any member groups for this item
if(conn.hasStatement(nextGroupUri, SesameRealmConstants.OAS_GROUPMEMBERGROUP, null, true,
this.getContexts()))
{
final List<Statement> nextMemberGroupStatements =
Iterations.asList(conn.getStatements(nextGroupUri, SesameRealmConstants.OAS_GROUPMEMBERGROUP,
null, true, this.getContexts()));
for(final Statement nextMemberGroupStatement : nextMemberGroupStatements)
{
if(nextMemberGroupStatement.getObject() instanceof URI)
{
// FIXME: Need to do cycle checking here to avoid infinite loops
// recursively call addGroup to add children to newGroup
this.createGroupHierarchy(newGroup, conn, (URI)nextMemberGroupStatement.getObject());
}
else
{
this.log.error("Found member group reference that was not a URI: {}", nextMemberGroupStatement);
}
}
}
return newGroup;
}
catch(final RepositoryException e)
{
this.log.error("Found error trying to examine member groups", e);
throw new RuntimeException(e);
}
}
public URI deleteUser(final User nextUser)
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
conn.begin();
URI nextUserUUID = null;
final PoddUser findUser = this.findUser(nextUser.getIdentifier(), conn);
if(findUser == null)
{
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "No such user found");
}
final List<Statement> userIdentifierStatements =
Iterations.asList(conn.getStatements(null, SesameRealmConstants.OAS_USERIDENTIFIER,
this.vf.createLiteral(nextUser.getIdentifier()), true, this.getContexts()));
if(!userIdentifierStatements.isEmpty())
{
for(final Statement nextUserIdentifierStatement : userIdentifierStatements)
{
if(nextUserIdentifierStatement.getSubject() instanceof URI)
{
// retrieve the user URI to persist it with the new statements
// does not matter if this is overwritten multiple times if there were
// multiple users with this identifier in the database
nextUserUUID = (URI)nextUserIdentifierStatement.getSubject();
}
final List<Statement> currentUserStatements =
Iterations.asList(conn.getStatements(nextUserIdentifierStatement.getSubject(), null, null,
true, this.getContexts()));
// remove all of the previously known statements
conn.remove(currentUserStatements, this.getContexts());
}
}
conn.commit();
return nextUserUUID;
}
catch(final OpenRDFException e)
{
this.log.error("Found repository exception while adding user", e);
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Found unexpected exception while rolling back repository connection after exception");
}
}
throw new RuntimeException("Found repository exception while adding user", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found unexpected repository exception", e);
}
}
}
}
/**
* Finds the set of groups where a given user is a member. Note that inheritable ancestors
* groups are also returned.
*
* @param user
* The member user.
* @return The set of groups.
*/
public Set<Group> findGroups(final PoddUser user)
{
return this.findGroups(user, true);
}
/**
* Finds the set of groups where a given user is a member.
*
* @param user
* The member user.
* @param inheritOnly
* Indicates if only the ancestors groups that have their "inheritRoles" property
* enabled should be added.
* @return The set of groups.
*/
public Set<Group> findGroups(final PoddUser user, final boolean inheritOnly)
{
final Set<Group> result = new HashSet<Group>();
Set<Group> stack;
// Recursively find user groups
for(final Group group : this.getRootGroups())
{
stack = new LinkedHashSet<Group>();
this.addGroupsForUser(user, result, group, stack, inheritOnly);
}
return result;
}
/**
* Finds the roles mapped to given user group.
*
* @param userGroup
* The user group.
* @return The roles found.
*/
public Set<Role> findRoles(final Group userGroup)
{
final Set<Role> result = new HashSet<Role>();
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
for(final RoleMapping mapping : this.getRoleMappings(conn))
{
final Object source = mapping.getSource();
if((userGroup != null) && userGroup.equals(source))
{
result.add(mapping.getTarget());
}
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding roles for group in repository", e);
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
return result;
}
/**
* Finds the roles mapped to given user groups.
*
* @param userGroups
* The user groups.
* @return The roles found.
*/
public Set<Role> findRoles(final Set<Group> userGroups)
{
final Set<Role> result = new HashSet<Role>();
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
for(final RoleMapping mapping : this.getRoleMappings(conn))
{
final Object source = mapping.getSource();
if((userGroups != null) && userGroups.contains(source))
{
result.add(mapping.getTarget());
}
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding roles for groups in repository", e);
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
return result;
}
/**
* Finds the roles mapped to a given user.
*
* @param user
* The user.
* @return The roles found.
*/
public Set<Role> findRoles(final User user)
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
return this.findRoles(user, conn);
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding roles for user in repository", e);
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
protected Set<Role> findRoles(final User user, final RepositoryConnection conn) throws OpenRDFException
{
final Set<Role> result = new HashSet<Role>();
final Collection<Entry<Role, URI>> mappings = this.getRolesWithObjectMappings(user);
for(final Entry<Role, URI> nextMapping : mappings)
{
result.add(nextMapping.getKey());
}
return result;
}
/**
* Finds a user in the organization based on its identifier.
*
* @param userIdentifier
* The identifier to match.
* @return The matched user or null.
*/
public PoddUser findUser(final String userIdentifier)
{
if(userIdentifier == null)
{
throw new NullPointerException("User identifier was null");
}
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
return this.findUser(userIdentifier, conn);
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
protected PoddUser findUser(final String userIdentifier, final RepositoryConnection conn) throws OpenRDFException
{
PoddUser result = null;
final String query = this.buildSparqlQueryToFindUser(userIdentifier, false);
this.log.debug("findUser: query={}", query);
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
if(!resultCollector.getHandledTuple() || resultCollector.getBindingSets().isEmpty())
{
this.log.info("Could not find user with identifier, returning null: {}", userIdentifier);
}
else
{
if(resultCollector.getBindingSets().size() > 1)
{
this.log.warn("Found multiple users with the same identifier: {}", resultCollector.getBindingSets());
}
result = this.buildRestletUserFromSparqlResult(userIdentifier, resultCollector.getBindingSets().get(0));
}
return result;
}
protected URI[] getContexts()
{
return this.userManagerContexts;
}
public Repository getRepository()
{
return this.repository;
}
/**
* @param role
* @return
*/
protected RestletUtilRole getRoleByName(final String name)
{
final RestletUtilRole oasRole = PoddRoles.getRoleByName(name);
return oasRole;
}
/**
* @param uri
* @return
*/
protected RestletUtilRole getRoleByUri(final URI uri)
{
final RestletUtilRole nextStandardRole = PoddRoles.getRoleByUri(uri);
return nextStandardRole;
}
protected List<RoleMapping> getRoleMappings()
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
return this.getRoleMappings(conn);
}
catch(final OpenRDFException e)
{
this.log.error("Found exception while retrieving role mappings", e);
throw new RuntimeException("Found exception while retrieving role mappings", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found exception closing repository connection", e);
}
}
}
}
private List<RoleMapping> getRoleMappings(final RepositoryConnection conn) throws OpenRDFException
{
final List<RoleMapping> results = new ArrayList<RoleMapping>();
final Map<String, PoddUser> usersMapByIdentifier = this.getUsersMapByIdentifier(conn);
final RepositoryResult<Statement> typeStatements =
conn.getStatements(null, RDF.TYPE, SesameRealmConstants.OAS_ROLEMAPPING, true, this.getContexts());
try
{
// We iterate through this gradually to reduce the load as the size of this
// collection will grow with users
while(typeStatements.hasNext())
{
final Statement next = typeStatements.next();
if(next.getSubject() instanceof URI)
{
final URI nextRoleMappingUri = (URI)next.getSubject();
final RoleMapping nextRoleMapping = new RoleMapping();
// dump all of these statements into a list as the size will be relatively
// constant and small for all scenarios
final List<Statement> nextRoleMappingStatements =
Iterations.asList(conn.getStatements(nextRoleMappingUri, null, null, true,
this.getContexts()));
for(final Statement nextRoleMappingStatement : nextRoleMappingStatements)
{
if(nextRoleMappingStatement.getPredicate().equals(SesameRealmConstants.OAS_ROLEMAPPEDROLE))
{
if(nextRoleMappingStatement.getObject() instanceof URI)
{
final RestletUtilRole nextStandardRole =
this.getRoleByUri((URI)nextRoleMappingStatement.getObject());
if(nextStandardRole == null)
{
this.log.warn(
"Failed to find an in-memory role for the given role mapped role: {}",
nextRoleMappingStatement);
}
else
{
nextRoleMapping.setTarget(nextStandardRole.getRole());
}
}
else
{
this.log.warn("Found a non-URI as the target for a role mapped role statement: {}",
nextRoleMappingStatement);
}
}
else if(nextRoleMappingStatement.getPredicate()
.equals(SesameRealmConstants.OAS_ROLEMAPPEDGROUP))
{
if(nextRoleMappingStatement.getObject() instanceof Literal)
{
final String nextGroupName =
((Literal)nextRoleMappingStatement.getObject()).stringValue();
// TODO: Support nested groups here
final List<Group> rootGroups = this.getRootGroups();
for(final Group nextRootGroup : rootGroups)
{
if(nextRootGroup.getName().equals(nextGroupName))
{
nextRoleMapping.setSource(nextRootGroup);
}
else
{
// TODO: need to check further for nested groups
}
}
}
else
{
this.log.warn(
"Found a non-Literal as the target for a role mapped group statement: {}",
nextRoleMappingStatement);
}
}
else if(nextRoleMappingStatement.getPredicate().equals(SesameRealmConstants.OAS_ROLEMAPPEDUSER))
{
if(nextRoleMappingStatement.getObject() instanceof Literal)
{
final String nextUserIdentifier =
((Literal)nextRoleMappingStatement.getObject()).stringValue();
final PoddUser nextUser = usersMapByIdentifier.get(nextUserIdentifier);
if(nextUser != null)
{
nextRoleMapping.setSource(nextUser);
}
else
{
this.log.info(
"Failed to find a role mapped user internally for the given user identifier: {}",
nextRoleMappingStatement);
}
}
else
{
this.log.warn(
"Found a non-Literal as the target for a role mapped group statement: {}",
nextRoleMappingStatement);
}
}
else if(nextRoleMappingStatement.getPredicate().equals(RDF.TYPE))
{
this.log.trace("Found rdf:type statement for role mapping: {}", nextRoleMappingStatement);
}
else
{
this.log.debug("Found unknown statement for role mapping: {}", nextRoleMappingStatement);
}
}
// verify that the source and target were both setup before adding this
// mapping to results
if(nextRoleMapping.getSource() != null && nextRoleMapping.getTarget() != null)
{
results.add(nextRoleMapping);
}
else
{
this.log.info("Not adding incomplete role mapping to results: uri={}, partialMapping={}",
nextRoleMappingUri, nextRoleMapping);
}
}
else
{
this.log.info("Found non-URI for role mapping, ignoring this role mapping: {}", next);
}
}
}
finally
{
typeStatements.close();
}
return results;
}
public Collection<Role> getRolesForObject(final User user, final URI objectUri)
{
final Set<Role> results = new HashSet<Role>();
final Collection<Collection<Role>> allResults =
this.getRolesForObjectAlternate(user.getIdentifier(), objectUri).values();
for(final Collection<Role> nextResult : allResults)
{
results.addAll(nextResult);
}
return results;
}
public Map<String, Collection<Role>> getRolesForObjectAlternate(final String userIdentifier, final URI objectUri)
{
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
return this.getRolesForObjectAlternate(userIdentifier, objectUri, conn);
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
}
public Map<String, Collection<Role>> getRolesForObjectAlternate(final String userIdentifier, final URI objectUri,
final RepositoryConnection conn) throws OpenRDFException
{
final ConcurrentMap<String, Collection<Role>> roleCollection =
new ConcurrentHashMap<String, Collection<Role>>();
final String query = this.buildSparqlQueryForObjectRoles(userIdentifier, objectUri);
if(this.log.isDebugEnabled())
{
this.log.debug("getCommonRolesForObjects: query={}", query);
}
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
if(!resultCollector.getHandledTuple() || resultCollector.getBindingSets().isEmpty())
{
this.log.warn("Could not find role with mappings for user: {}", userIdentifier);
}
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Role role = this.buildRoleFromSparqlResult(bindingSet);
if(!bindingSet.hasBinding(PoddSesameRealm.PARAM_USER_IDENTIFIER))
{
throw new RuntimeException("Query did not bind a user to the role : " + bindingSet.toString());
}
Collection<Role> nextRoles = new HashSet<Role>();
final Collection<Role> putIfAbsent =
roleCollection.putIfAbsent(bindingSet.getBinding(PoddSesameRealm.PARAM_USER_IDENTIFIER).getValue()
.stringValue(), nextRoles);
if(putIfAbsent != null)
{
nextRoles = putIfAbsent;
}
nextRoles.add(role);
}
return roleCollection;
}
public Collection<Entry<Role, URI>> getRolesWithObjectMappings(final User user)
{
if(user == null)
{
throw new NullPointerException("User was null");
}
// final Map<Role, URI> roleMap = new HashMap<Role, URI>();
final Collection<Entry<Role, URI>> roleCollection = new HashSet<Entry<Role, URI>>();
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
final String query = this.buildSparqlQueryForRolesWithObjects(user.getIdentifier());
if(this.log.isDebugEnabled())
{
this.log.debug("getRolesAndObjectsForUser: query={}", query);
}
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
if(!resultCollector.getHandledTuple() || resultCollector.getBindingSets().isEmpty())
{
this.log.warn("Could not find role with mappings for user: {}", user);
}
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final URI roleUri = (URI)bindingSet.getValue(PoddSesameRealm.PARAM_ROLE);
final Role role = PoddRoles.getRoleByUri(roleUri).getRole();
URI objectUri = null;
if(bindingSet.getValue(PoddSesameRealm.PARAM_OBJECT_URI) != null)
{
objectUri = (URI)bindingSet.getValue(PoddSesameRealm.PARAM_OBJECT_URI);
}
this.log.debug("Building map entry: {}, <{}>", role.getName(), objectUri);
final Entry<Role, URI> roleEntry = new AbstractMap.SimpleEntry<Role, URI>(role, objectUri);
// roleMap.put(roleEntry.getKey(), roleEntry.getValue());
roleCollection.add(roleEntry);
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
return roleCollection;
}
/**
* Returns the modifiable list of root groups.
*
* @return The modifiable list of root groups.
*/
public List<Group> getRootGroups()
{
List<Group> results = this.cachedRootGroups;
if(results == null)
{
synchronized(this)
{
results = this.cachedRootGroups;
if(results == null)
{
results = new ArrayList<Group>();
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
final RepositoryResult<Statement> rootGroupStatements =
conn.getStatements(null, RDF.TYPE, SesameRealmConstants.OAS_ROOTGROUP, true,
this.getContexts());
try
{
while(rootGroupStatements.hasNext())
{
final Statement nextRootGroupStatement = rootGroupStatements.next();
if(nextRootGroupStatement.getSubject() instanceof URI)
{
final URI nextRootGroupUri = (URI)nextRootGroupStatement.getSubject();
// add the group recursively to enable member groups to be added
// recursively
results.add(this.createGroupHierarchy(null, conn, nextRootGroupUri));
}
else
{
this.log.warn("Not including root group as it did not have a URI identifier: {}",
nextRootGroupStatement);
}
}
}
finally
{
rootGroupStatements.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Found exception while trying to get root groups", e);
}
finally
{
try
{
if(conn != null)
{
conn.close();
}
}
catch(final RepositoryException e)
{
this.log.error("Found unexpected exception while closing repository connection", e);
}
}
this.cachedRootGroups = results;
}
}
}
return results;
// throw new RuntimeException(
// "TODO: Implement code not to rely on getting a complete list of groups where possible");
// return this.rootGroups;
}
protected Dataset getSesameDataset()
{
final DatasetImpl result = new DatasetImpl();
result.setDefaultInsertGraph(this.getContexts()[0]);
for(final URI nextContext : this.getContexts())
{
result.addDefaultGraph(nextContext);
result.addDefaultRemoveGraph(nextContext);
result.addNamedGraph(nextContext);
}
return result;
}
public List<PoddUser> getUserByStatus(final PoddUserStatus status, final boolean isDescending, final int limit,
final int offset)
{
final List<PoddUser> result = new ArrayList<PoddUser>();
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
final String orderBy = "?" + PoddSesameRealm.PARAM_USER_IDENTIFIER;
final String query = this.buildSparqlQueryToGetUserByStatus(status, orderBy, isDescending, limit, offset);
this.log.debug("getUserByStatus: query={}", query);
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Binding binding = bindingSet.getBinding("userIdentifier");
result.add(this.buildRestletUserFromSparqlResult(binding.getValue().stringValue(), bindingSet));
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
return result;
}
public String getUsername(final URI userURI) throws RepositoryException
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
final Model result =
new LinkedHashModel(Iterations.asList(conn.getStatements(userURI,
SesameRealmConstants.OAS_USERIDENTIFIER, null, true, this.getContexts())));
for(final Value nextUsername : result.objects())
{
if(nextUsername instanceof Literal)
{
return ((Literal)nextUsername).getLabel();
}
}
return null;
}
catch(final RepositoryException e)
{
this.log.error("Found repository exception while adding user", e);
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Found unexpected exception while rolling back repository connection after exception");
}
}
throw new RuntimeException(e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found unexpected repository exception", e);
throw new RuntimeException(e);
}
}
}
}
/**
* Returns an unmodifiable list of users.
*
* @return An unmodifiable list of users.
*/
public List<PoddUser> getUsers()
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
return this.getUsers(conn);
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
}
protected List<PoddUser> getUsers(final RepositoryConnection conn) throws OpenRDFException
{
final List<PoddUser> result = new ArrayList<PoddUser>();
final String query = this.buildSparqlQueryToFindUser(null, true);
this.log.debug("findUser: query={}", query);
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Binding binding = bindingSet.getBinding("userIdentifier");
result.add(this.buildRestletUserFromSparqlResult(binding.getValue().stringValue(), bindingSet));
}
return Collections.unmodifiableList(result);
}
/**
* Helper method to find PODD Users who are assigned the 'roles of interest' for the given
* artifact.
*
* For security purposes, the returned Users only have their Identifier, First name, Last name,
* Status and Organization filled.
*
* @param rolesOfInterest
* @param artifactUri
* @return
*/
public Map<RestletUtilRole, Collection<PoddUser>> getUsersForRole(
final Collection<RestletUtilRole> rolesOfInterest, final URI artifactUri)
{
final ConcurrentMap<RestletUtilRole, Collection<PoddUser>> userList = new ConcurrentHashMap<>();
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
// final PoddSesameRealm nextRealm =
// ((PoddWebServiceApplication)this.getApplication()).getRealm();
final Map<String, Collection<Role>> participantMap =
this.getRolesForObjectAlternate(null, artifactUri, conn);
final Collection<String> keySet = participantMap.keySet();
final Map<String, PoddUser> usersMapByIdentifier = this.getUsersMapByIdentifier(conn);
for(final String userIdentifier : keySet)
{
final Collection<Role> rolesOfUser = participantMap.get(userIdentifier);
for(final RestletUtilRole roleOfInterest : rolesOfInterest)
{
if(rolesOfUser.contains(roleOfInterest.getRole()))
{
this.log.info("User {} has Role {} ", userIdentifier, roleOfInterest.getName());
Collection<PoddUser> nextRoles = new ArrayList<PoddUser>();
final Collection<PoddUser> putIfAbsent = userList.putIfAbsent(roleOfInterest, nextRoles);
if(putIfAbsent != null)
{
nextRoles = putIfAbsent;
}
final PoddUser tempUser = usersMapByIdentifier.get(userIdentifier);
final PoddUser userToReturn =
new PoddUser(tempUser.getIdentifier(), null, tempUser.getFirstName(),
tempUser.getLastName(), null, tempUser.getUserStatus(), null,
tempUser.getOrganization(), null);
nextRoles.add(userToReturn);
}
}
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure getting users for roles in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
return userList;
}
public Map<String, PoddUser> getUsersMapByIdentifier(final RepositoryConnection conn) throws OpenRDFException
{
final ConcurrentMap<String, PoddUser> result = new ConcurrentHashMap<String, PoddUser>();
for(final PoddUser nextUser : this.getUsers(conn))
{
final PoddUser putIfAbsent = result.putIfAbsent(nextUser.getIdentifier(), nextUser);
if(putIfAbsent != null)
{
this.log.error("Found duplicate user identifier for different users: {} {}", putIfAbsent, nextUser);
}
}
return result;
}
public Map<URI, PoddUser> getUsersMapByURI(final RepositoryConnection conn) throws OpenRDFException
{
final ConcurrentMap<URI, PoddUser> result = new ConcurrentHashMap<URI, PoddUser>();
for(final PoddUser nextUser : this.getUsers(conn))
{
final PoddUser putIfAbsent = result.putIfAbsent(nextUser.getUri(), nextUser);
if(putIfAbsent != null)
{
this.log.error("Found duplicate user URI for different users: {} {}", putIfAbsent, nextUser);
}
}
return result;
}
public URI getUserUri(final String userIdentifier) throws RepositoryException
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
final Model result =
new LinkedHashModel(Iterations.asList(conn.getStatements(null,
SesameRealmConstants.OAS_USERIDENTIFIER, this.vf.createLiteral(userIdentifier), true,
this.getContexts())));
for(final Resource nextUri : result.subjects())
{
if(nextUri instanceof URI)
{
return (URI)nextUri;
}
}
return null;
}
catch(final RepositoryException e)
{
this.log.error("Found repository exception while adding user", e);
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Found unexpected exception while rolling back repository connection after exception");
}
}
throw new RuntimeException(e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found unexpected repository exception", e);
throw new RuntimeException(e);
}
}
}
}
/**
* Maps a group defined in a component to a role defined in the application.
*
* @param group
* The source group.
* @param role
* The target role.
*/
public void map(final Group group, final Role role)
{
try
{
this.addRoleMapping(new RoleMapping(group, role));
}
catch(final RepositoryException e)
{
throw new RuntimeException("Found unexpected exception while adding role mapping", e);
}
}
/**
* Maps a user defined in a component to a role defined in the application.
*
* @param user
* The source user.
* @param role
* The target role.
*/
public void map(final PoddUser user, final Role role)
{
try
{
this.addRoleMapping(new RoleMapping(user, role));
}
catch(final RepositoryException e)
{
throw new RuntimeException("Found unexpected exception while adding role mapping", e);
}
}
public void map(final User user, final Role role, final URI optionalObjectUri)
{
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
conn.begin();
final URI nextRoleMappingUUID = this.vf.createURI("urn:oas:rolemapping:", UUID.randomUUID().toString());
conn.add(this.vf.createStatement(nextRoleMappingUUID, RDF.TYPE, SesameRealmConstants.OAS_ROLEMAPPING),
this.getContexts());
conn.add(this.vf.createStatement(nextRoleMappingUUID, SesameRealmConstants.OAS_ROLEMAPPEDROLE, this
.getRoleByName(role.getName()).getURI()), this.getContexts());
conn.add(
this.vf.createStatement(nextRoleMappingUUID, SesameRealmConstants.OAS_ROLEMAPPEDUSER,
this.vf.createLiteral(user.getIdentifier())), this.getContexts());
if(optionalObjectUri != null)
{
conn.add(this.vf.createStatement(nextRoleMappingUUID, PODD.PODD_ROLEMAPPEDOBJECT, optionalObjectUri),
this.getContexts());
}
conn.commit();
}
catch(final RepositoryException e)
{
this.log.error("Found exception while adding role mapping", e);
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
// throw a RuntimeException to be consistent with the behaviour of
// super.map(user, role)
throw new RuntimeException("Found unexpected exception while adding role mapping", e);
}
}
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Found exception closing repository connection", e);
}
}
}
}
public List<PoddUser> searchUser(String searchTerm, final PoddUserStatus status, final boolean isDescending,
final int limit, final int offset)
{
final List<PoddUser> result = new ArrayList<PoddUser>();
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
final String orderBy = "?" + PoddSesameRealm.PARAM_USER_IDENTIFIER;
final String query = this.buildSparqlQueryToSearchUsers(status, orderBy, isDescending, limit, offset);
this.log.debug("searchUser: query={}", query);
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
if(searchTerm == null)
{
// could this lead to an inefficient sparql query?
searchTerm = "";
}
tupleQuery.setBinding(PoddSesameRealm.PARAM_SEARCH_TERM, PODD.VF.createLiteral(searchTerm));
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Binding binding = bindingSet.getBinding("userIdentifier");
result.add(this.buildRestletUserFromSparqlResult(binding.getValue().stringValue(), bindingSet));
}
}
catch(final OpenRDFException e)
{
throw new RuntimeException("Failure searching users in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
return result;
}
public void setContexts(final URI... contexts)
{
if(contexts.length == 0)
{
// for security and usability we insist that a named graph is provided
throw new IllegalArgumentException(
"Cannot create an PoddSesameRealm without specifying the contexts that are used to manage user data.");
}
this.userManagerContexts = contexts;
}
public void setRepository(final Repository repository)
{
this.repository = repository;
if(this.repository != null)
{
this.vf = this.repository.getValueFactory();
}
else
{
this.vf = ValueFactoryImpl.getInstance();
}
}
/**
* Sets the modifiable list of root groups. This method clears the current list and adds all
* entries in the parameter list.
*
* @param rootGroups
* A list of root groups.
*/
@Deprecated
public void setRootGroups(final List<Group> rootGroups)
{
throw new RuntimeException("TODO: Implement me if necessary, or convert to add and remove methods");
// synchronized(this.getRootGroups())
// {
// if(rootGroups != this.getRootGroups())
// {
// this.getRootGroups().clear();
//
// if(rootGroups != null)
// {
// this.getRootGroups().addAll(rootGroups);
// }
// }
// }
}
/**
* Stores the group, including a root group statement if rootGroup is true.
*
* @param nextGroup
* @param isRootGroup
* @throws OpenRDFException
*/
private void storeGroup(final Group nextGroup, final RepositoryConnection conn, final boolean isRootGroup)
throws OpenRDFException
{
if(conn.hasStatement(null, SesameRealmConstants.OAS_GROUPNAME, this.vf.createLiteral(nextGroup.getName()),
true, this.getContexts()))
{
// TODO: Create an update method
throw new RuntimeException(
"A user with the given identifier already exists. Cannot add a new user with that identifier.");
}
final URI nextGroupUUID =
this.vf.createURI("urn:oas:group:", nextGroup.getName() + ":" + UUID.randomUUID().toString());
conn.add(this.vf.createStatement(nextGroupUUID, RDF.TYPE, SesameRealmConstants.OAS_GROUP), this.getContexts());
if(isRootGroup)
{
conn.add(this.vf.createStatement(nextGroupUUID, RDF.TYPE, SesameRealmConstants.OAS_ROOTGROUP),
this.getContexts());
}
conn.add(
this.vf.createStatement(nextGroupUUID, SesameRealmConstants.OAS_GROUPNAME,
this.vf.createLiteral(nextGroup.getName())), this.getContexts());
conn.add(
this.vf.createStatement(nextGroupUUID, SesameRealmConstants.OAS_GROUPDESCRIPTION,
this.vf.createLiteral(nextGroup.getDescription())), this.getContexts());
conn.add(
this.vf.createStatement(nextGroupUUID, SesameRealmConstants.OAS_GROUPINHERITINGROLES,
this.vf.createLiteral(nextGroup.isInheritingRoles())), this.getContexts());
// only store users who cannot be found based on their identifier
for(final User nextUser : nextGroup.getMemberUsers())
{
if(this.findUser(nextUser.getIdentifier(), conn) == null)
{
final URI nextUserUri = this.addUser((PoddUser)nextUser);
}
}
if(!nextGroup.getMemberGroups().isEmpty())
{
for(final Group nextMemberGroup : nextGroup.getMemberGroups())
{
// always set rootGroup parameter to false when recursing into member groups
this.storeGroup(nextMemberGroup, conn, false);
}
}
}
/**
* Unmaps a group defined in a component from a role defined in the application.
*
* @param group
* The source group.
* @param role
* The target role.
*/
public void unmap(final Group group, final Role role)
{
this.unmap(role, SesameRealmConstants.OAS_ROLEMAPPEDGROUP, group.getName());
}
/**
* Unmaps a user defined in a component from a role defined in the application.
*
* @param user
* The source user.
* @param role
* The target role.
*/
public void unmap(final PoddUser user, final Role role)
{
this.unmap(role, SesameRealmConstants.OAS_ROLEMAPPEDUSER, user.getIdentifier());
}
public void unmap(final Role role, final URI mappingUri, final String identifier)
{
RepositoryConnection conn = null;
try
{
conn = this.repository.getConnection();
conn.begin();
final StringBuilder query = new StringBuilder();
final RestletUtilRole oasRole = this.getRoleByName(role.getName());
if(oasRole == null)
{
throw new IllegalArgumentException("Did not recognise role as a standard OAS role" + role.getName());
}
query.append(" SELECT ?roleMappingUri ");
query.append(" WHERE ");
query.append(" { ");
query.append(" ?roleMappingUri a <" + SesameRealmConstants.OAS_ROLEMAPPING + "> . ");
query.append(" ?roleMappingUri <" + mappingUri + "> ?identifier . ");
query.append(" ?roleMappingUri <" + SesameRealmConstants.OAS_ROLEMAPPEDROLE + "> ?roleUri . ");
query.append(" FILTER(str(?identifier) = \"" + NTriplesUtil.escapeString(identifier) + "\") ");
query.append(" } ");
if(this.log.isDebugEnabled())
{
this.log.debug("findUser: query={}", query.toString());
}
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
tupleQuery.setBinding("roleUri", oasRole.getURI());
final QueryResultCollector resultCollector = RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
if(!resultCollector.getHandledTuple() || resultCollector.getBindingSets().isEmpty())
{
this.log.info("Could not find any role mappings to remove for this role: {} and this target: {}", role,
identifier);
}
else if(resultCollector.getBindingSets().size() > 1)
{
this.log.warn(
"Found duplicate roleMapping, will remove all mappings for this role: {} and this target: {}",
role, identifier);
}
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Value roleMappingUri = bindingSet.getValue("roleMappingUri");
if(roleMappingUri instanceof Resource)
{
conn.remove((Resource)roleMappingUri, null, null, this.getContexts());
}
else
{
this.log.warn("This should not happen while RDF only allows URIs and blank nodes in the subject position of triples");
}
}
conn.commit();
}
catch(final OpenRDFException e)
{
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Repository Exception while rolling back connection");
}
}
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
}
public void unmap(final User user, final Role role, final URI optionalObjectUri)
{
// If they only want to unmap a role without an object, then use super implementation.
if(optionalObjectUri == null)
{
this.unmap(role, SesameRealmConstants.OAS_ROLEMAPPEDUSER, user.getIdentifier());
}
// Do our object reliant code instead
else
{
RepositoryConnection conn = null;
try
{
conn = this.getRepository().getConnection();
conn.begin();
final StringBuilder query = new StringBuilder();
final RestletUtilRole oasRole = this.getRoleByName(role.getName());
if(oasRole == null)
{
throw new IllegalArgumentException("Did not recognise role as a standard OAS role" + role.getName());
}
query.append(" SELECT ?roleMappingUri ");
query.append(" WHERE ");
query.append(" { ");
query.append(" ?roleMappingUri a <" + SesameRealmConstants.OAS_ROLEMAPPING + "> . ");
query.append(" ?roleMappingUri <" + SesameRealmConstants.OAS_ROLEMAPPEDUSER + "> ?identifier . ");
query.append(" ?roleMappingUri <" + SesameRealmConstants.OAS_ROLEMAPPEDROLE + "> ?role . ");
query.append(" ?roleMappingUri <" + PODD.PODD_ROLEMAPPEDOBJECT + "> ?object . ");
query.append(" FILTER(str(?identifier) = \"" + NTriplesUtil.escapeString(user.getIdentifier())
+ "\") ");
query.append(" } ");
final String queryString = query.toString();
this.log.debug("findUser: query={}", queryString);
final TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, queryString);
tupleQuery.setBinding("role", oasRole.getURI());
tupleQuery.setBinding("object", optionalObjectUri);
final QueryResultCollector resultCollector =
RdfUtility.executeTupleQuery(tupleQuery, this.getContexts());
if(!resultCollector.getHandledTuple() || resultCollector.getBindingSets().isEmpty())
{
this.log.info(
"Could not find any role mappings to remove for this role: {}, object <{}>, and this target: {}",
role, optionalObjectUri, user.getIdentifier());
}
else if(resultCollector.getBindingSets().size() > 1)
{
this.log.warn(
"Found duplicate roleMapping, will remove all mappings for this role: {}, object <{}>, and this target: {}",
role, optionalObjectUri, user.getIdentifier());
}
for(final BindingSet bindingSet : resultCollector.getBindingSets())
{
final Value roleMappingUri = bindingSet.getValue("roleMappingUri");
if(roleMappingUri instanceof Resource)
{
conn.remove((Resource)roleMappingUri, null, null, this.getContexts());
}
else
{
this.log.warn("This should not happen while RDF only allows URIs and blank nodes in the subject position of triples");
}
}
conn.commit();
}
catch(final OpenRDFException e)
{
if(conn != null)
{
try
{
conn.rollback();
}
catch(final RepositoryException e1)
{
this.log.error("Repository Exception while rolling back connection");
}
}
throw new RuntimeException("Failure finding user in repository", e);
}
finally
{
if(conn != null)
{
try
{
conn.close();
}
catch(final RepositoryException e)
{
this.log.error("Failure to close connection", e);
}
}
}
}
}
public URI updateUser(final PoddUser nextUser)
{
try
{
return this.addUser(nextUser, false);
}
catch(NoSuchAlgorithmException | InvalidKeySpecException | OpenRDFException e)
{
throw new PoddRuntimeException("Could not update user", e);
}
}
private static final class PoddUserSecretHash
{
private String hash;
private PoddUser user;
public PoddUserSecretHash(final String hash, final PoddUser user)
{
this.hash = hash;
this.user = user;
}
public final boolean compare(final char[] secret) throws NoSuchAlgorithmException, InvalidKeySpecException
{
return PasswordHash.validatePassword(secret, this.hash);
}
}
}