Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java (revision 0) @@ -0,0 +1,661 @@ +/* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.core.config.ConfigurationException; +import org.apache.jackrabbit.core.config.BeanConfig; +import org.apache.jackrabbit.core.config.AccessManagerConfig; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; +import org.apache.jackrabbit.core.config.WorkspaceConfig; +import org.apache.jackrabbit.core.security.authorization.ACLManagerImpl; +import org.apache.jackrabbit.core.security.authorization.DefaultACLProvider; +import org.apache.jackrabbit.core.security.authorization.DefaultCompiledACLProvider; +import org.apache.jackrabbit.core.security.authorization.DefaultACL; +import org.apache.jackrabbit.core.security.authentication.AuthContextProvider; +import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; +import org.apache.jackrabbit.core.security.principal.DefaultPrincipalProvider; +import org.apache.jackrabbit.core.security.principal.DefaultProviderRegistry; +import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl; +import org.apache.jackrabbit.core.security.user.UserManagerImpl; +import org.apache.jackrabbit.core.security.spi.ACLProvider; +import org.apache.jackrabbit.core.security.spi.CompiledACLProvider; +import org.apache.jackrabbit.core.security.spi.WorkspaceACLProvider; +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; +import org.apache.jackrabbit.core.security.spi.ACLProviderFactory; +import org.apache.jackrabbit.core.security.JackrabbitSecurityManager; +import org.apache.jackrabbit.core.security.SecuritySetup; +import org.apache.jackrabbit.core.security.AccessManager; +import org.apache.jackrabbit.core.security.AMContext; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.AuthContext; +import org.apache.jackrabbit.name.QName; +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.security.PrincipalManager; +import org.apache.jackrabbit.security.ACLManager; +import org.apache.jackrabbit.security.ActionSet; +import org.apache.jackrabbit.security.ACL; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.jcr.AccessDeniedException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Value; +import javax.jcr.Node; +import javax.jcr.Session; +import javax.security.auth.Subject; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.HashSet; +import java.security.Principal; +import java.io.File; + +/** + * The security manager acts as central managing class for all security related + * operations on a low-level non-protected level. It manages the + * + */ +public class DefaultSecurityManager implements JackrabbitSecurityManager { + + // TODO: should rather be placed in the core.security package. However protected access to SystemSession required to move here. + + /** + * the default logger + */ + private static final Logger log = LoggerFactory.getLogger(DefaultSecurityManager.class); + + /** + * the repository implementation + */ + private final RepositoryImpl rep; + + /** + * session on the system workspace. + */ + private final SystemSession securitySession; + + /** + * System user manager. Implementation needed here for the DefaultPrincipalProvider. + */ + private final UserManagerImpl systemUserManager; + + /** + * System Sessions PrincipalMangager used for internal access to Principals + */ + private final PrincipalManager systemPrincipalManager; + + /** + * contains the acl providers per workspace. + * key={@link String} workspacename, + * value={@link ACLProvider} + */ + private final Map aclProviders = new HashMap(); + + /** + * Reference to the fallback ACLProviderFactory + */ + private final ACLProviderFactory defaultACLProviderFac; + + /** + * map of {@link ACLProviderFactory}s. per Workspace + */ + private final Map aclProviderFactories = new HashMap(); + + /** + * the configured WorkspaceACLProvider + */ + private final WorkspaceACLProvider workspaceAclProvider; + + /** + * the principal provider registry + */ + private final PrincipalProviderRegistry principalProviderRegistry; + + /** + * set-up utillity serving as configuration + */ + private final SecuritySetup setup; + + /** + * factory for login-context {@see Repository#login()) + */ + private final AuthContextProvider authContextProvider; + + /** + * Creates a new AccessControllManager + * + * @throws RepositoryException + */ + public DefaultSecurityManager(SystemSession securitySession, + RepositoryImpl repository) + throws RepositoryException { + + this.securitySession = securitySession; + this.rep = repository; + // create the system userManager. + systemUserManager = new UserManagerImpl(securitySession); + + RepositoryConfig config = rep.getConfig(); + Properties props = config.getAccessManagerConfig().getParameters(); + this.setup = new SecuritySetup(securitySession, systemUserManager, + config.getDefaultWorkspaceName(), + props.getProperty(SecuritySetup.KEY_PROTECTION_LEVEL, + SecuritySetup.OPTION_PROTECTION_LEVEL_HIGH)); + + // init default acl-provider-factories as fall-back here-> if it failes + // start-up failes + defaultACLProviderFac = new DefaultACLProviderFactory(); + + workspaceAclProvider = createWorkspaceACLProvider(); + + // read an prepare LoginModule-Configuration + authContextProvider = new AuthContextProvider(config.getAppName(), config.getLoginModuleConfig()); + if (authContextProvider.isJAAS()) { + log.info("init: use JAAS login-configuration for " + config.getAppName()); + } else if (authContextProvider.isLocal()) { + log.info("init: use Repository Login-Configuration for " + config.getAppName()); + } else { + StringBuffer msg = new StringBuffer("Neither JAAS nor"); + msg.append("RepositoryConfig contained a valid Configuriation for "); + msg.append(config.getAppName()); + log.error(msg.toString()); + throw new RepositoryException(msg.toString()); + } + + // create additional principal providers and register them + // first assert minimal principals... + PrincipalProvider defaultProvider = new DefaultPrincipalProvider(securitySession, systemUserManager); + principalProviderRegistry = new DefaultProviderRegistry(defaultProvider); + Map[] moduleConfig = authContextProvider.getModuleConfig(); + for (int i = 0; i < moduleConfig.length; i++) { + // only add config, if they define a principal provider class + if (moduleConfig[i].containsKey(DefaultProviderRegistry.PRINCIPAL_PROVIDER_CLASS)) { + Properties ps = new Properties(); + Iterator iter = moduleConfig[i].keySet().iterator(); + while (iter.hasNext()) { + Object key = iter.next(); + ps.put(key, moduleConfig[i].get(key).toString()); + } + principalProviderRegistry.addProvider(ps); + } + } + + systemPrincipalManager = new PrincipalManagerImpl(securitySession.getSubject(), principalProviderRegistry.getProviders()); + } + + //------------------------------------------< JackrabbitSecurityManager >--- + /** + * @see JackrabbitSecurityManager#getAccessManager(Session) + */ + public AccessManager getAccessManager(Session session) throws RepositoryException { + if (!(session instanceof SessionImpl)) { + throw new RepositoryException("Failed to build AccessManager. SessionImpl expected."); + } + + SessionImpl sImpl = (SessionImpl) session; + AccessManagerConfig amConfig = rep.getConfig().getAccessManagerConfig(); + try { + String wspName = sImpl.getWorkspace().getName(); + CompiledACLProvider ap = createCompiledACLProvider(sImpl.getSubject(), wspName); + AMContext ctx = new AMContext(new File(rep.getConfig().getHomeDir()), + rep.getFileSystem(), + sImpl.getSubject(), + sImpl.getItemStateManager().getAtticAwareHierarchyMgr(), + rep.getNamespaceRegistry(), + wspName, + sImpl.getItemStateManager(), + ap, + workspaceAclProvider); + + AccessManager accessMgr = (AccessManager) amConfig.newInstance(); + accessMgr.init(ctx); + return accessMgr; + } catch (AccessDeniedException ade) { + // re-throw + throw ade; + } catch (Exception e) { + // wrap in RepositoryException + String msg = "failed to instantiate AccessManager implementation: " + amConfig.getClassName(); + log.error(msg, e); + throw new RepositoryException(msg, e); + } + } + + /** + * @see JackrabbitSecurityManager#getACLManager(Session, Subject) + */ + public ACLManager getACLManager(Session session, Subject subject) throws RepositoryException { + + ACLProvider aclProvider = createACLProvider(session.getWorkspace().getName()); + return new ACLManagerImpl(session, subject, aclProvider); + } + + /** + * @see JackrabbitSecurityManager#getPrincipalManager(Session, Subject) + */ + public synchronized PrincipalManager getPrincipalManager(Session session, Subject subject) + throws RepositoryException { + if (session == securitySession) { + return systemPrincipalManager; + } else { + return new PrincipalManagerImpl(subject, principalProviderRegistry.getProviders()); + } + } + + /** + * @see JackrabbitSecurityManager#getUserManager(Session) + */ + public UserManager getUserManager(Session session) throws RepositoryException { + if (session == securitySession) { + return systemUserManager; + } else if (session instanceof SessionImpl) { + return new UserManagerImpl(((SessionImpl) session).createSession(rep.repConfig.getSecurityConfig().getWorkspaceName())); + } else { + throw new RepositoryException("Internal error: JackrabbitSession expected."); + } + } + + /** + * @see JackrabbitSecurityManager#dispose(String) + */ + public void dispose(String workspaceName) { + synchronized (aclProviders) { + ACLProvider prov = (ACLProvider) aclProviders.remove(workspaceName); + if (prov != null) { + prov.close(); + } + } + } + + /** + * remove any unnecessesary references and clear the ACLCache + * @see JackrabbitSecurityManager#close() + */ + public void close() { + Iterator itr = aclProviders.values().iterator(); + while (itr.hasNext()) { + ((ACLProvider) itr.next()).close(); + } + } + + /** + * Get an AuthoContext for the given Credentials and an empty {@link Subject} + * + * @param creds + * @return + * @throws RepositoryException + * @see #getAuthContext(Credentials, Subject) + */ + public AuthContext getAuthContext(Credentials creds) throws RepositoryException { + return getAuthContext(creds, new Subject()); + } + + /** + * Creates an AuthContext for the given {@link Credentials} and + * {@link Subject}.
+ * 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) + throws RepositoryException { + return authContextProvider.getAuthContext(creds, subject, securitySession, principalProviderRegistry); + } + + /** + * Access the initalized ACLProviderFactory configured for this instance
+ * If no configuration can be found, the default factory is returned. + * Impelmentation keeps references to initalized factories for caching reasons + * + * @param workspaceName + * @return ACLProviderFactory configured or default one + * @throws ConfigurationException in case the configuration for the provider + * is invalid + */ + private synchronized ACLProviderFactory getACLProviderFactory(String workspaceName) + throws RepositoryException { + + ACLProviderFactory fac = null; + if (aclProviderFactories.containsKey(workspaceName)) { + fac = (ACLProviderFactory) aclProviderFactories.get(workspaceName); + } else { + WorkspaceConfig wspCfg = rep.getConfig().getWorkspaceConfig(workspaceName); + if (wspCfg != null) { + WorkspaceSecurityConfig secConf = wspCfg.getSecurityConfig(); + if (secConf != null) { + BeanConfig facCfg = secConf.getAclProviderFactoryConfig(); + if (facCfg != null) { + fac = (ACLProviderFactory) facCfg.newInstance(); + fac.init(this); + } + } + } + if (fac == null) { + fac = defaultACLProviderFac; + } + aclProviderFactories.put(workspaceName, fac); + } + return fac; + } + + /** + * Returns the acl provider for the given workspace name + * + * @param workspaceName + * @return + * @throws RepositoryException + */ + private ACLProvider createACLProvider(String workspaceName) + throws RepositoryException { + + synchronized (aclProviders) { + ACLProvider provider = (ACLProvider) aclProviders.get(workspaceName); + if (provider == null) { + SystemSession systemSession = rep.getSystemSession(workspaceName); + + ACLProviderFactory fac = getACLProviderFactory(workspaceName); + provider = fac.createACLProvider(systemSession); + + // never should happen + if (provider == null) { + provider = defaultACLProviderFac.createACLProvider(systemSession); + log.debug("createACLProvider: no factory configured or " + + "factory ignored workspace " + workspaceName + ": take default"); + } + aclProviders.put(workspaceName, provider); + } + return provider; + } + } + + /** + * Returns the acl provider for the given workspace name + * + * @param subject + * @param wspName + * @return + * @throws RepositoryException + */ + private CompiledACLProvider createCompiledACLProvider(Subject subject, + String wspName) + throws RepositoryException { + + ACLProviderFactory fac = getACLProviderFactory(wspName); + CompiledACLProvider provider = fac.createCompiledACLProvider(subject, wspName); + + //should never happen + if (provider == null) { + log.warn("No compiled acl provider for given subject on " + wspName); + } + return provider; + } + + /** + * @return the WorkspaceACLProvider responsible for the repository. + */ + private WorkspaceACLProvider createWorkspaceACLProvider() throws RepositoryException { + WorkspaceACLProvider prov; + BeanConfig config = rep.getConfig().getSecurityConfig().getWorkspaceACLConfig(); + if (config != null) { + prov = (WorkspaceACLProvider) config.newInstance(); + prov.init(this); + } else { + prov = new DefaultWorkspaceACLProvider(); + } + return prov; + } + + //------------------------------------------------------< inner classes >--- + /** + * DefaultACLProviderFactory + * Creates {@link DefaultACLProvider} instances
+ * Accepts all Workspaces, if initalized with a {@link DefaultSecurityManager} + * If a Provider for a Workspace is requested, that has no ACL defined, + * it triggers set-up of inital-workspace protection + * {@link SecuritySetup#setUpProtection(Session)} + * + * @see DefaultACLProvider + * @see ACLProviderFactory + */ + private class DefaultACLProviderFactory implements ACLProviderFactory { + + private final WorkspaceACLProvider wspAclProvider; + + private DefaultACLProviderFactory() throws RepositoryException { + wspAclProvider = new DefaultWorkspaceACLProvider(); + } + + //---------------------------------------------< ACLProviderFactory >--- + /** + * {@inheritDoc} + */ + public void init(JackrabbitSecurityManager securityMgr) { + // nothing to do + } + + /** + * {@inheritDoc} + */ + public void close() throws RepositoryException { + // nothing to do + } + + /** + * {@inheritDoc} + */ + public synchronized ACLProvider createACLProvider(Session wspSystemSession) + throws RepositoryException { + if (wspSystemSession instanceof SystemSession) { + // check if the requested workspace's security is initialized + setup.setUpProtection(wspSystemSession); + return new DefaultACLProvider(wspSystemSession, ((SystemSession) wspSystemSession).getItemManager()); + } else { + throw new RepositoryException("System session expected."); + } + } + + /** + * {@inheritDoc} + */ + public CompiledACLProvider createCompiledACLProvider(Subject subject, + String workspaceName) + throws RepositoryException { + ACLProvider provider = DefaultSecurityManager.this.createACLProvider(workspaceName); + return new DefaultCompiledACLProvider(provider, subject); + } + + /** + * {@inheritDoc} + */ + public WorkspaceACLProvider createWorkspaceACLProvider() throws RepositoryException { + return wspAclProvider; + } + } + + /** + * WorkspaceACLProvider that stores the workspace ACLs in the + * system workspace of the repository on a an Node of type rep:workspace + * with the name of the corresponding workspace. Access to a workspace is + * granted if their ACL grants the {@link ActionSet#ACTION_NAME_WORKSPACE_ACCESS} + * action for that 'workspace node'. + */ + private class DefaultWorkspaceACLProvider implements WorkspaceACLProvider { + + // rep:Workspace + private final QName QNT_REP_WORKSPACE = new QName(QName.NS_REP_URI, "Workspace"); + + // rep:WorkspaceAccess + private final QName QNT_REP_WORKSPACE_ACCESS = new QName(QName.NS_REP_URI, "WorkspaceAccess"); + + // rep:workspaces node name + private final QName QNODE_REP_WORKSPACES = new QName(QName.NS_REP_URI, "workspaces"); + + /** the root path of the workspace ACL nodes */ + private final String securityRoot; + + /** + * Initializes the rep:workspaces node + * + * @throws RepositoryException + */ + private DefaultWorkspaceACLProvider() throws RepositoryException { + try { + NodeImpl node = (NodeImpl) securitySession.getRootNode(); + if (node.hasNode(QNODE_REP_WORKSPACES)) { + node = node.getNode(QNODE_REP_WORKSPACES); + } else { + NodeImpl tmp = node.addNode( + QNODE_REP_WORKSPACES, + QNT_REP_WORKSPACE_ACCESS, null); + node.save(); + node = tmp; + } + securityRoot = node.getPath(); + } catch (AccessDeniedException e) { + log.error("do not have sufficent priveleges on: " + securitySession.getWorkspace().getName()); + throw e; + } + } + + //-------------------------------------------< WorkspaceACLProvider >--- + /** + * {@inheritDoc} + */ + public void init(JackrabbitSecurityManager securityManager) throws RepositoryException { + // nothing to do here. + } + + /** + * {@inheritDoc} + */ + public void close() throws RepositoryException { + // nothing to do here. + } + + /** + * {@inheritDoc} + */ + public ACL getAcl(String workspaceName) + throws RepositoryException, NoSuchWorkspaceException { + // check if the workspace exists + if (rep.getConfig().getWorkspaceConfig(workspaceName) == null) { + throw new NoSuchWorkspaceException(workspaceName); + } + StringBuffer absPath = new StringBuffer(securityRoot); + absPath.append("/"); + absPath.append(workspaceName); + absPath.append("/"); + absPath.append(SecurityConstants.N_REP_ACL); + if (!securitySession.itemExists(absPath.toString())) { + setWorkspaceAccess( + workspaceName, + getWorkspaceAccessPrincipals(workspaceName) + ); + } + return new DefaultACL((NodeImpl) securitySession.getItem(absPath.toString()), null); + } + + //---------------------------------------------------------------------- + /** + * Returns the names of the principal that are granted workspace access + * per default. + * + * @param workspaceName + * @return the names of the principals + */ + private String[] getWorkspaceAccessPrincipals(String workspaceName) { + + Set principals = new HashSet(2); + Principal admins = systemPrincipalManager.getPrincipal("administrators"); + if (admins != null) { + principals.add(admins.getName()); + } else { + log.warn("no administrators-group found: -> no one granted access" + + " to workspace " + workspaceName); + } + + if (rep.getConfig().getDefaultWorkspaceName().equals(workspaceName)) { + principals.add(systemPrincipalManager.getEveryone().getName()); + } + return (String[]) principals.toArray(new String[principals.size()]); + } + + /** + * Add the workspace-access acl to the given workspace name for the given + * principals, if not allready saved + * + * @param wspName name of the workspace to edit + * @param principalNames principals to be allowed to access the workspace + * @throws RepositoryException + */ + private void setWorkspaceAccess(String wspName, String[] principalNames) + throws RepositoryException { + + NodeImpl editRoot = (NodeImpl) securitySession.getItem(securityRoot); + NodeImpl wsp = getOrCreate(editRoot, + new QName(QName.NS_DEFAULT_URI, wspName), + QNT_REP_WORKSPACE); + wsp = getOrCreate(wsp, + SecurityConstants.QN_REP_ACL, + SecurityConstants.QNT_REP_ACL); + + Value[] actions = new Value[] {securitySession.getValueFactory().createValue(ActionSet.ACTION_NAME_WORKSPACE_ACCESS)}; + for (int i=0;i + * It implements an authentication by User-ID / Password - Credentials + * {@link SimpleCredentials}

+ * On successfull authentication it relates this credentials to principals + * by the use of * the {@link PrincipalProvider} configured for this LoginModule

+ * Jackrabbit knows about two typs of Login, the one for its "own" Credentials and one + * for Impersonation.
+ * {@link #login()}-method dispatches to + * {@link #authenticate(java.security.Principal, javax.jcr.Credentials)} and + * {@link #impersonate(java.security.Principal, javax.jcr.Credentials)} for the + * two cases for implemenations.
+ * This LoginModule imlements default behaviours for this methods. + * + * @see LoginModule + */ +public abstract class AbstractLoginModule implements LoginModule { + + private static final Logger log = LoggerFactory.getLogger(AbstractLoginModule.class); + + private boolean denyAnonymous; + private String anonymousPrincipal; + private Principal everyonePrincipal; + private CallbackHandler callbackHandler; + + protected Principal principal; + protected SimpleCredentials credentials; + protected boolean initialized; + protected Subject subject; + protected PrincipalProvider principalProvider; + protected UserManager userManager; + + private static final String DEFAULT_ANONYMOUS_PRINCIPAL = "anonymous"; + private static final String KEY_DENY_ANONYMOUS = "deny_anonymous_access"; + private static final String KEY_ANONYMOUS_PRINCIPAL = "anonymous_principal"; + private static final String KEY_SIMPLE_CREDENTIALS = "org.apache.jackrabbit.credentials.simple"; + private static final String KEY_LOGIN_NAME = "javax.security.auth.login.name"; + private static final String KEY_LOGIN_PWD = "javax.security.auth.login.password"; + + private Map sharedState; + + /** + * Initialize this LoginModule.
This abstract implementation, initalizes + * the following fields for later use:

  1. {@link JackrabbitSession} + * provided via {@link CallbackHandler}
  2. {@link + * PrincipalManager} for group-membership resoultion
  3. {@link + * PrincipalProvider} for user-{@link Principal} resolution.
  4. {@link + * AbstractLoginModule#KEY_DENY_ANONYMOUS} option is evaluated
  5. {@link + * AbstractLoginModule#DEFAULT_ANONYMOUS_PRINCIPAL} option is evaluated + * Implementations are called via + * {@link #doInit(CallbackHandler, JackrabbitSession, Map)} to implement + * additional initalization + * + * @param subject the Subject to be authenticated.

    + * @param callbackHandler a CallbackHandler for communicating + * with the end user (prompting for usernames and + * passwords, for example).

    + * @param sharedState state shared with other configured + * LoginModules.

    + * @param options options specified in the login Configuration + * for this particular LoginModule. + * @see LoginModule#initialize(Subject, CallbackHandler, Map, Map) + * @see #doInit(CallbackHandler, JackrabbitSession, Map) + */ + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + + //try repository access + try { + log.debug("initalize: "); + Properties configProps = new Properties(); + configProps.putAll(options); + RepositoryCallback repositoryCb = new RepositoryCallback(); + callbackHandler.handle(new Callback[]{repositoryCb}); + + // retrieve the principal-provider configured for this module + PrincipalProviderRegistry registry = repositoryCb.getPrincipalProviderRegistry(); + if (configProps.containsKey(PrincipalProviderRegistry.PRINCIPAL_PROVIDER_NAME)) { + String providerName = configProps.getProperty(PrincipalProviderRegistry.PRINCIPAL_PROVIDER_NAME); + principalProvider = registry.getProvider(providerName); + } + if (principalProvider == null) { + principalProvider = registry.getDefault(); + if (principalProvider==null) { + return; // abort even not a default provider + } + } + log.debug(" : PrincipalProvider -> ''{}''", principalProvider.getClass().getName()); + + userManager = repositoryCb.getSession().getUserManager(); + log.debug(" : UserManager -> ''{}''", userManager.getClass().getName()); + + //call implementation for additional setup + doInit(callbackHandler, repositoryCb.getSession(), options); + + String daOption = (String) options.get(KEY_DENY_ANONYMOUS); + denyAnonymous = Boolean.valueOf(daOption).booleanValue(); + if (!denyAnonymous) { + anonymousPrincipal = configProps.getProperty(KEY_ANONYMOUS_PRINCIPAL, DEFAULT_ANONYMOUS_PRINCIPAL); + } + + //common jaas state variables + this.callbackHandler = callbackHandler; + this.subject = subject; + everyonePrincipal = repositoryCb.getSession().getPrincipalManager().getEveryone(); + + //log config values for debug + if (log.isDebugEnabled()) { + Iterator itr = options.keySet().iterator(); + while (itr.hasNext()) { + String option = (String) itr.next(); + log.debug(" : {} -> ''{}''", option, options.get(option)); + + } + } + + this.sharedState = sharedState; + initialized = (this.subject != null); + + } catch (Exception e) { + log.error("LoginModule failed to initialize.", e); + } + } + + /** + * Implementations may set-up their own state. E. g. a DataSource if it is + * authorized against an external System + * + * @param callbackHandler as passed by {@link javax.security.auth.login.LoginContext} + * @param session to security-workspace of Jackrabbit + * @param options options from Logini config + * @throws LoginException in case initializeaiton failes + */ + protected abstract void doInit(CallbackHandler callbackHandler, + JackrabbitSession session, + Map options) + throws LoginException; + + /** + * Method to authenticate a Subject (phase 1).

    + * The login is devided into 3 Phases:

    + * + * 1) User-ID resolution
    + * In a first step it is tried to resolve a User-ID for further validation. + * As for JCR the identification is marked with the {@link Credentials} + * interface, credentials are accessed in this phase.
    If no User-ID can + * be found, anonymous access is granted with the ID of {@link + * SecurityConstants#ANONYMOUS_ID}. Anonymous access can be switched off by + * setting the corresponding configuration option with the name {@link + * #KEY_DENY_ANONYMOUS deny_anonymous_access} to true. + *
    This implementation uses two helper-methods, which allow for + * customization: + *

      + *
    • {@link #getCredentials()}
    • and + *
    • {@link #getUserID(Credentials)}
    • + *
    + *

    + * + * 2) User-Principal resolution
    + * In a second step it is tested, if the resolved User-ID belongs to a User + * known to the system, i.e. if the {@link PrincipalProvider} has a principal + * for the given ID and the principal can be found via + * {@link PrincipalProvider#searchPrincipal(String)}.
    + * The provider implemenation can be set by the configuration option with the + * name {@link PrincipalProviderRegistry#PRINCIPAL_PROVIDER_NAME principal_provider.name}. + * If the option is missing, the system default prinvipal provider will + * be used.

    + * + * 3) Verfication
    + * There are two cases, how the User-ID can be verfied: + * Either the login is the result of an impersonation request (see + * {@link javax.jcr.Session#impersonate(Credentials)} or of a login to the Repository ({@link + * javax.jcr.Repository#login(Credentials)}). The concrete implementation + * of the LoginModule is responsible for both impersonation and login: + *

      + *
    • {@link #authenticate(Principal, Credentials)}
    • + *
    • {@link #impersonate(Principal, Credentials)}
    • + *
    + * + * Under the following conditions, the login process is aborted and the + * module is marked to be ignored: + *
      + *
    • No User-ID could be resolve, and anyonymous access is switched off
    • + *
    • No Principal is found for the User-ID resolved
    • + *
    + * + * Under the follwoing conditions, the login process is marked to be invalid + * by throwing an LoginException: + *
      + *
    • It is an impersonation request, but the impersonator is not allowed + * to impersonate to the requested User-ID
    • + *
    • The user tries to login, but the Credentials can not be verified.
    • + *
    + *

    + * The LoginModule keeps the Credentials and the Principal as instance fields, + * to mark that login has been successfull. + * + * @return true if the authentication succeeded, or false if this + * LoginModule should be ignored. + * @throws LoginException if the authentication fails + * @see LoginModule#login() + * @see #getCredentials() + * @see #getUserID(Credentials) + * @see #getImpersonator(Credentials) + */ + public boolean login() throws LoginException { + if (!initialized) { + log.warn("Can't login initalization failed beforehand"); + return false; + } + + //check for availablity of Credentials; + Credentials creds = getCredentials(); + if (creds == null) { + log.warn("login: no credentials available" + + " -> try anonymous authentication attempt"); + } + try { + Principal userPrincipal = getPrincipal(creds); + if (userPrincipal == null) { + // unknown principal or a Group-principal + log.debug("login: unknown User ''{}'' -> set to ignore."); + return false; + } + boolean authenticated; + // test for anonymous, impersonation or common authentication. + if (isAnonymous(creds)) { + authenticated = !denyAnonymous; + if (!authenticated) { + log.debug("login: no UserID found -> ignore authentication."); + } + } else if (isImpersonation(creds)) { + authenticated = impersonate(userPrincipal, creds); + } else { + authenticated = authenticate(userPrincipal, creds); + } + + // process authenticated user or return false + if (authenticated) { + credentials = (creds instanceof SimpleCredentials) ? + (SimpleCredentials) creds : + new SimpleCredentials(getUserID(creds), new char[0]); + principal = userPrincipal; + return true; + } + } catch (RepositoryException e) { + log.error("login: failed {}", e); + } + return false; + } + + /** + * Method to commit the authentication process (phase 2). + *

    + *

    This method is called if the LoginContext's overall authentication + * succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL + * LoginModules succeeded). + *

    + *

    If this LoginModule's own authentication attempt succeeded (checked + * by retrieving the private state saved by the login method), + * then this method associates relevant Principals and Credentials with the + * Subject located in the LoginModule. If this + * LoginModule's own authentication attempted failed, then this method + * removes/destroys any state that was originally saved. + *

    + * The login is considers as succeeded if the credentials field is set. If + * there is no principalstate. the login is considered as ignored. + *

    + * The implementation stores the principal assoziated to the UserID and all + * the Groups it is member of. {@link PrincipalManager#getGroupMembership(Principal)} + * An instance of (#link SimpleCredentials} containing only the UserID used + * to login is set to the Subject's public Credentials + * + * @return true if this method succeeded, or false if this + * LoginModule should be ignored. + * @throws LoginException if the commit fails + * @see LoginModule#commit() + * @see AbstractLoginModule#login() + */ + public boolean commit() throws LoginException { + + //check login-state + if (credentials == null) { + abort(); + } + if (!initialized || principal == null) { + return false; + } + + subject.getPrincipals().addAll(getPrincipals()); + subject.getPublicCredentials().add(credentials); + return true; + } + + /** + * Method to abort the authentication process (phase 2). + *

    + *

    This method is called if the LoginContext's overall authentication + * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL + * LoginModules did not succeed). + *

    + *

    If this LoginModule's own authentication attempt succeeded (checked + * by retrieving the private state saved by the login method), + * then this method cleans up any state that was originally saved. + *

    + *

    + * + * @return true if this method succeeded, or false if this + * LoginModule should be ignored. + * @throws LoginException if the abort fails + */ + public boolean abort() throws LoginException { + if (!initialized) { + return false; + } else { + this.sharedState.remove(KEY_SIMPLE_CREDENTIALS); + this.callbackHandler = null; + this.principal = null; + this.credentials = null; + return logout(); + } + } + + /** + * Method which logs out a Subject. + *

    + *

    An implementation of this method might remove/destroy a Subject's + * Principals and Credentials. + *

    + *

    + * + * @return true if this method succeeded, or false if this + * LoginModule should be ignored. + * @throws LoginException if the logout fails + */ + public boolean logout() throws LoginException { + Set thisPrincipals = subject.getPrincipals(); + Set thisCredentials = subject.getPublicCredentials(SimpleCredentials.class); + if (thisPrincipals == null || thisCredentials == null + || thisPrincipals.isEmpty() || thisCredentials.isEmpty()) { + return false; + } else { + thisPrincipals.removeAll(getPrincipals()); + thisCredentials.clear(); + return true; + } + } + + /** + * + * @param principal + * @param credentials + * @return true if Credentails authenticate, + * false if no Authentication can handle + * the given Credentials + * @throws javax.security.auth.login.FailedLoginException + * if the authentication failed. + * @see AbstractLoginModule#getAuthentication(java.security.Principal, javax.jcr.Credentials) + * @see AbstractLoginModule#authenticate(java.security.Principal, javax.jcr.Credentials) + */ + protected boolean authenticate(Principal principal, Credentials credentials) + throws RepositoryException, FailedLoginException { + + Authentication auth = getAuthentication(principal, credentials); + if(auth==null) { + return false; + } else if (auth.authenticate(credentials)){ + return true; + } + throw new FailedLoginException(); + } + + /** + * Indicate if the given Credentials are considered to be anonymous. + * This could be by its beeing empty, by a specific id, etc...

    + * Implemnts empty, null, or {@link SecurityConstants#ANONYMOUS_ID} + * + * @param credentials + * @return true if is anonymous + */ + private boolean isAnonymous(Credentials credentials) { + String userId = getUserID(credentials); + return userId == null || "".equals(userId) || SecurityConstants.ANONYMOUS_ID.equals(userId); + } + + /** + * Authentication Process associates a Principal to Credentials
    + * This Method resolves the Principal for the geiven Credentials, irrespective + * if the authenticate or not.
    + * if there is no Principal for the Credentials, the LoginModlue should + * be considered to be ignored.

    + * This Abstract implementation uses the {@link PrincipalProvider} configured + * for it, to resolve this association. + * It takes the {@link PrincipalProvider#searchPrincipal(String)} for the User-ID + * resolved by {@link #getUserID(Credentials)} + * + * @param credentials + * @return if credentials are associated to one or null if none found + */ + private Principal getPrincipal(Credentials credentials) { + Principal principal = null; + if (credentials == null || isAnonymous(credentials)) { + if (principalProvider.hasPrincipal(anonymousPrincipal)) { + principal = principalProvider.getPrincipal(anonymousPrincipal); + } else { + principal = everyonePrincipal; + } + } else { + // note: it is possible to get an null user-id, but than check + // for isAnonymous would be true + String userId = getUserID(credentials); + PrincipalIterator res = principalProvider.searchPrincipal(userId, PrincipalManager.SEARCH_TYPE_NOT_GROUP); + if (!res.hasNext()) { + log.warn("userId represents a Group and not a user principal"); + // ... and return null + } else { + principal = res.nextPrincipal(); + } + } + return principal; + } + + /** + * @return a Set of principals that contains the current user principal + * and all groups it is member of. + */ + protected Set getPrincipals() { + Set principals = new HashSet(); + principals.add(principal); + Iterator groups = principalProvider.memberOf(principal); + while (groups.hasNext()) { + principals.add(groups.next()); + } + return principals; + } + + /** + * Check if the current request is an Impersonation attempt.

    + * + * @param credentials potentially containing impersonation data + * @return true if this is an impersonation attempt + * @see #getImpersonator(Credentials) + */ + abstract protected boolean isImpersonation(Credentials credentials); + + /** + * Handles the impersonation of given Credentials.

    + * Current implementation takes {@link User} for the given Principal and + * delegates the check to {@link Impersonation#allows(javax.security.auth.Subject)} } + * + * @param principal + * @param credentials + * @return false, if there is no User to impersonate, + * true if impersonation is allowed + * @throws RepositoryException + * @throws FailedLoginException if credentials don't allow to impersonate to principal + */ + abstract protected boolean impersonate(Principal principal, Credentials credentials) + throws RepositoryException, LoginException; + + /** + * + * @param principal + * @param creds + * @return + * @throws RepositoryException + */ + abstract protected Authentication getAuthentication(Principal principal, Credentials creds) + throws RepositoryException; + + /** + * Method tries to resolve the {@link Credentials} used for login. It takes + * into account, that an authentication-extension of an allready + * authenticate {@link Subject} could take place

    Therefore the + * credentials are searchred for in the following search-order:

    1. + * Ask CallbackHandler for Credentials with use of {@link + * CredentialsCallback}. Expects {@link CredentialsCallback#getCredentials} + * to return an instance of {@link SimpleCredentials}.
    2. Ask the + * Subject for its public credentials {@link Subject#getPublicCredentials(Class)}, + * with {@link SimpleCredentials#getClass()} as argument.

      This enables to + * preauthenticate the Subject.

    NOTE: While the method signiture + * works with {@link Credentials} it actually searches and returns {@link + * SimpleCredentials}.
    This is done to allow implementations to make use + * of this abstract class, without beeing bound to a {@link Credentials} + * implementation. + * + * @return Credentials or null if not found + * @see #login() + */ + protected Credentials getCredentials() { + SimpleCredentials credentials = null; + if (sharedState.containsKey(KEY_SIMPLE_CREDENTIALS)) { + credentials = (SimpleCredentials) sharedState.get(KEY_SIMPLE_CREDENTIALS); + } else { + try { + CredentialsCallback callback = new CredentialsCallback(); + callbackHandler.handle(new Callback[]{callback}); + Credentials creds = callback.getCredentials(); + if (null != creds && creds instanceof SimpleCredentials) { + credentials = (SimpleCredentials) creds; + sharedState.put(KEY_SIMPLE_CREDENTIALS, credentials); + log.debug("login: found authentication attempt: take those" + + " credentials"); + } + } catch (UnsupportedCallbackException e) { + log.warn("login: Credentials-Callback not supported try " + + "Name-Callback"); + } catch (IOException e) { + log.error("login: Credentials-Callback failed: " + e.getMessage() + + ": try Name-Callback"); + } + } + // ask subject if still no credentials + if (null == credentials) { + Set preAuthCreds = subject.getPublicCredentials(SimpleCredentials.class); + if (!preAuthCreds.isEmpty()) { + credentials = + (SimpleCredentials) subject.getPublicCredentials(SimpleCredentials.class).iterator().next(); + log.debug("login: found pre-authenticated subject: take those" + + " credentials"); + } + } + return credentials; + } + + /** + * Method supports tries to acquire a UserID in the follwing order:
      + *
    1. Try to access it from the {@link Credentials} via {@link + * SimpleCredentials#getUserID()}
    2. Ask CallbackHandler for User-ID + * with use of {@link NameCallback}.
    + * + * @param credentials which, may contain a User-ID + * @return User-ID or null if none found + * @see #login() + */ + protected String getUserID(Credentials credentials) { + String userId = null; + if (null != credentials && (credentials instanceof SimpleCredentials)) { + userId = ((SimpleCredentials) credentials).getUserID(); + } + if (userId == null) { + try { + NameCallback callback = new NameCallback("User-ID: "); + callbackHandler.handle(new Callback[]{callback}); + userId = callback.getName(); + } catch (UnsupportedCallbackException e) { + log.warn("login: failed: Credentials- or NameCallback must be" + + " supported"); + } catch (IOException e) { + log.error("login: Name-Callback failed: " + e.getMessage()); + } + } + if (userId == null && sharedState.containsKey(KEY_LOGIN_NAME)) { + userId = (String) sharedState.get(KEY_LOGIN_NAME); + log.debug("login: no userId found, fallback to shared userId {}", userId); + } + return userId; + } + + /** + * Password is searched via 2 meachanisms:
    • Ask {@link + * SimpleCredentials#getPassword()} if the user-ID has been found in + * SimpleCredentials. {@link CredentialsCallback}. Expects {@link + * CredentialsCallback#getCredentials} to return an instance of {@link + * SimpleCredentials}.
    • Ask CallbackHandler for password with use + * of {@link PasswordCallback}, if user-ID was accessed via {@link + * NameCallback}.
    + * + * @param credentials to access password or empty char[] + * @see #login() + */ + protected char[] getPassword(Credentials credentials) { + char[] pwd = new char[0]; + if (credentials != null && credentials instanceof SimpleCredentials) { + pwd = ((SimpleCredentials) credentials).getPassword(); + } + if (pwd == null || pwd.length == 0) { + try { + PasswordCallback pass = new PasswordCallback("Password: ", false); + callbackHandler.handle(new Callback[]{pass}); + pwd = pass.getPassword(); + } catch (UnsupportedCallbackException e) { + log.warn("login: " + e.getCallback().getClass().getName()); + } catch (IOException e) { + log.error("login: Password-Callback failed: " + + e.getMessage() + ": empty-passwords not allowed"); + } + } + if ((pwd == null || pwd.length == 0) && + sharedState.containsKey(AbstractLoginModule.KEY_LOGIN_PWD)) { + + pwd = ((String) sharedState.get(AbstractLoginModule.KEY_LOGIN_PWD)).toCharArray(); + log.debug("login: Fallback to use Password from shared state"); + } + return pwd; + } + + /** + * Method tries to acquire an Impersonator in the follwing order: + *
    1. Try to access it from the {@link Credentials} via {@link + * SimpleCredentials#getAttribute(String)}
    2. Ask CallbackHandler for + * Impersonator with use of {@link ImpersonationCallback}.
    + * + * @param credentials which, may contain an impersonation Subject + * @return impersonation subject or null if non contained + * @see #login() + * @see #impersonate(java.security.Principal, javax.jcr.Credentials) + */ + protected Subject getImpersonator(Credentials credentials) { + Subject impersonator = null; + if (null == credentials) { + try { + ImpersonationCallback impers = new ImpersonationCallback(); + callbackHandler.handle(new Callback[]{impers}); + impersonator = impers.getImpersonator(); + } catch (UnsupportedCallbackException e) { + log.warn("login: " + e.getCallback().getClass().getName() + + " not supported: Impersionation is not possible"); + } catch (IOException e) { + log.error("login: Impersonation-Callback failed: " + + e.getMessage() + ": Impersionation is not possible"); + } + } else if (credentials instanceof SimpleCredentials) { + SimpleCredentials sc = (SimpleCredentials) credentials; + impersonator = (Subject) sc.getAttribute(SecurityConstants.IMPERSONATOR_ATTRIBUTE); + } + return impersonator; + } + + /** + * Helper to compaire the given plain password with a crypted one. + * Tries to extract an encryption from the crypted password + * + * @param savedPwd as stored in the authentication source + * @param password as given by login + * @return true if the both match + */ + public static boolean verifyCrypted(String savedPwd, char[] password) { + boolean verfied = false; + int start = savedPwd.indexOf("{"); + int end = savedPwd.indexOf("}"); + if (start == 0 && end > -1 && end < savedPwd.length()) { + String algo = savedPwd.substring(1, end); + String pwd = savedPwd.substring(end + 1); + try { + verfied = pwd.equals(Text.digest(algo, new String(password), "UTF-8")); + } catch (NoSuchAlgorithmException e) { + log.error("tried to verify password against unsupported encryption: " + + algo); + } catch (UnsupportedEncodingException e) { + log.error("tried to verify password against unsupported encoding: UTF-8"); + } + } + return verfied; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authentication\AbstractLoginModule.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AuthContextProvider.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AuthContextProvider.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AuthContextProvider.java (revision 0) @@ -0,0 +1,189 @@ +/* + * 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.authentication; + +import org.apache.jackrabbit.core.config.LoginModuleConfig; +import org.apache.jackrabbit.core.security.AuthContext; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry; +import org.apache.jackrabbit.api.JackrabbitSession; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * A AuthContextProvider selects how the current request for login is handled. + * It selects the mechanism and set-up according configuration.
    + * The handler selects if the JAAS-configuration + * {@link javax.security.auth.login.Configuration} is taken or the fall-back as + * configured via {@link org.apache.jackrabbit.core.config.RepositoryConfig}.

    + * This implementaion selects JAAS under the following condition: + *

      + *
    • a JAAS Login-{@link javax.security.auth.login.Configuration} is available + *
    • the configuration contains the configured application name + *
    + * If the conditions are not met AND a LoginModule is configured in + * {@link org.apache.jackrabbit.core.config.RepositoryConfig}, the one is taken

    + * The LoginContext is initalized with a Session to accesss the + * {@link javax.jcr.Workspace} containing the Login-Data + * {@link SecurityConstants#SYSTEM_WORKSPACE_NAME} + * + * It is invoked by {@link javax.jcr.Repository#login(Credentials)}. + */ +public class AuthContextProvider { + + private boolean initialized; + + /** + * configuration state -> if a JAAS Configuration exists for this application + */ + private boolean isJAAS; + + /** + * Configuration of the optional local LoginModule + */ + private final LoginModuleConfig config; + + /** + * Application Name for the LoginConfig entry + */ + private final String appName; + + /** + * @param jaasAppName LoginConfig application name used for this instance + * @param config optional LoginModule-configuration to use without JAAS + */ + public AuthContextProvider(String jaasAppName, LoginModuleConfig config) { + this.appName = jaasAppName; + this.config = config; + } + + /** + * @param credentials to authenticate + * @param subject subject to extend authentication + * @param session Session to pass to the login-modules + * @return context of for authentication and log-out + * @throws RepositoryException in any other exceptional state + */ + public AuthContext getAuthContext(Credentials credentials, + Subject subject, + JackrabbitSession session, + PrincipalProviderRegistry principalProviderRegistry) + throws RepositoryException { + + AuthContext context; + + // check, if another than default provider is configured + Properties configProps = new Properties(); + configProps.putAll(config.getParameters()); + + CallbackHandler cbHandler = new CredentialsCallbackHandler(credentials, session, principalProviderRegistry); + Principal everyOne = session.getPrincipalManager().getEveryone(); + + if (isJAAS()) { + context = new JAASAuthContext(appName, cbHandler, subject, everyOne); + } else if (isLocal()){ + context = new LocalAuthContext(config, cbHandler, subject, everyOne); + } else { + throw new RepositoryException("No Login-Configuration"); + } + return context; + } + + /** + * @return true if a application entry is available in a JAAS- {@link Configuration} + */ + public boolean isJAAS() { + if (!initialized) { + init(); + } + return isJAAS; + } + + /** + * @return true if {@link #isJAAS()} is false and a login-module is configured + */ + public boolean isLocal() { + return !(isJAAS() || config==null); + } + + /** + * @return options configured for the LoginModules to use + */ + public Properties[] getModuleConfig() { + Properties[] config = new Properties[0]; + if (isLocal()) { + config = new Properties[] {this.config.getParameters()}; + } else { + AppConfigurationEntry[] entries = getJAASConfig(); + if(entries != null) { + List tmp = new ArrayList(entries.length); + for(int i=0;i0; + initialized = true; + } + + /** + * @return all JAAS-Login Modules for this application or null if none + */ + private AppConfigurationEntry[] getJAASConfig() { + + // check if jaas-loginModule or fallback is configured + Configuration logins = null; + try { + logins = Configuration.getConfiguration(); + } catch (Exception e) { + // means no JAAS configuration file OR no permission to read it + } + if (logins != null) { + try { + return logins.getAppConfigurationEntry(appName); + } catch (Exception e) { + // WLP 9.2.0 throws IllegalArgumentException for unknown appName + } + } + return null; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authentication\AuthContextProvider.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java (revision 0) @@ -0,0 +1,139 @@ +/* + * 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.authentication; + +import org.apache.jackrabbit.util.Text; +import org.apache.jackrabbit.core.security.SecurityConstants; + +import javax.jcr.Credentials; +import javax.jcr.SimpleCredentials; +import java.util.Map; +import java.util.HashMap; +import java.security.NoSuchAlgorithmException; +import java.io.UnsupportedEncodingException; + + /** + * Tool to hash {@link javax.jcr.SimpleCredentials SimpleCredentials} + * aka an UserID / Password pair + * @author ckeller + */ + public class CryptedSimpleCredentials implements Credentials { + + private final Map attributes = new HashMap(); + private final String algorithem; + private final String password; + private final String userID; + + /** + * Take {@link javax.jcr.SimpleCredentials SimpleCredentials} and + * digest the password if it is plain-text + * + * @param credentials + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException + */ + public CryptedSimpleCredentials(SimpleCredentials credentials) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + + if (credentials.getPassword()==null || credentials.getPassword().length<=0) { + password = ""; + algorithem = null; + } else { + String algo = getAlgorithm(new String(credentials.getPassword())); + if (algo==null) { + algorithem = SecurityConstants.DEFAULT_DIGEST; + password = crypt(algorithem, new String(credentials.getPassword())); + } else { + algorithem = algo; + password = new String(credentials.getPassword()); + } + } + userID= credentials.getUserID(); + String[] attrNames = credentials.getAttributeNames(); + for (int i = 0; i < attrNames.length; i++) { + String attrName = attrNames[i]; + attributes.put(attrName, credentials.getAttribute(attrName)); + } + } + + public String getUserID() { + return userID; + } + + public Object getAttribute(String name) { + return attributes.get(name); + } + + public String[] getAttributeNames() { + return (String[]) attributes.keySet().toArray(new String[attributes.size()]); + } + + public String getAlgorithem() { + return algorithem; + } + + public String getPassword() { + return password; + } + + /** + * Compair this instance with an instance of SimpleCredentials. + * If one the other Credentials' Password is plain-text treies to encode + * it with the current Digest. + * + * @param sc + * @return true if {@link SimpleCredentials#getUserID() UserID} + * and {@link SimpleCredentials#getPassword() Password} match + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException + */ + public boolean match(SimpleCredentials sc) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + + if (getUserID().matches(sc.getUserID())) { + String toMatch = new String(sc.getPassword()); + String alg = getAlgorithm(toMatch); + if (alg==null && algorithem!=null) { + return crypt(algorithem, toMatch).equals(password); + } else if (alg!=null && algorithem==null) { + return crypt(password, alg).equals(toMatch); + } + + //this falsifies unequal algorithems, two + return toMatch.equals(password); + } + return false; + } + + private static String crypt(String algorithem, String pwd) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + + StringBuffer password = new StringBuffer(); + password.append("{").append(algorithem).append("}"); + password.append(Text.digest(algorithem, pwd.getBytes("UTF-8"))); + return password.toString(); + } + + private String getAlgorithm(String password) { + int end = password.indexOf("}"); + if (password.startsWith("{") && end>0) { + return password.substring(1,end); + } else { + return null; + } + } +} \ No newline at end of file Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authentication\CryptedSimpleCredentials.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/JackrabbitLoginModule.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/JackrabbitLoginModule.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/JackrabbitLoginModule.java (revision 0) @@ -0,0 +1,110 @@ +/* + * 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.authentication; + +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.Impersonation; +import org.apache.jackrabbit.security.User; +import org.apache.jackrabbit.security.Authentication; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import java.security.Principal; +import java.util.Map; + +/** + * LoginModule authenticates Credentials related to a {@link User} of the Repostiroy
    + * In any other case it is marked to be ignored.

    + * This Module can deal only with SimpleCredentials as it supports only one + * {@link Authentication Authentication}: + * {@link SimpleAuthentication SimpleAuthentication}.
    + * Impersonation is delegated to the User's + * {@link User#getImpersonation() Impersonation} + * @see AbstractLoginModule + */ +public class JackrabbitLoginModule extends AbstractLoginModule { + + /** + * Does nothing. + * + * @see AbstractLoginModule#doInit(CallbackHandler, JackrabbitSession, Map) + */ + protected void doInit(CallbackHandler callbackHandler, JackrabbitSession session, Map options) throws LoginException { + // nothing to do. + } + + protected Authentication getAuthentication(Principal principal, Credentials creds) + throws RepositoryException { + + Authorizable authrz = userManager.getAuthorizable(principal); + if (authrz == null || authrz.isGroup()) { + return null; + } + Authentication authentication = new SimpleAuthentication((User) authrz); + if (authentication.handles(creds)) { + return authentication; + } else { + return null; + } + } + + /** + * Return true, if an 'impersonator' can be retrieved. + * + * @param credentials + * @return + * @see #getImpersonator(Credentials) + */ + protected boolean isImpersonation(Credentials credentials) { + return getImpersonator(credentials) != null; + } + + /** + * Handles the impersonation of given Credentials.

    + * Current implementation takes {@link User} for the given Principal and + * delegates the check to {@link Impersonation#allows(javax.security.auth.Subject)} + * + * @param principal + * @param credentials + * @return false, if there is no User to impersonate, + * true if impersonation is allowed + * @throws javax.jcr.RepositoryException + * @throws javax.security.auth.login.FailedLoginException + * if credentials don't allow to impersonate to principal + * @see AbstractLoginModule#impersonate(Principal, Credentials) + */ + protected boolean impersonate(Principal principal, Credentials credentials) + throws RepositoryException, FailedLoginException { + + Authorizable authrz = userManager.getAuthorizable(principal); + if (authrz == null || authrz.isGroup()) { + return false; + } + Subject impersSubject = getImpersonator(credentials); + User user = (User) authrz; + if (user.getImpersonation().allows(impersSubject)) { + return true; + } else { + throw new FailedLoginException("attempt to impersonate denied for " + principal.getName()); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authentication\JackrabbitLoginModule.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/SimpleAuthentication.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/SimpleAuthentication.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/SimpleAuthentication.java (revision 0) @@ -0,0 +1,113 @@ +/* + * 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.authentication; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.collections.Predicate; +import org.apache.commons.collections.iterators.FilterIterator; +import org.apache.commons.collections.functors.InstanceofPredicate; +import org.apache.jackrabbit.security.Authentication; +import org.apache.jackrabbit.security.User; + +import javax.jcr.RepositoryException; +import javax.jcr.SimpleCredentials; +import javax.jcr.Credentials; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.security.NoSuchAlgorithmException; +import java.io.UnsupportedEncodingException; + +/** + * This {@link Authentication Authentication} implementation + * Handles all {@link javax.jcr.SimpleCredentials SimpleCredentials} stored + * for a given {@link org.apache.jackrabbit.security.User#getCredentials() User}.
    + * For verication the SimplCredentials + * {@link javax.jcr.SimpleCredentials#getUserID() UserID} and + * {@link javax.jcr.SimpleCredentials#getPassword() Password} are tested. + * If both are equal to the ones stored at the User, verification succeeded + * + * @see org.apache.jackrabbit.security.Authentication + * @see javax.jcr.SimpleCredentials + */ +public class SimpleAuthentication implements Authentication { + + private static final Logger log = LoggerFactory.getLogger(SimpleAuthentication.class); + + private final Collection credentials = new HashSet(); + + /** + * Create an Authentication for this User + * + * @param user to create the Authentication for + * @throws javax.jcr.RepositoryException + */ + public SimpleAuthentication(User user) throws RepositoryException { + Predicate predicate = new InstanceofPredicate(SimpleCredentials.class); + Iterator itr = new FilterIterator(user.getCredentials(), predicate); + while(itr.hasNext()) { + credentials.add(itr.next()); + } + } + + //------------------------------------------------< Authentication >-------- + + /** + * This Authentication is able to handle the validation of SimpleCredentials. + * + * @param credentials to test + * @return true if the given Credentials are of type + * {@link javax.jcr.SimpleCredentials SimpleCredentials} and if the + * User used to construct this Autentication + * has any SimpleCredentials + * @see Authentication#handles(javax.jcr.Credentials) + */ + public boolean handles(Credentials credentials) { + return this.credentials.size()>0 && credentials instanceof SimpleCredentials; + } + + /** + * Compairs any of the SimpleCredentials of the User + * with the one given.
    + * If both, UserID and Password of the credentials are equal, the authentication + * succeded and true is returned; + * + * @param toVerify + * @return true if the given Credentials' UserID/Passowrd pair match a Users's one + * @throws RepositoryException + */ + public boolean authenticate(Credentials toVerify) throws RepositoryException { + SimpleCredentials sCreds = (SimpleCredentials) toVerify; + Iterator itr = credentials.iterator(); + while(itr.hasNext()) { + try { + CryptedSimpleCredentials creds = new CryptedSimpleCredentials((SimpleCredentials) itr.next()); + if (creds.match(sCreds)) { + return true; + } + } catch (NoSuchAlgorithmException e) { + log.warn("failed to verify Credentials with {}: {} -> test next", + sCreds.toString(), e); + } catch (UnsupportedEncodingException e) { + log.warn("failed to verify Credentials with {}: {} -> test next", + sCreds.toString(), e); + } + } + return false; + } +} \ No newline at end of file Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authentication\SimpleAuthentication.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLCache.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLCache.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLCache.java (revision 0) @@ -0,0 +1,388 @@ +/* + * 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.authorization; + +import org.apache.commons.collections.map.LinkedMap; +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.NodeId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * ACLCache
    + * Caches allready resolved ACLs for one workspace.

    + * The cache keeps per item the ACL if locally defined AND a reference to + * the next id, which defines an ACL. + * Thus if no entry is contained in the Cache a new one has to be build + */ +class ACLCache { + + /** the default logger */ + private static final Logger log = LoggerFactory.getLogger(ACLCache.class); + + /** the acl entries stored by the id of their applying item */ + private final Map entryByItemId = new HashMap(); + + /** map of recently used leaf entries */ + private final LinkedMap lruMap = new LinkedMap(); + + /** the acl entries stored by their id */ + private final Map entryByAclId = new HashMap(); + + /** the maximum size of the cache */ + private int maxSize = 0x10000; + + /** the name of this cache */ + private final String name; + + /** + * creates a new ACLCache with the given name + * + * @param name + */ + ACLCache(String name) { + this.name = name; + } + + /** + * Returns the maximum cache size + * @return the maximum cache size + */ + int getMaxSize() { + return maxSize; + } + + /** + * Sets the maximum cache size + * @param maxSize + */ + void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Return the effective ACL for the given item id.
    + * + * @param id of the Item the ACL should apply + * @param touch if true, the item is pot. added to the lruMap + * + * @return the ACL or null if none is stored + */ + DefaultACL getAcl(ItemId id, boolean touch) { + Entry entry = (Entry) entryByItemId.get(id); + if (entry == null) { + return null; + } else if (touch && !entry.hasChildren()) { + // this is the only potential read-only access, so synchronize + synchronized (lruMap) { + lruMap.remove(id); + lruMap.put(id, id); + } + if (log.isDebugEnabled()) { + log.debug("Added entry to lruMap. {}", this); + } + } + return entry.getAcl(); + } + + /** + * Puts an ACL into this cache.

    + * + * @param id the acl applies + * @param parentId id of the item the acl inherits from + * @param acl + */ + void cache(ItemId id, ItemId parentId, DefaultACL acl) + throws RepositoryException { + Entry entry = (Entry) entryByItemId.get(id); + if (entry == null) { + if (entryByItemId.size() > maxSize) { + purge(); + } + if (parentId == null) { + new Entry(id, null, acl); + } else { + Entry parent = (Entry) entryByItemId.get(parentId); + if (parent == null) { + throw new RepositoryException("Illegal state...parent ACL must be cached."); + } + new Entry(id, parent, acl); + } + } else { + if (entry.hasAcl(acl)) { + entry.invalidate(); + } + entry.setAcl(acl); + } + } + + /** + * purges leaf-entries until 10% of max size is reached + */ + private void purge() { + // purge 10% of max size + int goal = (maxSize * 90) / 100; + int size = entryByItemId.size(); + while (entryByItemId.size() > goal && !lruMap.isEmpty()) { + NodeId id = (NodeId) lruMap.remove(0); + removeItem(id); + } + if (log.isDebugEnabled()) { + log.debug("Purged {} entries. {}", String.valueOf(size - entryByItemId.size()), this); + } + } + + /** + * Invalidates the acl with the given id + * + * @param aclId + */ + void invalidateAcl(NodeId aclId) { + Entry entry = (Entry) entryByAclId.get(aclId); + if (entry != null) { + if (entry.parent!=null) { + entry.parent.invalidate(); + } else { + entry.invalidate(); + } + } + } + + /** + * Invalidates the acl with the given item id + * + * @param itemId + */ + void removeItem(NodeId itemId) { + Entry entry = (Entry) entryByItemId.get(itemId); + if (entry != null) { + entry.remove(); + } + } + + /** + * closes the cache and clears all maps + */ + void close() { + entryByAclId.clear(); + entryByItemId.clear(); + lruMap.clear(); + } + + /** + * {@inheritDoc} + */ + public String toString() { + return "name=" + name + ", entries=" + entryByItemId.size() + ", leaves=" + lruMap.size(); + } + + /** + * Entry class for the acl cache. Every entry represents a accessontrollable + * item of the workspace. the entry might have an assosiated ACL. the + * parent-child relationships of parent-child ACLs are also recorded in the + * entry. please note, that this is not the same as the parent-child + * relationship of the actual repository items, since not all items must be + * access controllable. + */ + private class Entry { + + /** + * the id of the access controlled item + */ + private final ItemId id; + + /** + * the ACL for the access controlled item + */ + private DefaultACL acl; + + /** + * the parent entry or null if root + */ + private final Entry parent; + + /** + * the set of child entries or null if no children + */ + private Set children; + + /** + * Creates a new ACLEntry at adds it to the cache + * + * @param id + * @param acl + */ + private Entry(ItemId id, Entry parent, DefaultACL acl) { + this.id = id; + this.acl = acl; + this.parent = parent; + if (parent != null) { + parent.attachChild(this); + } + entryByItemId.put(id, this); + setAcl(acl); + if (log.isDebugEnabled()) { + log.debug("Added new entry. {}", ACLCache.this); + } + } + + /** + * Attaches a child entry to this one + * + * @param child + */ + private void attachChild(Entry child) { + if (children == null) { + children = new HashSet(); + } + children.add(child); + // remove from lruMap + if (lruMap.remove(id) != null) { + if (log.isDebugEnabled()) { + log.debug("attachChild removed item from lru map. {}", ACLCache.this); + } + } + + } + + /** + * Detaches a child entry + * @param child + */ + private void detachChild(Entry child) { + if (children != null) { + children.remove(child); + if (children.isEmpty()) { + lruMap.put(id, id); + if (log.isDebugEnabled()) { + log.debug("detachChild added item to lru map. {}", ACLCache.this); + } + } + } + } + + /** + * sets the acl + * + * @param acl + */ + private void setAcl(DefaultACL acl) { + if (this.acl != null) { + this.acl.invalidate(); + entryByAclId.remove(this.acl.getId()); + } + this.acl = acl; + if (acl != null) { + entryByAclId.put(acl.getId(), this); + } + } + + /** + * checks if this entry has children + * @return true if this enrty has children + */ + private boolean hasChildren() { + return children != null && children.size() > 0; + } + + /** + * checks if this entry has the given acl assigned + * + * @param acl + * @return true if this entry has the given acl assigned + */ + private boolean hasAcl(DefaultACL acl) { + return this.acl != null && this.acl.equals(acl); + } + + /** + * returns the assigned acl + * @return the acl + */ + private DefaultACL getAcl() { + return acl; + } + + /** + * invalidates this entry, i.e. clears the assigned acl and recursivly + * invalidates all its children. + */ + private void invalidate() { + if (acl != null) { + acl.invalidate(); + } + setAcl(null); + if (children != null) { + Iterator iter = children.iterator(); + while (iter.hasNext()) { + ((Entry) iter.next()).invalidate(); + } + } + } + + /** + * removes this entry from the cache and recursivly all children. + */ + private void remove() { + entryByItemId.remove(id); + lruMap.remove(id); + if (children != null) { + Iterator iter = children.iterator(); + while (iter.hasNext()) { + ((Entry) iter.next()).remove(); + iter = children.iterator(); // to avoid concurrent mod excp. + } + } + if (parent != null) { + parent.detachChild(this); + } + setAcl(null); + if (log.isDebugEnabled()) { + log.debug("Removed entry. {}", ACLCache.this); + } + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object obj) { + if (obj==this) { + return true; + } + if (obj instanceof Entry) { + return id.equals(((Entry)obj).id); + } + return false; + } + + } + +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\ACLCache.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLManagerImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLManagerImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ACLManagerImpl.java (revision 0) @@ -0,0 +1,168 @@ +/* + * 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.authorization; + +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.ItemImpl; +import org.apache.jackrabbit.core.security.SystemPrincipal; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.apache.jackrabbit.core.security.spi.ACLEditor; +import org.apache.jackrabbit.core.security.spi.ACLProvider; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ACLManager; +import org.apache.jackrabbit.security.ACLTemplate; +import org.apache.jackrabbit.security.ActionSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.AccessDeniedException; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.security.auth.Subject; + +/** + * Default implementation for the ACLManager interface. + * + * @see ACLManager for more details. + */ +public class ACLManagerImpl implements ACLManager { + + private static final Logger log = LoggerFactory.getLogger(ACLManagerImpl.class); + + /** + * ACLProvider to use to access the ACLEditor + */ + private final ACLProvider aclProvider; + + /** + * The user session this manager was build for + */ + private final Session userSession; + + private final Subject subject; + + /** + * Creates a new ACL manager. + * + * @param userSession + */ + public ACLManagerImpl(Session userSession, Subject subject, ACLProvider aclProvider) { + this.userSession = userSession; + this.subject = subject; + this.aclProvider = aclProvider; + } + + /** + * {@inheritDoc} + */ + public ACL getAcl(String absPath) throws RepositoryException { + ItemId id = getItemId(absPath); + checkPermission(id, ActionSetImpl.ACL_READ); + return aclProvider.getAcl(id); + } + + /** + * {@inheritDoc} + */ + public ACLTemplate editAcl(String absPath) + throws RepositoryException, AccessDeniedException { + ACLEditor editor = getEditor(); + if (editor == null) { + return null; + } + ItemId id = getItemId(absPath); + checkPermission(id, ActionSetImpl.ACL_MODIFY); + return editor.editAcl(id); + } + + /** + * {@inheritDoc} + */ + public void setAcl(String absPath, ACLTemplate acl) + throws RepositoryException, AccessDeniedException { + ItemId id = getItemId(absPath); + checkPermission(id, ActionSetImpl.ACL_MODIFY); + ACLEditor editor = getEditor(); + if (editor == null) { + throw new UnsupportedRepositoryOperationException("ACLProvider for does not define an Editor"); + } + editor.setAcl(id, acl); + } + + /** + * {@inheritDoc} + */ + public void removeAcl(String absPath) throws RepositoryException, AccessDeniedException { + ItemId id = getItemId(absPath); + checkPermission(id, ActionSetImpl.ACL_MODIFY); + ACLEditor editor = getEditor(); + if (editor == null) { + throw new UnsupportedRepositoryOperationException("ACLProvider for does not define an Editor"); + } + editor.removeAcl(id); + } + + /** + * @return ACLEditor of the given aclProvider + */ + private ACLEditor getEditor() { + return aclProvider.getEditor(); + } + + /** + * @return if session is build with Admin or System principal + */ + private boolean isAdmin() { + return !subject.getPrincipals(SystemPrincipal.class).isEmpty() || !subject.getPrincipals(AdminPrincipal.class).isEmpty(); + } + + /** + * Return the ItemId for the given absPath. + * + * @param absPath + * @return + * @throws RepositoryException + */ + private ItemId getItemId(String absPath) throws RepositoryException { + ItemImpl item = ((ItemImpl) userSession.getItem(absPath)); + return item.getId(); + } + + /** + * Check actions to be granted for the acl requested.
    + * If the provider doesn't contain an ACL for the given ItemId null + * is returned. + * + * @param id to access an acl to + * @param actionSet the acl should be granted + * @throws AccessDeniedException + * if user is not allowed the actions on requested ACL + * @throws RepositoryException + */ + private void checkPermission(ItemId id, ActionSet actionSet) throws RepositoryException { + if (isAdmin()) { + return; + } + ACL acl = aclProvider.getAcl(id); + if (acl == null) { + log.debug("Item at {} not Protected: grant all access to {}", id, userSession.getUserID()); + } else if (!acl.grants(subject.getPrincipals(), actionSet)) { + throw new AccessDeniedException("User " + userSession.getUserID() + " has missing permission for " + id); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\ACLManagerImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ActionSetImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ActionSetImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/ActionSetImpl.java (revision 0) @@ -0,0 +1,317 @@ +/* + * 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.authorization; + +import org.apache.jackrabbit.security.ActionSet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * This action set implementation uses a bitwise representation of the individual + * actions. The action 'bits' are stored within a integer, thus limits the + * set to 32 different actions. + */ +public class ActionSetImpl implements ActionSet { + + public static final int ACTION_READ = 1; + public static final int ACTION_SET_PROPERTY = 2; + public static final int ACTION_ADD_NODE = 4; + public static final int ACTION_REMOVE = 8; + public static final int ACTION_ACL_READ = 16; + public static final int ACTION_ACL_MODIFY = 32; + public static final int ACTION_WORKSPACE_ACCESS = 64; + public static final int ACTION_SUDO = 128; + + /** + * Build an index bits to name for conversion + */ + private static final List BIT_INDEX = new ArrayList(8); + static { + BIT_INDEX.add(ACTION_NAME_READ); + BIT_INDEX.add(ACTION_NAME_SET_PROPERTY); + BIT_INDEX.add(ACTION_NAME_ADD_NODE); + BIT_INDEX.add(ACTION_NAME_REMOVE); + BIT_INDEX.add(ACTION_NAME_ACL_READ); + BIT_INDEX.add(ACTION_NAME_ACL_MODIFY); + BIT_INDEX.add(ACTION_NAME_WORKSPACE_ACCESS); + BIT_INDEX.add(ACTION_NAME_SUDO); + } + + /** + * Build an index name to bit for conversion + */ + private static final Map NAME_INDEX = new HashMap(11); + static { + for (int i=0;i----------- + /** + * calculate the hash-code based on the actions contained + * @return a hash code value for this object. + * @see Object#equals(Object) + * @see Hashtable + */ + public int hashCode() { + return actions; + } + + /** + * A set is considered equal if the set Actions are the same + * if (this.getActions() == other.getActions()) + * this.equals(other) + * + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof ActionSetImpl) { + return ((ActionSetImpl) obj).actions == actions; + } else if (obj instanceof ActionSet) { + return getActions().equals(((ActionSet) obj).getActions()); + } else { + return false; + } + } + + //----------------------------------------------------------< ActionSet >--- + + /** + * {@inheritDoc} + */ + public boolean intersects(ActionSet other) { + int otherBits; + if (other instanceof ActionSetImpl) { + otherBits = ((ActionSetImpl)other).actions; + } else { + otherBits = getBitsForNames(other.getActions()); + } + // a and b intersect, when at least one (a & b) == true + // + // b\a | 0 | 1 + // ----+---+--- + // 0 | 0 | 0 + // 1 | 0 | 1 + return (otherBits & actions) != 0; + } + + /** + * {@inheritDoc} + */ + public boolean includes(ActionSet other) { + int otherBits; + if (other instanceof ActionSetImpl) { + otherBits = ((ActionSetImpl)other).actions; + } else { + otherBits = getBitsForNames(other.getActions()); + } + // a includes b, when all (a|~b) == true + // + // b\a | 0 | 1 + // ----+---+--- + // 0 | 1 | 1 + // 1 | 0 | 1 + return (actions | ~otherBits) == -1; + } + + /** + * {@inheritDoc} + */ + public ActionSet diff(ActionSet other) { + int otherBits; + if (other instanceof ActionSetImpl) { + otherBits = ((ActionSetImpl)other).actions; + } else { + otherBits = getBitsForNames(other.getActions()); + } + int result = actions & ~otherBits; + if (result == actions) { + return this; + } else { + return create(result); + } + } + + /** + * {@inheritDoc} + */ + public synchronized String[] getActions() { + if (names == null) { + names = getNamesForBits(actions); + } + return names; + } + + /** + * {@inheritDoc} + */ + public boolean contains(String action) { + Integer pos = (Integer) NAME_INDEX.get(action); + return pos!=null && (actions & pos.intValue()) > 0; + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return actions == 0; + } + + //-------------------------------------------------------------------------- + /** + * @param actionSet the ActionSet the bits should be calculated for. + * @return bit representation of contained actions + */ + public static int getActionBits(ActionSet actionSet) { + if (actionSet instanceof ActionSetImpl) { + return ((ActionSetImpl)actionSet).actions; + } else { + return getBitsForNames(actionSet.getActions()); + } + } + + /** + * @param actionNames to get the bit representation for + * @return bit representing the Action + */ + private static int getBitsForNames(String[] actionNames) { + int bits = 0; + if (actionNames != null && actionNames.length >0) { + synchronized (ActionSetImpl.class) { + for (int i=0;i + * + * @see ACE + */ +public class DefaultACE implements ACE { + + /** + * The empty ACE array + */ + public static final DefaultACE[] EMPTY = new DefaultACE[0]; + + /** the acl this entry is contained in */ + private final DefaultACL acl; + + /** the name of this ace */ + private final ItemId id; + + /** the principal for this ace */ + private final Principal principal; + + /** if this ace grants or denies the action-set to the principal */ + private final boolean allow; + + /** internal representation of the getActionSet contained */ + private final ActionSet actions; + + private int hashCode = - 1; + + /** + * Construct an ACL entry for a principal-name and a given + * polarity (deny or allow) + * + * @param principal + * @param allow + * @param actions + * @param id + */ + DefaultACE(DefaultACL acl, String principal, boolean allow, + ActionSet actions, ItemId id) { + this.acl = acl; + this.principal = new PrincipalImpl(principal); + this.allow = allow; + this.actions = actions; + this.id = id; + } + + /** + * @param actionSet + * @return return all actions which are only contained in the given set + */ + ActionSet complementActions(ActionSet actionSet) { + return actionSet.diff(actions); + } + //-----------------------------------------------------< ACE >-------------- + /** + * {@inheritDoc} + */ + public String getName() { + return id.toString(); + } + + /** + * {@inheritDoc} + */ + public ActionSet getActionSet() { + return actions; + } + + /** + * {@inheritDoc} + */ + public boolean isAllow() { + return allow; + } + + /** + * {@inheritDoc} + */ + public boolean containsAnyAction(ActionSet actionSet) { + return actions.intersects(actionSet); + } + + /** + * {@inheritDoc} + */ + public Principal getPrincipal() { + return principal; + } + + /** + * {@inheritDoc} + */ + public ACL getContainingACL() { + return acl; + } + + //-------------------------------------------------------------< Object >--- + public int hashCode() { + if (hashCode==-1) { + hashCode = (id.toString() + principal.toString()).hashCode(); + } + return hashCode; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ACE) { + ACE other = (ACE) obj; + return isAllow()==other.isAllow() && + getPrincipal().equals(other.getPrincipal()) && + getActionSet().equals(other.getActionSet()); + } + return false; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultACE.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACETemplate.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACETemplate.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACETemplate.java (revision 0) @@ -0,0 +1,187 @@ +/* + * 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.authorization; + +import org.apache.jackrabbit.core.security.spi.ACLEditor; +import org.apache.jackrabbit.security.ACE; +import org.apache.jackrabbit.security.ACETemplate; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ACLTemplate; +import org.apache.jackrabbit.security.ActionSet; + +import java.security.Principal; + +/** + * {@link ACETemplate}-Implementation for {@link DefaultACE}s. + * + * @see ACETemplate + * @see DefaultACE + */ +class DefaultACETemplate implements ACETemplate { + + /** + * field indicateing if any changes have been applied to this ACE + */ + private boolean modified; + + /** + * Actions contained in this ACE + */ + private ActionSet actionSet; + + /** + * if the actions contained are allowed or denied + */ + private final boolean allow; + + /** + * the Principal of this ACE + */ + private final Principal principal; + + /** + * The containing ACL + */ + private final ACLTemplate acl; + + /** + * The name of this ACE + */ + private final String name; + + /** + * Constructs an ACE which is already an Entry of an ACL + * + * @param source the persistent ACE backing this ACETemplate + * @param acl containing this Entry + */ + protected DefaultACETemplate(ACE source, ACLTemplate acl) { + this.acl = acl; + this.name = source.getName(); + this.allow = source.isAllow(); + this.principal = source.getPrincipal(); + this.actionSet = source.getActionSet(); + } + + + protected DefaultACETemplate(Principal principal, boolean allow, + ActionSet actionSet, ACLTemplate acl) { + this.acl = acl; + this.principal = principal; + this.name = allow ? "grant" + principal.getName() : "deny" + principal.getName(); + this.allow = allow; + this.actionSet = actionSet; + } + + //-----------------------------------------------------< ACE >-------------- + /** + * For {@link DefaultACE#getName()} the name is the ID of the related Node. + * In case of the editable ACE it may be the case that there is no Node + * at prcessing-time. In this case the name changes after commit + * without any further notice. + * + * @return name of the entry. + * @see ACE#getName() + */ + public String getName() { + return name; + } + + /** + * @return principal this ace is effective for + * @see ACE#getPrincipal() + */ + public Principal getPrincipal() { + return principal; + } + + /** + * @return true if all actions contained in this Entry are allowed + * @see ACE#isAllow() + */ + public boolean isAllow() { + return allow; + } + + /** + * @return Actions contained in this ACE + * @see ACE#getActionSet() + */ + public ActionSet getActionSet() { + return actionSet; + } + + /** + * @see ACE#containsAnyAction(ActionSet) + */ + public boolean containsAnyAction(ActionSet actionSet) { + return getActionSet().intersects(actionSet); + } + + /** + * @return ACL this ACE is part of + * @see ACE#getContainingACL() + */ + public ACL getContainingACL() { + return acl; + } + + //-----------------------------------------------------< ACETemplate >------ + /** + * @param actionSet for this ACE + * @see ACETemplate#setActionSet(ActionSet) + */ + public void setActionSet(ActionSet actionSet) { + this.actionSet = actionSet; + this.modified = true; + } + + /** + * @return if any changes have been made since the Entry has been acquired + * from {@link ACLEditor} + * @see ACETemplate#isModified() + */ + public boolean isModified() { + return modified; + } + + //-----------------------------------------------------< Object >----------- + /** + * @see Object#hashCode() + */ + public int hashCode() { + return (name + principal.toString()).hashCode(); + } + + /** + * An ACE equals an other ACE if Principal, allow and ActionSet are equal + * + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ACE) { + ACE other = (ACE) obj; + return isAllow() == other.isAllow() && + getPrincipal().equals(other.getPrincipal()) && + getActionSet().equals(other.getActionSet()); + } + return false; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultACETemplate.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACL.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACL.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACL.java (revision 0) @@ -0,0 +1,382 @@ +/* + * 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.authorization; + +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.NodeImpl; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.security.ACE; +import org.apache.jackrabbit.security.ACEIterator; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ActionSet; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.security.auth.Subject; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * AccessControlList
    + * The List consists of entries, each representing a set of Actions + * which are either granted or denied to one principal.
    + * The entries are orderd. + * The ACL implemnts the following evaluation rule for a principal + *

      + *
    • getActionSet not explicitly granted are denied + *
    • each action gets the validity of the nearest ace assigned + *
    + * The list implementation allows to grant getActionSet to a + * {@link javax.security.auth.Subject}.
    + * This means grant getActionSet to a set of {@link java.security.Principal}s.
    + * Thus only {@link DefaultACE}s are applied that are for a principal contained in + * in the {@link javax.security.auth.Subject}s principal list. + * + * @see ACL + */ +public class DefaultACL implements ACL { + /** + * the entries of this ACL + */ + private final DefaultACE[] localEntries; + + /** + * the id of the node this item is stored + */ + private final NodeId id; + + /** + * the base ACL, if existent + */ + private final DefaultACL base; + + /** + * flag indicating, if this ACL is used to protect an item that builds this + * acl. + */ + private final boolean protectsACL; + + /** + * flag if this acl is still valid + */ + private boolean valid = true; + + /** + * Creates a new default ACL for an + * {@link SecurityConstants#NT_REP_ACCESS_CONTROLLABLE "rep:AccessControllable"} + * node. ACL objects created this way will have {@link #protectsAcl()} + * == false. + * + * @param node of type rep:ACL + * @param base the list to add the current aces to + */ + public DefaultACL(NodeImpl node, DefaultACL base) throws RepositoryException { + if (node == null || !node.isNodeType(SecurityConstants.NT_REP_ACL)) { + throw new IllegalArgumentException("Node must be of type: " + SecurityConstants.NT_REP_ACL); + } + this.id = (NodeId) node.getId(); + this.localEntries = readEntries(this, node); + this.base = base; + this.protectsACL = false; + } + + /** + * Creates a new default ACL for an item that is not access controlled. If + * the item is part of the ACL itself protectsACL should be + * set to true. + *

    + * Since the actual ACL is the same as the one of the base acl, the access + * controll node does not need to be specified. + * + * @param id the id of the ACL item + * @param base the list to add the current aces to + * @param protectsACL true if this item is used to build an acl + */ + public DefaultACL(NodeId id, DefaultACL base, boolean protectsACL) { + this.id = id; + this.localEntries = DefaultACE.EMPTY; + this.base = base; + this.protectsACL = protectsACL; + } + + /** + * Returns the ID of Node storing this ACL. + * + * @return the item id + */ + public ItemId getId() { + return id; + } + + boolean isValid() { + return valid; + } + + /** + * Invalidates this acl + */ + void invalidate() { + valid = false; + } + + /** + * Returns all entries of this ACL combined with the base one, unless + * local is true. + * + * @param local if true only ACEs are returned which are not inherited + * @return ACEs of this List + */ + ACEIterator getEntries(boolean local) { + return new ACEIteratorImpl(localEntries, local ? null : base); + } + + /** + * Tests if this ACL is used to protect an item that is used for building + * and ACL itself, e.g. a rep:ACL. + * + * @return true if this ACL is used to protect an ACL; + * false otherwise. + */ + boolean protectsAcl() { + return protectsACL; + } + + //----------------------------------------------------------------< ACL >--- + /** + * @return the name of the current ACL, + * @see ACL#getName() + */ + public String getName() { + return id.toString(); + } + + /** + * Checks if set of Principals are granted all of the actions contained + * in the ActionSet + * + * @param principals Set principal to grant actions to + * @param actions to test + * @return true if the current ACL grants all of the given Actions + * @see ACL#grants(Set, ActionSet) + */ + public boolean grants(Set principals, ActionSet actions) { + for (int i=0; i= entries.length) { + throw new NoSuchElementException(); + } + ACE ret = entries[nextPosition++]; + while (nextPosition >= entries.length && base != null) { + nextPosition = 0; + entries = base.localEntries; + base = base.base; + } + pos++; + return ret; + } + + /** + * {@inheritDoc} + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return nextPosition < entries.length; + } + + /** + * {@inheritDoc} + */ + public Object next() { + return nextACE(); + } + + /** + * {@inheritDoc} + */ + public void skip(long skipNum) { + // dumb implementation just counts down the number of skip steps + // until it becomes zero or no more elements are available + for (; skipNum > 0; skipNum--) { + nextACE(); + } + } + + /** + * {@inheritDoc} + */ + public synchronized long getSize() { + if (size < 0) { + // count of already returned, the size of the current without the remaining + size = pos + entries.length - nextPosition; + + // than add the size of all base that have not been processed yet + DefaultACL acl = this.base; + while (acl != null) { + size += acl.localEntries.length; + acl = acl.base; + } + } + return size; + } + + /** + * {@inheritDoc} + */ + public long getPosition() { + return pos; + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultACL.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLProvider.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLProvider.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLProvider.java (revision 0) @@ -0,0 +1,692 @@ +/* + * 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.authorization; + +import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock; +import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.name.MalformedPathException; +import org.apache.jackrabbit.name.PathFormat; +import org.apache.jackrabbit.core.ItemManager; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.NodeImpl; +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.ItemImpl; +import org.apache.jackrabbit.core.security.spi.ACLProvider; +import org.apache.jackrabbit.core.security.spi.ACLEditor; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.observation.EventImpl; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ACLTemplate; +import org.apache.jackrabbit.security.ACEIterator; +import org.apache.jackrabbit.security.ACE; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.jcr.observation.ObservationManager; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * The DefaultACLProvider generates DefaultACLs out of the items stored in the + * workspace. The ACLs are generated using the following rules: + *

      + *
    • a node is considered access controlled if it is of node type + * rep:AccessControllable and if it has the child node + * rep:acl that forms the acl. + *
    • a property is considered 'access controlled' if its parent node is. + *
    • an item that is not access controlled inherits the ACL of the closest + * access controlled ancestor. + *
    • if the root node is not access controlled, there can exist items with + * no effective ACL. + *
    • an item is considered an ACL item if it is used to define an ACL. + * ACL items inherit the ACL from node they defined the ACL for. + *
    + *

    + * Transient items cannot be handled in the ACLProvider since it is bound to + * a single SystemSession and not to the Session the ACLManager is attached + * to.
    + * Requests for ACLs of inexistent items will return + * null and the respective compiled acl provider or the access + * manager itself must handle those cases correctly. It is advisable that the + * ACL of the closest access controlled item is used. Further will changes of + * ACLs not be visible to the session until the ACL is saved. + */ +public class DefaultACLProvider implements ACLProvider { + + /** + * the default logger + */ + private static final Logger log = LoggerFactory.getLogger(DefaultACLProvider.class); + + /** + * ItemManager to access the ACL-Nodes + */ + private final ItemManager itemMgr; + + /** + * Cache for allready resolved ACLs + */ + private final ACLCache cache; + + /** + * The node id of the root node + */ + private final NodeId rootNodeId; + + /** + * Read-/Write-Lock to synchronize access to the ache + */ + private final ReadWriteLock lock = new ReentrantWriterPreferenceReadWriteLock(); + + /** + * listener on the permission nodes + */ + private final PermissionListener permListener; + + /** + * listener on the access controlled nodes + */ + private final AclListener aclListener; + + /** + * listener on the all nodes + */ + private final ItemListener itemListener; + + /** + * the system session that accesses the workspace + */ + private final Session systemSession; + + /** + * @param systemSession system session on the respective workspace + * @param itemManager + */ + public DefaultACLProvider(Session systemSession, ItemManager itemManager) throws RepositoryException { + this.itemMgr = itemManager; + this.cache = new ACLCache(systemSession.getWorkspace().getName()); + this.systemSession = systemSession; + this.rootNodeId = (NodeId) ((ItemImpl) systemSession.getRootNode()).getId(); + + // listens on property change events on permission nodes, and node events + // on the ACL nodes. properties are never removed or added, always the + // entire permission node + permListener = new PermissionListener(); + ObservationManager om = systemSession.getWorkspace().getObservationManager(); + om.addEventListener(permListener, + Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED, + "/", + true, + null, + new String[]{SecurityConstants.NT_REP_ACE, SecurityConstants.NT_REP_ACL}, + false); + + // listens on access controll nodes that are added to or removed from + // access controlled nodes. + aclListener = new AclListener(); + om.addEventListener(aclListener, + Event.NODE_ADDED | Event.NODE_REMOVED, + "/", + true, + null, + new String[]{SecurityConstants.NT_REP_ACCESS_CONTROLLABLE}, + false); + + // listens for nodes that are removed + itemListener = new ItemListener(); + om.addEventListener(itemListener, + Event.NODE_REMOVED, + "/", + true, + null, + null, + false); + } + + /** + * {@inheritDoc} + * + * Removes the listeners from the observation manager. + */ + public void close() { + try { + ObservationManager om = systemSession.getWorkspace().getObservationManager(); + om.removeEventListener(permListener); + om.removeEventListener(aclListener); + om.removeEventListener(itemListener); + } catch (RepositoryException e) { + log.warn("close: failed to unregister Listeners: {}", e.getMessage()); + } + + acquireWriteLock(); + try { + cache.close(); + } finally { + releaseWriteLock(); + } + } + + /** + * {@inheritDoc} + */ + public ACL getAcl(ItemId itemId) throws RepositoryException { + NodeId id = itemId.denotesNode() ? (NodeId) itemId : ((PropertyId) itemId).getParentId(); + // check cache first + acquireReadLock(); + try { + DefaultACL acl = cache.getAcl(id, true); + if (acl!=null && acl.isValid()) { + return acl; + } + } finally { + releaseReadLock(); + } + + // check respective item + if (!itemMgr.itemExists(id)) { + return null; + } + + // write to cache + acquireWriteLock(); + try { + // check cache again + DefaultACL acl = cache.getAcl(id, true); + if (acl==null || !acl.isValid()) { + NodeImpl node = (NodeImpl) itemMgr.getItem(id); + // check for special ACL building item + if (node.isNodeType(SecurityConstants.NT_REP_ACL)) { + acl = buildAcl(id, (NodeImpl) node.getParent(), true); + } else if (node.isNodeType(SecurityConstants.NT_REP_ACE)) { + acl = buildAcl(id, (NodeImpl) node.getParent().getParent(), true); + } else { + acl = buildAcl(id, node, false); + } + } + return acl; + } finally { + releaseWriteLock(); + } + } + + /** + * {@inheritDoc} + */ + public ACLEditor getEditor() { + return new DefaultACLEditor(); + } + + /** + * Constructs the DefaultACL based on the given node and id. + *

    + * If the item is not part of the building blocks of an ACL iteself + * (isAclItem == false), the itemid is the id of the given node. The new + * default acl will be created using the respective parent acl. The parent + * acl is the acl belonging to the next ancestor that is access + * controllable. + *

    + * If the item is part of the building blocks of an ACL (isAclItem == true), + * the itemid is the one of that item, but the node is the one of the closest + * ancestor that is access controllable, i.e. the node of which the ACL + * belongs the item defines. In this case, the ACL of that node is the parent + * ACL for the ACL of this item. + * + * @param id the id of the item this ACL is to build for + * @param node the starting point to search for access controllable nodes. + * @param isAclItem true if the item with the given id is used + * to build the ACL itself. + * + * @return acl of null if none applies + * @throws RepositoryException + */ + private DefaultACL buildAcl(ItemId id, NodeImpl node, boolean isAclItem) + throws RepositoryException { + // preconditions: + // - in write lock + // - node is not null + // - acl for node is not cached + // - node is never an ACL building item + // - if isAclItem then node is access controlled + + NodeImpl parentNode = node; + ItemId parentId = parentNode.getId(); + + if (!isAclItem) { + if (!id.equals(rootNodeId)) { + // find next access controllable parent + parentNode = (NodeImpl) parentNode.getParent(); + parentId = parentNode.getId(); + while (parentNode != null && !isAccessControlled(parentNode)) { + if (parentId.equals(rootNodeId)) { + parentNode = null; + parentId = null; + } else { + parentNode = (NodeImpl) parentNode.getParent(); + parentId = parentNode.getId(); + } + } + } else { + parentId = null; + parentNode = null; + } + } + + // get parent ACL + DefaultACL parentAcl = null; + if (parentNode != null) { + parentAcl = cache.getAcl(parentId, false); + if (parentAcl == null) { + parentAcl = buildAcl(parentId, parentNode, false); + } + } + if (!isAclItem && isAccessControlled(node)) { + // build acl from node + NodeImpl aclNode = node.getNode(SecurityConstants.QN_REP_ACL); + DefaultACL acl = new DefaultACL(aclNode, parentAcl); + cache.cache(id, parentId, acl); + return acl; + } else if (parentAcl!=null) { + // build acl for a non-access controlled item + DefaultACL acl = new DefaultACL((NodeId) id, parentAcl, isAclItem); + cache.cache(id, parentId, acl); + return acl; + } + return null; + } + + /** + * Checks if the given node is access controlled. The node is access + * controlled if it is of nodetype + * {@link SecurityConstants#QNT_REP_ACCESS_CONTROLLABLE "rep:AccessControllable"} + * and if it has a child node named + * {@link SecurityConstants#QN_REP_ACL "rep:ACL"}. + * + * @param node + * @return true if the node is access controlled; + * false otherwise. + * @throws RepositoryException + */ + private boolean isAccessControlled(NodeImpl node) throws RepositoryException { + return (node.isNodeType(SecurityConstants.QNT_REP_ACCESS_CONTROLLABLE) && + node.hasNode(SecurityConstants.QN_REP_ACL)); + } + + /** + * acquires the read lock on the cache + */ + private void acquireReadLock() { + try { + lock.readLock().acquire(); + } catch (InterruptedException e) { + log.error("interrupted while waiting for read-lock: {} ", e.getMessage()); + } + } + + /** + * Releases the readlock + */ + private void releaseReadLock() { + lock.readLock().release(); + } + + /** + * Acquires the writelock + */ + private void acquireWriteLock() { + try { + lock.writeLock().acquire(); + } catch (InterruptedException e) { + log.error("interrupted while waiting for write-lock {}", e.getMessage()); + } + } + + /** + * Releases the writelock + */ + private void releaseWriteLock() { + lock.writeLock().release(); + } + + //-----------------------------------------------------< Listeners >-------- + + /** + * Listens on Event.NODE_ADDED events of rep:AccessControllable nodes + */ + private class AclListener implements EventListener { + public void onEvent(EventIterator eventIterator) { + Set aclUUIDs = new HashSet(); + while (eventIterator.hasNext()) { + // node was added, need to find next ACL parent in order to + // invalidate the children + EventImpl event = (EventImpl) eventIterator.nextEvent(); + if (event.getType() == Event.NODE_ADDED) { + try { + NodeImpl node = (NodeImpl) systemSession.getItem(event.getPath()).getParent(); + // search next access controllable node + NodeId id = (NodeId) node.getId(); + while (!id.equals(rootNodeId)) { + node = (NodeImpl) node.getParent(); + id = (NodeId) node.getId(); + if (isAccessControlled(node)) { + break; + } + } + aclUUIDs.add(id); + } catch (RepositoryException e) { + log.info("Error while accessing node: {}", e.toString()); + } + } else { + // ACL node was removed + aclUUIDs.add(event.getParentId()); + } + } + Iterator iter = aclUUIDs.iterator(); + if (iter.hasNext()) { + acquireWriteLock(); + try { + while (iter.hasNext()) { + // need to remove item and its children + NodeId id = (NodeId) iter.next(); + cache.removeItem(id); + } + } finally { + releaseWriteLock(); + } + } + } + } + + /** + * Listens on Event.NODE_REMOVED events for invalidating the cache + */ + private class ItemListener implements EventListener { + public void onEvent(EventIterator eventIterator) { + Set aclIDs = new HashSet(); + while (eventIterator.hasNext()) { + EventImpl event = (EventImpl) eventIterator.nextEvent(); + aclIDs.add(event.getChildId()); + } + Iterator iter = aclIDs.iterator(); + if (iter.hasNext()) { + acquireWriteLock(); + try { + while (iter.hasNext()) { + NodeId id = (NodeId) iter.next(); + cache.removeItem(id); + } + } finally { + releaseWriteLock(); + } + } + } + } + + /** + * Listens on Event.PROPERTY_* events of rep:ACE nodes + */ + private class PermissionListener implements EventListener { + public void onEvent(EventIterator eventIterator) { + Set aclIDs = new HashSet(); + while (eventIterator.hasNext()) { + EventImpl event = (EventImpl) eventIterator.nextEvent(); + if (event.getType() == Event.PROPERTY_CHANGED) { + // property was altered + try { + NodeImpl aclNode = (NodeImpl) systemSession.getItem( + event.getPath()).getParent().getParent(); + aclIDs.add(aclNode.getId()); + } catch (RepositoryException e) { + if (log.isDebugEnabled()) { + try { + log.debug("CacheEvent: acl-node not found for: {}", event.getPath()); + } catch (RepositoryException e1) { + // ignore + } + } + } + } else { + // permission node was added or removed + aclIDs.add(event.getParentId()); + } + } + Iterator iter = aclIDs.iterator(); + if (iter.hasNext()) { + acquireWriteLock(); + try { + while(iter.hasNext()) { + cache.invalidateAcl((NodeId) iter.next()); + } + } finally { + releaseWriteLock(); + } + } + } + } + + //-----------------------------------------------------< ACLEditor >-------- + + private class DefaultACLEditor implements ACLEditor { + + private static final String DEFAULT_PERMISSION_NAME = "permission"; + + /** + * Access an editable view of an ACL. + * If the requested Item is not allready under AccessControll an empty + * {@link ACLTemplate} is generated. But it is not asserted that + * the requested {@link ItemId} is known to the Session of this + * ACLProvider. This allows for editing of the Item and its ACL at + * the same time. + * + * @param id of the Item an ACL should be edited for + * @return the ACL or null if not editable + * @throws RepositoryException + * @see ACLEditor#editAcl(ItemId) + */ + public ACLTemplate editAcl(ItemId id) throws RepositoryException { + ACLTemplate edit = null; + DefaultACL acl = (DefaultACL) getAcl(id); + if (acl == null) { + edit = new DefaultACLTemplate(id); + } else if (itemMgr.itemExists(id)) { + edit = new DefaultACLTemplate(id, acl); + } + return edit; + } + + /** + * Stores the ACL for the given ItemId.
    + * The Item for the given Id has to be known to the ACLProvider. + * If not, a RepositoryException is thrown.
    + * As this Provider stores ACLs as Items, it expects the Item at the + * given id to be modifyable. Thus neither locked nor checked-in nor + * modified.
    + * In case of the Item is modified it will apply the changes, but won't + * save.
    + * In the other cases method call ends with exception + * + * @param id the ACL should be set to + * @param acl to Set + * @throws RepositoryException + * @see ACLEditor#setAcl(ItemId, ACLTemplate) + */ + public void setAcl(ItemId id, ACLTemplate acl) throws RepositoryException { + writeAcl(acl, getAclNode(id, true)); + } + + /** + * Removes an ACL at the given Item, if any present. + * Excpects the Item to be modifyable. + * The method succeeds without further notic, if there is no ACL for + * the Item with the given Id. + * + * @param id + * @throws RepositoryException + * @see ACLEditor#removeAcl(ItemId) + */ + public void removeAcl(ItemId id) throws RepositoryException { + Node aclNode = getAclNode(id, false); + if (aclNode!=null) { + Node modParent = aclNode.getParent(); + aclNode.remove(); + try { + modParent.save(); + } catch (RepositoryException e) { + modParent.refresh(false); + throw e; + } + } + } + + /** + * Resolves the AccessControl-Node for the given Id.
    + * Therefore: checks if the Id is known to the provider's Session.
    + * Checks if the Id points to the ACL-Node itself
    + * In case the create argument is false the Node is returned unchanged + * or null is returned if not present.
    + * If case the argument is true, and a Node is present all + * Permission-Nodes are removed. If no Node is present, it will be added + * + * @param id + * @param create if a new Node for the ACL should be added if missing + * @return node or null + * @throws RepositoryException + */ + private NodeImpl getAclNode(ItemId id, boolean create) + throws RepositoryException { + + Node aclNode = null; + Item item = itemMgr.getItem(id); + Node protectedNode; + if (item.isNode()) { + protectedNode = (Node) item; + } else { + protectedNode = item.getParent(); + } + if(protectedNode.isNodeType(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE) && + protectedNode.hasNode(SecurityConstants.N_REP_ACL)) { + aclNode = protectedNode.getNode(SecurityConstants.N_REP_ACL); + } else if (protectedNode.isNodeType(SecurityConstants.NT_REP_ACL)) { + aclNode = protectedNode; + protectedNode = aclNode.getParent(); + } + if (create) { + boolean canSave = !(protectedNode.isModified() && + protectedNode.isNew()); + if (aclNode == null) { + if (!protectedNode.isNodeType(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE)) { + protectedNode.addMixin(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE); + } + aclNode = protectedNode.addNode(SecurityConstants.N_REP_ACL, SecurityConstants.NT_REP_ACL); + } else { + NodeIterator itr = aclNode.getNodes(); + while(itr.hasNext()) { + Node perm = itr.nextNode(); + if (perm.isNodeType(SecurityConstants.NT_REP_ACE)) { + perm.remove(); + } + } + } + if (canSave) { + try { + protectedNode.save(); + } catch (RepositoryException e) { + protectedNode.refresh(false); + log.error("setAcl: failed to create Node for ACL " + + id +": " + e); + throw e; + } + } + } + return (NodeImpl) aclNode; + } + + /** + * Simply writes the ACL to the given node + * @throws RepositoryException + */ + private void writeAcl(ACLTemplate acl, NodeImpl aclNode) throws RepositoryException { + + if (aclNode==null || acl==null) { + return; + } + try { + ACEIterator itr = acl.getEntries(); + while(itr.hasNext()) { + ACE ace = itr.nextACE(); + if (ace.getActionSet().isEmpty()) { + continue; + } + String nodeName = getItemName(aclNode, ace.getName()); + String ntName = (ace.isAllow()) ? + SecurityConstants.NT_REP_GRANT_ACE : + SecurityConstants.NT_REP_DENY_ACE; + Node permNode = aclNode.addNode(nodeName, ntName); + permNode.setProperty(SecurityConstants.P_PRINCIPAL, ace.getPrincipal().getName()); + if (!ace.getActionSet().isEmpty()) { + permNode.setProperty(SecurityConstants.P_ACTIONS, ace.getActionSet().getActions()); + } + } + aclNode.save(); + cache.invalidateAcl((NodeId) aclNode.getId()); + } catch (RepositoryException e) { + aclNode.refresh(false); + log.error("setAcl: failed to persiste ACL "+acl.getName()+ ": "+e); + throw e; + } + } + + /** + * Resolve a unique valid name for the Permission nodes to be save. + * + * @param node a name for the child is resolved + * @param name if missing the {@link #DEFAULT_PERMISSION_NAME} is taken + * @return + * @throws RepositoryException + */ + private String getItemName(Node node, String name) throws RepositoryException { + + if (name==null) { + name = DEFAULT_PERMISSION_NAME; + } else { + try { + PathFormat.checkFormat(name); + } catch (MalformedPathException e) { + name = DEFAULT_PERMISSION_NAME; + log.debug("Invalid path name for Permission: " + name + + " set to " + DEFAULT_PERMISSION_NAME); + } + } + int i=0; + String check = name; + while (node.hasNode(check)) { + check = name + i; + i++; + } + return check; + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultACLProvider.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLTemplate.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLTemplate.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultACLTemplate.java (revision 0) @@ -0,0 +1,331 @@ +/* + * 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.authorization; + +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.security.spi.ACLProvider; +import org.apache.jackrabbit.security.ACE; +import org.apache.jackrabbit.security.ACEIterator; +import org.apache.jackrabbit.security.ACETemplate; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ACLTemplate; +import org.apache.jackrabbit.security.ActionSet; + +import javax.security.auth.Subject; +import java.security.Principal; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * {@link ACLTemplate}-Implementation for the {@link DefaultACL}. + * + * @see ACLTemplate + * @see DefaultACL + */ +class DefaultACLTemplate implements ACLTemplate { + + /** + * List containing the entries of this ACL + */ + private LinkedList entries; + + /** + * field indicating if any modifications took place on the current instance + */ + private boolean modified; + + /** + * Id of the current ACL + */ + private final ItemId id; + + /** + * Peristent non-editable ACL backing this instance + */ + private final DefaultACL source; + + /** + * Construct a new {@link ACLTemplate} which has no persistent ACL at its + * {@link ACLProvider}.
    + * As this ACL is new {@link #isModified()} will be true + * + * @param id of the Item this ACL controls + */ + protected DefaultACLTemplate(ItemId id) { + this(id, null); + this.modified = true; + } + + /** + * Construct a new {@link ACLTemplate} which has a persistent ACL at its + * {@link ACLProvider}.
    + * + * @param id of the Item this ACL controls + * @param acl Persistent ACL + */ + protected DefaultACLTemplate(ItemId id, DefaultACL acl) { + this.id = id; + this.source = acl; + } + + //-----------------------------------------------------< ACL >-------------- + /** + * @return name of the ACL + * @see ACL#getName() + */ + public String getName() { + return id.toString(); + } + + /** + * @return Iterator containing editable ACEs + * @see ACL#getEntries() + */ + public ACEIterator getEntries() { + return new ACEIteratorImpl(entries()); + } + + /** + * @param principals to grant + * @param actions to be granted + * @return true if all actions are granted to the set of principals + * @see ACL#grants(Set, ActionSet) + */ + public boolean grants(Set principals, ActionSet actions) { + if (isEmpty()) { + return false; + } + return compile(principals).includes(actions); + } + + /** + * @param subject to compileEntries an ActionSet + * @return compiled ActionSet for this Subject + * @see ACL#compileEntries(Subject) + */ + public ActionSet compileEntries(Subject subject) { + return compile(subject.getPrincipals()); + } + + //-----------------------------------------------------< ACLTemplate >------ + /** + * @return count of ACEs contained in this ACL + * @see ACLTemplate#size() + */ + public int size() { + return entries().size(); + } + + /** + * @return true if ACL does not contain any Entries + * @see ACLTemplate#isEmpty() + */ + public boolean isEmpty() { + return entries().isEmpty(); + } + + /** + * Removes all ACEs from this ACL + * @see ACLTemplate#clear() + */ + public void clear() { + modified |= isEmpty(); + entries().clear(); + } + + /** + * @param ace to get the index in the current ACL + * @return 0 based index or -1 if not contained + * @see ACLTemplate#indexOf(ACETemplate) + */ + public int indexOf(ACETemplate ace) { + return entries().indexOf(ace); + } + + /** + * @param ace to append to the List + * @see ACLTemplate#add(ACETemplate) + */ + public void add(ACETemplate ace) { + modified = true; + entries().add(0,ace); + } + + /** + * @param index to add the ACE at + * @param ace to add + * @see ACLTemplate#add(int, ACETemplate) + */ + public void add(int index, ACETemplate ace) { + modified = true; + entries().add(index, ace); + } + + /** + * @param index to get the Entry for + * @return + * @see ACLTemplate#get(int) + */ + public ACETemplate get(int index) { + return (ACETemplate) entries().get(index); + } + + /** + * @param index to remove from ACL + * @return the Entry which had been at the given index or < + * code>null if none + * @see ACLTemplate#remove(int) + */ + public ACETemplate remove(int index) { + ACETemplate ace = (ACETemplate) entries().remove(index); + modified |= ace!=null; + return ace; + } + + /** + * @param ace Entry to remove + * @return true if the ACE had been contained + * @see ACLTemplate#remove(ACETemplate) + */ + public boolean remove(ACETemplate ace) { + boolean removed = entries.remove(ace); + modified |= removed; + return removed; + } + + /** + * @return if any modifications took place, since acquired from ACLEditor + * @see ACLTemplate#isModified() + */ + public boolean isModified() { + if (!modified) { + Iterator itr = entries().iterator(); + while(!modified && itr.hasNext()) { + ACETemplate ace = (ACETemplate) itr.next(); + if (ace instanceof DefaultACETemplate) { + modified = ace.isModified(); + } + } + } + return modified; + } + + public ACETemplate create(Principal principal, boolean isAllow, ActionSet actionSet) { + return new DefaultACETemplate(principal, isAllow, actionSet, this); + } + + //-------------------------------------------------------------------------- + /** + * @return true if ACL is currently effective + */ + public boolean isValid() { + return source==null || source.isValid(); + } + + /** + * @param principals to compileEntries an ActionSet for + * @return the compiled ActionSet + */ + private ActionSet compile(Set principals) { + int allows = 0; + int denies = 0; + ACEIterator itr = getEntries(); + while(itr.hasNext()) { + ACE ace = itr.nextACE(); + if (principals.contains(ace.getPrincipal())) { + ActionSet set = ace.getActionSet(); + int entryActions = ActionSetImpl.getActionBits(set); + if (ace.isAllow()) { + allows |= entryActions & ~denies; + } else { + denies |= entryActions & ~allows; + } + } + } + return ActionSetImpl.create(allows); + } + + /** + * @return create EditableACEs for the sources ACEs + */ + private LinkedList entries() { + if (entries==null) { + entries = new LinkedList(); + if (source!=null) { + ACEIterator itr = source.getEntries(true); + while(itr.hasNext()) { + entries.add(new DefaultACETemplate(itr.nextACE(), this)); + } + } + } + return entries; + } + + //-----------------------------------------------------< ACEIterator >------ + private class ACEIteratorImpl implements ACEIterator { + + private List entries; + + private long pos; + + /** + * @see ACEIterator + */ + private ACEIteratorImpl(List entries) { + this.entries = entries; + this.pos = 0; + } + + public long getSize() { + return entries.size(); + } + + public long getPosition() { + return pos; + } + + public void skip(long l) { + if (pos+l< getSize()) { + pos += l; + } else { + pos = entries.size(); + } + } + + public ACE nextACE() throws NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return (ACE) entries.get((int) pos++); + } + + public boolean hasNext() { + return pos < getSize(); + } + + public Object next() { + return nextACE(); + } + + public void remove() { + entries.remove((int)pos-1); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultACLTemplate.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultCompiledACLProvider.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultCompiledACLProvider.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/DefaultCompiledACLProvider.java (revision 0) @@ -0,0 +1,208 @@ +/* + * 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.authorization; + +import org.apache.commons.collections.map.LRUMap; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.security.spi.ACLProvider; +import org.apache.jackrabbit.core.security.spi.CompiledACLProvider; +import org.apache.jackrabbit.core.security.spi.CompiledACL; +import org.apache.jackrabbit.security.ACE; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ActionSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import javax.jcr.ItemNotFoundException; +import javax.security.auth.Subject; + +/** + * DefaultCompiledACLProvider... + */ +public class DefaultCompiledACLProvider implements CompiledACLProvider { + + /** + * the default logger + */ + private static final Logger log = LoggerFactory.getLogger(DefaultCompiledACLProvider.class); + + /** + * the underlying acl provider + */ + private final ACLProvider provider; + + /** + * the subject the ACLs are to be compiled for + */ + private final Subject subject; + + /** + * Simple LRU cache + */ + private final LRUMap cache = new LRUMap(1000); + + /** + * @param provider + * @param subject + */ + public DefaultCompiledACLProvider(ACLProvider provider, Subject subject) { + this.provider = provider; + this.subject = subject; + } + + /** + * {@inheritDoc} + */ + public Subject getSubject() { + return subject; + } + + /** + * {@inheritDoc} + * @param nodeId + */ + public CompiledACL getAcl(NodeId nodeId) throws RepositoryException { + synchronized (cache) { + DefaultCompiledACL acl = (DefaultCompiledACL) cache.get(nodeId); + if (acl == null) { + try { + acl = new DefaultCompiledACL(nodeId); + cache.put(nodeId, acl); + } catch (ItemNotFoundException e) { + // don't return an invalid compiledACL -> return null. + } + } + return acl; + } + } + + /** + * {@inheritDoc} + */ + public void close() { + // ignore so far + } + + //-------------------------------------------------------------------------- + /** + * ACL implementaion, which compiles the {@link ACE}s for one Subject + */ + private class DefaultCompiledACL implements CompiledACL { + + /** + * id of this acl + */ + private final NodeId id; + + /** + * the source acl + */ + private ACL source; + + /** + * set of compiled actions + */ + private ActionSet actions; + + /** + * Creates a new compiled acl + * + * @param id + * @throws ItemNotFoundException + * @throws RepositoryException + */ + private DefaultCompiledACL(NodeId id) throws ItemNotFoundException, RepositoryException { + if (id == null) { + throw new IllegalArgumentException("Cannot build a DefaultCompiledACLProvider from a 'null' NodeId."); + } + this.id = id; + this.source = provider.getAcl(id); + + if (source == null) { + throw new ItemNotFoundException("Node " + id + " not found."); + } + } + + /** + * If the underlying ACL became invalid this compiled ACL needs to + * be recompiled. + */ + private synchronized ACL getSource() throws RepositoryException { + if (!((DefaultACL) source).isValid()) { + source = provider.getAcl(id); + if (source == null) { + throw new ItemNotFoundException("Node " + id + " not found."); + } + actions = null; + } + return source; + } + + //----------------------------------------------------< CompiledACL >--- + /** + * {@inheritDoc} + */ + public boolean protectsACL() { + try { + DefaultACL source = (DefaultACL) getSource(); + // source could not be found for transient items + return source != null && source.protectsAcl(); + } catch (RepositoryException e) { + log.warn("Error while evaluating:" + e, e); + return false; + } + } + + /** + * @param actions to test + * @return true if all actions are allowed by the ACL used to compileEntries this + * @see CompiledACL#grants(ActionSet) + */ + public boolean grants(ActionSet actions) { + try { + return getActionSet().includes(actions); + } catch (RepositoryException e) { + return false; + } + } + + /** + * @return a ActionSet representing the actions allowed by this ACL + * @see CompiledACL#getActionSet() + */ + public synchronized ActionSet getActionSet() throws RepositoryException { + if (actions == null) { + ACL src = getSource(); + if (src == null) { + actions = ActionSetImpl.NONE; + } else { + actions = src.compileEntries(subject); + } + } + return actions; + } + + /** + * @return Subject this ACL has been compiled for + * @see CompiledACL#getSubject() + */ + public Subject getSubject() { + return subject; + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\authorization\DefaultCompiledACLProvider.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java (revision 0) @@ -0,0 +1,333 @@ +/* + * 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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.HierarchyManager; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.core.state.ItemStateException; +import org.apache.jackrabbit.core.state.ItemState; +import org.apache.jackrabbit.core.state.NodeState; +import org.apache.jackrabbit.core.state.ItemStateManager; +import org.apache.jackrabbit.core.security.authorization.ActionSetImpl; +import org.apache.jackrabbit.core.security.authorization.DefaultACL; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.apache.jackrabbit.core.security.spi.CompiledACLProvider; +import org.apache.jackrabbit.core.security.spi.WorkspaceACLProvider; +import org.apache.jackrabbit.core.security.spi.CompiledACL; +import org.apache.jackrabbit.name.Path; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ActionSet; + +import javax.jcr.AccessDeniedException; +import javax.jcr.ItemNotFoundException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.RepositoryException; +import javax.security.auth.Subject; +import java.util.Set; + +/** + * DefaultAccessManager

    + * This {@link AccessManager} implementation, controls access via ACLs related + * to Items.
    + * It builds an {@link ActionSet} of the internal actions to grant + * {@link AccessManager#isGranted(ItemId, int)}.

    + * This AccessManager allows everything under the following conditions: + *
      + *
    • If the Session's Subject contains a {@link SystemPrincipal} OR + * a {@link AdminPrincipal}
    • + *
    • No {@link CompiledACLProvider} is set
    • + *
    + *

    + * The relation of {@link ACL} to {@link javax.jcr.Item} itself are defined by + * the {@link CompiledACLProvider} set to this AccessManager. + * + * @version $Rev$, $Date$ + * @see AccessManager + * @see CompiledACLProvider + */ +public class DefaultAccessManager implements AccessManager { + + /** + * indicates if it is setup properly and not closed + */ + private boolean initialized; + + /** + * this AccssManager is build for + */ + private Subject subject; + + /** + * ACL provider configured for this Session + */ + private CompiledACLProvider compiledAclProvider; + + /** + * the global workspace acl provider + */ + private WorkspaceACLProvider wspAclProvider; + + /** + * + */ + private ItemStateManager itemStateMgr; + + /** + * root-node of repository + */ + private ItemId rootNodeId; + + /** + * access items for resolution of last persisted item in hierarchy + */ + private HierarchyManager hierMgr; + + private static final Logger log = LoggerFactory.getLogger(DefaultAccessManager.class); + + //-----------------------------------------------------< AccessManager >---- + /** + * @see AccessManager#init(AMContext) + */ + public void init(AMContext amContext) throws AccessDeniedException, Exception { + hierMgr = amContext.getHierarchyManager(); + subject = amContext.getSubject(); + + itemStateMgr = amContext.getItemStateManager(); + + initialized = true; + if (!canAccess(amContext.getWorkspaceName())) { + throw new AccessDeniedException("Not allowed to access Workspace " + amContext.getWorkspaceName()); + } + + compiledAclProvider = amContext.getCompiledACLProvider(); + wspAclProvider = amContext.getWorkspaceACLProvider(); + } + + /** + * Close this access manager. After having closed an access manager, + * further operations on this object are treated as illegal and throw + * + * @throws Exception if an error occurs + */ + public void close() throws Exception { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + initialized = false; + hierMgr = null; + itemStateMgr = null; + compiledAclProvider = null; + wspAclProvider = null; + } + + /** + * @see AccessManager#checkPermission(ItemId, int) + */ + public void checkPermission(ItemId id, int permissions) + throws AccessDeniedException, ItemNotFoundException, + RepositoryException { + if (!isGranted(id, permissions)) { + throw new AccessDeniedException("Not sufficient priveleges for permissions : " + + permissions + " on " + hierMgr.getPath(id)); + } + } + + /** + * The current implementation, treats a read access to workspace root as + * rule to allow access to a workspace. + *

    + *

    + * {@inheritDoc} + */ + public boolean canAccess(String workspaceName) + throws NoSuchWorkspaceException, RepositoryException { + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + if (isSystem() || wspAclProvider == null) { + return true; + } + return wspAclProvider.getAcl(workspaceName).grants(subject.getPrincipals(), ActionSetImpl.WORKSPACE_ACCESS); + } + + /** + * Resolves the ACL for the Item in question and invoces the + * {@link DefaultACL#grants(Set, java.util.Collection)} + *

    + * If no ACL can be found at all, all getActions are granted to everyone + * + * @param subject to grant permissions + * @param id of the resource to be checked + * @param getActions the getActions which are in question to be granted + * @return true if on of the permissions apply for the current principals + * @see DefaultACL#grants(Set, java.util.Collection) for ACL evalutation + */ + /** + * @see AccessManager#isGranted(ItemId, int) + */ + public boolean isGranted(ItemId id, int actions) + throws ItemNotFoundException, RepositoryException { + + //according AM interface + if (!initialized) { + throw new IllegalStateException("not initialized"); + } + + //allow system all + if (isSystem()) { + // return true; + } + if (compiledAclProvider == null) { + log.error("isGranted: no ACLProvider definied for workspace : grant access"); + return true; + } + + NodeId nodeId = id.denotesNode() ? (NodeId) id : ((PropertyId) id).getParentId(); + // since the compiledAclProvider is not bound to the current Session, + // it ignores of transient changes and therefore gets the nearest + // persisted ACL in the hierarchy. + CompiledACL acl = compiledAclProvider.getAcl(nodeId); + if (acl == null) { + acl = getInheritedAcl(nodeId); + } + + // even no acl for persistent Items + if (acl == null) { + log.info("isGranted: no ACL definied for " + id + " : grant access"); + return true; + } + + // if the ACL is used to protect and ACL item, use other actiond + int bits = 0; + if (acl.protectsACL()) { + if ((actions & AccessManager.READ) == AccessManager.READ) { + bits |= ActionSetImpl.ACTION_ACL_READ; + } + if ((actions & AccessManager.WRITE) == AccessManager.WRITE) { + bits |= ActionSetImpl.ACTION_ACL_MODIFY; + } + if ((actions & AccessManager.REMOVE) == AccessManager.REMOVE) { + bits |= ActionSetImpl.ACTION_ACL_MODIFY; + } + } else { + if ((actions & AccessManager.READ) == AccessManager.READ) { + bits |= ActionSetImpl.ACTION_READ; + } + if ((actions & AccessManager.WRITE) == AccessManager.WRITE) { + if (id.denotesNode()) { + try { + /** + * detect the actual changes wich caused the need to write + * the node. + * child additions -> add_node + * child removals -> remove + * property additions / removals -> set_property + * All may be contained in same commit + */ + NodeState state = (NodeState) itemStateMgr.getItemState(id); + if (state.getAddedChildNodeEntries().size()>0) { + bits |= ActionSetImpl.ACTION_ADD_NODE; + } + if (state.getRemovedChildNodeEntries().size()>0) { + bits |= ActionSetImpl.ACTION_REMOVE; + } + if (state.getAddedPropertyNames().size()>0 + || state.getRemovedPropertyNames().size()>0) { + bits |= ActionSetImpl.ACTION_SET_PROPERTY; + } + } catch (ItemStateException e) { + log.error("attempt to test permissions on inexistant state {}", e); + return false; + } + } else { + bits |= ActionSetImpl.ACTION_SET_PROPERTY; + } + } + if ((actions & AccessManager.REMOVE) == AccessManager.REMOVE) { + bits |= ActionSetImpl.ACTION_REMOVE; + } + } + return acl.grants(ActionSetImpl.create(bits)); + } + + /** + * Search for the next ancestor, which contains ACL information.
    + * Itemstates' States are inspected in order to avoid recusive incovation + * of this AccessManager instance.
    + * If no state is persistent or if no ItemStateManager is set, + * null is returned. + * + * @param id + * @return + * @throws RepositoryException if parent for Id can not be accessed + */ + private CompiledACL getInheritedAcl(NodeId id) throws RepositoryException { + if (itemStateMgr == null || !itemStateMgr.hasItemState(id)) { + log.debug("getInheritedAcl: can't access item-states for " + id); + return null; + } + try { + ItemState state = itemStateMgr.getItemState(id); + while (state.getStatus() == ItemState.STATUS_NEW && state.getId() != rootNodeId) { + id = getParentId(id); + if (id == null) { + return null; + } + state = itemStateMgr.getItemState(id); + } + } catch (ItemStateException e) { + throw new ItemNotFoundException(e); + } + return compiledAclProvider.getAcl(id); + } + + /** + * Resolve the parent for the given id. + * + * @return Id or null if workspace's root-node + * @throws ItemNotFoundException + */ + private NodeId getParentId(NodeId id) throws ItemNotFoundException { + NodeId parentId = null; + try { + if (rootNodeId == null) { + rootNodeId = hierMgr.resolvePath(Path.ROOT); + } + if (!rootNodeId.equals(id)) { + Path path = hierMgr.getPath(id); + parentId = (NodeId) hierMgr.resolvePath(path.getAncestor(1)); + } + return parentId; + } catch (RepositoryException e) { + throw new ItemNotFoundException("Parent for id \'" + id + + "\' could not be resolved"); + } + } + + + /** + * @return if created with system-privileges + */ + private boolean isSystem() { + return !subject.getPrincipals(SystemPrincipal.class).isEmpty() + || !subject.getPrincipals(AdminPrincipal.class).isEmpty(); + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\DefaultAccessManager.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java (revision 0) @@ -0,0 +1,145 @@ +/* + * 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.principal; + +import org.apache.commons.collections.BeanMap; +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; + +import java.security.Principal; +import java.util.Iterator; +import java.util.Properties; + +/** + * A base class of a principal provider implementing common tasks and a + * caching facility. Extending classes should only deal with the retrieval of + * {@link Principal}s from their source, the caching of the principials is done + * by this implementation. + *

    + * The {@link PrincipalProvider} mehtods that that involve searching like + * {@link PrincipalProvider#memberOf(Principal)} are not cached. + */ +public abstract class AbstractPrincipalProvider implements PrincipalProvider { + + /** Constant inddicating the name of configuration property */ + public static final String EXPIRATION_KEY = "cacheExpiration"; + + /** Constant inddicating the name of configuration property */ + public static final String MAXSIZE_KEY = "cacheMaxSize"; + + /** flag indicating if the instance hasn't been closed {@link #close()} */ + private boolean alive = true; + + /** the principal cache */ + private final PrincipalCache cache; + + /** + * Constructor initalizing with default-values for the cache
    + * Configuration is done via {@link #setOptions(Properties)} + */ + protected AbstractPrincipalProvider() { + cache = new PrincipalCache(PrincipalCache.DEFAULT_EXPIRATION); + } + + /** + * Check if the instance has been closed {@link #close()}. + * + * @throws IllegalStateException if this instance was closed. + */ + protected void sanityCheck() { + if (!alive) { + throw new IllegalStateException("AbstractPrincipalProvider instance has been closed."); + } + } + + /** + * Flushes the principal with the given name from the cache. + */ + protected synchronized void flushAll() { + synchronized(cache) { + cache.clear(); + } + } + + /** + * Called if the cache does not contain the principal requested.
    + * Implementations should return a {@link Principal} from their source, + * if it contains one for the given name or null. + * + * @param principalName + * @return Principal or null, if non provided for the given name + * @see #getPrincipal(String) + */ + protected abstract Principal providePrincipal(String principalName); + + //--------------------------------------------------< PrincipalProvider >--- + /** + * {@inheritDoc} + */ + public void setOptions(Properties options) { + BeanMap bm = new BeanMap(this); + Iterator itr = bm.keyIterator(); + while(itr.hasNext()) { + String key = (String) itr.next(); + if (options.containsKey(key)) { + bm.put(key, options.get(key)); + } + } + } + + /** + * {@inheritDoc} + */ + public boolean hasPrincipal(String principalName) { + return getPrincipal(principalName) != null; + } + + /** + * {@inheritDoc} + * + * {@link #providePrincipal(String)} is called, if a principal is not found in + * the cache. + */ + public Principal getPrincipal(String principalName) { + sanityCheck(); + + Principal principal; + synchronized(cache) { + if (cache.containsKey(principalName)) { + principal = ((Principal) cache.get(principalName)); + } else { + principal = providePrincipal(principalName); + if (principal != null) { + cache.put(principalName, principal); + } + } + } + return principal; + } + + /** + * Clears the cache and calls the implementation to close their resources + * @see PrincipalProvider#close() + * @see PrincipalCache#close() + */ + public void close() { + sanityCheck(); + synchronized (cache) { + cache.close(); + } + alive = false; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\AbstractPrincipalProvider.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AdminPrincipal.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AdminPrincipal.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AdminPrincipal.java (revision 0) @@ -0,0 +1,35 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.security.SecurityConstants; + +import java.security.Principal; + +/** + * This principal represents the admin user as a distinct principal having all + * the access rights. + */ +public class AdminPrincipal implements Principal { + + AdminPrincipal() { + } + + public String getName() { + return SecurityConstants.ADMIN_ID; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\AdminPrincipal.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AuthorizableNodeResolver.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AuthorizableNodeResolver.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AuthorizableNodeResolver.java (revision 0) @@ -0,0 +1,226 @@ +/* + * 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.principal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Item; +import java.util.HashSet; +import java.util.Set; + +/** + * Resolver: searches for Principals stored in Nodes of a {@link javax.jcr.Workspace} + * which match a certain criteria

    + * The principalNames are assumed to be stored in properties. + * As default the UUID of a Node is searched for the principal names. + * Additionally other PropertyNames can be added to be searched + * {@link #addTargetProperty(String)} + */ +public abstract class AuthorizableNodeResolver { + + public static final Node[] EMPTY_RESULT = new Node[0]; + + private static final Logger log = LoggerFactory.getLogger(AuthorizableNodeResolver.class); + + private static final String DEFAULT_TARGET_PROPERTY = "jcr:uuid"; + + private final String searchRoot; + private final String nodeTypeName; + private final Set targetProperties = new HashSet(); + private final Session session; + + /** + * Create a new AuthorizableNodeResolver. + * + * @param session; + * @param nodeTypeName + * @param absPath + * @throws RepositoryException if instanciation fails + */ + AuthorizableNodeResolver(Session session, String nodeTypeName, String absPath) + throws RepositoryException { + + this.session = session; + this.session.itemExists(absPath); + this.nodeTypeName = nodeTypeName; + this.searchRoot = absPath; + addTargetProperty(DEFAULT_TARGET_PROPERTY); + } + + /** + * @param propertyName to be searched for a principal-name in question + */ + public void addTargetProperty(String propertyName) { + targetProperties.add(propertyName); + } + + /** + * Tests if the {@link javax.jcr.Node Node} at the given path has the + * {@link #getNodeTypeName()} NodeType} of this Resolver + * + * @param path {@link javax.jcr.Node#getPath Path} to locate the Node + * @return Node if Item exists, is a Node and NodeType + * {@link Node#isNodeType(String) matches} or null + * @throws RepositoryException in any other exceptional state + */ + public Node getNode(String path) throws RepositoryException { + + //first normalize path + String normalPath; + if (path.startsWith("/")) { + normalPath = path; + } else { + normalPath = getSearchRoot() + "/" + path; + } + + //apply check, if within search + Node res = null; + if (getSession().itemExists(normalPath)) { + Item item = getSession().getItem(normalPath); + if (item.isNode() && ((Node) item).isNodeType(getNodeTypeName())) { + res = (Node) item; + } + } + return res; + } + + /** + * @param principalName + * @return true if the filter-setting contain a Principal for the given name + */ + public boolean hasPrincipalNode(String principalName) { + try { + return getPrincipalNode(principalName) != null; + } catch (RepositoryException e) { + log.warn("hasPrincipalNode: failed to check existance: {}", e.getMessage()); + log.debug("Stack: ", e); + return false; + } + } + + /** + * @param principalName to get the Node for + * @return Node if existing for the requested Principal-Name argument. + * null if {@link #hasPrincipalNode(String)} is false + * @throws RepositoryException + */ + public Node getPrincipalNode(String principalName) throws RepositoryException { + return provideNode(principalName, targetProperties, nodeTypeName); + } + + /** + * Search all nodes with a given nameFragment. + * The argument is matches as subtrsing
    + * Thus it would be true, that
    + * findNode(value, true) = findNode(value, false) + findByName(value) + * + * @param nameFragment to be search for + * @param exact + * @return Nods containing the argument in their names + * @throws RepositoryException + */ + public abstract Node[] findByName(String nameFragment, boolean exact) + throws RepositoryException; + + /** + * Search the value as a substring match in all properties as set with + * {@link #addTargetProperty(String)} + * + * @param value + * @return matches may be empty but not null + */ + public Node[] findNode(String value) throws RepositoryException { + return findNodes(value, targetProperties, nodeTypeName, false); + } + + /** + * Search for Nodes which contain an excact match for the given value in + * their property as indicated by the propertyName argument.
    + * The setting for NodeType and search root are respected. + * + * @param propertyName property to be searched + * @param value value to be matched exactly + * @param exact if true value has to match exactly + * @return matches, may be empty but not null + */ + public Node[] findNode(String propertyName, String value, boolean exact) + throws RepositoryException { + + Set props = new HashSet(); + props.add(propertyName); + return findNodes(value, props, nodeTypeName, exact); + } + + /** + * Get the Node wich stores a Principal for the given name. + * Restrict the search by NodeType and Properties the principal-name may be + * storted + * + * @param principalName + * @param targetProperties + * @param nodeTypeName + * @return + * @throws RepositoryException + */ + abstract Node provideNode(String principalName, + Set targetProperties, + String nodeTypeName) throws RepositoryException; + + /** + * Search nodes. Take the arguments as search cirteria. + * The queried value has to be a string fragment of one of the Properties + * contained in the given set. And the node have to be of a requested nodetype + * + * @param val substring to be contained in the Nodes' Property-Values + * @param props Properties to be searched for + * @param ntName NodeType the hits have to have + * @param exact if true match must be exact + * @return + * @throws RepositoryException + */ + abstract Node[] findNodes(String val, Set props, String ntName, boolean exact) + throws RepositoryException; + + /** + * + * @return The nodetype name + */ + String getNodeTypeName() { + return nodeTypeName; + } + + /** + * @return the absolut start for {@link java.security.Principal}s + * to be searched + */ + String getSearchRoot() { + return searchRoot; + } + + /** + * @return Session this instance has been constructed with + */ + Session getSession() { + return session; + } +} + + Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\AuthorizableNodeResolver.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/CyclicGroupDefinitionException.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/CyclicGroupDefinitionException.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/CyclicGroupDefinitionException.java (revision 0) @@ -0,0 +1,28 @@ +/* + * 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.principal; + +/** + * Indicate that the current Group can not be processed as it contains + * a cyclic membership definition + */ +public class CyclicGroupDefinitionException extends RuntimeException { + + public CyclicGroupDefinitionException(String msg) { + super(msg); + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\CyclicGroupDefinitionException.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalIterator.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalIterator.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalIterator.java (revision 0) @@ -0,0 +1,185 @@ +/* + * 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.principal; + +import org.apache.commons.collections.iterators.IteratorChain; +import org.apache.jackrabbit.security.PrincipalIterator; +import org.apache.jackrabbit.security.PrincipalManager; + +import javax.jcr.RangeIterator; +import java.security.Principal; +import java.security.acl.Group; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * @see PrincipalIterator + * @see RangeIterator + */ +public class DefaultPrincipalIterator implements PrincipalIterator { + + public static final PrincipalIterator EMPTY = new DefaultPrincipalIterator(Collections.EMPTY_SET, PrincipalManager.SEARCH_TYPE_ALL); + + private final long size; + private final Iterator iterator; + private final int filter; + + private long position; + private Principal next; + + /** + * @param iterators as content of this one + * @param filter + */ + public DefaultPrincipalIterator(PrincipalIterator[] iterators, int filter) { + if (iterators == null || iterators.length == 0 || filter < PrincipalManager.SEARCH_TYPE_ALL || filter > PrincipalManager.SEARCH_TYPE_NOT_GROUP) { + throw new IllegalArgumentException(); + } + iterator = iterators.length == 1 ? (Iterator) iterators[0] : new IteratorChain(iterators); + if (filter == PrincipalManager.SEARCH_TYPE_ALL) { + long sz = 0; + for (int i = 0; i < iterators.length; i++) { + sz += iterators[i].getSize(); + } + size = sz; + } else { + size = -1; + } + this.filter = filter; + next = seekNext(); + } + + /** + * Collection backing this iterator. The collection has to contain only + * Principals. + * + * @param collection of {@link Principal}s + * @param filter + */ + public DefaultPrincipalIterator(Collection collection, int filter) { + iterator = new IteratorChain(collection.iterator()); + size = (filter == PrincipalManager.SEARCH_TYPE_ALL) ? collection.size() : -1; + this.filter = filter; + next = seekNext(); + } + + /** + * Collection backing this iterator. The collection has to contain only + * Principals. + * + * @param collection of {@link Principal}s + */ + public DefaultPrincipalIterator(Collection collection) { + iterator = new IteratorChain(collection.iterator()); + size = collection.size(); + this.filter = PrincipalManager.SEARCH_TYPE_ALL; + next = seekNext(); + } + + //------------------------------------------------------< RangeIterator >--- + /** + * @param skipNum number of entries to be skipped + * @see RangeIterator#skip(long) + */ + public void skip(long skipNum) { + while (skipNum-- > 0) { + next(); + } + } + + /** + * @see RangeIterator#getSize() + */ + public long getSize() { + return size; + } + + /** + * @see RangeIterator#getPosition() + */ + public long getPosition() { + return position; + } + + //-----------------------------------------------------------< Iterator >--- + /** + * @see Iterator#remove() + */ + public void remove() { + iterator.remove(); + } + + /** + * @see Iterator#hasNext() + */ + public boolean hasNext() { + return next != null; + } + + /** + * @see Iterator#next() + */ + public Object next() { + return nextPrincipal(); + } + + //--------------------------------------------------< PrincipalIterator >--- + /** + * @see PrincipalIterator#nextPrincipal() + */ + public Principal nextPrincipal() { + Principal p = next; + if (p == null) { + throw new NoSuchElementException(); + } + next = seekNext(); + position++; + return p; + } + + //-------------------------------------------------------------------------- + /** + * @return a Principal if one of the possible iterators still has a next + */ + protected Principal seekNext() { + while (iterator.hasNext()) { + Object o = iterator.next(); + if (o instanceof Principal) { + Principal prncpl = (Principal) o; + switch (filter) { + case PrincipalManager.SEARCH_TYPE_ALL: + return prncpl; + case PrincipalManager.SEARCH_TYPE_GROUP: + if (prncpl instanceof Group) { + return prncpl; + } + break; + case PrincipalManager.SEARCH_TYPE_NOT_GROUP: + if (!(prncpl instanceof Group)) { + return prncpl; + } + break; + // no default. + } + } + } + // no matching Principal found -> iteration is completed. + return null; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\DefaultPrincipalIterator.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java (revision 0) @@ -0,0 +1,311 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.security.SystemPrincipal; +import org.apache.jackrabbit.core.security.authorization.ActionSetImpl; +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; +import org.apache.jackrabbit.core.security.user.UserManagerImpl; +import org.apache.jackrabbit.security.ActionSet; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.Group; +import org.apache.jackrabbit.security.PrincipalIterator; +import org.apache.jackrabbit.security.PrincipalManager; +import org.apache.jackrabbit.security.User; +import org.apache.jackrabbit.security.UserManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.security.auth.Subject; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Provides principals for the Users contained within the Repository.

    + * Each {@link Authorizable} accessable via {@link UserManager} + * is respected.
    + * The Proveder serves the Authorizables {@link Authorizable#getPrincipal() Principal}. + *

    + * This provider exposes the everyone principal. This is a group + * principal that contains all other principals. It exists only virtual and has + * no content represention. + */ +public class DefaultPrincipalProvider extends AbstractPrincipalProvider implements EventListener { + + /** + * the 'admin' principal + */ + public static final AdminPrincipal ADMIN_PRINCIPAL = new AdminPrincipal(); + + /** + * the 'everyone' principal + */ + public static final EveryonePrincipal EVERYONE_PRINCIPAL = new EveryonePrincipal(); + + /** + * the default logger + */ + private static final Logger log = LoggerFactory.getLogger(DefaultPrincipalProvider.class); + + /** + * a cache for group memberships: maps principal-name to a set of principals + * representing the members. + */ + private final PrincipalCache membershipCache = new PrincipalCache(PrincipalCache.DEFAULT_EXPIRATION); + + /** + * "Principal-Base of this Provider + */ + private final UserManagerImpl userManager; + + /** + * Creates a new DefaultPrincipalProvider reading the principals from the + * storage below the given security root node. + * + * @param securitySession for Repository Access + * @throws RepositoryException if an error accessing the repository occurs. + */ + public DefaultPrincipalProvider(Session securitySession, UserManagerImpl userManager) + throws RepositoryException { + + this.userManager = userManager; + + // add to observation listener + //todo: this could be done by a UserManager listener + securitySession.getWorkspace().getObservationManager().addEventListener(this, + Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED, + userManager.getRootPath(), + true, + null, + null, + false); + } + + //----------------------------------------------- + /** + * {@inheritDoc} + *

    + * This implementation uses the user and node resolver to find the + * appropriate nodes. + */ + protected Principal providePrincipal(String principalName) { + + // check for 'everyone' + if (EVERYONE_PRINCIPAL.getName().equals(principalName)) { + return EVERYONE_PRINCIPAL; + } + try { + Principal principal = new PrincipalImpl(principalName); + synchronized (userManager) { + Authorizable ath = userManager.getAuthorizable(principal); + + //don't provide Principals the Auth is referent for + if (ath != null && principal.equals(ath.getPrincipal())) { + if (!ath.isGroup() && ((User)ath).isAdmin()) { + return ADMIN_PRINCIPAL; + } else { + return ath.getPrincipal(); + } + } + } + } catch (RepositoryException e) { + log.error("providePrincipal: failed to access Users for Principal", e); + } + return null; + } + + //------------------------------------------------< PrincipalProvider >----- + /** + * @see PrincipalProvider#searchPrincipal(String) + */ + public PrincipalIterator searchPrincipal(String simpleFilter) { + return searchPrincipal(simpleFilter, PrincipalManager.SEARCH_TYPE_ALL); + } + + public PrincipalIterator searchPrincipal(String simpleFilter, int searchType) { + sanityCheck(); + switch (searchType) { + case PrincipalManager.SEARCH_TYPE_GROUP: + return getGroups(simpleFilter); + case PrincipalManager.SEARCH_TYPE_NOT_GROUP: + return getUsers(simpleFilter); + case PrincipalManager.SEARCH_TYPE_ALL: + PrincipalIterator[] its = new PrincipalIterator[] {getUsers(simpleFilter), getGroups(simpleFilter)}; + return new DefaultPrincipalIterator(its, PrincipalManager.SEARCH_TYPE_ALL); + default: + throw new IllegalArgumentException("Invalid searchType"); + } + } + + /** + * @see PrincipalProvider#getPrincipals(int) + * @param searchType + */ + public synchronized PrincipalIterator getPrincipals(int searchType) { + sanityCheck(); + switch (searchType) { + case PrincipalManager.SEARCH_TYPE_GROUP: + return getGroups(""); + case PrincipalManager.SEARCH_TYPE_NOT_GROUP: + return getUsers(""); + case PrincipalManager.SEARCH_TYPE_ALL: + PrincipalIterator[] its = new PrincipalIterator[] {getUsers(""), getGroups("")}; + return new DefaultPrincipalIterator(its, PrincipalManager.SEARCH_TYPE_ALL); + default: + throw new IllegalArgumentException("Invalid searchType"); + } + } + + /** + * @see PrincipalProvider#memberOf(Principal) + */ + public PrincipalIterator memberOf(Principal user) { + sanityCheck(); + if (user == null) { + throw new IllegalArgumentException("User argument must not be null"); + } + synchronized (membershipCache) { + Set members = (Set) membershipCache.get(user.getName()); + if (members == null) { + members = internalGetGroupMembership(user); + membershipCache.put(user.getName(), members); + } + return new DefaultPrincipalIterator(members, PrincipalManager.SEARCH_TYPE_ALL); + } + } + + /** + * @see PrincipalProvider#memberOf(Principal) + */ + private Set internalGetGroupMembership(Principal user) { + if (!hasPrincipal(user.getName())) { + return Collections.EMPTY_SET; + } + Set membership = new HashSet(); + if (EVERYONE_PRINCIPAL.isMember(user)) { + membership.add(EVERYONE_PRINCIPAL); + } + try { + Authorizable cov = userManager.getAuthorizable(user); + if (cov == null) { + return membership; + } + + Iterator itr = cov.memberOf(); + while (itr.hasNext()) { + Group group = (Group) itr.next(); + Principal principal = group.getPrincipal(); + if (!membership.add(principal)) { + log.warn("getGroupMemberhisp: detected cyclic GroupMemberhsip of {} to {} -> break", + user, principal); + return membership; + } + membership.addAll(internalGetGroupMembership(principal)); + } + } catch (RepositoryException e) { + log.error("memberOf: failed to access membership for ''{}'': {}", user, e.getMessage()); + log.debug("", e); + } + return membership; + } + + /** + * {@inheritDoc} + */ + public boolean hasPermission(Subject subject, Principal principal, ActionSet actions) { + //short-cut admin + Set principals = subject.getPrincipals(); + if (principals.contains(ADMIN_PRINCIPAL) || principals.contains(new SystemPrincipal())) { + return true; + } + //short-cut everyone + if (EVERYONE_PRINCIPAL.equals(principal)) { + return true; + } + + //check if the userManager session is granted read to the node. + // if not -> not principal or no read-rights => denyed + // if yes -> read is granted any other actions are of no sense so far -> deny + try { + Authorizable authz = userManager.getAuthorizable(principal); + return authz!=null && actions.diff(ActionSetImpl.READ).isEmpty(); + } catch (RepositoryException e) { + log.warn("hasPermission failed {} -> deny access", e); + } + return false; + } + + //------------------------------------------------------< EventListener >--- + public void onEvent(EventIterator eventIterator) { + // simple rule: flush all cached + flushAll(); + synchronized (membershipCache) { + membershipCache.clear(); + } + } + + //-------------------------------------------------------------------------- + private PrincipalIterator getUsers(String simpleFilter) { + sanityCheck(); + synchronized (userManager) { + Collection all = new HashSet(); + try { + Iterator itr = userManager.findUsers(simpleFilter); + while (itr.hasNext()) { + User user = (User) itr.next(); + if (user.isAdmin()) { + all.add(ADMIN_PRINCIPAL); + } else { + all.add(user.getPrincipal()); + } + } + } catch (RepositoryException e) { + log.error("getUsers: faild to access users {}", e); + } + return new DefaultPrincipalIterator(all); + } + } + + private PrincipalIterator getGroups(String simpleFilter) { + sanityCheck(); + synchronized (userManager) { + Collection all = new HashSet(); + if (simpleFilter.equals("")) { + all.add(EVERYONE_PRINCIPAL); + } + try { + Iterator itr = userManager.findGroups(simpleFilter); + while (itr.hasNext()) { + Group group = (Group) itr.next(); + all.add(group.getPrincipal()); + } + } catch (RepositoryException e) { + log.error("getGroups: faild to access Groups {}", e); + } + return new DefaultPrincipalIterator(all); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\DefaultPrincipalProvider.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultProviderRegistry.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultProviderRegistry.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultProviderRegistry.java (revision 0) @@ -0,0 +1,141 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.config.BeanConfig; +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.RepositoryException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * This is the default implementation of the {@link PrincipalProviderRegistry} + * interface. + */ +public class DefaultProviderRegistry implements PrincipalProviderRegistry { + + /** the default logger */ + private static final Logger log = LoggerFactory.getLogger(DefaultProviderRegistry.class); + + private final List list = new ArrayList(); + + private PrincipalProvider[] cachedList; + + private final Map providers = new HashMap(); + + public DefaultProviderRegistry(PrincipalProvider defaultPrincipalProvider) { + addProvider(defaultPrincipalProvider.getClass().getName(), defaultPrincipalProvider); + } + + /** + * {@inheritDoc} + */ + public PrincipalProvider addProvider(String name, PrincipalProvider provider) { + synchronized(providers) { + PrincipalProvider old = (PrincipalProvider) providers.put(name, provider); + if (old != null) { + list.remove(old); + } + list.add(provider); + cachedList = null; + return old; + } + } + + /** + * {@inheritDoc} + */ + public PrincipalProvider addProvider(Properties config) throws RepositoryException { + PrincipalProvider pp = createProvider(config); + String providerName = (String) config.get(PRINCIPAL_PROVIDER_NAME); + if (null == providerName || "".equals(providerName)) { + providerName = pp.getClass().getName(); + } + return addProvider(providerName, pp); + } + + /** + * {@inheritDoc} + */ + public PrincipalProvider getDefault() { + return getProviders()[0]; + } + + /** + * {@inheritDoc} + */ + public PrincipalProvider getProvider(String name) { + synchronized(providers) { + return (PrincipalProvider) providers.get(name); + } + } + + /** + * {@inheritDoc} + */ + public PrincipalProvider[] getProviders() { + synchronized (providers) { + if (cachedList == null) { + cachedList = (PrincipalProvider[]) list.toArray(new PrincipalProvider[list.size()]); + } + return cachedList; + } + } + + /** + * Read the map and instanciate the class indicated by the + * {@link DefaultProviderRegistry#PRINCIPAL_PROVIDER_CLASS} key.
    + * The class gets set the properties of the given map, via a Bean mechanism + * + * @param config + * @return + * @throws RepositoryException if the provider could not be created from the + * given config. + */ + private PrincipalProvider createProvider(Properties config) + throws RepositoryException { + + String className = (String) config.get(PRINCIPAL_PROVIDER_CLASS); + if (className == null) { + String msg = ("createProvider: configuration did not contain a class name at key " + + PRINCIPAL_PROVIDER_CLASS); + log.error(msg); + throw new RepositoryException(msg); + } + + try { + Class pc = Class.forName(className, true, BeanConfig.getDefaultClassLoader()); + PrincipalProvider pp = (PrincipalProvider) pc.newInstance(); + pp.setOptions(config); + return pp; + } catch (ClassNotFoundException e) { + throw new RepositoryException("Unable to create new principal provider.", e); + } catch (IllegalAccessException e) { + throw new RepositoryException("Unable to create new principal provider.", e); + } catch (InstantiationException e) { + throw new RepositoryException("Unable to create new principal provider.", e); + } catch (ClassCastException e) { + throw new RepositoryException("Unable to create new principal provider.", e); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\DefaultProviderRegistry.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/EveryonePrincipal.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/EveryonePrincipal.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/EveryonePrincipal.java (revision 0) @@ -0,0 +1,52 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.security.SecurityConstants; + +import java.security.Principal; +import java.util.Enumeration; + +/** + * The EveryonePrincipal contains all principlas but the admin.
    + * Thus it should assert, that the admin is not exluded by denys on everyone. + */ +public class EveryonePrincipal implements java.security.acl.Group { + + EveryonePrincipal() { + } + + public String getName() { + return SecurityConstants.EVERYONE_NAME; + } + + public boolean addMember(Principal user) { + return false; + } + + public boolean removeMember(Principal user) { + return false; + } + + public boolean isMember(Principal member) { + return !member.equals(this); + } + + public Enumeration members() { + throw new UnsupportedOperationException("Not implemented."); + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\EveryonePrincipal.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/GroupImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/GroupImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/GroupImpl.java (revision 0) @@ -0,0 +1,97 @@ +/* + * 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.principal; + +import java.security.Principal; +import java.security.acl.Group; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Set; + +/** + * This class is the default Group implementation of the + * JackrabbitPrincipal.
    + * It's members can only be set upon creation. Calls to {@link #addMember(Principal)} + * or {@link #removeMember(Principal)} always return false and the Group remains + * unchanged. + */ +public class GroupImpl extends PrincipalImpl implements Group { + + /** the serial number */ + // TODO: check if necessary to generate new ser-ID + static final long serialVersionUID = 7303441075017045294L; + + /** set of resolved getMembers */ + private final Set members; + + /** + * Creates a new group with the given name and principal provider. + * + * @param name the name of this group + * @param description a (human readable) description of this group + * @param members the members to be contained in this Group + */ + public GroupImpl(String name, String description, Set members) { + super(name, description); + this.members = Collections.unmodifiableSet(members); + } + + /** + * {@inheritDoc} + * + * @throws CyclicGroupDefinitionException if the member is a group having + * this group as member. + */ + public boolean addMember(Principal member) { + return false; + } + + /** + * {@inheritDoc} + */ + public boolean removeMember(Principal member) { + return false; + } + + /** + * {@inheritDoc} + */ + public boolean isMember(Principal member) { + // check if direct member of this group + if (members.contains(member)) { + return true; + } + // check if any of the loaded groups has this memeber + Iterator iter = members.iterator(); + while (iter.hasNext()) { + Principal p = (Principal) iter.next(); + if (p instanceof Group && ((Group) p).isMember(member)) { + return true; + } + } + return false; + } + + /** + * @return getMembers as Principals + * @see Group#members() + */ + public Enumeration members() { + return Collections.enumeration(members); + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\GroupImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/IndexNodeResolver.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/IndexNodeResolver.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/IndexNodeResolver.java (revision 0) @@ -0,0 +1,205 @@ +/* + * 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.principal; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; +import java.util.HashSet; +import java.util.regex.PatternSyntaxException; + +import org.apache.jackrabbit.util.ISO9075; +import org.apache.jackrabbit.util.Text; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author ckeller + * @version $Rev$, $Date$ + */ +public class IndexNodeResolver extends AuthorizableNodeResolver { + + private static final Logger log = LoggerFactory.getLogger(IndexNodeResolver.class); + + private final QueryManager queryManager; + + public IndexNodeResolver(Session session, String nodeType, String rootPath) + throws RepositoryException { + + super(session, nodeType, rootPath); + this.queryManager = session.getWorkspace().getQueryManager(); + addTargetProperty(SecurityConstants.P_PRINCIPAL_NAME); + } + + //----------------------------------------------< AuthorizableNodeResolver > + + /** + * @inheritDoc + */ + public Node[] findByName(String name, boolean exact) throws RepositoryException { + if(exact) { + return findByExactName(name); + } else { + return findByFuzzyName(name); + } + } + + /** + * @inheritDoc + */ + Node provideNode(String principalName, Set props, String nodeType) + throws RepositoryException { + + Query query = buildQuery(principalName, props, nodeType, true); + NodeIterator res = query.execute().getNodes(); + if (res.hasNext()) { + return res.nextNode(); + } + return null; + } + + /** + * Search nodes. Take the arguments as search cirteria. + * The queried value has to be a string fragment of one of the Properties + * contained in the given set. And the node have to be of a requested nodetype + * + * @param val substring to be contained in the Nodes' Property-Values + * @param props Properties to be searched for + * @param ntName NodeType the hits have to have + * @param exact if true match must be exact + * @return + * @throws javax.jcr.RepositoryException + */ + Node[] findNodes(String val, Set props, String ntName, boolean exact) throws RepositoryException { + Query query = this.buildQuery(val, props, ntName, exact); + NodeIterator res = query.execute().getNodes(); + Collection tmp = new HashSet((int) res.getSize()); + while(res.hasNext()) { + tmp.add(res.nextNode()); + } + return (Node[]) tmp.toArray(new Node[tmp.size()]); + } + + //-------------------------------------------------------------------------- + /** + * + * @param value + * @param props + * @param ntName + * @param exact + * @return + * @throws RepositoryException + */ + private Query buildQuery(String value, Set props, String ntName, boolean exact) + throws RepositoryException { + + StringBuffer query = new StringBuffer("/jcr:root"); + query.append(getSearchRoot()); + query.append("//element(*,"); + query.append(ntName); + query.append(")["); + appendCondition(query, props, value, exact); + query.append("]"); + return queryManager.createQuery(query.toString(), Query.XPATH); + } + + /** + * + * @param query + * @param props + * @param value + * @param exact + */ + private void appendCondition(StringBuffer query, Set props, String value, boolean exact) { + if (exact) { + int i = 0; + Iterator itr = props.iterator(); + while (itr.hasNext()) { + query.append("@"); + query.append(ISO9075.encode((String) itr.next())); + query.append("='"); + query.append(value); + query.append("'"); + if (++i < props.size()) { + query.append(" or "); + } + } + } else { + int i = 0; + Iterator itr = props.iterator(); + while (itr.hasNext()) { + query.append("jcr:like("); + query.append("@"); + query.append(ISO9075.encode((String) itr.next())); + query.append(",'%"); + query.append(value); + query.append("%')"); + if (++i < props.size()) { + query.append(" or "); + } + } + } + } + + private Node[] findByFuzzyName(String name) throws RepositoryException { + StringBuffer stmt = new StringBuffer("/jcr:root"); + stmt.append(getSearchRoot()); + stmt.append("//element(*,"); + stmt.append(getNodeTypeName()); + stmt.append(")"); + Query query = queryManager.createQuery(stmt.toString(), Query.XPATH); + NodeIterator res = query.execute().getNodes(); + Collection tmp = new ArrayList((int) res.getSize()); + while (res.hasNext()) { + Node node = res.nextNode(); + try { + if (node.getName().matches(".*"+name+".*")) { + tmp.add(node); + } + } catch (PatternSyntaxException pe) { + log.debug("couldn't search for {}, pattern invalid: {}", + name, pe.getMessage()); + } + } + return (Node[]) tmp.toArray(new Node[tmp.size()]); + } + + private Node[] findByExactName(String name) throws RepositoryException { + StringBuffer stmt = new StringBuffer("/jcr:root"); + stmt.append(getSearchRoot()); + stmt.append("//element("); + stmt.append(ISO9075.encode(Text.escapeIllegalJcrChars(name))); + stmt.append(","); + stmt.append(getNodeTypeName()); + stmt.append(")"); + Query query = queryManager.createQuery(stmt.toString(), Query.XPATH); + Iterator res = query.execute().getNodes(); + Collection tmp = new ArrayList(); + while (res.hasNext()) { + tmp.add(res.next()); + } + return (Node[]) tmp.toArray(new Node[tmp.size()]); + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\IndexNodeResolver.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalCache.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalCache.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalCache.java (revision 0) @@ -0,0 +1,213 @@ +/* + * 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.principal; + +import org.apache.commons.collections.map.LRUMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Simple cache implemenation. Its invalidation is controlled by two parameters: + *

      + *
    • Expiration time: the time an entry is considered to be valid
    • + *
    • Size of the cache: the amount of entries in this cache
    • + *
    + * In case the maximal size of the cache is reached, the entry with the maximal + * timespan since the last access is removed upon a put to this cache. + * In case of an entry is expired is removed on next request for it. + * The passive nature of this caches invalidation behavior limits the usage of + * this cache to object with small memory consumption and a small amount of + * entries. + */ +class PrincipalCache { + + private final LRUMap cache; + + /** configured life-time of entries */ + private final long expiration; + + /** default life-time of entires */ + public static final long DEFAULT_EXPIRATION = 600; + + /** default maximum size of this cache */ + public static final int DEFAULT_SIZE = 1000; + + private static final Logger log = LoggerFactory.getLogger(PrincipalCache.class); + + /** + * Construct with default size + * + * @param expiration amount of tim in seconds, an entry remains valid + * @see #DEFAULT_SIZE + * @see #PrincipalCache(long, int) + */ + PrincipalCache(long expiration) { + this(expiration, DEFAULT_SIZE); + } + + /** + * Construct a cache which entries are valid for the geiven amount of time + * and which size is limited. + * + * @param expiration time in seconds, entries remain valid + * @param maxSize maximal amount of entries in this cache + */ + PrincipalCache(long expiration, int maxSize) { + cache = new LRUMap(maxSize); + this.expiration = expiration; + } + + /** + * removes all cached entries + */ + public void close() { + clear(); + } + + /** + * @return true if cache has no valid entry + */ + public boolean isEmpty() { + return entries().isEmpty(); + } + + /** + * @param key + * @return if a valid entry exists for this key + */ + public boolean containsKey(Object key) { + return null!=get(key); + } + + /** + * removes all invalid entries from this cache + */ + public void cleanUp() { + if (!cache.isEmpty()) { + Object[] keys = cache.keySet().toArray(); + for (int i=0;i If the entry has been allready + * contained, its expiration is reset + * + * @param key + * @param object + * @return the object contained previously associated with this key + */ + public Object put(Object key, Object object) { + CacheEntry entry = (CacheEntry) cache.put(key, new CacheEntry(object)); + return (null!=entry) ? entry.getEntry() : null; + } + + /** + * @param key + * @return Object for the given key, or null if not contained or invalid + */ + public Object get(Object key) { + Object cached = null; + if (cache.containsKey(key)) { + CacheEntry entry = (CacheEntry) cache.get(key); + if (entry.isValid()) { + cached = entry.getEntry(); + } else { + remove(key); + } + } + return cached; + } + + /** + * removes all cached objects + */ + public void clear() { + log.debug("cleared " + cache.size() + " objects"); + cache.clear(); + } + + /** + * remove the entry for the given key + * @param key + * @return entry previously asociated with this key or null if none + */ + public Object remove(Object key) { + log.debug("removed expired value for key: " + key); + CacheEntry entry = (CacheEntry) cache.remove(key); + return (null!=entry) ? entry.getEntry() : null; + } + + /** + * Cache entry combining the cached object with its expiraion time. + * The equality and hash-code are delegated to the objects cached. + */ + private class CacheEntry { + + private final Object entry; + private final long validity; + + CacheEntry(Object object) { + this.entry = object; + this.validity = System.currentTimeMillis() + (expiration * 1000); + } + + public Object getEntry() { + return entry; + } + + public boolean isValid() { + return validity > System.currentTimeMillis(); + } + + public long getExpiration() { + return validity; + } + + public boolean equals(Object obj) { + if (obj instanceof CacheEntry) { + return ((CacheEntry) obj).entry.equals(entry); + } else { + return false; + } + } + + public int hashCode() { + return entry.hashCode(); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\PrincipalCache.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalImpl.java (revision 0) @@ -0,0 +1,102 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.security.JackrabbitPrincipal; + +import java.io.Serializable; +import java.security.Principal; + +/** + * This class is the base for all principals resulting from authorization + * against the Repository itself or for principals which are stored as items + * within the Repository. + */ +public class PrincipalImpl implements JackrabbitPrincipal, Serializable { + + /** the serial number */ + private static final long serialVersionUID = 384040549033267804L; + + /** the name of this principal */ + private final String name; + + /** the description of this principal */ + private final String description; + + /** + * Creates a new principal with the given name. + * + * @param name the name of this principal + */ + public PrincipalImpl(String name) { + this(name, null); + } + + /** + * Creates a new principal with the given name and description. + * + * @param name the name of this principal + * @param description a (human readable) version of the name of this principal. + */ + public PrincipalImpl(String name, String description) { + this.name = name; + this.description = description; + } + + /** + * {@inheritDoc} + */ + public String getName() { + return name; + } + + /** + * {@inheritDoc} + * + * Two principals are equal, if their names are. + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Principal) { + return name.equals(((Principal) obj).getName()); + } + return false; + } + + /** + * {@inheritDoc} + * + * This method returns the hash code of the principals name. + */ + public int hashCode() { + return name.hashCode(); + } + + /** + * {@inheritDoc} + */ + public String toString() { + if (description != null) { + return new StringBuffer(description).append(" (").append(name).append(")").toString(); + } else { + return name; + } + } +} + Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\PrincipalImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java (revision 0) @@ -0,0 +1,370 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.security.authorization.ActionSetImpl; +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; +import org.apache.jackrabbit.security.PrincipalIterator; +import org.apache.jackrabbit.security.PrincipalManager; + +import javax.security.auth.Subject; +import java.security.Principal; +import java.security.acl.Group; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * This principal manager implementation uses the {@link DefaultProviderRegistry} + * in order to dispatch the respective requests and assemble the required + * data. It is bound to a session and therefore obliges the access restrictions + * of the respective subject. + *

    + * This manager caches the retrieved principals for the direct accessing + * methods but not for searches ({@link #searchPrincipal(String)} and + * {@link PrincipalManager#searchPrincipal(String,int)}) or iterations. The default metrics of + * the cache provide a cache size of 1000 principals whith an expiry time of 5 minutes. + * Please note that this implies that changes done by other managers are not + * reflected in the cached principals. + */ +public class PrincipalManagerImpl implements PrincipalManager { + + /** flag to indicate that the instance has been closed */ + private boolean closed; + + /** the principalProviders */ + private final PrincipalProvider[] principalProviders; + + /** the cache for resolved principals */ + private final PrincipalCache cache; + + /** the subject this manager works for */ + private final Subject subject; + + /** + * Creates a new default principal manager implementation. + * + * @param subject the subject for which this provider is built + * @param principalProviders + */ + public PrincipalManagerImpl(Subject subject, PrincipalProvider[] principalProviders) { + this.subject = subject; + this.principalProviders = principalProviders; + this.cache = new PrincipalCache(300); + } + + /** + * {@inheritDoc} + */ + public boolean hasPrincipal(String principalName) { + return internalGetPrincipal(principalName) != null; + } + + /** + * {@inheritDoc} + */ + public Principal getPrincipal(String principalName) { + Principal principal = internalGetPrincipal(principalName); + return disguise(principal); + } + + /** + * {@inheritDoc} + */ + public PrincipalIterator searchPrincipal(String simpleFilter) { + sanityCheck(); + CheckedPrincipalIterator iter = new CheckedPrincipalIterator(); + for (int j = 0; j < principalProviders.length; j++) { + PrincipalProvider pp = principalProviders[j]; + PrincipalIterator it = pp.searchPrincipal(simpleFilter); + iter.addSource(it, pp); + } + return iter; + } + + /** + * {@inheritDoc} + */ + public PrincipalIterator searchPrincipal(String simpleFilter, int searchType) { + sanityCheck(); + CheckedPrincipalIterator iter = new CheckedPrincipalIterator(); + for (int j = 0; j < principalProviders.length; j++) { + PrincipalProvider pp = principalProviders[j]; + PrincipalIterator it = pp.searchPrincipal(simpleFilter, searchType); + iter.addSource(it, pp); + } + return iter; + } + + /** + * {@inheritDoc} + * @param searchType + */ + public PrincipalIterator getPrincipals(int searchType) { + sanityCheck(); + CheckedPrincipalIterator all = new CheckedPrincipalIterator(); + for (int i = 0; i < principalProviders.length; i++) { + all.addSource(principalProviders[i].getPrincipals(searchType), principalProviders[i]); + } + return all; + } + + /** + * {@inheritDoc} + */ + public boolean isGroup(Principal principal) { + Principal pp = internalGetPrincipal(principal.getName()); + return pp != null && (pp instanceof Group); + } + + /** + * {@inheritDoc} + */ + public PrincipalIterator getGroupMembership(Principal principal) { + sanityCheck(); + Set copy = new HashSet(); + for (int i = 0; i < principalProviders.length; i++) { + PrincipalProvider pp = principalProviders[i]; + PrincipalIterator members = pp.memberOf(principal); + while (members.hasNext()) { + synchronized(cache) { + Principal member = members.nextPrincipal(); + if (cache.containsKey(member.getName())) { + member = (Principal) cache.get(member.getName()); + } else { + // check if provider allows access + if (!principalProviders[i].hasPermission(subject, member, ActionSetImpl.READ)) { + continue; + } + cache.put(member.getName(), member); + } + copy.add(disguise(member)); + } + } + copy.add(getEveryone()); + } + return new DefaultPrincipalIterator(copy, PrincipalManager.SEARCH_TYPE_ALL); + } + + /** + * {@inheritDoc} + */ + public PrincipalIterator getMembers(Principal principal) { + Principal p = internalGetPrincipal(principal.getName()); + if (p instanceof Group) { + return new MemberIterator((Group) p); + } + return DefaultPrincipalIterator.EMPTY; + } + + /** + * {@inheritDoc} + */ + public Principal getAdmin() { + return DefaultPrincipalProvider.ADMIN_PRINCIPAL; + } + + /** + * {@inheritDoc} + */ + public Principal getEveryone() { + return DefaultPrincipalProvider.EVERYONE_PRINCIPAL; + } + + //-------------------------------------------------------------------------- + /** + * Closes all Providers, allow them to disconnect from back-end systems for + * example. + */ + public synchronized void close() { + sanityCheck(); + synchronized (cache) { + cache.close(); + } + closed = true; + } + + /** + * Check if the instance has been closed {@link #close()}. + * + * @throws IllegalStateException if this instance was closed. + */ + private void sanityCheck() { + if (closed) { + throw new IllegalStateException("PrincipalManagerImpl instance has been closed."); + } + } + + /** + * Retrieves the principal with the given name by asking all principal + * providers registered to the principal provider registry. + * + * @param principalName the name of the principal to find + * @return the principal or null if not found + * + * @see PrincipalManager#getPrincipal(String) + */ + private synchronized Principal internalGetPrincipal(String principalName) { + sanityCheck(); + if (cache.containsKey(principalName)) { + return (Principal) cache.get(principalName); + } else { + for (int i = 0; i < principalProviders.length; i++) { + Principal principal = principalProviders[i].getPrincipal(principalName); + if (principal == null) { + continue; + } + // check if provider allows access + if (!principalProviders[i].hasPermission(subject, principal, ActionSetImpl.READ)) { + continue; + } + // cache principal and return + cache.put(principalName, principal); + return principal; + } + return null; + } + } + + private static Principal disguise(Principal principal) { + if (principal instanceof Group) { + return new PrincipalImpl(principal.getName()); + } + return principal; + } + + //-------------------------------------------------------------------------- + /** + * + */ + private class CheckedPrincipalIterator implements PrincipalIterator { + + private long pos; + + private Principal next; + private final List sources = new ArrayList(); + + private void addSource(PrincipalIterator itr, PrincipalProvider provider) { + sources.add(new Entry(itr, provider)); + } + + //--------------------------------------------< RangeIterator >--------- + public void skip(long l) { + for(int i=0;i----- + public Principal nextPrincipal() { + if (hasNext()) { + Principal principal = next; + pos++; + next = null; + return principal; + } + throw new NoSuchElementException("call has next first"); + } + + //--------------------------------------------< Iterator >-------------- + public Object next() { + return nextPrincipal(); + } + + public boolean hasNext() { + if (next==null) { + seekNext(); + } + return next!=null; + } + + public void remove() { + throw new UnsupportedOperationException("remove not supported"); + } + + private void seekNext() { + while(sources.size()>0) { + Entry current = (Entry) sources.get(0); + PrincipalIterator iterator = current.iterator; + while(iterator.hasNext()) { + Principal chk = iterator.nextPrincipal(); + if (current.provider.hasPermission(subject, chk, ActionSetImpl.READ)) { + next = disguise(chk); + return; + } + } + sources.remove(0); + } + } + + private class Entry { + + private final PrincipalProvider provider; + private final PrincipalIterator iterator; + + private Entry(PrincipalIterator itr, PrincipalProvider provider) { + this.provider = provider; + this.iterator = itr; + } + } + } + + /** + * + */ + private static final class MemberIterator extends DefaultPrincipalIterator { + + private MemberIterator memberIterator; + + public MemberIterator(Group group) { + super(Collections.list(group.members()), PrincipalManager.SEARCH_TYPE_ALL); + } + + //--------------------------------------------< PrincipalIterator >----- + public Principal nextPrincipal() { + return disguise(super.nextPrincipal()); + } + + //---------------------------------------------------------------------- + /** + * @return a Principal if one of the possible iterators still has a next + */ + protected Principal seekNext() { + Principal principal; + if (memberIterator != null && memberIterator.hasNext()) { + principal = memberIterator.nextPrincipal(); + } else { + principal = super.seekNext(); + if(principal instanceof Group) { + memberIterator = new MemberIterator((Group) principal); + } + } + return principal; + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\PrincipalManagerImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java (revision 0) @@ -0,0 +1,88 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.core.security.spi.PrincipalProvider; + +import javax.jcr.RepositoryException; +import java.util.Properties; + +/** + * Registry used by the Repository to store and retrieve PrincipalProviders + * for a given name + * + * @see PrincipalProvider + */ +public interface PrincipalProviderRegistry { + + /** + * Property-Key for the fully qualified class name of implementing + * {@link PrincipalProvider} + */ + String PRINCIPAL_PROVIDER_CLASS = "principal_provider.class"; + + /** + * Property-Key for the name the PrincipalProvider should be registred + */ + String PRINCIPAL_PROVIDER_NAME = "principal_provider.name"; + + /** + * Adds (registers) a new provider to this registry. + * + * @param name under which the provider is registered. + * @param provider provider to register + * @return the previously registered provider or null if new. + */ + PrincipalProvider addProvider(String name, PrincipalProvider provider); + + /** + * Adds (registers) a new provider by means of a configuration. The + * registry expects the properties to contain a + * {@link PrincipalProviderRegistry#PRINCIPAL_PROVIDER_CLASS} to be able to + * intstanciate the provider instance. The + * {@link PrincipalProviderRegistry#PRINCIPAL_PROVIDER_NAME} property should + * contain the name of the Provider to register. If this property is not + * present, the name of the principal class is used to identify the + * principal. + *

    + * The Properties will be passed to the instanciated Proverider via + * {@link PrincipalProvider#setOptions(Properties)} + * + * @param configuration Properties for the Provider + * @return the newly added Provider + * @throws RepositoryException in case Registration is not possible + */ + PrincipalProvider addProvider(Properties configuration) + throws RepositoryException; + + /** + * @return the default principal provider + */ + PrincipalProvider getDefault(); + + /** + * @param name for a provider to access + * @return PrincipalProvider or null if not registered at this manager + */ + PrincipalProvider getProvider(String name); + + /** + * Returns all providers registered. + * @return the providers. + */ + PrincipalProvider[] getProviders(); +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\PrincipalProviderRegistry.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/TraversingAuthorizableNodeResolver.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/TraversingAuthorizableNodeResolver.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/TraversingAuthorizableNodeResolver.java (revision 0) @@ -0,0 +1,184 @@ +/* + * 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.principal; + +import org.apache.jackrabbit.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.Session; +import javax.jcr.RepositoryException; +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.NodeIterator; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Collection; +import java.util.regex.PatternSyntaxException; + +/** + * @author ckeller + */ +public class TraversingAuthorizableNodeResolver extends AuthorizableNodeResolver { + + private static final Logger log = LoggerFactory.getLogger(TraversingAuthorizableNodeResolver.class); + + /** + * Additonally to the NodeType-Argument the resolvers searched is narrowed + * by indicating a Path to an {@link javax.jcr.Item} as start for the search + * + * @param session to use for repository access + * @param nodeTypeName name of Node-Types and its supclasses to accept + * @param absPath absolut path of the repository to search below + */ + public TraversingAuthorizableNodeResolver(Session session, + String nodeTypeName, String absPath) throws RepositoryException { + super(session, nodeTypeName, absPath); + } + + //----------------------------------------------< AuthorizableNodeResolver > + + /** + * @inheritDoc + */ + public Node[] findByName(String name, boolean exact) throws RepositoryException { + Node root = (Node) getSession().getItem(getSearchRoot()); + return collectNodes( + Text.escapeIllegalJcrChars(name), + Collections.EMPTY_SET, + getNodeTypeName(), + root.getNodes(), exact); + } + + /** + * @inheritDoc + */ + Node provideNode(String prName, Set props, String nodeTypeName) + throws RepositoryException { + + try { + Node root = (Node) getSession().getItem(getSearchRoot()); + Node[] nodes = collectNodes(prName, props, nodeTypeName, root.getNodes(), true); + if (nodes.length > 0) { + return nodes[0]; + } else { + return null; + } + } catch (PathNotFoundException e) { + log.warn("getPrincipalNode: failed to access search-root at " + + getSearchRoot() + ": " + e); + throw e; + } + } + + /** + * @inheritDoc + */ + Node[] findNodes(String val, Set props, String ntName, boolean exact) + throws RepositoryException { + + Node root = (Node) getSession().getItem(getSearchRoot()); + return collectNodes(val, props, ntName, root.getNodes(), exact); + } + + //-------------------------------------------------------------------------- + /** + * searches the given value in the range of the given NodeIterator. + * recurses unitll all matching values in all configured props are found. + * + * @param value the value to be found in the nodes + * @param props property to be searched, or null if {@link javax.jcr.Item#getName()} + * @param ntName to filter search + * @param nodes range of nodes and descendants to be searched + * @param exact if set to true the value has to match exactly else a substring is searched + */ + private Node[] collectNodes(String value, Set props, String ntName, + NodeIterator nodes, boolean exact) { + + Set matches = new HashSet(); + collectNodes(value, props, ntName, nodes, matches, exact); + return (Node[]) matches.toArray(new Node[matches.size()]); + } + + /** + * searches the given value in the range of the given NodeIterator. + * recurses unitll all matching values in all configured properties are found. + * + * @param value the value to be found in the nodes + * @param propertyNames property to be searched, or null if {@link javax.jcr.Item#getName()} + * @param nodeTypeName name of nodetypes to search + * @param itr range of nodes and descendants to be searched + * @param matches Set of found matches to append results + * @param exact if set to true the value has to match exact + */ + private void collectNodes(String value, + Set propertyNames, + String nodeTypeName, + NodeIterator itr, + Set matches, + boolean exact) { + + while (itr.hasNext()) { + Node node = itr.nextNode(); + try { + if (matches(node, nodeTypeName, propertyNames, value, exact)) { + matches.add(node); + } + if (node.hasNodes()) { + collectNodes(value, propertyNames, nodeTypeName, node.getNodes(), matches, exact); + } + } catch (RepositoryException e) { + log.warn("failed to access Node at " + e); + } + } + } + + private boolean matches(Node node, + String nodeTypeName, + Collection propertyNames, + String value, + boolean exact) throws RepositoryException { + + boolean match = false; + if (node.isNodeType(nodeTypeName)) { + try { + if (propertyNames.isEmpty()) { + match = (exact) ? node.getName().equals(value) : + node.getName().matches(".*"+value+".*"); + } else { + Iterator pItr = propertyNames.iterator(); + while (!match && pItr.hasNext()) { + String propertyName = (String) pItr.next(); + if (node.hasProperty(propertyName)) { + String toMatch = node.getProperty(propertyName).getString(); + match = (exact) ? + toMatch.equals(value) : + toMatch.matches(".*"+value+".*"); + } + } + } + } catch (PatternSyntaxException pe) { + log.debug("couldn't search for {}, pattern invalid: {}", + value, pe.getMessage()); + } + } + return match; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\principal\TraversingAuthorizableNodeResolver.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/SecuritySetup.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/SecuritySetup.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/SecuritySetup.java (revision 0) @@ -0,0 +1,249 @@ +/* + * 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; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.core.security.principal.PrincipalImpl; +import org.apache.jackrabbit.security.ActionSet; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.Group; +import org.apache.jackrabbit.security.User; +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; +import javax.jcr.Value; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; + +/** + * This class implements the means to install security related content.
    + * This includes:

      + *
    • The inital set of Principals: + * {@link #assertPrincipals(javax.jcr.Session, UserManager)}
    • + *
    • The inital set of User and Groups: + * {@link #assertUserAndGroups(Session)}
    • + *
    • The inital ACL on a workspace's Root: + * {@link #setUpProtection(Session)}
    • + *
    + * NOTE: currently the name and value of the items to be created are + * retrieved from {@link SecurityConstants constants}. + * + */ +public class SecuritySetup { + + private static final Logger log = LoggerFactory.getLogger(SecuritySetup.class); + + public static final String KEY_PROTECTION_LEVEL = "defaultSecurityLevel"; + + public static final String OPTION_PROTECTION_LEVEL_HIGH = "high"; + public static final String OPTION_PROTECTION_LEVEL_LOW = "low"; + + private final String defaultWorkspaceName; + + private boolean fullAccess; + + private static final String ADMINISTRATORS_NAME = "administrators"; + + /** + * Assert existance of security relevant content for the repository.
    + * There are two modes how the repository is protected. + * The one named "low" allows full access for anonymous users, the high one + * only allows read accesss for anonymous and full access for administrators + * only.
    + * In mode low, the anonymous is made member of the adminsitrators group. + * + * @param session for the workspace to store the principals to + * @param securityMode mode the repository gets secured + */ + public SecuritySetup(Session session, UserManager uMgr, String defaultWorkspaceName, + String securityMode) throws RepositoryException { + this.defaultWorkspaceName = defaultWorkspaceName; + this.fullAccess = OPTION_PROTECTION_LEVEL_LOW.equalsIgnoreCase(securityMode); + + assertUserAndGroups(session); + assertPrincipals(session, uMgr); + } + + /** + * Set-up minimal permissions for the workspace, given the name.
    + * This contains the following + *
      + *
    • adminstartors-principal -> all getActions + *
    • everyone-principal -> read action + *
    + * + * @param systemSession to the workspace to set-up inital ACL to + * @throws RepositoryException + */ + public void setUpProtection(Session systemSession) + throws RepositoryException { + + // if already protected, return + if (systemSession.getRootNode().hasNode(SecurityConstants.N_REP_ACL)) { + return; + } + + String workspaceName = systemSession.getWorkspace().getName(); + try { + log.info("install initial ACL:..."); + Node editRoot = systemSession.getRootNode(); + if (!editRoot.isNodeType(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE)) { + editRoot.addMixin(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE); + } + Node acl = editRoot.addNode(SecurityConstants.N_REP_ACL, SecurityConstants.NT_REP_ACL); + log.info("...edit for administrators..."); + Node permission = acl.addNode("allowAdminAll", SecurityConstants.NT_REP_GRANT_ACE); + permission.setProperty(SecurityConstants.P_PRINCIPAL, ADMINISTRATORS_NAME); + permission.setProperty(SecurityConstants.P_ACTIONS, ActionSet.ALL_ACTION_NAMES); + if (fullAccess || defaultWorkspaceName.equals(workspaceName)) { + log.info("...allow read for all..."); + permission = acl.addNode("allowEveryoneRead", SecurityConstants.NT_REP_GRANT_ACE); + permission.setProperty(SecurityConstants.P_PRINCIPAL, SecurityConstants.EVERYONE_NAME); + permission.setProperty(SecurityConstants.P_ACTIONS, new String[] {ActionSet.ACTION_NAME_READ}); + } + + //allow write on system, for node-types and icons + editRoot = editRoot.getNode(JcrConstants.JCR_SYSTEM); + if (!editRoot.isNodeType(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE)) { + editRoot.addMixin(SecurityConstants.NT_REP_ACCESS_CONTROLLABLE); + } + if (!editRoot.hasNode(SecurityConstants.N_REP_ACL)) { + acl = editRoot.addNode(SecurityConstants.N_REP_ACL, SecurityConstants.NT_REP_ACL); + permission = acl.addNode("denyEveryoneRemove", SecurityConstants.NT_REP_DENY_ACE); + permission.setProperty(SecurityConstants.P_PRINCIPAL, SecurityConstants.EVERYONE_NAME); + permission.setProperty(SecurityConstants.P_ACTIONS, new String[]{ActionSet.ACTION_NAME_REMOVE}); + permission = acl.addNode("allowEveryoneEdit", SecurityConstants.NT_REP_GRANT_ACE); + permission.setProperty(SecurityConstants.P_PRINCIPAL, SecurityConstants.EVERYONE_NAME); + permission.setProperty(SecurityConstants.P_ACTIONS, + new String[]{ActionSet.ACTION_NAME_READ, + ActionSet.ACTION_NAME_SET_PROPERTY, + ActionSet.ACTION_NAME_ADD_NODE}); + } + + // save changes + systemSession.getRootNode().save(); + log.info("...done"); + + } catch (RepositoryException e) { + log.error("failed to set-up inital protection ->" + + " workspace will be editable for all"); + systemSession.getRootNode().refresh(true); + } + } + + private void assertUserAndGroups(Session session) + throws RepositoryException { + + try { + if (!session.itemExists(SecurityConstants.SECURITY_ROOT_PATH)) { + createNode(session, SecurityConstants.SECURITY_ROOT_PATH, "nt:unstructured"); + } + String path = SecurityConstants.AUTHORIZABLES_PATH; + if (!session.itemExists(path)) { + createNode(session, path, SecurityConstants.NT_REP_AUTHORIZABLE_FOLDER); + } + path = SecurityConstants.USERS_PATH; + if (!session.itemExists(path)) { + createNode(session, path, SecurityConstants.NT_REP_AUTHORIZABLE_FOLDER); + } + path = SecurityConstants.GROUPS_PATH; + if (!session.itemExists(path)) { + createNode(session, path, SecurityConstants.NT_REP_AUTHORIZABLE_FOLDER); + } + session.getRootNode().save(); + } finally { + if (session.getRootNode().isModified()) { + session.getRootNode().refresh(false); + } + } + } + + private void assertPrincipals(Session session, UserManager uMgr) + throws RepositoryException { + + Principal pr = new PrincipalImpl("administrators"); + Group admins = (Group) uMgr.getAuthorizable(pr); + if (admins == null) { + admins = uMgr.createGroup(ADMINISTRATORS_NAME); + Value adminsName = session.getValueFactory().createValue(ADMINISTRATORS_NAME); + admins.setProperty(SecurityConstants.P_PRINCIPAL_NAME, adminsName); + log.debug("...created administrators group with name '"+ADMINISTRATORS_NAME+"'"); + } + Authorizable admin = uMgr.getAuthorizable(SecurityConstants.ADMIN_ID); + if (admin == null) { + admin = createUser(session, uMgr, SecurityConstants.ADMIN_ID, "admin"); + log.info("...created admin-user with id \'"+SecurityConstants.ADMIN_ID+"\' ..."); + admins.addMember(admin); + } + if (uMgr.getAuthorizable(SecurityConstants.ANONYMOUS_ID) == null) { + User anonymous = createUser(session, uMgr, SecurityConstants.ANONYMOUS_ID, null); + log.info("...created anonymous-user with id \'"+SecurityConstants.ANONYMOUS_ID+"\' ..."); + if (fullAccess) { + admins.addMember(anonymous); + } + } + } + + private void createNode(Session session, String path, String folderNType) + throws RepositoryException { + + String[] ancest = Text.explode(path, '/'); + Node node = session.getRootNode(); + for (int i = 0; i < ancest.length; i++) { + String name = ancest[i]; + if (node.hasNode(name)) { + node = node.getNode(name); + } else { + node = node.addNode(name, folderNType); + } + } + } + + private User createUser(Session session, UserManager uMgr, String userId, String password) + throws RepositoryException { + + try { + char[] pwd; + if (password==null || password.length()==0) { + pwd = new char[0]; + } else { + StringBuffer crypted = new StringBuffer(); + crypted.append("{").append(SecurityConstants.DEFAULT_DIGEST).append("}"); + crypted.append( + Text.digest(SecurityConstants.DEFAULT_DIGEST, + password.getBytes("UTF-8"))); + pwd = crypted.toString().toCharArray(); + } + SimpleCredentials creds = new SimpleCredentials(userId, pwd); + User user = uMgr.createUser(userId, creds); + Value value = session.getValueFactory().createValue(userId); + user.setProperty(SecurityConstants.P_PRINCIPAL_NAME, value); + return user; + } catch (NoSuchAlgorithmException e) { //sha1 exists + throw new RepositoryException(e); + } catch (UnsupportedEncodingException e) { //utf-8 exists + throw new RepositoryException(e); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\SecuritySetup.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java (revision 0) @@ -0,0 +1,504 @@ +/* + * 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.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.AuthorizableExistsException; +import org.apache.jackrabbit.security.PrincipalIterator; +import org.apache.jackrabbit.security.PrincipalManager; +import org.apache.jackrabbit.security.Group; +import org.apache.jackrabbit.security.ItemBasedPrincipal; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.PrincipalImpl; +import org.apache.jackrabbit.api.JackrabbitSession; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.PropertyIterator; +import javax.jcr.Value; +import javax.jcr.Property; +import javax.jcr.PathNotFoundException; +import java.security.Principal; +import java.util.Iterator; +import java.util.Collection; +import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Collections; + +/** + * AuthorizableImpl + */ +abstract class AuthorizableImpl implements Authorizable { + + static final Logger log = LoggerFactory.getLogger(AuthorizableImpl.class); + final UserManager userManager; + Principal principal; + + private final Node node; + private final String id; + /** + * @param node the Authorizable is persisted to. + * @param id + * @param userManager UserManager that created this Authorizable. + * @throws RepositoryException + */ + protected AuthorizableImpl(Node node, String id, UserManager userManager) + throws RepositoryException { + + if (!node.isNodeType(SecurityConstants.NT_REP_AUTHORIZABLE)) { + throw new IllegalArgumentException("Node argument of NodeType " + + SecurityConstants.NT_REP_AUTHORIZABLE + " required"); + } + this.node = node; + this.id = id; + this.userManager = userManager; + } + + /** + * @return node The underlying Node object. + */ + protected Node getNode() throws RepositoryException { + node.refresh(false); + return node; + } + + //-------------------------------------------------------< Authorizable >--- + /** + * Return the unique identification for this Authorizable. + * + * @return unique identification for this Authorizable. + * @see Authorizable#getID() + */ + public String getID() throws RepositoryException { + return id; + } + + /** + * @see Authorizable#addReferee(Principal) + */ + public boolean addReferee(Principal principal) throws RepositoryException { + if (referees().contains(principal)) { + return false; + } + + if (userManager.getAuthorizable(principal) != null) { + throw new AuthorizableExistsException("Has already a Reference for" + principal.getName()); + } + Node node = getNode(); + Value added = getSession().getValueFactory().createValue(principal.getName()); + Value[] vals; + if (!node.hasProperty(SecurityConstants.P_REFEREE)) { + vals = new Value[1]; + vals[0] = added; + } else { + Collection tmp = new ArrayList(); + Property prop = node.getProperty(SecurityConstants.P_REFEREE); + Value[] saved = prop.getValues(); + for (int i = 0; i < saved.length; i++) { + String name = saved[i].getString(); + if (!name.equals(principal.getName())) { + tmp.add(saved[i]); + } + } + tmp.add(added); + vals = (Value[]) tmp.toArray(new Value[tmp.size()]); + } + node.setProperty(SecurityConstants.P_REFEREE, vals); + node.save(); + return true; + } + + /** + * @see Authorizable#getPrincipals() + */ + public PrincipalIterator getPrincipals() throws RepositoryException { + return new RefereeIterator(); + } + + /** + * @see Authorizable#removeReferee(Principal) + */ + public boolean removeReferee(Principal principal) throws RepositoryException { + if (!referees().contains(principal) || getPrincipal().equals(principal)) { + return false; + } + Node node = getNode(); + if (!node.hasProperty(SecurityConstants.P_REFEREE)) { + log.warn("removeReferent: hasRerent()==true but not saved for ''{}''", + principal); + return false; + } + + PrincipalManager prManager = getSession().getPrincipalManager(); + Property prop = node.getProperty(SecurityConstants.P_REFEREE); + Value[] vals = prop.getValues(); + Collection maintained = new ArrayList(); + for (int i = 0; i < vals.length; i++) { + String name = vals[i].getString(); + Principal p = prManager.getPrincipal(principal.getName()); + if (p != null && !name.equals(principal.getName())) { + maintained.add(vals[i]); + } else { + log.debug("removeReferent: removed Referent {}", name); + } + } + if (maintained.isEmpty()) { + prop.remove(); + node.save(); + } else if (maintained.size() < vals.length) { + node.setProperty(SecurityConstants.P_REFEREE, + (Value[]) maintained.toArray(new Value[maintained.size()])); + node.save(); + } else { + // nothing changed (and referee was neither contained nor removed) + return false; + } + return true; + } + + /** + * @see Authorizable#memberOf() + */ + public Iterator memberOf() throws RepositoryException { + Node node = getNode(); + PropertyIterator itr = node.getReferences(); + Collection tmp = new HashSet((int) itr.getSize()); + while (itr.hasNext()) { + Node groupNode = itr.nextProperty().getParent(); + Group group = GroupImpl.newInstance(groupNode, userManager); + tmp.add(group); + } + return tmp.iterator(); + } + + /** + * Tests if a Value exists for a property at the given name. + * + * @param name + * @return + * @throws javax.jcr.RepositoryException + * @see #getProperty(String) + */ + public boolean hasProperty(String name) throws RepositoryException { + return getNode().hasProperty(name); + } + + /** + * @param name + * @return the value or null if no value exists for the given name + * @throws javax.jcr.RepositoryException + * @see #hasProperty(String) + * @see Authorizable#getProperty(String) + */ + public Value[] getProperty(String name) throws RepositoryException { + if (hasProperty(name)) { + Property prop = getNode().getProperty(name); + if (prop.getDefinition().isMultiple()) { + return prop.getValues(); + } else { + return new Value[] {prop.getValue()}; + } + } + return null; + } + + /** + * Sets the Value for the given name. If a value existed, it is replaced, + * if not it is created. + * + * @param name + * @param value + * @see Authorizable#setProperty(String, Value) + */ + public void setProperty(String name, Value value) throws RepositoryException { + Node node = getNode(); + try { + node.setProperty(name, value); + node.save(); + } finally { + if (node != null && node.isModified()) { + node.refresh(false); + log.warn("setProperty: removed transient changes on {}, due to failer on commit", node.getPath()); + } + } + } + + /** + * Sets the Value[] for the given name. If a value existed, it is replaced, + * if not it is created. + * + * @param name + * @param value + * @see Authorizable#removeProperty(String) + */ + public void setProperty(String name, Value[] value) throws RepositoryException { + Node node = getNode(); + try { + node.setProperty(name, value); + node.save(); + } finally { + if (node != null && node.isModified()) { + node.refresh(false); + log.warn("setProperty: removed transient changes on {}, due to failer on commit", node.getPath()); + } + } + } + /** + * @param name + * @see #hasProperty(String) + */ + public void removeProperty(String name) throws RepositoryException { + Node node = getNode(); + try { + if (node.hasProperty(name)) { + node.getProperty(name).remove(); + node.save(); + } + } finally { + if (node != null && node.isModified()) { + node.refresh(false); + log.warn("deleteProperty: removed transient changes on {}.", node.getPath()); + } + } + return; + } + + /** + * @see Authorizable#remove() + */ + public void remove() throws RepositoryException { + Node node = getNode(); + Node parent = node.getParent(); + node.remove(); + parent.save(); + } + //-------------------------------------------------------------< Object >--- + /** + * @param obj the reference object with which to compare. + * @return true if this object is the same as the obj + * argument; false otherwise. + * @see #hashCode() + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof AuthorizableImpl) { + try { + return node.getUUID().equals(((AuthorizableImpl) obj).node.getUUID()); + } catch (RepositoryException e) { + // ignore and return false + } + } + return false; + } + + /** + * Returns a hash code value for the object. This method is + * supported for the benefit of hashtables such as those provided by + * java.util.Hashtable. + *

    + * The general contract of hashCode is: + *

      + *
    • Whenever it is invoked on the same object more than once during + * an execution of a Java application, the hashCode method + * must consistently return the same integer, provided no information + * used in equals comparisons on the object is modified. + * This integer need not remain consistent from one execution of an + * application to another execution of the same application. + *
    • If two objects are equal according to the equals(Object) + * method, then calling the hashCode method on each of + * the two objects must produce the same integer result. + *
    • It is not required that if two objects are unequal + * according to the {@link Object#equals(Object)} + * method, then calling the hashCode method on each of the + * two objects must produce distinct integer results. However, the + * programmer should be aware that producing distinct integer results + * for unequal objects may improve the performance of hashtables. + *
    + *

    + * As much as is reasonably practical, the hashCode method defined by + * class Object does return distinct integers for distinct + * objects. (This is typically implemented by converting the internal + * address of the object into an integer, but this implementation + * technique is not required by the + * JavaTM programming language.) + * + * @return a hash code value for this object. + * @see Object#equals(Object) + * @see java.util.Hashtable + */ + public int hashCode() { + try { + return node.getUUID().hashCode(); + } catch (RepositoryException e) { + return -1; + + } + } + + //-------------------------------------------------------------------------- + + JackrabbitSession getSession() throws RepositoryException { + return (JackrabbitSession) node.getSession(); + } + + String getPrincipalName() throws RepositoryException { + String name; + if (node.hasProperty(SecurityConstants.P_PRINCIPAL_NAME)) { + name = node.getProperty(SecurityConstants.P_PRINCIPAL_NAME).getString(); + } else { + name = getNode().getUUID(); + } + return name; + } + + private List referees() throws RepositoryException { + List referees = new ArrayList(); + referees.add(getPrincipal()); + try { + Value[] refProp = node.getProperty(SecurityConstants.P_REFEREE).getValues(); + PrincipalManager prMgr = getSession().getPrincipalManager(); + for (int i = 0; i < refProp.length; i++) { + Principal principal; + String name = refProp[i].getString(); + if (prMgr.hasPrincipal(name)) { + principal = prMgr.getPrincipal(name); + } else { + principal = new PrincipalImpl(name); + log.warn("Contained Principal ''{}'' unkown to Manager", + name); + } + referees.add(principal); + } + } catch (PathNotFoundException e) { + log.debug("no Referent"); + } + return referees; + } + + //-------------------------------------------------------------------------- + /** + * + */ + public class NodeBasedPrincipal extends PrincipalImpl implements ItemBasedPrincipal { + + /** + * @param name for the principal + */ + NodeBasedPrincipal(String name) { + this(name, name); + } + + /** + * @param name for the principal + * @param description + */ + NodeBasedPrincipal(String name, String description) { + super(name, description); + } + + /** + * Method revealing the path to the Node that represents the + * Authorizable this principal is created for. + * + * @return + * @see ItemBasedPrincipal#getPath() + */ + public String getPath() throws RepositoryException { + return node.getPath(); + } + } + + /** + * + */ + private final class RefereeIterator implements PrincipalIterator { + + private long pos = 0; + + private final PrincipalManager prMgr; + private final List refereesNames; + private final Principal principal; + + private RefereeIterator() throws RepositoryException { + if (getNode().hasProperty(SecurityConstants.P_REFEREE)) { + Property p = getNode().getProperty(SecurityConstants.P_REFEREE); + Value[] referees = p.getValues(); + refereesNames = new ArrayList(); + for (int i = 0; i < referees.length; i++) { + refereesNames.add(referees[i].getString()); + } + } else { + refereesNames = Collections.EMPTY_LIST; + } + principal = getPrincipal(); + prMgr = getSession().getPrincipalManager(); + } + + public long getSize() { + return refereesNames.size() + 1; + } + + public long getPosition() { + return pos; + } + + public void skip(long skipNum) { + while (skipNum-- > 0) { + next(); + } + } + + public Principal nextPrincipal() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Principal principal; + if (pos == refereesNames.size()) { + principal = this.principal; + pos++; + } else { + String name = refereesNames.get((int) pos++).toString(); + if (prMgr.hasPrincipal(name)) { + principal = prMgr.getPrincipal(name); + } else { + principal = new PrincipalImpl(name); + log.warn("Contained Principal ''{}'' unkown to Manager", name); + } + } + return principal; + } + + public boolean hasNext() { + return pos < getSize(); + } + + public Object next() { + return nextPrincipal(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\user\AuthorizableImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java (revision 0) @@ -0,0 +1,290 @@ +/* + * 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.user; + +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.Group; +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.util.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * GroupImpl + * @see Group + */ +class GroupImpl extends AuthorizableImpl implements Group { + + private static final Logger log = LoggerFactory.getLogger(GroupImpl.class); + + private static final String[] EXPLODED_ROOT = Text.explode(SecurityConstants.GROUPS_PATH, '/'); + + private GroupImpl(Node node, String id, UserManager userManager) throws RepositoryException { + super(node, id, userManager); + } + + static Group newInstance(Node node, UserManager userManager) throws RepositoryException { + if (node == null || !node.isNodeType(SecurityConstants.NT_REP_GROUP)) { + throw new IllegalArgumentException(); + } + if(!Text.isDescendant(SecurityConstants.GROUPS_PATH, node.getPath())) { + throw new IllegalArgumentException("User has to be within the User Path"); + } + String[] path = Text.explode(node.getPath(), '/'); + String[] relPath = new String[path.length - EXPLODED_ROOT.length]; + for (int i=EXPLODED_ROOT.length; i < path.length; i++) { + relPath[i-EXPLODED_ROOT.length] = path[i]; + } + return new GroupImpl(node, Text.implode(relPath, "/"), userManager); + } + + static Group create(Node parent, String name, UserManagerImpl userManager) + throws RepositoryException { + try { + Node group = parent.addNode( + Text.escapeIllegalJcrChars(name), + SecurityConstants.NT_REP_GROUP); + group.setProperty(SecurityConstants.P_PRINCIPAL_NAME, name); + parent.save(); + return newInstance(group, userManager); + } finally { + if (parent != null && parent.isModified()) { + parent.refresh(false); + log.debug("newInstance new Group failed, revert changes on parent"); + } + } + } + + + //------------------------------------------------< Authorizable >---------- + + public boolean isGroup() { + return true; + } + + public Principal getPrincipal() throws RepositoryException { + if (principal == null) { + principal = new NodeBasedGroup(getPrincipalName()); + } + return principal; + } + + //--------------------------------------------------------------< Group >--- + public Iterator getMembers() throws RepositoryException { + return members().iterator(); + } + + public boolean isMember(Authorizable authorizable) throws RepositoryException { + return authorizable != null && members().contains(authorizable); + } + + public boolean addMember(Authorizable authorizable) throws RepositoryException { + if (authorizable == null || members().contains(authorizable) || + !(authorizable instanceof AuthorizableImpl)) { + return false; + } + Node member; + try { + member = ((AuthorizableImpl)authorizable).getNode(); + } catch (RepositoryException e) { + log.warn("addMember: failed to add member {} to ''{}'': {}", + new Object[]{authorizable, this, e.getMessage()}); + return false; + + } + Value[] vals; + Value added = getSession().getValueFactory().createValue(member); + Node node = getNode(); + if (node.hasProperty(SecurityConstants.P_MEMBER)) { + Value[] old = node.getProperty(SecurityConstants.P_MEMBER).getValues(); + vals = new Value[old.length + 1]; + System.arraycopy(old, 0, vals, 0, old.length); + } else { + vals = new Value[1]; + } + vals[vals.length - 1] = added; + node.setProperty(SecurityConstants.P_MEMBER, vals); + node.save(); + return true; + } + + public boolean removeMember(Authorizable authorizable) throws RepositoryException { + if (!isMember(authorizable) || !(authorizable instanceof AuthorizableImpl)) { + return false; + } + Node node = getNode(); + if (!node.hasProperty(SecurityConstants.P_MEMBER)) { + log.warn("removeMember: No such member ''{}''", authorizable); + return false; + } + Property property = node.getProperty(SecurityConstants.P_MEMBER); + Value[] old = property.getValues(); + Collection removed = new ArrayList(old.length); + for (int i = 0; i < old.length; i++) { + String id = old[i].getString(); + String uuid = ((AuthorizableImpl)authorizable).getNode().getUUID(); + if (id.equals(uuid)) { + continue; + } + removed.add(old[i]); + } + if (removed.isEmpty()) { + Node parent = property.getParent(); + property.remove(); + parent.save(); + } else if (removed.size() < old.length) { + property.setValue((Value[]) removed.toArray(new Value[removed.size()])); + property.save(); + } else { + return false; + } + return true; + } + + private Collection members() throws RepositoryException { + Collection tmp = new HashSet(); + if (getNode().hasProperty(SecurityConstants.P_MEMBER)) { + Property prop = getNode().getProperty(SecurityConstants.P_MEMBER); + if (prop.getType() == PropertyType.REFERENCE) { + Value[] val = prop.getValues(); + for (int i = 0; i < val.length; i++) { + Node mem = getSession().getNodeByUUID(val[i].getString()); + if (mem.isNodeType(SecurityConstants.NT_REP_GROUP)) { + tmp.add(newInstance(mem, userManager)); + } else { + tmp.add(UserImpl.newInstance(mem, userManager)); + } + } + } + } + return tmp; + } + + //-------------------------------------------------------------------------- + /** + * + */ + private class NodeBasedGroup extends NodeBasedPrincipal implements java.security.acl.Group { + + private Set members; + + private NodeBasedGroup(String name) { + super(name); + } + + /** + * @return Always false. Group membership must be edited + * using the enclosing GroupImpl object. + * @see java.security.acl.Group#addMember(Principal) + */ + public boolean addMember(Principal user) { + return false; + } + + /** + * Returns true, if the given Principal is represented by + * a Authorizable, that is a member of the underlying UserGroup. + * + * @see java.security.acl.Group#isMember(Principal) + */ + public boolean isMember(Principal member) { + Collection members = getMembers(); + if (members.contains(member)) { + // shortcut. + return true; + } + + // test if member of a member-group + for (Iterator it = members.iterator(); it.hasNext();) { + Principal p = (Principal) it.next(); + if (p instanceof java.security.acl.Group && + ((java.security.acl.Group) p).isMember(member)) { + return true; + } + } + return false; + } + + /** + * @return Always false. Group membership must be edited using the + * enclosing GroupImpl object. + * + * @see java.security.acl.Group#isMember(Principal) + */ + public boolean removeMember(Principal user) { + return false; + } + + /** + * Return all principals that refer to every member of the underlying + * user group. + * + * @see java.security.acl.Group#members() + */ + public Enumeration members() { + return Collections.enumeration(getMembers()); + } + + private Collection getMembers() { + if (members == null) { + members = new HashSet(); + try { + for (Iterator it = GroupImpl.this.getMembers(); it.hasNext();) { + Authorizable authrz = (Authorizable) it.next(); + for (Iterator pit = authrz.getPrincipals(); pit.hasNext();) { + members.add(pit.next()); + } + } + } catch (RepositoryException e) { + // should not occur. + throw new IllegalStateException("Unable to get Group members"); + } + } + return members; + } + + //---------------------------------------------------< Serializable >--- + /** + * implement the writeObject method to assert initalization of all members + * before serialization. + * + * @param stream + * @throws IOException + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + getMembers(); + stream.defaultWriteObject(); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\user\GroupImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/ImpersonationImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/ImpersonationImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/ImpersonationImpl.java (revision 0) @@ -0,0 +1,148 @@ +/* + * 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.user; + +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.SystemPrincipal; +import org.apache.jackrabbit.core.security.authorization.ActionSetImpl; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.apache.jackrabbit.security.ACEIterator; +import org.apache.jackrabbit.security.ACETemplate; +import org.apache.jackrabbit.security.ACL; +import org.apache.jackrabbit.security.ACLManager; +import org.apache.jackrabbit.security.ACLTemplate; +import org.apache.jackrabbit.security.Impersonation; + +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; +import javax.security.auth.Subject; +import java.security.Principal; +import java.util.Iterator; + +/** + * ImpersonationImpl + */ +class ImpersonationImpl implements Impersonation { + + private final UserImpl user; + private final ACLManager aclManager; + + ImpersonationImpl(UserImpl user, ACLManager aclManager) { + this.user = user; + this.aclManager = aclManager; + } + + //--------------------------------------------------< Impersonation >--- + /** + * {@inheritDoc} + */ + public boolean grantImpersonation(Principal principal) throws RepositoryException { + ACLTemplate aclTemplate = getEditableACL(true); + if (aclTemplate == null) { + return false; + } + + ACETemplate ace = aclTemplate.create(principal, true, ActionSetImpl.SUDO); + aclTemplate.add(ace); + aclManager.setAcl(getPath(), aclTemplate); + return true; + } + + /** + * {@inheritDoc} + */ + public boolean revokeImpersonation(Principal principal) throws RepositoryException { + ACLTemplate aclTemplate = getEditableACL(false); + if (aclTemplate != null) { + ACEIterator itr = aclTemplate.getEntries(); + while (itr.hasNext()) { + ACETemplate ace = (ACETemplate) itr.nextACE(); + if (ace.getPrincipal().equals(principal) && + ace.containsAnyAction(ActionSetImpl.SUDO)) { + // we know that the sudo-ace only contains a single action + // thus its fine to simply remove the entry. + itr.remove(); + aclManager.setAcl(getPath(), aclTemplate); + return true; + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public boolean allows(Subject subject) throws RepositoryException { + if (subject == null) { + return false; + } + //shortcut admin; + if (!(subject.getPrincipals(AdminPrincipal.class).isEmpty() + || subject.getPrincipals(SystemPrincipal.class).isEmpty())) { + return true; + } + boolean allows=false; + try { + ACL sudoersACL = aclManager.getAcl(getPath()); + allows = sudoersACL.grants(subject.getPrincipals(), ActionSetImpl.SUDO); + } catch (PathNotFoundException e) { + } // means no sodoers at all + + //test if self + if (!allows) { + Iterator itr = user.getPrincipals(); + while(itr.hasNext() && !allows) { + allows = subject.getPrincipals().contains(itr.next()); + } + } + return allows; + } + + //--------------------------------------------------------< private >--- + /** + * + * @param create + * @return + * @throws RepositoryException + */ + private ACLTemplate getEditableACL(boolean create) throws RepositoryException { + if (aclManager != null) { + Node userNode = user.getNode(); + if (!userNode.hasNode(SecurityConstants.SUDOERS_PATH)) { + if (create) { + userNode.addNode(SecurityConstants.SUDOERS_PATH, SecurityConstants.NT_REP_SUDOERS); + userNode.save(); + } else { + return null; + } + } + return aclManager.editAcl(getPath()); + } + return null; + } + + /** + * + * @return + * @throws RepositoryException + */ + private String getPath() throws RepositoryException { + return user.getNode().getPath() + "/" + SecurityConstants.SUDOERS_PATH; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\user\ImpersonationImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java (revision 0) @@ -0,0 +1,260 @@ +/* + * 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.user; + +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.security.Impersonation; +import org.apache.jackrabbit.security.User; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.authentication.CryptedSimpleCredentials; +import org.apache.jackrabbit.core.security.principal.DefaultPrincipalProvider; +import org.apache.jackrabbit.util.Text; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Credentials; +import javax.jcr.ValueFactory; +import javax.jcr.SimpleCredentials; +import javax.jcr.Value; +import java.security.Principal; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Collections; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; + +/** + * UserImpl + */ +public class UserImpl extends AuthorizableImpl implements User { + + private Impersonation impersonation; + private final boolean isAdmin; + + private final static String[] EXPLODED_ROOT = Text.explode(SecurityConstants.USERS_PATH, '/'); + private UserImpl(Node node, String id, UserManager userManager, boolean isAdmin) + throws RepositoryException { + + super(node, id, userManager); + this.isAdmin = isAdmin; + } + + static User newInstance(Node node, UserManager userManager) + throws RepositoryException { + + return newInstance(node, userManager, SecurityConstants.ADMIN_PATH.equals(node.getPath())); + } + + static User newInstance(Node node, UserManager userManager, boolean admin) + throws RepositoryException { + + if (node == null || !node.isNodeType(SecurityConstants.NT_REP_USER)) { + throw new IllegalArgumentException(); + } + if(!Text.isDescendant(SecurityConstants.USERS_PATH, node.getPath())) { + throw new IllegalArgumentException("User has to be within the User Path"); + } + String[] path = Text.explode(node.getPath(), '/'); + String[] relPath = new String[path.length - EXPLODED_ROOT.length]; + for (int i=EXPLODED_ROOT.length; i < path.length; i++) { + relPath[i-EXPLODED_ROOT.length] = path[i]; + } + return new UserImpl(node, Text.implode(relPath, "/"), userManager, admin); + } + + static User create(Node parent, + String name, + UserManager userMgr, + Credentials credentials) throws RepositoryException { + + ByteArrayInputStream bai = null; + ByteArrayOutputStream bao = null; + ObjectOutputStream oos = null; + try { + String escapedName = Text.escapeIllegalJcrChars(name); + Node userNode = parent.addNode(escapedName, SecurityConstants.NT_REP_USER); + userNode.setProperty(SecurityConstants.P_USERID, name); + + //todo: the credentials store sould be crypted + //todo: make it on password as long + if (credentials!=null) { + ValueFactory valFactory = parent.getSession().getValueFactory(); + if (credentials instanceof SimpleCredentials) { + try { + CryptedSimpleCredentials cc = new CryptedSimpleCredentials((SimpleCredentials) credentials); + Value userId = valFactory.createValue(cc.getUserID()); + userNode.setProperty(SecurityConstants.P_USERID, userId); + if (cc.getPassword().length()>0) { + Value pwd = valFactory.createValue(cc.getPassword()); + userNode.setProperty(SecurityConstants.P_PASSWORD, pwd); + } + credentials = cc; + } catch (NoSuchAlgorithmException e) { + log.warn("coulden't encrypt password with {} ", //should not happen + SecurityConstants.DEFAULT_DIGEST, e); + } + } + bao = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(bao); + oos.writeObject(credentials); + bai = new ByteArrayInputStream(bao.toByteArray()); + Value[] creds = new Value[] {valFactory.createValue(bai)}; + userNode.setProperty(SecurityConstants.P_CREDENTIALS, creds); + } + parent.save(); + return newInstance(userNode, userMgr); + } catch (IOException e) { + log.debug("creation of User failed: {}", e.getMessage()); + throw new RepositoryException("Failed to store credentials to new User: ", e); + } finally { + if (parent != null && parent.isModified()) { + parent.refresh(false); + log.debug("newInstance new User failed, revert changes on parent"); + } + if (bao!=null) { + try { + bao.close(); + } catch (IOException e) { + log.warn("Failed to close tremparary stream used for saving of credentials", + e); + } + } + if (bai!=null) { + try { + bai.close(); + } catch (IOException e) { + log.warn("Failed to close tremparary stream used for saving of credentials", + e); + } + } + if (oos!=null) { + try { + oos.close(); + } catch (IOException e) { + log.warn("Failed to close tremparary stream used for saving of credentials", + e); + } + } + } + } + + //-------------------------------------------------------< Authorizable >--- + /** + * @see Authorizable#getID() + */ + public String getID() throws RepositoryException { + return getNode().getProperty(SecurityConstants.P_USERID).getString(); + } + + //------------------------------------------------< User >------------------ + /** + * @see User#isAdmin() + */ + public boolean isAdmin() { + return isAdmin; + } + + /** + * {@inheritDoc} + */ + public Iterator getCredentials() throws RepositoryException { + Collection res = new ArrayList(); + ObjectInputStream ois = null; + try { + if (hasProperty(SecurityConstants.P_CREDENTIALS)) { + Value[] vals = getProperty(SecurityConstants.P_CREDENTIALS); + for (int i = 0; i < vals.length; i++) { + Value val = vals[i]; + ois = new ObjectInputStream(val.getStream()); + Object o = ois.readObject(); + if (o instanceof CryptedSimpleCredentials) { + CryptedSimpleCredentials cc = (CryptedSimpleCredentials) o; + SimpleCredentials sc = new SimpleCredentials( + cc.getUserID(), + cc.getPassword().toCharArray()); + String[] names = cc.getAttributeNames(); + for (int j = 0; j < names.length; j++) { + String name = names[j]; + sc.setAttribute(name, cc.getAttribute(name)); + } + o = sc; + } + res.add(o); + } + } + } catch (IOException e) { + log.warn("could not get Credentials of User '" + getID() + "': ", e); + throw new RepositoryException(e); + } catch (ClassNotFoundException e) { + log.warn("could not get Credentials of User '" + getID() + "': ", e); + throw new RepositoryException(e); + } finally { + if (ois!=null) { + try { + ois.close(); + } catch (IOException e) { + log.warn("could not close stream, used to access credentials for '" + getID() + "': ", e); + } + } + } + + //for backward compatibility, add userId and password as Credential + if (hasProperty(SecurityConstants.P_USERID) + && hasProperty(SecurityConstants.P_PASSWORD)) { + String userId = getProperty(SecurityConstants.P_USERID)[0].getString(); + String pwd = getProperty(SecurityConstants.P_PASSWORD)[0].getString(); + res.add(new SimpleCredentials(userId, pwd.toCharArray())); + } + return (res==null) ? Collections.EMPTY_SET.iterator() : res.iterator(); + } + /** + * @see User#isGroup() + */ + public boolean isGroup() { + return false; + } + + /** + * @see User#getPrincipal() + */ + public Principal getPrincipal() throws RepositoryException { + if (principal == null) { + if (isAdmin()) { + principal = DefaultPrincipalProvider.ADMIN_PRINCIPAL; + } else { + principal = new NodeBasedPrincipal(getPrincipalName(), getID()); + } + } + return principal; + } + + /** + * @see User#getImpersonation() + */ + public Impersonation getImpersonation() throws RepositoryException { + if (impersonation == null) { + impersonation = new ImpersonationImpl(this, getSession().getACLManager()); + } + return impersonation; + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\user\UserImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java =================================================================== --- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (revision 0) +++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (revision 0) @@ -0,0 +1,388 @@ +/* + * 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.user; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.util.Text; +import org.apache.jackrabbit.security.UserManager; +import org.apache.jackrabbit.security.User; +import org.apache.jackrabbit.security.Authorizable; +import org.apache.jackrabbit.security.AuthorizableExistsException; +import org.apache.jackrabbit.security.Group; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.AuthorizableNodeResolver; +import org.apache.jackrabbit.core.security.principal.IndexNodeResolver; +import org.apache.jackrabbit.core.security.principal.TraversingAuthorizableNodeResolver; +import org.apache.jackrabbit.api.JackrabbitSession; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Credentials; +import javax.jcr.SimpleCredentials; +import javax.jcr.query.QueryManager; +import java.security.Principal; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.HashSet; +import java.util.Set; + +/** + * UserManagerImpl + */ +public class UserManagerImpl implements UserManager { + + private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class); + + private static final Node[][] EMPTY_SET = new Node[0][0]; + + private User admin; + + private final String rootPath; + + private final JackrabbitSession session; + + private final AuthorizableNodeResolver userResolver; + + private final AuthorizableNodeResolver groupResolver; + + public UserManagerImpl(JackrabbitSession session) throws RepositoryException { + this.rootPath = SecurityConstants.SECURITY_ROOT_PATH; + this.session = session; + QueryManager qm = null; + try { + qm = session.getWorkspace().getQueryManager(); + } catch (RepositoryException e) { + log.debug("UserManger: no QueryManager availbale for workspace{}, use traversing", + session.getWorkspace().getName()); + } + if (qm==null) { + userResolver = new TraversingAuthorizableNodeResolver(session, + SecurityConstants.NT_REP_USER, SecurityConstants.USERS_PATH); + groupResolver = new TraversingAuthorizableNodeResolver(session, + SecurityConstants.NT_REP_GROUP, SecurityConstants.GROUPS_PATH); + } else { + userResolver = new IndexNodeResolver(session, SecurityConstants.NT_REP_USER, SecurityConstants.USERS_PATH); + groupResolver = new IndexNodeResolver(session, SecurityConstants.NT_REP_GROUP, SecurityConstants.GROUPS_PATH); + } + userResolver.addTargetProperty(SecurityConstants.P_USERID); + userResolver.addTargetProperty(SecurityConstants.P_PRINCIPAL_NAME); + groupResolver.addTargetProperty(SecurityConstants.P_PRINCIPAL_NAME); + } + + //--------------------------------------------------------< UserManager >--- + /** + * {@inheritDoc} + */ + public Authorizable getAuthorizable(String id) throws RepositoryException { + Authorizable authorz = null; + if (id==null || id.length()==0) { + return authorz; + } + + //assumes the id to be the relative path -> serach amoung the users than + //the groups + Node node = userResolver.getNode(id); + if (node!=null) { + authorz = UserImpl.newInstance(node, this); + } else { + Node group = groupResolver.getNode(id); + if (group!=null) { + authorz = GroupImpl.newInstance(group, this); + } + } + return authorz; + } + + /** + * {@inheritDoc} + */ + public Authorizable getAuthorizable(Principal principal) + throws RepositoryException { + + String name = principal.getName(); + if (userResolver.hasPrincipalNode(name)) { + return UserImpl.newInstance(userResolver.getPrincipalNode(name), this); + } else if (groupResolver.hasPrincipalNode(name)) { + return GroupImpl.newInstance(groupResolver.getPrincipalNode(name), this); + } else { + return null; + } + } + + /** + * {@inheritDoc} + * @param propertyName + * @param value + */ + public Iterator findAuthorizable(String propertyName, String value) throws RepositoryException { + Node[][] users = new Node[][]{userResolver.findNode(propertyName, value, true)}; + Node[][] groups = new Node[][]{groupResolver.findNode(propertyName, value, true)}; + return new AuthorizableIterator(users, groups); + } + + /** + * This {@link UserManager UserManager} implemenation + * creates the user for the given userID.
    + * The ID is expected to be a syntactical valid JCR-Path. + * The User will be created relative to this UserManager's + * {@link SecurityConstants#USERS_PATH user root}.
    + * If the {@link javax.jcr.Credentials Credentials} are of type + * {@link javax.jcr.SimpleCredentials SimpleCredentials}, this implementation + * extracts the {@link javax.jcr.SimpleCredentials#getUserID() userID} and + * {@link javax.jcr.SimpleCredentials#getPassword() password} from it and + * saves it as property. This is for convenienc reasons. Its assumed that + * the installation use SimpleCredentails.

    + * NOTE: Dont mix-up this {@link User#getID() userID} + * with the one from {@link javax.jcr.SimpleCredentials#getUserID() SimpleCredentials}. + * A User with ID id may user SimpleCredentials with + * UserID of value login-name.
    + * Only for for {@link javax.jcr.Credentials Credentials} of type + * {@link javax.jcr.SimpleCredentials SimpleCredentials} it is asserted that + * the same Credentials are unique. The UserManager needs to know the + * semantics of the Credentials to teset for uniquness. Only the one of + * the SimpleCredentials are known.
    + * + * @param userID + * @param credentials + * @see UserManager#createUser(String, Credentials) + * @inheritDoc + */ + public User createUser(String userID, Credentials credentials) + throws RepositoryException { + + if (credentials==null) { + throw new IllegalArgumentException("Not possible to create user with null Credentials"); + } + if (getAuthorizable(userID) != null) { + throw new AuthorizableExistsException("User for '" + userID + "' already exists"); + } + + //this should assert that the credentials are unique within the scope + //of this managager..but + //todo: implement credentials comparator. + if (credentials instanceof SimpleCredentials) { + String loginName = ((SimpleCredentials) credentials).getUserID(); + Node[] res = userResolver.findNode(SecurityConstants.P_USERID, loginName, true); + if (res.length>0) { + throw new AuthorizableExistsException("User with Credentials for '" + + loginName + "' already exists"); + } + } + String path = buildPath(SecurityConstants.USERS_PATH, userID); + Node parent = getNode(Text.getRelativeParent(path, 1)); + return UserImpl.create(parent, Text.getName(path), this, credentials); + } + + /** + * Create a new Group with the given ID. + * The ID is expected to be a syntactical valid JCR-Name. It + * will be sotred below the this UserManager's root Path. + * If non-existant elements of the Path will be created as Nodes + * of type {@link SecurityConstants#NT_REP_AUTHORIZABLE_FOLDER rep:AuthorizableFolder} + * + * @param id + * @see UserManager#createGroup(String); + * @inheritDoc + */ + public Group createGroup(String id) throws RepositoryException { + if (getAuthorizable(id) != null) { + throw new AuthorizableExistsException("Group for '" + id + "' already exists: "); + } + String path = buildPath(SecurityConstants.GROUPS_PATH, id); + Node parent = getNode(Text.getRelativeParent(path, 1)); + return GroupImpl.create(parent, Text.getName(path), this); + } + + //-------------------------------------------------------------------------- + /** + * @return the root path under which all user and group nodes are created. + */ + public String getRootPath() { + return rootPath; + } + + /** + * @return User marked as admin maybe used to get special priveleges assigned, etc + * @throws RepositoryException + */ + public User getAdmin() throws RepositoryException { + if (admin == null) { + Node adminNode = userResolver.getNode(SecurityConstants.ADMIN_PATH); + if (adminNode != null) { + admin = UserImpl.newInstance(adminNode, this, true); + } + } + return admin; + } + + /** + * Simple search for a User
    + * The argument is a substring which must match the User-Id or PrincipalName + * + * @param match substring to match against. Empty String matches all + * @return Iterator containing Authorizable-objects + * @throws RepositoryException + */ + public Iterator findUsers(String match) throws RepositoryException { + return new AuthorizableIterator(new Node[][] {userResolver.findNode(match)}); + } + + /** + * Simple search for a Group
    + * The argument is a substring which must match the Group's Name or PrincipalName + * + * @param match substring to match against. Empty String matches all + * @return Iterator containing Authorizable-objects + * @throws RepositoryException + */ + public Iterator findGroups(String match) throws RepositoryException { + Node[] names = groupResolver.findByName(match, false); + Node[] prs = groupResolver.findNode(match); + return new AuthorizableIterator(new Node[][]{names, prs}); + } + + private String buildPath(String root, String relative) { + if (relative==null || relative.length()==0) { + return root; + } else if (relative.startsWith("/")) { + return root + relative; + } else { + return root + "/" + relative; + } + } + + /** + + /** + * @param path node to access, newInstance intermeadate missing Nodes as AuthorizableFolder + * @return + * @throws RepositoryException + */ + private Node getNode(String path) throws RepositoryException { + return assertParent(path); + } + + private Node assertParent(String absPath) throws RepositoryException { + Node parent = session.getRootNode(); + String[] elem = absPath.split("/"); + for (int i = 0; i < elem.length; i++) { + String name = elem[i]; + if (name.length() < 1) { + continue; + } + if (!parent.hasNode(name)) { + Node added = parent.addNode(name, SecurityConstants.NT_REP_AUTHORIZABLE_FOLDER); + parent.save(); + parent = added; + } else { + parent = parent.getNode(name); + } + } + return parent; + } + + //-------------------------------------------------------------------------- + /** + * Inner class + */ + private final class AuthorizableIterator implements Iterator { + + private boolean group; + private int pos = -1; + private int setIdx = -1; + private Authorizable next; + private Node[] currentSet; + private final Node[][] users; + private final Node[][] groups; + private final Set served = new HashSet(); + + private AuthorizableIterator(Node[][] users) { + this(users, EMPTY_SET); + } + private AuthorizableIterator(Node[][] users, Node[][] groups) { + this.users = users; + this.groups = groups; + this.pos = 0; + this.setIdx = 1; + if (users.length==0) { + this.group = true; + this.currentSet = groups[0]; + } else { + this.group = false; + this.currentSet = users[0]; + } + } + + /** + * @return true if the iterator has more elements. + */ + public boolean hasNext() { + while (next == null && currentSet!=null) { + try { + + //current exhausted -> navigate to next if possible; + if (pos==currentSet.length) { + Node[][] set = (group) ? groups : users; + + //test if a set has to change or at end + if (setIdx == set.length) { + if (group || groups.length==0) { + currentSet = null; + } else { + currentSet = groups[0]; + setIdx=1; + group = true; + } + } else { + currentSet = set[setIdx++]; + } + pos=0; + continue; + } + + //here it is asserted that the set has an entry + Node chk = currentSet[pos++]; + if (!served.contains(chk.getPath())) { + if (group) { + next = GroupImpl.newInstance(chk, UserManagerImpl.this); + } else { + next = UserImpl.newInstance(chk, UserManagerImpl.this); + } + served.add(chk.getPath()); + } + } catch (RepositoryException e) { + // ignore + } + } + return next != null; + } + + public Object next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Authorizable ret = next; + next = null; + return ret; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} Property changes on: jackrabbit-core\src\main\java\org\apache\jackrabbit\core\security\user\UserManagerImpl.java ___________________________________________________________________ Name: svn:keywords + author date id rev url Name: svn:eol-style + native