org.apache.sling.jcr.base.AbstractSlingRepository2 Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* 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;
}
}