/*
* 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.security.simple;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.security.auth.Subject;
import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.RepositoryImpl;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.config.AccessManagerConfig;
import org.apache.jackrabbit.core.config.LoginModuleConfig;
import org.apache.jackrabbit.core.config.SecurityConfig;
import org.apache.jackrabbit.core.config.SecurityManagerConfig;
import org.apache.jackrabbit.core.security.AMContext;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.core.security.AnonymousPrincipal;
import org.apache.jackrabbit.core.security.JackrabbitSecurityManager;
import org.apache.jackrabbit.core.security.SecurityConstants;
import org.apache.jackrabbit.core.security.UserPrincipal;
import org.apache.jackrabbit.core.security.authentication.AuthContext;
import org.apache.jackrabbit.core.security.authentication.AuthContextProvider;
import org.apache.jackrabbit.core.security.authorization.AccessControlProvider;
import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.core.security.principal.PrincipalIteratorAdapter;
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.principal.ProviderRegistryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <code>SimpleSecurityManager</code>: simple implementation ignoring both
* configuration entries for 'principalProvider' and for 'workspaceAccessManager'.
* The AccessManager is initialized using
* {@link AccessManager#init(org.apache.jackrabbit.core.security.AMContext)}.
*/
public class SimpleSecurityManager implements JackrabbitSecurityManager {
private static Logger log = LoggerFactory.getLogger(SimpleSecurityManager.class);
private boolean initialized;
private SecurityConfig config;
/**
* session on the system workspace.
*/
private Session systemSession;
/**
* the principal provider registry
*/
private PrincipalProviderRegistry principalProviderRegistry;
/**
* The workspace access manager
*/
private WorkspaceAccessManager workspaceAccessManager;
/**
* factory for login-context {@see Repository#login())
*/
private AuthContextProvider authCtxProvider;
private String adminID;
private String anonymID;
/**
* Always returns <code>null</code>. AccessControlProvider configuration
* is ignored with this security manager. Subclasses may overwrite this
* lazy behavior that originates from the <code>SimpleAccessManager</code>.
*
* @param systemSession The system session used to init the security manager.
* @param workspaceName The name of the workspace for which the provider
* should be retrieved.
* @return Always returns <code>null</code>.
*/
protected AccessControlProvider getAccessControlProvider(Session systemSession, String workspaceName) {
return null;
}
//------------------------------------------< JackrabbitSecurityManager >---
/**
* @see JackrabbitSecurityManager#init(Repository, Session)
*/
public void init(Repository repository, Session systemSession) throws RepositoryException {
if (initialized) {
throw new IllegalStateException("already initialized");
}
if (!(repository instanceof RepositoryImpl)) {
throw new RepositoryException("RepositoryImpl expected");
}
this.systemSession = systemSession;
config = ((RepositoryImpl) repository).getConfig().getSecurityConfig();
// read the LoginModule configuration
LoginModuleConfig loginModConf = config.getLoginModuleConfig();
authCtxProvider = new AuthContextProvider(config.getAppName(), loginModConf);
if (authCtxProvider.isLocal()) {
log.info("init: using Repository LoginModule configuration for " + config.getAppName());
} else if (authCtxProvider.isJAAS()) {
log.info("init: using JAAS LoginModule configuration for " + config.getAppName());
} else {
String msg = "No valid LoginModule configuriation for " + config.getAppName();
log.error(msg);
throw new RepositoryException(msg);
}
Properties[] moduleConfig = authCtxProvider.getModuleConfig();
// retrieve default-ids (admin and anonymous) from login-module-configuration.
for (Properties aModuleConfig1 : moduleConfig) {
if (aModuleConfig1.containsKey(LoginModuleConfig.PARAM_ADMIN_ID)) {
adminID = aModuleConfig1.getProperty(LoginModuleConfig.PARAM_ADMIN_ID);
}
if (aModuleConfig1.containsKey(LoginModuleConfig.PARAM_ANONYMOUS_ID)) {
anonymID = aModuleConfig1.getProperty(LoginModuleConfig.PARAM_ANONYMOUS_ID);
}
}
// fallback:
if (adminID == null) {
log.debug("No adminID defined in LoginModule/JAAS config -> using default.");
adminID = SecurityConstants.ADMIN_ID;
}
if (anonymID == null) {
log.debug("No anonymousID defined in LoginModule/JAAS config -> using default.");
anonymID = SecurityConstants.ANONYMOUS_ID;
}
// most simple principal provider registry, that does not read anything
// from configuration
PrincipalProvider principalProvider = new SimplePrincipalProvider();
// skip init of provider (nop)
principalProviderRegistry = new ProviderRegistryImpl(principalProvider);
// register all configured principal providers.
for (Properties aModuleConfig : moduleConfig) {
principalProviderRegistry.registerProvider(aModuleConfig);
}
SecurityManagerConfig smc = config.getSecurityManagerConfig();
if (smc != null && smc.getWorkspaceAccessConfig() != null) {
workspaceAccessManager = (WorkspaceAccessManager) smc.getWorkspaceAccessConfig().newInstance();
} else {
// fallback -> the default simple implementation
log.debug("No WorkspaceAccessManager configured; using default.");
workspaceAccessManager = new SimpleWorkspaceAccessManager();
}
workspaceAccessManager.init(systemSession);
initialized = true;
}
/**
* @see JackrabbitSecurityManager#dispose(String)
*/
public void dispose(String workspaceName) {
checkInitialized();
// nop
}
/**
* @see JackrabbitSecurityManager#close()
*/
public void close() {
checkInitialized();
}
/**
* @see JackrabbitSecurityManager#getAccessManager(Session,AMContext)
*/
public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
checkInitialized();
try {
String wspName = session.getWorkspace().getName();
AccessControlProvider acP = getAccessControlProvider(systemSession, wspName);
AccessManagerConfig amc = config.getAccessManagerConfig();
AccessManager accessMgr;
if (amc == null) {
accessMgr = new SimpleAccessManager();
} else {
accessMgr = (AccessManager) amc.newInstance();
}
accessMgr.init(amContext, acP, workspaceAccessManager);
return accessMgr;
} catch (AccessDeniedException ade) {
// re-throw
throw ade;
} catch (Exception e) {
// wrap in RepositoryException
String msg = "failed to instantiate AccessManager implementation: " + SimpleAccessManager.class.getName();
log.error(msg, e);
throw new RepositoryException(msg, e);
}
}
/**
* @see JackrabbitSecurityManager#getPrincipalManager(Session)
*/
public synchronized PrincipalManager getPrincipalManager(Session session)
throws RepositoryException {
checkInitialized();
if (session instanceof SessionImpl) {
SessionImpl sImpl = ((SessionImpl)session);
return new PrincipalManagerImpl(sImpl, principalProviderRegistry.getProviders());
} else {
throw new RepositoryException("Internal error: SessionImpl expected.");
}
}
/**
* @see JackrabbitSecurityManager#getUserManager(Session)
*/
public UserManager getUserManager(Session session) throws RepositoryException {
checkInitialized();
throw new UnsupportedRepositoryOperationException("UserManager not supported.");
}
/**
* @see JackrabbitSecurityManager#getUserID(javax.security.auth.Subject, String)
*/
public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
String uid = null;
// if SimpleCredentials are present, the UserID can easily be retrieved.
Iterator<SimpleCredentials> creds = subject.getPublicCredentials(SimpleCredentials.class).iterator();
if (creds.hasNext()) {
SimpleCredentials sc = creds.next();
uid = sc.getUserID();
} else if (anonymID != null && !subject.getPrincipals(AnonymousPrincipal.class).isEmpty()) {
uid = anonymID;
} else {
// assume that UserID and principal name
// are the same (not totally correct) and thus return the name
// of the first non-group principal.
for (Principal p : subject.getPrincipals()) {
if (!(p instanceof Group)) {
uid = p.getName();
break;
}
}
}
return uid;
}
/**
* Creates an AuthContext for the given {@link Credentials} and
* {@link Subject}.<br>
* This includes selection of applicatoin specific LoginModules and
* initalization with credentials and Session to System-Workspace
*
* @return an {@link AuthContext} for the given Credentials, Subject
* @throws RepositoryException in other exceptional repository states
*/
public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
throws RepositoryException {
checkInitialized();
return authCtxProvider.getAuthContext(creds, subject, systemSession, principalProviderRegistry, adminID, anonymID);
}
//--------------------------------------------------------------------------
private void checkInitialized() {
if (!initialized) {
throw new IllegalStateException("Not initialized");
}
}
/**
* Simple Principal provider
*/
private class SimplePrincipalProvider implements PrincipalProvider {
private final Map<String, Principal> principals = new HashMap<String, Principal>();
private SimplePrincipalProvider() {
if (adminID != null) {
principals.put(adminID, new AdminPrincipal(adminID));
}
if (anonymID != null) {
principals.put(anonymID, new AnonymousPrincipal());
}
EveryonePrincipal everyone = EveryonePrincipal.getInstance();
principals.put(everyone.getName(), everyone);
}
public Principal getPrincipal(String principalName) {
if (principals.containsKey(principalName)) {
return principals.get(principalName);
} else {
return new UserPrincipal(principalName);
}
}
public PrincipalIterator findPrincipals(String simpleFilter) {
return findPrincipals(simpleFilter, PrincipalManager.SEARCH_TYPE_ALL);
}
public PrincipalIterator findPrincipals(String simpleFilter, int searchType) {
Principal p = getPrincipal(simpleFilter);
if (p == null) {
return PrincipalIteratorAdapter.EMPTY;
} else if (p instanceof Group && searchType == PrincipalManager.SEARCH_TYPE_NOT_GROUP ||
!(p instanceof Group) && searchType == PrincipalManager.SEARCH_TYPE_GROUP) {
return PrincipalIteratorAdapter.EMPTY;
} else {
return new PrincipalIteratorAdapter(Collections.singletonList(p));
}
}
public PrincipalIterator getPrincipals(int searchType) {
PrincipalIterator it;
switch (searchType) {
case PrincipalManager.SEARCH_TYPE_GROUP:
it = new PrincipalIteratorAdapter(Collections.singletonList(EveryonePrincipal.getInstance()));
break;
case PrincipalManager.SEARCH_TYPE_NOT_GROUP:
Set<Principal> set = new HashSet<Principal>(principals.values());
set.remove(EveryonePrincipal.getInstance());
it = new PrincipalIteratorAdapter(set);
break;
case PrincipalManager.SEARCH_TYPE_ALL:
it = new PrincipalIteratorAdapter(principals.values());
break;
// no default
default:
throw new IllegalArgumentException("Unknown search type " + searchType);
}
return it;
}
public PrincipalIterator getGroupMembership(Principal principal) {
if (principal instanceof EveryonePrincipal) {
return PrincipalIteratorAdapter.EMPTY;
} else {
return new PrincipalIteratorAdapter(Collections.singletonList(EveryonePrincipal.getInstance()));
}
}
public void init(Properties options) {
// nothing to do
}
public void close() {
// nothing to do
}
public boolean canReadPrincipal(Session session, Principal principal) {
return true;
}
}
}