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 + *
DefaultACLProviderFactory
+ * Creates {@link DefaultACLProvider} instancesWorkspaceACLProvider 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;iSubject 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).
true.
+ * 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.
+ *
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.
+ *
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 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:This enables to + * preauthenticate the Subject.
+ * This implementaion selects JAAS under the following condition: + *
+ * 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;i
+ * 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 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
+ * This {@link AccessManager} implementation, controls access via ACLs related
+ * to Items.
+ * 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.
+ * 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
+ * NOTE: Dont mix-up this {@link User#getID() userID}
+ * with the one from {@link javax.jcr.SimpleCredentials#getUserID() SimpleCredentials}.
+ * A User with ID
+ * 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.
+ *
+ * @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.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 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
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ * 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.rep:AccessControllable and if it has the child node
+ * rep:acl that forms the acl.
+ *
+ * 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
+ * It builds an {@link ActionSet} of the internal actions to grant
+ * {@link AccessManager#isGranted(ItemId, int)}.
+ *
+ *
+ * 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)}
+ *
+ * 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.
+ *
+ * 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 criteriaAuthorizableNodeResolver.
+ *
+ * @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.
+ * 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);
+ }
+
+ //-----------------------------------------------
+ * 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:
+ *
+ *
+ * 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;inull 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;inull 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:
+ *
+ * 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
+ *
+ *
+ *
+ * @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:
+ *
+ *
+ *
+ * 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 hashCode method on each of
+ * the two objects must produce the same integer result.
+ * 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. 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