/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.userdirectory;
import org.opencastproject.security.api.Group;
import org.opencastproject.security.api.Role;
import org.opencastproject.security.api.RoleProvider;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.security.api.UserProvider;
import org.opencastproject.security.impl.jpa.JpaOrganization;
import org.opencastproject.security.impl.jpa.JpaRole;
import org.opencastproject.security.impl.jpa.JpaUser;
import org.opencastproject.userdirectory.utils.UserDirectoryUtils;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.PasswordEncoder;
import org.opencastproject.util.data.Monadics;
import org.opencastproject.util.data.Option;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
/**
* Manages and locates users using JPA.
*/
public class JpaUserAndRoleProvider implements UserProvider, RoleProvider {
/** The logger */
private static final Logger logger = LoggerFactory.getLogger(JpaUserAndRoleProvider.class);
public static final String PERSISTENCE_UNIT = "org.opencastproject.common";
/** The user provider name */
public static final String PROVIDER_NAME = "opencast";
/** Username constant used in JSON formatted users */
public static final String USERNAME = "username";
/** Role constant used in JSON formatted users */
public static final String ROLES = "roles";
/** Encoding expected from all inputs */
public static final String ENCODING = "UTF-8";
/** The delimiter for the User cache */
private static final String DELIMITER = ";==;";
/** The security service */
protected SecurityService securityService = null;
/** Group provider */
protected JpaGroupRoleProvider groupRoleProvider;
/** A cache of users, which lightens the load on the SQL server */
private LoadingCache<String, Object> cache = null;
/** A token to store in the miss cache */
protected Object nullToken = new Object();
/** OSGi DI */
void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
/**
* @param securityService
* the securityService to set
*/
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* @param groupRoleProvider
* the groupRoleProvider to set
*/
void setGroupRoleProvider(JpaGroupRoleProvider groupRoleProvider) {
this.groupRoleProvider = groupRoleProvider;
}
/** The factory used to generate the entity manager */
protected EntityManagerFactory emf = null;
/**
* Callback for activation of this component.
*
* @param cc
* the component context
*/
public void activate(ComponentContext cc) {
logger.debug("activate");
// Setup the caches
cache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<String, Object>() {
@Override
public Object load(String id) {
String[] key = id.split(DELIMITER);
logger.trace("Loading user '{}':'{}' from database", key[0], key[1]);
User user = loadUser(key[0], key[1]);
return user == null ? nullToken : user;
}
});
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.RoleProvider#getRolesForUser(String)
*/
@Override
public List<Role> getRolesForUser(String userName) {
ArrayList<Role> roles = new ArrayList<Role>();
User user = loadUser(userName);
if (user == null)
return roles;
roles.addAll(user.getRoles());
return roles;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.UserProvider#findUsers(String, int, int)
*/
@Override
public Iterator<User> findUsers(String query, int offset, int limit) {
if (query == null)
throw new IllegalArgumentException("Query must be set");
String orgId = securityService.getOrganization().getId();
List<JpaUser> users = UserDirectoryPersistenceUtil.findUsersByQuery(orgId, query, limit, offset, emf);
return Monadics.mlist(users).map(addProviderName).iterator();
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.RoleProvider#findRoles(String, Role.Target, int, int)
*/
@Override
public Iterator<Role> findRoles(String query, Role.Target target, int offset, int limit) {
if (query == null)
throw new IllegalArgumentException("Query must be set");
String orgId = securityService.getOrganization().getId();
// This provider persists roles but is not authoritative for any roles, so return an empty set
return new ArrayList<Role>().iterator();
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.UserProvider#loadUser(java.lang.String)
*/
@Override
public User loadUser(String userName) {
String orgId = securityService.getOrganization().getId();
Object user = cache.getUnchecked(userName.concat(DELIMITER).concat(orgId));
if (user == nullToken) {
return null;
} else {
return (User) user;
}
}
@Override
public Iterator<User> getUsers() {
String orgId = securityService.getOrganization().getId();
List<JpaUser> users = UserDirectoryPersistenceUtil.findUsers(orgId, 0, 0, emf);
return Monadics.mlist(users).map(addProviderName).iterator();
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.RoleDirectoryService#getRoles()
*/
@Override
public Iterator<Role> getRoles() {
return getRolesAsList().iterator();
}
public List<Role> getRolesAsList() {
String orgId = securityService.getOrganization().getId();
List<JpaRole> rolesIterator = UserDirectoryPersistenceUtil.findRoles(orgId, 0, 0, emf);
return new ArrayList<Role>(rolesIterator);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.security.api.UserProvider#getOrganization()
*/
@Override
public String getOrganization() {
return ALL_ORGANIZATIONS;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getName();
}
/**
* Loads a user from persistence
*
* @param userName
* the user name
* @param organization
* the organization id
* @return the loaded user or <code>null</code> if not found
*/
public User loadUser(String userName, String organization) {
JpaUser user = UserDirectoryPersistenceUtil.findUser(userName, organization, emf);
return Option.option(user).map(addProviderName).getOrElseNull();
}
/**
* Loads a user from persistence
*
* @param userId
* the user's id
* @param organization
* the organization id
* @return the loaded user or <code>null</code> if not found
*/
public User loadUser(long userId, String organization) {
JpaUser user = UserDirectoryPersistenceUtil.findUser(userId, organization, emf);
return Option.option(user).map(addProviderName).getOrElseNull();
}
/**
* Adds a user to the persistence
*
* @param user
* the user to add
*
* @throws org.opencastproject.security.api.UnauthorizedException
* if the user is not allowed to create other user with the given roles
*/
public void addUser(JpaUser user) throws UnauthorizedException {
if (!UserDirectoryUtils.isCurrentUserAuthorizedHandleRoles(securityService, user.getRoles()))
throw new UnauthorizedException("The user is not allowed to set the admin role on other users");
// Create a JPA user with an encoded password.
String encodedPassword = PasswordEncoder.encode(user.getPassword(), user.getUsername());
// Only save internal roles
Set<JpaRole> roles = UserDirectoryPersistenceUtil.saveRoles(filterRoles(user.getRoles()), emf);
JpaOrganization organization = UserDirectoryPersistenceUtil.saveOrganization(
(JpaOrganization) user.getOrganization(), emf);
JpaUser newUser = new JpaUser(user.getUsername(), encodedPassword, organization, user.getName(), user.getEmail(),
user.getProvider(), user.isManageable(), roles);
// Then save the user
EntityManager em = null;
EntityTransaction tx = null;
try {
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin();
em.persist(newUser);
tx.commit();
cache.put(user.getUsername() + DELIMITER + user.getOrganization().getId(), newUser);
} finally {
if (tx.isActive()) {
tx.rollback();
}
if (em != null)
em.close();
}
updateGroupMembership(user);
}
/**
* Updates a user to the persistence
*
* @param user
* the user to save
* @throws NotFoundException
* @throws org.opencastproject.security.api.UnauthorizedException
* if the current user is not allowed to update user with the given roles
*/
public User updateUser(JpaUser user) throws NotFoundException, UnauthorizedException {
if (!UserDirectoryUtils.isCurrentUserAuthorizedHandleRoles(securityService, user.getRoles()))
throw new UnauthorizedException("The user is not allowed to set the admin role on other users");
JpaUser updateUser = UserDirectoryPersistenceUtil.findUser(user.getUsername(), user.getOrganization().getId(), emf);
if (updateUser == null)
throw new NotFoundException("User " + user.getUsername() + " not found.");
logger.debug("updateUser({})", user.getUsername());
if (!UserDirectoryUtils.isCurrentUserAuthorizedHandleRoles(securityService, updateUser.getRoles()))
throw new UnauthorizedException("The user is not allowed to update an admin user");
String encodedPassword = null;
//only update Password if a value is set
if (user.getPassword().isEmpty()) {
JpaUser old = UserDirectoryPersistenceUtil.findUser(user.getUsername(),user.getOrganization().getId(), emf);
encodedPassword = old.getPassword();
} else {
// Update an JPA user with an encoded password.
encodedPassword = PasswordEncoder.encode(user.getPassword(), user.getUsername());
}
// Only save internal roles
Set<JpaRole> roles = UserDirectoryPersistenceUtil.saveRoles(filterRoles(user.getRoles()), emf);
JpaOrganization organization = UserDirectoryPersistenceUtil.saveOrganization(
(JpaOrganization) user.getOrganization(), emf);
JpaUser updatedUser = UserDirectoryPersistenceUtil.saveUser(
new JpaUser(user.getUsername(), encodedPassword, organization, user.getName(), user.getEmail(), user
.getProvider(), true, roles), emf);
cache.put(user.getUsername() + DELIMITER + organization.getId(), updatedUser);
updateGroupMembership(user);
return updatedUser;
}
/**
* Select only internal roles
*
* @param userRoles
* the user's full set of roles
*/
private Set<JpaRole> filterRoles(Set<Role> userRoles) {
Set<JpaRole> roles = new HashSet<JpaRole>();
for (Role role : userRoles) {
if (Role.Type.INTERNAL.equals(role.getType()) && !role.getName().startsWith(Group.ROLE_PREFIX)) {
JpaRole jpaRole = (JpaRole) role;
roles.add(jpaRole);
}
}
return roles;
}
/**
* Updates a user's groups based on assigned roles
*
* @param user
* the user for whom groups should be updated
* @throws NotFoundException
*/
private void updateGroupMembership(JpaUser user) {
logger.debug("updateGroupMembership({}, roles={})", user.getUsername(), user.getRoles().size());
List<String> internalGroupRoles = new ArrayList<String>();
for (Role role : user.getRoles()) {
if (Role.Type.GROUP.equals(role.getType())
|| (Role.Type.INTERNAL.equals(role.getType()) && role.getName().startsWith(Group.ROLE_PREFIX))) {
internalGroupRoles.add(role.getName());
}
}
groupRoleProvider.updateGroupMembershipFromRoles(user.getUsername(), user.getOrganization().getId(), internalGroupRoles);
}
/**
* Delete the given user
*
* @param username
* the name of the user to delete
* @param orgId
* the organization id
* @throws NotFoundException
* if the requested user is not exist
* @throws org.opencastproject.security.api.UnauthorizedException
* if you havn't permissions to delete an admin user (only admins may do that)
* @throws Exception
*/
public void deleteUser(String username, String orgId) throws NotFoundException, UnauthorizedException, Exception {
User user = loadUser(username, orgId);
if (user != null && !UserDirectoryUtils.isCurrentUserAuthorizedHandleRoles(securityService, user.getRoles()))
throw new UnauthorizedException("The user is not allowed to delete an admin user");
// Remove the user's group membership
groupRoleProvider.updateGroupMembershipFromRoles(username, orgId, new ArrayList<String>());
// Remove the user
UserDirectoryPersistenceUtil.deleteUser(username, orgId, emf);
cache.invalidate(username + DELIMITER + orgId);
}
/**
* Adds a role to the persistence
*
* @param jpaRole
* the role
*/
public void addRole(JpaRole jpaRole) {
HashSet<JpaRole> roles = new HashSet<JpaRole>();
roles.add(jpaRole);
UserDirectoryPersistenceUtil.saveRoles(roles, emf);
}
@Override
public String getName() {
return PROVIDER_NAME;
}
private static org.opencastproject.util.data.Function<JpaUser, User> addProviderName = new org.opencastproject.util.data.Function<JpaUser, User>() {
@Override
public User apply(JpaUser a) {
a.setProvider(PROVIDER_NAME);
return a;
}
};
@Override
public long countUsers() {
String orgId = securityService.getOrganization().getId();
return UserDirectoryPersistenceUtil.countUsers(orgId, emf);
}
@Override
public void invalidate(String userName) {
String orgId = securityService.getOrganization().getId();
cache.invalidate(userName + DELIMITER + orgId);
}
}