/*
* 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.jackrabbit.core;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.config.LoginModuleConfig;
import org.apache.jackrabbit.core.config.UserManagerConfig;
import org.apache.jackrabbit.core.security.authentication.AuthContext;
import org.apache.jackrabbit.core.security.authentication.AuthContextProvider;
import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
import org.apache.jackrabbit.core.security.principal.AbstractPrincipalProvider;
import org.apache.jackrabbit.core.security.principal.DefaultPrincipalProvider;
import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl;
import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry;
import org.apache.jackrabbit.core.security.simple.SimpleWorkspaceAccessManager;
import org.apache.jackrabbit.core.security.user.MembershipCache;
import org.apache.jackrabbit.core.security.user.UserPerWorkspaceUserManager;
import org.apache.jackrabbit.core.security.user.UserManagerImpl;
import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.security.auth.Subject;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Derived security manager implementation that expects that users information
* is present in each workspace instead of having a single, dedicated
* "security-workspace" that provides user information. Consequently, the
* UserManager used to retrieve and manipulate user content is always
* bound to the <code>Session</code> passed to {@link #getUserManager(Session)}.
* <p> In addition the default (user-based) principal provider created by
* {@link org.apache.jackrabbit.core.DefaultSecurityManager}
* cannot be used to retrieve principals. Instead this implementation keeps
* a distinct pp-registry for each workspace.
* </p>
* NOTE: While this security manager asserts that a minimal set of system
* users (admin and anonymous) is present in each workspace
* it doesn't make any attempt to set or define the access permissions on the
* tree containing user related information.
*/
public class UserPerWorkspaceSecurityManager extends DefaultSecurityManager {
private final Map<String, PrincipalProviderRegistry> ppRegistries = new HashMap<String, PrincipalProviderRegistry>();
private final Object monitor = new Object();
/**
* List of workspace names for which {@link #createSystemUsers} has already
* been called.
*/
private final List<String> systemUsersInitialized = new ArrayList<String>();
private PrincipalProviderRegistry getPrincipalProviderRegistry(SessionImpl s) throws RepositoryException {
String wspName = s.getWorkspace().getName();
synchronized (monitor) {
PrincipalProviderRegistry p = ppRegistries.get(wspName);
if (p == null) {
SystemSession systemSession;
if (s instanceof SystemSession) {
systemSession = (SystemSession) s;
} else {
RepositoryImpl repo = (RepositoryImpl) getRepository();
systemSession = repo.getSystemSession(wspName);
// TODO: review again... this workaround is used in several places.
repo.markWorkspaceActive(wspName);
}
Properties[] moduleConfig = new AuthContextProvider("", ((RepositoryImpl) getRepository()).getConfig().getSecurityConfig().getLoginModuleConfig()).getModuleConfig();
PrincipalProvider defaultPP = new DefaultPrincipalProvider(systemSession, (UserManagerImpl) getUserManager(systemSession));
boolean initialized = false;
for (Properties props : moduleConfig) {
//GRANITE-4470: apply config to DefaultPrincipalProvider if there is no explicit PrincipalProvider configured
if (!props.containsKey(LoginModuleConfig.PARAM_PRINCIPAL_PROVIDER_CLASS) && props.containsKey(AbstractPrincipalProvider.MAXSIZE_KEY)) {
defaultPP.init(props);
initialized = true;
break;
}
}
if (!initialized) {
defaultPP.init(new Properties());
}
p = new WorkspaceBasedPrincipalProviderRegistry(defaultPP);
ppRegistries.put(wspName, p);
}
return p;
}
}
//------------------------------------------< JackrabbitSecurityManager >---
/**
* @see org.apache.jackrabbit.core.security.JackrabbitSecurityManager#init(Repository, Session)
*/
@Override
public void init(Repository repository, Session systemSession) throws RepositoryException {
super.init(repository, systemSession);
systemUsersInitialized.add(systemSession.getWorkspace().getName());
}
/**
* @see org.apache.jackrabbit.core.security.JackrabbitSecurityManager#dispose(String)
*/
@Override
public void dispose(String workspaceName) {
super.dispose(workspaceName);
synchronized (monitor) {
PrincipalProviderRegistry reg = ppRegistries.remove(workspaceName);
if (reg != null) {
reg.getDefault().close();
}
}
}
/**
* @see org.apache.jackrabbit.core.security.JackrabbitSecurityManager#close()
*/
@Override
public void close() {
super.close();
synchronized (monitor) {
for (PrincipalProviderRegistry registry : ppRegistries.values()) {
registry.getDefault().close();
}
ppRegistries.clear();
}
}
/**
* As this implementation expects that users information in present in
* every workspace, the UserManager is always created with the given
* session.
*
* @see org.apache.jackrabbit.core.security.JackrabbitSecurityManager#getUserManager(javax.jcr.Session)
*/
@Override
public UserManager getUserManager(Session session) throws RepositoryException {
checkInitialized();
if (session == getSystemSession()) {
return super.getUserManager(session);
} else if (session instanceof SessionImpl) {
UserManager uMgr = createUserManager((SessionImpl) session);
// Since users are not stored in a dedicated security workspace:
// make sure the system users are present. this is always the case
// for the configured security-workspace (or if missing the default
// workspace) but not for other workspaces.
// However, the check is only executed if the given session is a
// SystemSession (see also #getPrincipalProviderRegistry(Session)
// that initializes a SystemSession based UserManager for each workspace).
String wspName = session.getWorkspace().getName();
if (session instanceof SystemSession && !systemUsersInitialized.contains(wspName)) {
createSystemUsers(uMgr, (SystemSession) session, adminId, anonymousId);
systemUsersInitialized.add(wspName);
}
return uMgr;
} else {
throw new RepositoryException("Internal error: SessionImpl expected.");
}
}
/**
* Creates an AuthContext for the given {@link javax.jcr.Credentials} and
* {@link javax.security.auth.Subject}.<br>
* This includes selection of application specific LoginModules and
* initialization with credentials and Session to System-Workspace
*
* @return an {@link org.apache.jackrabbit.core.security.authentication.AuthContext} for the given Credentials, Subject
* @throws javax.jcr.RepositoryException in other exceptional repository states
*/
@Override
public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
throws RepositoryException {
checkInitialized();
SystemSession systemSession = ((RepositoryImpl) getRepository()).getSystemSession(workspaceName);
return getAuthContextProvider().getAuthContext(creds, subject, systemSession,
getPrincipalProviderRegistry(systemSession), adminId, anonymousId);
}
//--------------------------------------------------------------------------
/**
* Always returns <code>null</code>. The default principal provider is
* workspace depending as users are expected to exist in every workspace.
*
* @return <code>null</code>
* @throws RepositoryException
*/
@Override
protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
return null;
}
@Override
protected UserManager getSystemUserManager(String workspaceName) throws RepositoryException {
if (workspaceName.equals(getSystemSession().getWorkspace().getName())) {
return super.getSystemUserManager(workspaceName);
} else {
return ((RepositoryImpl) getRepository()).getWorkspaceInfo(workspaceName).getSystemSession().getUserManager();
}
}
/**
* Creates a new instanceof <code>TransientChangeUserManagerImpl</code>.
*
* @param session session
* @return an instanceof <code>TransientChangeUserManagerImpl</code>
* @throws RepositoryException
*/
@Override
protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException {
UserManagerConfig umc = getConfig().getUserManagerConfig();
UserManagerImpl umgr;
// in contrast to the DefaultSecurityManager users are not retrieved
// from a dedicated workspace: the system session of each workspace must
// get a system user manager that asserts the existence of the admin user.
if (umc != null) {
Class<?>[] paramTypes = new Class[] {
SessionImpl.class,
String.class,
Properties.class,
MembershipCache.class};
umgr = (UserPerWorkspaceUserManager) umc.getUserManager(UserPerWorkspaceUserManager.class,
paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session));
} else {
umgr = new UserPerWorkspaceUserManager(session, adminId, null, getMembershipCache(session));
}
if (umc != null && !(session instanceof SystemSession)) {
AuthorizableAction[] actions = umc.getAuthorizableActions();
umgr.setAuthorizableActions(actions);
}
return umgr;
}
/**
* @param session Session for the principal manager must be created.
* @return A new instance of PrincipalManagerImpl. Note that this implementation
* uses a workspace specific principal provider registry, that retrieves
* the configured providers from the registry obtained through
* {@link #getPrincipalProviderRegistry()} but has a workspace specific
* default provider.
* @throws RepositoryException
*/
@Override
protected PrincipalManager createPrincipalManager(SessionImpl session) throws RepositoryException {
return new PrincipalManagerImpl(session, getPrincipalProviderRegistry(session).getProviders());
}
/**
* Returns a new instance of <code>SimpleWorkspaceAccessManager</code>, since
* with the <code>DefaultLoginModule</code> the existence of the user
* is checked in order to successfully complete the login. Since with this
* SecurityManager users are stored separately in each workspace, a user
* may only login to a workspace if the corresponding user node exists.
* Consequently a lazy workspace access manager is sufficient.
* <p>
* If this SecurityManager is used with a distinct <code>LoginModule</code>
* implementation, the {@link org.apache.jackrabbit.core.config.SecurityManagerConfig#getWorkspaceAccessConfig() configuration}
* for <code>WorkspaceAccessManager</code> should be adjusted as well.
*
* @return An new instance of {@link SimpleWorkspaceAccessManager}.
*/
@Override
protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
return new WorkspaceAccessManagerImpl();
}
//--------------------------------------------------------------------------
/**
* Workaround to get a default (user-based) principal provider depending
* on the workspace being accessed. This is required for this security
* manager as users aren't stored in a single, dedicated workspace.
*/
private final class WorkspaceBasedPrincipalProviderRegistry implements PrincipalProviderRegistry {
private final PrincipalProvider defaultPrincipalProvider;
public WorkspaceBasedPrincipalProviderRegistry(PrincipalProvider defaultPrincipalProvider) {
this.defaultPrincipalProvider = defaultPrincipalProvider;
}
public PrincipalProvider registerProvider(Properties configuration) throws RepositoryException {
return getPrincipalProviderRegistry().registerProvider(configuration);
}
public PrincipalProvider getDefault() {
return defaultPrincipalProvider;
}
public PrincipalProvider getProvider(String className) {
PrincipalProvider p = getPrincipalProviderRegistry().getProvider(className);
if (p == null && defaultPrincipalProvider.getClass().getName().equals(className)) {
p = defaultPrincipalProvider;
}
return p;
}
public PrincipalProvider[] getProviders() {
List<PrincipalProvider> l = new ArrayList<PrincipalProvider>();
l.addAll(Arrays.asList(getPrincipalProviderRegistry().getProviders()));
l.add(defaultPrincipalProvider);
return l.toArray(new PrincipalProvider[l.size()]);
}
}
private final class WorkspaceAccessManagerImpl implements WorkspaceAccessManager {
/**
* Does nothing.
* @see WorkspaceAccessManager#init(javax.jcr.Session)
*/
public void init(Session systemSession) throws RepositoryException {
// nothing to do.
}
/**
* Does nothing.
* @see org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager#close()
*/
public void close() throws RepositoryException {
// nothing to do.
}
/**
* Returns <code>true</code> if a workspace with the given
* <code>workspaceName</code> exists and if that workspace defines a
* user that matches any of the given <code>principals</code>;
* <code>false</code> otherwise.
*
* @see WorkspaceAccessManager#grants(java.util.Set, String)
*/
public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
if (!(Arrays.asList(((RepositoryImpl) getRepository()).getWorkspaceNames())).contains(workspaceName)) {
return false;
} else {
UserManager umgr = UserPerWorkspaceSecurityManager.this.getSystemUserManager(workspaceName);
for (Principal principal : principals) {
if (!(principal instanceof Group)) {
// check if the workspace identified by the given workspace
// name contains a user with this principal
if (umgr.getAuthorizable(principal) != null) {
return true;
}
}
}
}
return false;
}
}
}