blob: 236fbc4b6b94ca063f3e86a3258f3de7b9b0da8f [file] [log] [blame]
/*
* 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.oak.security.internal;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.apache.jackrabbit.oak.commons.PropertiesUtil;
import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
import org.apache.jackrabbit.oak.plugins.tree.RootProvider;
import org.apache.jackrabbit.oak.plugins.tree.TreeProvider;
import org.apache.jackrabbit.oak.security.authorization.composite.CompositeAuthorizationConfiguration;
import org.apache.jackrabbit.oak.security.authorization.restriction.WhiteboardRestrictionProvider;
import org.apache.jackrabbit.oak.security.user.UserConfigurationImpl;
import org.apache.jackrabbit.oak.security.user.whiteboard.WhiteboardAuthorizableActionProvider;
import org.apache.jackrabbit.oak.security.user.whiteboard.WhiteboardAuthorizableNodeName;
import org.apache.jackrabbit.oak.security.user.whiteboard.WhiteboardUserAuthenticationFactory;
import org.apache.jackrabbit.oak.spi.security.CompositeConfiguration;
import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
import org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authentication.token.CompositeTokenConfiguration;
import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.principal.CompositePrincipalConfiguration;
import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConfiguration;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableNodeName;
import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider;
import org.jetbrains.annotations.NotNull;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.osgi.service.metatype.annotations.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newCopyOnWriteArrayList;
import static org.apache.jackrabbit.oak.spi.security.RegistrationConstants.OAK_SECURITY_NAME;
import static org.apache.jackrabbit.oak.spi.security.ConfigurationParameters.EMPTY;
@Component(immediate=true)
@Designate(ocd = SecurityProviderRegistration.Configuration.class)
@SuppressWarnings("unused")
public class SecurityProviderRegistration {
@ObjectClassDefinition(
name = "Apache Jackrabbit Oak SecurityProvider",
description = "The default SecurityProvider embedded in Apache Jackrabbit Oak"
)
@interface Configuration {
@AttributeDefinition(
name = "Required Services",
description = "The SecurityProvider will not register itself " +
"unless the services identified by the following service pids " +
"or the oak.security.name properties are registered first. The class name is " +
"identified by checking the service.pid property. If that property " +
"does not exist, the oak.security.name property is used as a fallback." +
"Only implementations of the following interfaces are checked :" +
"AuthorizationConfiguration, PrincipalConfiguration, " +
"TokenConfiguration, AuthorizableActionProvider, " +
"RestrictionProvider and UserAuthenticationFactory."
)
String[] requiredServicePids() default {
"org.apache.jackrabbit.oak.security.authorization.AuthorizationConfigurationImpl",
"org.apache.jackrabbit.oak.security.principal.PrincipalConfigurationImpl",
"org.apache.jackrabbit.oak.security.authentication.token.TokenConfigurationImpl",
"org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider",
"org.apache.jackrabbit.oak.security.authorization.restriction.RestrictionProviderImpl",
"org.apache.jackrabbit.oak.security.user.UserAuthenticationFactoryImpl"
};
@AttributeDefinition(
name = "Authorization Composition Type",
description = "The Composite Authorization model uses this flag to determine what type of logic "
+ "to apply to the existing providers (default value is AND).",
options = {
@Option(label = "AND", value = "AND"),
@Option(label = "OR", value = "OR")
}
)
String authorizationCompositionType() default "AND";
}
private static final Logger log = LoggerFactory.getLogger(SecurityProviderRegistration.class);
private AuthenticationConfiguration authenticationConfiguration;
private PrivilegeConfiguration privilegeConfiguration;
private UserConfiguration userConfiguration;
private BundleContext context;
private ServiceRegistration registration;
private boolean registering;
private final Preconditions preconditions = new Preconditions();
private final CompositeAuthorizationConfiguration authorizationConfiguration = new CompositeAuthorizationConfiguration();
private final CompositePrincipalConfiguration principalConfiguration = new CompositePrincipalConfiguration();
private final CompositeTokenConfiguration tokenConfiguration = new CompositeTokenConfiguration();
private final List<AuthorizableNodeName> authorizableNodeNames = newCopyOnWriteArrayList();
private final List<AuthorizableActionProvider> authorizableActionProviders = newCopyOnWriteArrayList();
private final List<RestrictionProvider> restrictionProviders = newCopyOnWriteArrayList();
private final List<UserAuthenticationFactory> userAuthenticationFactories = newCopyOnWriteArrayList();
private RootProvider rootProvider;
private TreeProvider treeProvider;
//----------------------------------------------------< SCR integration >---
@Activate
public void activate(BundleContext context, Configuration configuration) {
String[] requiredServicePids = configuration.requiredServicePids();
synchronized (this) {
for (String pid : requiredServicePids) {
preconditions.addPrecondition(pid);
}
this.context = context;
}
this.authorizationConfiguration.withCompositionType(configuration.authorizationCompositionType());
maybeRegister();
}
@Modified
public void modified(Configuration configuration) {
String[] requiredServicePids = configuration.requiredServicePids();
synchronized (this) {
preconditions.clearPreconditions();
for (String pid : requiredServicePids) {
preconditions.addPrecondition(pid);
}
}
this.authorizationConfiguration.withCompositionType(configuration.authorizationCompositionType());
maybeUnregister();
maybeRegister();
}
@Deactivate
public void deactivate() {
ServiceRegistration registration;
synchronized (this) {
registration = this.registration;
this.registration = null;
this.registering = false;
this.context = null;
this.preconditions.clearPreconditions();
}
if (registration != null) {
registration.unregister();
}
}
//--------------------------------------< unary security configurations >---
@Reference(name = "authenticationConfiguration")
public void bindAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = authenticationConfiguration;
}
public void unbindAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationConfiguration = null;
}
@Reference(name = "privilegeConfiguration")
public void bindPrivilegeConfiguration(PrivilegeConfiguration privilegeConfiguration) {
this.privilegeConfiguration = privilegeConfiguration;
}
public void unbindPrivilegeConfiguration(PrivilegeConfiguration privilegeConfiguration) {
this.privilegeConfiguration = null;
}
@Reference(name = "userConfiguration")
public void bindUserConfiguration(UserConfiguration userConfiguration) {
this.userConfiguration = userConfiguration;
}
public void unbindUserConfiguration(UserConfiguration userConfiguration) {
this.userConfiguration = null;
}
//-------------------------------------------< unary tree/root provider >---
@Reference(name = "rootProvider")
public void bindRootProvider(RootProvider rootProvider) {
this.rootProvider = rootProvider;
}
public void unbindRootProvider(RootProvider rootProvider) {
this.rootProvider = null;
}
@Reference(name = "treeProvider")
public void bindTreeProvider(TreeProvider treeProvider) {
this.treeProvider = treeProvider;
}
public void unbindTreeProvider(TreeProvider treeProvider) {
this.treeProvider = null;
}
//-----------------------------------< multiple security configurations >---
@Reference(
name = "authorizationConfiguration",
service = AuthorizationConfiguration.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindAuthorizationConfiguration(AuthorizationConfiguration configuration, Map<String, Object> properties) {
bindConfiguration(authorizationConfiguration, configuration, properties);
}
public void unbindAuthorizationConfiguration(AuthorizationConfiguration configuration, Map<String, Object> properties) {
unbindConfiguration(authorizationConfiguration, configuration, properties);
}
@Reference(
name = "principalConfiguration",
service = PrincipalConfiguration.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindPrincipalConfiguration(PrincipalConfiguration configuration, Map<String, Object> properties) {
bindConfiguration(principalConfiguration, configuration, properties);
}
public void unbindPrincipalConfiguration(PrincipalConfiguration configuration, Map<String, Object> properties) {
unbindConfiguration(principalConfiguration, configuration, properties);
}
@Reference(
name = "tokenConfiguration",
service = TokenConfiguration.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindTokenConfiguration(TokenConfiguration configuration, Map<String, Object> properties) {
bindConfiguration(tokenConfiguration, configuration, properties);
}
public void unbindTokenConfiguration(TokenConfiguration configuration, Map<String, Object> properties) {
unbindConfiguration(tokenConfiguration, configuration, properties);
}
private <T extends SecurityConfiguration> void bindConfiguration(@NotNull CompositeConfiguration<T> composite, @NotNull T configuration, Map<String, Object> properties) {
synchronized (this) {
composite.addConfiguration(configuration, ConfigurationParameters.of(properties));
addCandidate(properties);
}
maybeRegister();
}
private <T extends SecurityConfiguration> void unbindConfiguration(@NotNull CompositeConfiguration<T> composite, @NotNull T configuration, Map<String, Object> properties) {
synchronized (this) {
composite.removeConfiguration(configuration);
removeCandidate(properties);
}
maybeUnregister();
}
//------------------------------------------------------------< add ons >---
@Reference(
name = "authorizableNodeName",
service = AuthorizableNodeName.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindAuthorizableNodeName(AuthorizableNodeName authorizableNodeName, Map<String, Object> properties) {
synchronized (this) {
authorizableNodeNames.add(authorizableNodeName);
addCandidate(properties);
}
maybeRegister();
}
public void unbindAuthorizableNodeName(AuthorizableNodeName authorizableNodeName, Map<String, Object> properties) {
synchronized (this) {
authorizableNodeNames.remove(authorizableNodeName);
removeCandidate(properties);
}
maybeUnregister();
}
@Reference(
name = "authorizableActionProvider",
service = AuthorizableActionProvider.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindAuthorizableActionProvider(AuthorizableActionProvider authorizableActionProvider, Map<String, Object> properties) {
synchronized (this) {
authorizableActionProviders.add(authorizableActionProvider);
addCandidate(properties);
}
maybeRegister();
}
public void unbindAuthorizableActionProvider(AuthorizableActionProvider authorizableActionProvider, Map<String, Object> properties) {
synchronized (this) {
authorizableActionProviders.remove(authorizableActionProvider);
removeCandidate(properties);
}
maybeUnregister();
}
@Reference(
name = "restrictionProvider",
service = RestrictionProvider.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindRestrictionProvider(RestrictionProvider restrictionProvider, Map<String, Object> properties) {
synchronized (this) {
restrictionProviders.add(restrictionProvider);
addCandidate(properties);
}
maybeRegister();
}
public void unbindRestrictionProvider(RestrictionProvider restrictionProvider, Map<String, Object> properties) {
synchronized (this) {
restrictionProviders.remove(restrictionProvider);
removeCandidate(properties);
}
maybeUnregister();
}
@Reference(
name = "userAuthenticationFactory",
service = UserAuthenticationFactory.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC
)
public void bindUserAuthenticationFactory(UserAuthenticationFactory userAuthenticationFactory, Map<String, Object> properties) {
synchronized (this) {
userAuthenticationFactories.add(userAuthenticationFactory);
addCandidate(properties);
}
maybeRegister();
}
public void unbindUserAuthenticationFactory(UserAuthenticationFactory userAuthenticationFactory, Map<String, Object> properties) {
synchronized (this) {
userAuthenticationFactories.remove(userAuthenticationFactory);
removeCandidate(properties);
}
maybeUnregister();
}
private void maybeRegister() {
BundleContext context;
log.info("Trying to register a SecurityProvider...");
synchronized (this) {
// The component is not activated, yet. We have no means of registering
// the SecurityProvider. This method will be called again after
// activation completes.
if (this.context == null) {
log.info("Aborting: no BundleContext is available");
return;
}
// The preconditions are not satisifed. This may happen when this
// component is activated but not enough mandatory services are bound
// to it.
if (!preconditions.areSatisfied()) {
log.info("Aborting: preconditions are not satisfied: {}", preconditions);
return;
}
// The SecurityProvider is already registered. This may happen when a
// new dependency is added to this component, but the requirements are
// already satisfied.
if (registration != null) {
log.info("Aborting: a SecurityProvider is already registered");
return;
}
// If the component is in the process of registering an instance of
// SecurityProvider, return. This check is necessary because we don't
// want to call createSecurityProvider() more than once. That method,
// in fact, changes the state of the bound dependencies (it sets a
// back-reference from the security configurations to the new
// SecurityProvider). We want those dependencies to change state only
// when we are sure that we will register the SecurityProvider we
// are creating.
if (registering) {
log.info("Aborting: a SecurityProvider is already being registered");
return;
}
// Mark the start of a registration process.
registering = true;
// Save the BundleContext for local usage.
context = this.context;
}
// Register the SecurityProvider.
Dictionary<String, Object> properties = new Hashtable<String, Object>();
properties.put("type", "default");
ServiceRegistration registration = context.registerService(
SecurityProvider.class.getName(),
createSecurityProvider(context),
properties
);
synchronized (this) {
this.registration = registration;
this.registering = false;
}
log.info("SecurityProvider instance registered");
}
private void maybeUnregister() {
ServiceRegistration registration;
log.info("Trying to unregister the SecurityProvider...");
synchronized (this) {
// If there is nothing to register, we obviously have nothing to do.
if (this.registration == null) {
log.info("Aborting: no SecurityProvider is registered");
return;
}
// The preconditions are still satisfied. This may happen when a
// dependency is unbound while not being listed as required service.
if (preconditions.areSatisfied()) {
log.info("Aborting: preconditions are satisfied");
return;
}
// Save the ServiceRegistration for local use.
registration = this.registration;
this.registration = null;
}
registration.unregister();
log.info("SecurityProvider instance unregistered");
}
private SecurityProvider createSecurityProvider(@NotNull BundleContext context) {
ConfigurationParameters userParams = ConfigurationParameters.of(
ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, createWhiteboardAuthorizableActionProvider()),
ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_NODE_NAME, createWhiteboardAuthorizableNodeName()),
ConfigurationParameters.of(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, createWhiteboardUserAuthenticationFactory()));
ConfigurationParameters authorizationParams = ConfigurationParameters
.of(AccessControlConstants.PARAM_RESTRICTION_PROVIDER, createWhiteboardRestrictionProvider());
return SecurityProviderBuilder.newBuilder().withRootProvider(rootProvider).withTreeProvider(treeProvider)
.with(authenticationConfiguration, EMPTY, privilegeConfiguration, EMPTY, userConfiguration, userParams,
authorizationConfiguration, authorizationParams, principalConfiguration, EMPTY,
tokenConfiguration, EMPTY)
.withWhiteboard(new OsgiWhiteboard(context)).build();
}
private RestrictionProvider createWhiteboardRestrictionProvider() {
return new WhiteboardRestrictionProvider() {
@Override
protected List<RestrictionProvider> getServices() {
return newArrayList(restrictionProviders);
}
};
}
private AuthorizableActionProvider createWhiteboardAuthorizableActionProvider() {
return new WhiteboardAuthorizableActionProvider() {
@Override
protected List<AuthorizableActionProvider> getServices() {
return newArrayList(authorizableActionProviders);
}
};
}
private AuthorizableNodeName createWhiteboardAuthorizableNodeName() {
return new WhiteboardAuthorizableNodeName() {
@Override
protected List<AuthorizableNodeName> getServices() {
return newArrayList(authorizableNodeNames);
}
};
}
private UserAuthenticationFactory createWhiteboardUserAuthenticationFactory() {
return new WhiteboardUserAuthenticationFactory(UserConfigurationImpl.getDefaultAuthenticationFactory()) {
@Override
protected List<UserAuthenticationFactory> getServices() {
return newArrayList(userAuthenticationFactories);
}
};
}
private void addCandidate(Map<String, Object> properties) {
String pidOrName = getServicePidOrComponentName(properties);
if (pidOrName == null) {
return;
}
preconditions.addCandidate(pidOrName);
}
private void removeCandidate(Map<String, Object> properties) {
String pidOrName = getServicePidOrComponentName(properties);
if (pidOrName == null) {
return;
}
preconditions.removeCandidate(pidOrName);
}
private static String getServicePidOrComponentName(Map<String, Object> properties) {
String servicePid = PropertiesUtil.toString(properties.get(Constants.SERVICE_PID), null);
if ( servicePid != null ) {
return servicePid;
}
return PropertiesUtil.toString(properties.get(OAK_SECURITY_NAME), null);
}
}