/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.felix.useradmin.impl; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.felix.useradmin.BackendException; import org.apache.felix.useradmin.RoleFactory; import org.apache.felix.useradmin.RoleRepositoryStore; import org.apache.felix.useradmin.impl.role.ObservableRole; import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; import org.osgi.service.useradmin.UserAdminPermission; /** * Provides a manager and entry-point for accessing {@link Role}s. */ public final class RoleRepository { /** * Hands off all obtained role change event to a local set of listeners. */ final class RoleChangeReflector implements RoleChangeListener { /** * {@inheritDoc} */ public void propertyAdded(Role role, Object key, Object value) { Iterator iterator = createListenerIterator(); while (iterator.hasNext()) { ((RoleChangeListener) iterator.next()).propertyAdded(role, key, value); } } /** * {@inheritDoc} */ public void propertyChanged(Role role, Object key, Object oldValue, Object newValue) { Iterator iterator = createListenerIterator(); while (iterator.hasNext()) { ((RoleChangeListener) iterator.next()).propertyChanged(role, key, oldValue, newValue); } } /** * {@inheritDoc} */ public void propertyRemoved(Role role, Object key) { Iterator iterator = createListenerIterator(); while (iterator.hasNext()) { ((RoleChangeListener) iterator.next()).propertyRemoved(role, key); } } /** * {@inheritDoc} */ public void roleAdded(Role role) { Iterator iterator = createListenerIterator(); while (iterator.hasNext()) { ((RoleChangeListener) iterator.next()).roleAdded(role); } } /** * {@inheritDoc} */ public void roleRemoved(Role role) { Iterator iterator = createListenerIterator(); while (iterator.hasNext()) { ((RoleChangeListener) iterator.next()).roleRemoved(role); } } } /** The single predefined role. */ private static final Role USER_ANYONE = RoleFactory.createRole(Role.USER_ANYONE); private final RoleRepositoryStore m_store; private final CopyOnWriteArrayList m_listeners; private final RoleChangeReflector m_roleChangeReflector; /** * Creates a new {@link RoleRepository} instance. * * @param store the {@link RoleRepositoryStore} to use, cannot be <code>null</code>. */ public RoleRepository(RoleRepositoryStore store) { m_store = store; m_listeners = new CopyOnWriteArrayList(); m_roleChangeReflector = new RoleChangeReflector(); } /** * Adds a given role to this manager. * * @param role the role to add, cannot be <code>null</code>. If it is already contained by this manager, this method will not do anything. * @return the given role if added, <code>null</code> otherwise. */ public Role addRole(String name, int type) { if ((name == null) || "".equals(name.trim())) { throw new IllegalArgumentException("Name cannot be null or empty!"); } if (type != Role.GROUP && type != Role.USER) { throw new IllegalArgumentException("Invalid role type!"); } checkPermissions(); try { Role result = m_store.addRole(name, type); if (result != null) { result = wireChangeListener(result); m_roleChangeReflector.roleAdded(result); } return result; } catch (Exception e) { throw new BackendException("Adding role " + name + " failed!", e); } } /** * Adds the given role change listener to be called for upcoming changes in roles. * * @param listener the listener to register, cannot be <code>null</code>. * @throws IllegalArgumentException in case the given listener was <code>null</code>. */ public void addRoleChangeListener(RoleChangeListener listener) { if (listener == null) { throw new IllegalArgumentException("RoleChangeListener cannot be null!"); } m_listeners.addIfAbsent(listener); } /** * Returns the by its given name. * * @param roleName the name of the role to return, cannot be <code>null</code>. * @return the role matching the given name, or <code>null</code> if no role matched the given name. */ public Role getRoleByName(String roleName) { try { Role result; if (isPredefinedRole(roleName)) { result = getPredefinedRole(roleName); } else { result = m_store.getRoleByName(roleName); } return wireChangeListener(result); } catch (Exception e) { throw new BackendException("Failed to get role by name: " + roleName + "!", e); } } /** * Returns a collection with all roles matching a given filter. * * @param filter the filter to match the individual roles against, can be <code>null</code> if all roles should be returned. * @return a list with all matching roles, can be empty, but never <code>null</code>. */ public List getRoles(String filter) { List matchingRoles = new ArrayList(); try { Role[] roles = m_store.getRoles(sanitizeFilter(filter)); for (int i = 0; i < roles.length; i++) { Role role = roles[i]; if (!isPredefinedRole(role.getName())) { matchingRoles.add(wireChangeListener(role)); } } } catch (Exception e) { throw new BackendException("Failed to get roles!", e); } return matchingRoles; } /** * Returns a collection with all roles matching a given key-value pair. * * @param key the key to search for; * @param value the value to search for. * @return a list with all matching roles, can be empty, but never <code>null</code>. */ public List getRoles(String key, String value) { if (key == null) { throw new IllegalArgumentException("Key cannot be null!"); } if (value == null) { throw new IllegalArgumentException("Value cannot be null!"); } List matchingRoles = new ArrayList(); try { String criteria = "(".concat(key).concat("=").concat(value).concat(")"); Role[] roles = m_store.getRoles(criteria); for (int i = 0; i < roles.length; i++) { Role role = roles[i]; if (!isPredefinedRole(role.getName())) { matchingRoles.add(wireChangeListener(role)); } } } catch (Exception e) { throw new BackendException("Failed to get roles!", e); } return matchingRoles; } /** * Removes a given role from this manager. * * @param role the role to remove, cannot be <code>null</code>. * @return <code>true</code> if the role was removed (i.e., it was managed by this manager), or <code>false</code> if it was not found. */ public boolean removeRole(String name) { if (name == null) { throw new IllegalArgumentException("Name cannot be null!"); } checkPermissions(); // Cannot remove predefined roles... if (isPredefinedRole(name)) { return false; } try { Role result = m_store.removeRole(name); if (result != null) { // FELIX-3755: Remove the role as (required)member from all groups... removeRoleFromAllGroups(result); unwireChangeListener(result); m_roleChangeReflector.roleRemoved(result); return true; } return false; } catch (Exception e) { throw new BackendException("Failed to remove role " + name + "!", e); } } /** * Removes the given role change listener from be called for changes in roles. * * @param listener the listener to unregister, cannot be <code>null</code>. * @throws IllegalArgumentException in case the given listener was <code>null</code>. */ public void removeRoleChangeListener(RoleChangeListener listener) { if (listener == null) { throw new IllegalArgumentException("RoleChangeListener cannot be null!"); } m_listeners.remove(listener); } /** * Creates a new iterator for iterating over all listeners. * * @return a new {@link Iterator} instance, never <code>null</code>. */ final Iterator createListenerIterator() { return m_listeners.iterator(); } /** * Verifies whether the caller has the right permissions to add or remove roles. * * @throws SecurityException in case the caller has not the right permissions to perform the action. */ private void checkPermissions() throws SecurityException { SecurityManager securityManager = System.getSecurityManager(); if (securityManager != null) { securityManager.checkPermission(new UserAdminPermission(UserAdminPermission.ADMIN, null)); } } /** * Returns whether or not the given role is a predefined role. * <p> * Currently, there's only a single predefined role: {@link Role#USER_ANYONE}. * </p> * * @param roleName the role name to check, may be <code>null</code>. * @return <code>true</code> if the given role is predefined, <code>false</code> otherwise. */ private boolean isPredefinedRole(String roleName) { return Role.USER_ANYONE.equals(roleName); } /** * Returns the predefined role with the given name. * * @param roleName the name of the predefined role to return, cannot be <code>null</code>. * @return a predefined role instance, never <code>null</code>. * @see #isPredefinedRole(String) */ private Role getPredefinedRole(String roleName) { return USER_ANYONE; } /** * Removes a given role as (required)member from any groups it is member of. * * @param removedRole the role that is removed from the store already, cannot be <code>null</code>. * @throws BackendException in case of problems accessing the store. */ private void removeRoleFromAllGroups(Role removedRole) { try { Role[] roles = m_store.getRoles(null); for (int i = 0; i < roles.length; i++) { if (roles[i].getType() == Role.GROUP) { Group group = (Group) roles[i]; // Don't check whether the given role is actually a member // of the group, but let the group itself figure this out... group.removeMember(removedRole); } } } catch (Exception e) { throw new BackendException("Failed to get all roles!", e); } } /** * Sanitizes the given filter string. * * @param filter the filter string to sanitize, can be <code>null</code>. * @return the sanitized filter, or <code>null</code> if the given filter was <code>null</code> or empty. */ private String sanitizeFilter(String filter) { if (filter == null || "".equals(filter.trim())) { return null; } return filter.trim(); } /** * Unwires the given role to this repository so it no longer listens for its changes. * * @param role the role to unwire, cannot be <code>null</code>. */ private void unwireChangeListener(Object role) { if (role instanceof ObservableRole) { ((ObservableRole) role).setRoleChangeListener(null); } } /** * Wires the given role to this repository so it can listen for its changes. * * @param role the role to listen for its changes, cannot be <code>null</code>. * @return the given role. */ private Role wireChangeListener(Role role) { Role result = ObservableRole.wrap(role); if (result instanceof ObservableRole) { // Keep track of all changes made to the given role, to fire the // proper events to everyone interested... ((ObservableRole) result).setRoleChangeListener(m_roleChangeReflector); } return result; } }