All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.jcr.base.AbstractSlingRepository2 Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * 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.sling.jcr.base;

import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.jcr.Credentials;
import javax.jcr.GuestCredentials;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Value;
import javax.security.auth.Subject;

import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.base.internal.mount.ProxyRepository;
import org.apache.sling.serviceusermapping.ServiceUserMapper;
import org.osgi.annotation.versioning.ProviderType;
import org.osgi.framework.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The AbstractSlingRepository2 is an abstract implementation of
 * the {@link SlingRepository} version 2.3 interface (phasing
 * {@link #loginAdministrative(String)} out in favor of
 * {@link #loginService(String, String)}) which provides default support for
 * attached repositories.
 * 
 * Implementations of the SlingRepository interface may wish to
 * extend this class to benefit from default implementations of most methods.
 * 
 * To be able to know the calling bundle to implement the
 * {@link #loginService(String, String)} method the bundle using the repository
 * service has to be provided in the
 * {@link #AbstractSlingRepository2(AbstractSlingRepositoryManager, Bundle)
 * constructor}.
 * 
 * The premise of this abstract class is that an instance of an implementation
 * of this abstract class is handed out to each consumer of the
 * {@code SlingRepository} service. Each instance is generally based on the same
 * {@link #getRepository() delegated repository} instance.
 *
 * @see AbstractSlingRepositoryManager
 * @since API version 2.4 (bundle version 2.3)
 */
@ProviderType
public abstract class AbstractSlingRepository2 implements SlingRepository {

    /** The logger. */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** The repository manager. */
    private final AbstractSlingRepositoryManager manager;

    /** The bundle using this repository instance. */
    private final Bundle usingBundle;

    /**
     * Sets up this abstract SlingRepository implementation.
     *
     * @param manager The {@link AbstractSlingRepositoryManager} controlling
     *            this instance as well as the actual JCR repository instance
     *            used by this.
     * @param usingBundle The bundle using this instance. This is used by the
     *            {@link #loginService(String, String)} method, which will not
     *            be able to login if this parameter is {@code null}
     */
    protected AbstractSlingRepository2(final AbstractSlingRepositoryManager manager, final Bundle usingBundle) {
        this.manager = manager;
        this.usingBundle = usingBundle;

        if(usingBundle == null) {
            throw new IllegalArgumentException("usingBundle is null");
        }
    }

    /**
     * @return The {@link AbstractSlingRepositoryManager} controlling this
     *         instances
     */
    protected final AbstractSlingRepositoryManager getSlingRepositoryManager() {
        return this.manager;
    }

    /**
     * Returns the actual repository to which all JCR Repository interface
     * methods implemented by this class are delegated.
     *
     * @return The delegated repository.
     */
    protected final Repository getRepository() {
        return this.getSlingRepositoryManager().getRepository();
    }

    /**
     * Returns the default workspace to login to if any of the {@code login} and
     * {@code createSession} methods is called without an explicit workspace
     * name.
     * 

* This method may return {@code null} in which case the actual default * workspace used depends on the underlying JCR Repository implementation. */ @Override public final String getDefaultWorkspace() { return this.getSlingRepositoryManager().getDefaultWorkspace(); } /** * Creates an administrative session to access the indicated workspace. *

* This method is called by the {@link #loginAdministrative(String)} and * {@link #createServiceSession(String, String)} methods. * * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return An administrative session * @throws RepositoryException If a general error occurs during login * @see #createServiceSession(String, String) * @see #loginAdministrative(String) */ protected abstract Session createAdministrativeSession(final String workspace) throws RepositoryException; /** * Creates a new service-session. This method is used by {@link #loginService(String, String)} * and {@link #impersonateFromService(String, Credentials, String)} to avoid * code duplication. * * @param usingBundle The bundle using a service session. * @param subServiceName The optional name of the subservice. * @param workspaceName The JCR workspace name or {@code null}. * @return A new service session or {@code null} if neither a user-name nor a * principal names mapping has been defined. * @throws RepositoryException If an error occurs while creating the service session. */ private Session createServiceSession(Bundle usingBundle, String subServiceName, String workspaceName) throws RepositoryException { final ServiceUserMapper serviceUserMapper = this.getSlingRepositoryManager().getServiceUserMapper(); if (serviceUserMapper != null) { Session session = null; final Iterable principalNames = serviceUserMapper.getServicePrincipalNames(usingBundle, subServiceName); if (principalNames != null) { session = createServiceSession(principalNames, workspaceName); } else { final String userName = serviceUserMapper.getServiceUserID(usingBundle, subServiceName); if (userName != null) { session = createServiceSession(userName, workspaceName); } } if (session != null) { Repository repository = getRepository(); return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(session) : session; } } return null; } /** * Creates a service-session for the service's {@code serviceUserName} by * impersonating the user from an administrative session. *

* The administrative session is created calling * {@link #createAdministrativeSession(String) * createAdministrativeSession(workspace)} and is logged out before this * method returns. *

* Implementations of this class may overwrite this method with a better * implementation, notably one which does not involve a temporary creation * of an administrative session. * * @param serviceUserName The name of the user to create the session for * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return A session for the given user * @throws RepositoryException If a general error occurs while creating the * session */ protected Session createServiceSession(final String serviceUserName, final String workspace) throws RepositoryException { Session admin = null; try { admin = this.createAdministrativeSession(workspace); Session result = admin.impersonate(new SimpleCredentials(serviceUserName, new char[0])); Repository repository = getRepository(); return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(result) : result; } finally { if (admin != null) { admin.logout(); } } } /** * Creates a service-session for the service's {@code servicePrincipalNames} * using a pre-authenticated {@link Subject}. *

* Implementations of this class may overwrite this method to meet additional * needs wrt the the nature of the principals, the {@code Subject} or additional * attributes passed to the repository login. * * @param servicePrincipalNames The names of the service principals to create the session for * @param workspaceName The workspace to access or {@code null} to access the {@link #getDefaultWorkspace() default workspace} * @return A new service session * @throws RepositoryException If a general error occurs while creating the session. */ protected Session createServiceSession(final Iterable servicePrincipalNames, final String workspaceName) throws RepositoryException { Set principals = new HashSet<>(); for (final String pName : servicePrincipalNames) { if (pName != null && !pName.isEmpty()) { principals.add(new Principal() { @Override public String getName() { return pName; } }); } }; Subject subject = new Subject(true, principals, Collections.emptySet(), Collections.emptySet()); try { return Subject.doAsPrivileged(subject, new PrivilegedExceptionAction() { @Override public Session run() throws Exception { return AbstractSlingRepository2.this.getRepository().login(null, workspaceName); } }, null); } catch (PrivilegedActionException e) { throw new RepositoryException("failed to retrieve service session.", e); } } // login implementations (may be overwritten) /** * Same as calling {@code login(null, null)}. *

* This method may be overwritten. * * @return the result of calling {@link #login(Credentials, String) * login(null, null)}. * @throws LoginException If login is not possible * @throws RepositoryException If another error occurrs during login * @see #login(Credentials, String) */ @Override public Session login() throws LoginException, RepositoryException { return this.login(null, null); } /** * Same as calling {@code login(credentials, null)}. *

* This method may be overwritten. * * @param credentials The {@code Credentials} to use to login. * @return the result of calling {@link #login(Credentials, String) * login(credentials, null)}. * @throws LoginException If login is not possible * @throws RepositoryException If another error occurrs during login * @see #login(Credentials, String) */ @Override public Session login(final Credentials credentials) throws LoginException, RepositoryException { return this.login(credentials, null); } /** * Same as calling {@code login(null, workspace)}. *

* This method may be overwritten. * * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return the result of calling {@link #login(Credentials, String) * login(null, workspace)}. * @throws LoginException If login is not possible * @throws RepositoryException If another error occurrs during login * @see #login(Credentials, String) */ @Override public Session login(final String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException { return this.login(null, workspace); } /** * Logs into the repository at the given {@code workspace} with the given * {@code credentials} and returns the session returned from * the repository. *

* This method logs in as a guest if {@code null} credentials are provided. * The method may be overwritten to implement a different behaviour as * indicated by the JCR specification for this method to use external * mechanisms to login instead of leveraging provided credentials. *

* This method may be overwritten. * * @param credentials The {@code Credentials} to use to login. If this is * {@code null} JCR {@code GuestCredentials} are used to login. * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @throws LoginException If login is not possible * @throws NoSuchWorkspaceException if the desired workspace is not * available * @throws RepositoryException If another error occurrs during login */ @Override public Session login(Credentials credentials, String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException { if (credentials == null) { credentials = new GuestCredentials(); } // check the workspace if (workspace == null) { workspace = this.getDefaultWorkspace(); } try { logger.debug("login: Logging in to workspace '" + workspace + "'"); final Repository repository = this.getRepository(); if (this.getRepository() == null) { throw new RepositoryException("Sling Repository not ready"); } final Session session = repository.login(credentials, workspace); return session; } catch (final RuntimeException re) { // SLING-702: Jackrabbit throws IllegalStateException if the // repository has already been shut down ... throw new RepositoryException(re.getMessage(), re); } } // service login /** * Actual implementation of the {@link #loginService(String, String)} method * taking into account the bundle calling this method. * * This method uses the * {@link AbstractSlingRepositoryManager#getServiceUserMapper() * ServiceUserMapper} service to map the named service to a user and then * calls the {@link #createServiceSession(String, String)} method actually * create a session for that user. * * @param subServiceName An optional subService identifier (may be * {@code null}) * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return A session authenticated with the service user * @throws LoginException if the service name cannot be derived or if * logging is as the user to which the service name maps is not * allowed * @throws RepositoryException If a general error occurs while creating the * session */ @Override public final Session loginService(final String subServiceName, final String workspace) throws LoginException, RepositoryException { Session s = createServiceSession(usingBundle, subServiceName, workspace); if (s != null) { return s; } else { throw new LoginException("Can neither derive user name nor principal names for bundle " + usingBundle + " and sub service " + subServiceName); } } /** * Default implementation of the {@link #impersonateFromService(String, Credentials, String)} * method taking into account the bundle calling this method. * * This method uses the * {@link AbstractSlingRepositoryManager#getServiceUserMapper() * ServiceUserMapper} service to map the named service to a user and then * calls the {@link #createServiceSession(String, String)} method actually * create a session for that user. This service session is then impersonated * to the subject identified by the specified {@code credentials}. * * @param subServiceName An optional subService identifier (may be {@code null}) * @param credentials A valid non-null {@code Credentials} object * @param workspaceName The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return a new {@code Session} object * @throws LoginException If the current session does not have sufficient access to perform the operation. * @throws RepositoryException If another error occurs. * @since 2.4 */ @Override public Session impersonateFromService(final String subServiceName, final Credentials credentials, final String workspaceName) throws LoginException, RepositoryException { Session serviceSession = null; try { serviceSession = createServiceSession(usingBundle, subServiceName, workspaceName); if (serviceSession == null) { throw new LoginException("Cannot create service session for bundle " + usingBundle + " and sub service " + subServiceName); } return serviceSession.impersonate(credentials); } finally { if (serviceSession != null) { serviceSession.logout(); } } } /** * Login as an administrative user. This method is deprecated and its use * can be completely disabled by setting {@code disableLoginAdministrative} * to {@code true}. *

* This implementation cannot be overwritten but, unless disabled, forwards * to the {@link #createAdministrativeSession(String)} method. * * @param workspace The workspace to access or {@code null} to access the * {@link #getDefaultWorkspace() default workspace} * @return An administrative session * @throws RepositoryException If the login fails or has been disabled */ @Override public final Session loginAdministrative(final String workspace) throws RepositoryException { final boolean whitelisted = getSlingRepositoryManager().allowLoginAdministrativeForBundle(usingBundle); if(!whitelisted) { final String symbolicName = usingBundle.getSymbolicName(); logger.error("Bundle {} is NOT whitelisted to use SlingRepository.loginAdministrative", symbolicName); throw new LoginException("Bundle " + symbolicName +" is NOT whitelisted"); } else if (this.getSlingRepositoryManager().isDisableLoginAdministrative()) { logger.error("SlingRepository.loginAdministrative is disabled. Please use SlingRepository.loginService."); throw new LoginException("SlingRepository.loginAdministrative is disabled."); } logger.debug("SlingRepository.loginAdministrative is deprecated. Please use SlingRepository.loginService."); Session result = createAdministrativeSession(workspace); Repository repository = getRepository(); return repository instanceof ProxyRepository ? ((ProxyRepository) repository).wrap(result) : result; } // Remaining Repository service methods all backed by the actual // repository instance. They may be overwritten, but generally are not. @Override public String getDescriptor(String name) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptor(name); } logger.error("getDescriptor: Repository not available"); return null; } @Override public String[] getDescriptorKeys() { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorKeys(); } logger.error("getDescriptorKeys: Repository not available"); return new String[0]; } @Override public Value getDescriptorValue(String key) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorValue(key); } logger.error("getDescriptorValue: Repository not available"); return null; } @Override public Value[] getDescriptorValues(String key) { Repository repo = getRepository(); if (repo != null) { return repo.getDescriptorValues(key); } logger.error("getDescriptorValues: Repository not available"); return null; } @Override public boolean isSingleValueDescriptor(String key) { Repository repo = getRepository(); if (repo != null) { return repo.isSingleValueDescriptor(key); } logger.error("isSingleValueDescriptor: Repository not available"); return false; } @Override public boolean isStandardDescriptor(String key) { Repository repo = getRepository(); if (repo != null) { return repo.isStandardDescriptor(key); } logger.error("isStandardDescriptor: Repository not available"); return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy