/** * 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.Role; import org.opencastproject.security.api.RoleProvider; import org.opencastproject.security.api.SecurityService; 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.JpaUserReference; import org.opencastproject.userdirectory.api.UserReferenceProvider; 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.Collections; import java.util.Date; 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; import javax.persistence.NoResultException; import javax.persistence.Query; /** * Manages and locates users references using JPA. */ public class JpaUserReferenceProvider implements UserReferenceProvider, UserProvider, RoleProvider { /** The logger */ private static final Logger logger = LoggerFactory.getLogger(JpaUserReferenceProvider.class); public static final String PROVIDER_NAME = "matterhorn-reference"; /** 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 security service */ protected SecurityService securityService = null; /** The delimiter for the User cache */ private static final String DELIMITER = ";==;"; /** 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 final Object nullToken = new Object(); /** The factory used to generate the entity manager */ protected EntityManagerFactory emf = null; /** OSGi DI */ void setEntityManagerFactory(EntityManagerFactory emf) { this.emf = emf; } /** * @param securityService * the securityService to set */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * 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 reference database", key[0], key[1]); User user = loadUser(key[0], key[1]); return user == null ? nullToken : user; } }); // Set up persistence } @Override public String getName() { return PROVIDER_NAME; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return getClass().getName(); } /** * {@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) 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<User> users = new ArrayList<User>(); for (JpaUserReference userRef : findUserReferencesByQuery(orgId, query, limit, offset, emf)) { users.add(userRef.toUser(PROVIDER_NAME)); } return users.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) { // The roles are returned from the JpaUserAndRoleProvider return Collections.<Role> emptyList().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; } } /** * 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 */ private User loadUser(String userName, String organization) { JpaUserReference userReference = findUserReference(userName, organization, emf); if (userReference != null) return userReference.toUser(PROVIDER_NAME); return null; } @Override public Iterator<User> getUsers() { String orgId = securityService.getOrganization().getId(); List<User> users = new ArrayList<User>(); for (JpaUserReference userRef : findUserReferences(orgId, 0, 0, emf)) { users.add(userRef.toUser(PROVIDER_NAME)); } return users.iterator(); } /** * {@inheritDoc} * * @see org.opencastproject.security.api.RoleDirectoryService#getRoles() */ @Override public Iterator<Role> getRoles() { // The roles are returned from the JpaUserAndRoleProvider return Collections.<Role> emptyList().iterator(); } /** * {@inheritDoc} * * @see org.opencastproject.security.api.UserProvider#getOrganization() */ @Override public String getOrganization() { return ALL_ORGANIZATIONS; } /** * {@inheritDoc} */ public void addUserReference(JpaUserReference user, String mechanism) { // Create a JPA user with an encoded password. Set<JpaRole> roles = UserDirectoryPersistenceUtil.saveRoles(user.getRoles(), emf); JpaOrganization organization = UserDirectoryPersistenceUtil.saveOrganization( (JpaOrganization) user.getOrganization(), emf); JpaUserReference userReference = new JpaUserReference(user.getUsername(), user.getName(), user.getEmail(), mechanism, new Date(), organization, roles); // Then save the user reference EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); JpaUserReference foundUserRef = findUserReference(user.getUsername(), user.getOrganization().getId(), emf); if (foundUserRef == null) { em.persist(userReference); } else { throw new IllegalStateException("User '" + user.getUsername() + "' already exists"); } tx.commit(); cache.put(user.getUsername() + DELIMITER + user.getOrganization().getId(), user.toUser(PROVIDER_NAME)); } finally { if (tx.isActive()) { tx.rollback(); } if (em != null) em.close(); } } /** * {@inheritDoc} */ public void updateUserReference(JpaUserReference user) { EntityManager em = null; EntityTransaction tx = null; try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); JpaUserReference foundUserRef = findUserReference(user.getUsername(), user.getOrganization().getId(), emf); if (foundUserRef == null) { throw new IllegalStateException("User '" + user.getUsername() + "' does not exist"); } else { foundUserRef.setName(user.getName()); foundUserRef.setEmail(user.getEmail()); foundUserRef.setLastLogin(new Date()); foundUserRef.setRoles(UserDirectoryPersistenceUtil.saveRoles(user.getRoles(), emf)); em.merge(foundUserRef); } tx.commit(); cache.put(user.getUsername() + DELIMITER + user.getOrganization().getId(), user.toUser(PROVIDER_NAME)); } finally { if (tx.isActive()) { tx.rollback(); } if (em != null) em.close(); } } /** * Returns the persisted user reference by the user name and organization id * * @param userName * the user name * @param organizationId * the organization id * @return the user or <code>null</code> if not found */ public JpaUserReference findUserReference(String userName, String organizationId) { return findUserReference(userName, organizationId, emf); } /** * Returns the persisted user reference by the user name and organization id * * @param userName * the user name * @param organizationId * the organization id * @param emf * the entity manager factory * @return the user or <code>null</code> if not found */ private JpaUserReference findUserReference(String userName, String organizationId, EntityManagerFactory emf) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("UserReference.findByUsername"); q.setParameter("u", userName); q.setParameter("org", organizationId); return (JpaUserReference) q.getSingleResult(); } catch (NoResultException e) { return null; } finally { if (em != null) em.close(); } } /** * Returns a list of user references by a search query if set or all user references if search query is * <code>null</code> * * @param orgId * the organization identifier * @param query * the query to search * @param limit * the limit * @param offset * the offset * @param emf * the entity manager factory * @return the user references list */ @SuppressWarnings("unchecked") private List<JpaUserReference> findUserReferencesByQuery(String orgId, String query, int limit, int offset, EntityManagerFactory emf) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("UserReference.findByQuery").setMaxResults(limit).setFirstResult(offset); q.setParameter("query", query.toUpperCase()); q.setParameter("org", orgId); return q.getResultList(); } finally { if (em != null) em.close(); } } /** * Returns all user references * * @param orgId * the organization identifier * @param limit * the limit * @param offset * the offset * @param emf * the entity manager factory * @return the user references list */ @SuppressWarnings("unchecked") private List<JpaUserReference> findUserReferences(String orgId, int limit, int offset, EntityManagerFactory emf) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("UserReference.findAll").setMaxResults(limit).setFirstResult(offset); q.setParameter("org", orgId); return q.getResultList(); } finally { if (em != null) em.close(); } } @Override public long countUsers() { String orgId = securityService.getOrganization().getId(); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("UserReference.countAll"); q.setParameter("org", orgId); return ((Number) q.getSingleResult()).longValue(); } finally { if (em != null) em.close(); } } @Override public void invalidate(String userName) { String orgId = securityService.getOrganization().getId(); cache.invalidate(userName.concat(DELIMITER).concat(orgId)); } }