| /* |
| * 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); |
| } |
| } |