Index: oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugSecurityProvider.java
===================================================================
--- oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugSecurityProvider.java (revision 1703179)
+++ oak-authorization-cug/src/test/java/org/apache/jackrabbit/oak/spi/security/authorization/cug/impl/CugSecurityProvider.java (working copy)
@@ -33,12 +33,8 @@
composite.setDefaultConfig(authorizationConfiguration);
composite.addConfiguration(new CugConfiguration(this));
composite.addConfiguration(authorizationConfiguration);
- ((CugSecurityProvider) this).bindAuthorizationConfiguration(composite);
+ setAuthorizationConfiguration(composite);
}
}
- @Override
- protected void bindAuthorizationConfiguration(@Nonnull AuthorizationConfiguration reference) {
- super.bindAuthorizationConfiguration(reference);
- }
}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/Preconditions.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/Preconditions.java (revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/Preconditions.java (working copy)
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import static com.google.common.collect.Sets.newHashSet;
+
+/**
+ * Represents a preconditions set that may be satisfied by adding the right
+ * candidates.
+ *
+ * Initially, a set of preconditions is empty. An empty set of preconditions is
+ * always satisfied. If candidates are added, but the precondition set is empty,
+ * the preconditions are considered satisfied.
+ *
+ * When some preconditions are added, the preconditions set may enter into the
+ * unsatisfied state. In this case, the preconditions set may be come satisfied
+ * again only with the addition of the right candidates.
+ *
+ * This class doesn't admit duplicates for preconditions or candidates. Adding
+ * the same precondition (or candidate) twice doesn't have any effect on the
+ * state of the preconditions set.
+ */
+class Preconditions {
+
+ private final Set preconditions = newHashSet();
+
+ private final Set candidates = newHashSet();
+
+ private boolean dirty = false;
+
+ private boolean satisfied = true;
+
+ /**
+ * Add a precondition to this preconditions set. If the precondition already
+ * belongs to this set, this operation has no effect.
+ *
+ * @param precondition The precondition to be added.
+ */
+ public void addPrecondition(String precondition) {
+ if (preconditions.add(precondition)) {
+ dirty = true;
+ }
+ }
+
+ /**
+ * Remove all the preconditions to this set. This makes the set of
+ * preconditions empty and, as such, satisfied.
+ */
+ public void clearPreconditions() {
+ preconditions.clear();
+ dirty = false;
+ satisfied = true;
+ }
+
+ /**
+ * Add a candidate to this preconditions set. If the candidate already
+ * belongs to this set, this operation has no effect.
+ *
+ * @param candidate The candidate to be added.
+ */
+ public void addCandidate(String candidate) {
+ if (candidates.add(candidate)) {
+ dirty = true;
+ }
+ }
+
+ /**
+ * Remove a candidate from this preconditions set. If the candidate doesn't
+ * belong to this set, this operation has no effect.
+ *
+ * @param candidate The candidate to be removed.
+ */
+ public void removeCandidate(String candidate) {
+ if (candidates.remove(candidate)) {
+ dirty = true;
+ }
+ }
+
+ /**
+ * Check if the preconditions set are satisfied.
+ *
+ * @return {@code true} if the preconditions set is satisfied, {@code false}
+ * otherwise.
+ */
+ public boolean areSatisfied() {
+ if (dirty) {
+ Set unsatisfied = newHashSet(preconditions);
+ unsatisfied.removeAll(candidates);
+ satisfied = unsatisfied.isEmpty();
+ dirty = false;
+ }
+
+ return satisfied;
+ }
+
+}
Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/security/Preconditions.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderImpl.java (working copy)
@@ -16,118 +16,96 @@
*/
package org.apache.jackrabbit.oak.security;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.google.common.collect.ImmutableMap;
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
import org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl;
import org.apache.jackrabbit.oak.security.authentication.token.TokenConfigurationImpl;
import org.apache.jackrabbit.oak.security.authorization.AuthorizationConfigurationImpl;
import org.apache.jackrabbit.oak.security.principal.PrincipalConfigurationImpl;
import org.apache.jackrabbit.oak.security.privilege.PrivilegeConfigurationImpl;
import org.apache.jackrabbit.oak.security.user.UserConfigurationImpl;
-import org.apache.jackrabbit.oak.spi.security.ConfigurationBase;
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.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.UserConfiguration;
-import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableActionProvider;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableNodeName;
import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAware;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardRestrictionProvider;
-import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUserAuthenticationFactory;
-import org.osgi.framework.BundleContext;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Sets.newHashSet;
-@Component
-@Service(value = {SecurityProvider.class})
public class SecurityProviderImpl implements SecurityProvider, WhiteboardAware {
- @Reference
- private volatile AuthorizationConfiguration authorizationConfiguration;
+ private AuthenticationConfiguration authenticationConfiguration;
- @Reference
- private volatile AuthenticationConfiguration authenticationConfiguration;
+ private AuthorizationConfiguration authorizationConfiguration;
- @Reference
- private volatile PrivilegeConfiguration privilegeConfiguration;
+ private UserConfiguration userConfiguration;
- @Reference
- private volatile UserConfiguration userConfiguration;
+ private PrincipalConfiguration principalConfiguration;
- @Reference(referenceInterface = PrincipalConfiguration.class,
- name = "principalConfiguration",
- bind = "bindPrincipalConfiguration",
- unbind = "unbindPrincipalConfiguration",
- policy = ReferencePolicy.DYNAMIC,
- cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
- private final CompositePrincipalConfiguration principalConfiguration = new CompositePrincipalConfiguration(this);
+ private PrivilegeConfiguration privilegeConfiguration;
- @Reference(referenceInterface = TokenConfiguration.class,
- name = "tokenConfiguration",
- bind = "bindTokenConfiguration",
- unbind = "unbindTokenConfiguration",
- policy = ReferencePolicy.DYNAMIC,
- cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
- private final CompositeTokenConfiguration tokenConfiguration = new CompositeTokenConfiguration(this);
+ private TokenConfiguration tokenConfiguration;
- private final WhiteboardAuthorizableNodeName authorizableNodeName = new WhiteboardAuthorizableNodeName();
- private final WhiteboardAuthorizableActionProvider authorizableActionProvider = new WhiteboardAuthorizableActionProvider();
- private final WhiteboardRestrictionProvider restrictionProvider = new WhiteboardRestrictionProvider();
- private final WhiteboardUserAuthenticationFactory userAuthenticationFactory = new WhiteboardUserAuthenticationFactory(UserConfigurationImpl.getDefaultAuthenticationFactory());
-
private ConfigurationParameters configuration;
private Whiteboard whiteboard;
/**
- * Default constructor used in OSGi environments.
+ * Default constructor using an empty configuration and default
+ * implementations for the security configurations.
*/
public SecurityProviderImpl() {
this(ConfigurationParameters.EMPTY);
}
/**
- * Create a new {@code SecurityProvider} instance with the given configuration
- * parameters.
+ * Create a new {@code SecurityProvider} instance with the given
+ * configuration parameters.
*
* @param configuration security configuration
*/
public SecurityProviderImpl(@Nonnull ConfigurationParameters configuration) {
- checkNotNull(configuration);
- this.configuration = configuration;
+ this.configuration = checkNotNull(configuration);
+ this.authenticationConfiguration = new AuthenticationConfigurationImpl(this);
+ this.authorizationConfiguration = new AuthorizationConfigurationImpl(this);
+ this.userConfiguration = new UserConfigurationImpl(this);
+ this.principalConfiguration = new PrincipalConfigurationImpl(this);
+ this.privilegeConfiguration = new PrivilegeConfigurationImpl();
+ this.tokenConfiguration = new TokenConfigurationImpl(this);
+ }
- authenticationConfiguration = new AuthenticationConfigurationImpl(this);
- authorizationConfiguration = new AuthorizationConfigurationImpl(this);
- userConfiguration = new UserConfigurationImpl(this);
- privilegeConfiguration = new PrivilegeConfigurationImpl();
+ protected void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
+ this.authenticationConfiguration = checkNotNull(authenticationConfiguration);
+ }
- principalConfiguration.setDefaultConfig(new PrincipalConfigurationImpl(this));
- tokenConfiguration.setDefaultConfig(new TokenConfigurationImpl(this));
+ protected void setAuthorizationConfiguration(AuthorizationConfiguration authorizationConfiguration) {
+ this.authorizationConfiguration = authorizationConfiguration;
}
+ protected void setUserConfiguration(UserConfiguration userConfiguration) {
+ this.userConfiguration = userConfiguration;
+ }
+
+ protected void setPrincipalConfiguration(PrincipalConfiguration principalConfiguration) {
+ this.principalConfiguration = principalConfiguration;
+ }
+
+ protected void setPrivilegeConfiguration(PrivilegeConfiguration privilegeConfiguration) {
+ this.privilegeConfiguration = privilegeConfiguration;
+ }
+
+ protected void setTokenConfiguration(TokenConfiguration tokenConfiguration) {
+ this.tokenConfiguration = tokenConfiguration;
+ }
+
@Override
public void setWhiteboard(@Nonnull Whiteboard whiteboard) {
this.whiteboard = whiteboard;
@@ -144,7 +122,9 @@
if (name == null) {
return configuration;
}
+
ConfigurationParameters params = configuration.getConfigValue(name, ConfigurationParameters.EMPTY);
+
for (SecurityConfiguration sc : getConfigurations()) {
if (sc != null && sc.getName().equals(name)) {
return ConfigurationParameters.of(params, sc.getParameters());
@@ -156,14 +136,14 @@
@Nonnull
@Override
public Iterable extends SecurityConfiguration> getConfigurations() {
- Set scs = new HashSet();
- scs.add(authenticationConfiguration);
- scs.add(authorizationConfiguration);
- scs.add(userConfiguration);
- scs.add(principalConfiguration);
- scs.add(privilegeConfiguration);
- scs.add(tokenConfiguration);
- return scs;
+ return newHashSet(
+ authenticationConfiguration,
+ authorizationConfiguration,
+ userConfiguration,
+ principalConfiguration,
+ privilegeConfiguration,
+ tokenConfiguration
+ );
}
@SuppressWarnings("unchecked")
@@ -187,89 +167,4 @@
}
}
- //----------------------------------------------------------------< SCR >---
- @Activate
- protected void activate(BundleContext context) {
- whiteboard = new OsgiWhiteboard(context);
- authorizableActionProvider.start(whiteboard);
- authorizableNodeName.start(whiteboard);
- restrictionProvider.start(whiteboard);
- userAuthenticationFactory.start(whiteboard);
-
- initializeConfigurations();
- }
-
- @Deactivate
- protected void deactivate() {
- authorizableActionProvider.stop();
- authorizableNodeName.stop();
- restrictionProvider.stop();
- userAuthenticationFactory.stop();
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void bindPrincipalConfiguration(@Nonnull PrincipalConfiguration reference) {
- principalConfiguration.addConfiguration(initConfiguration(reference));
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void unbindPrincipalConfiguration(@Nonnull PrincipalConfiguration reference) {
- principalConfiguration.removeConfiguration(reference);
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void bindTokenConfiguration(@Nonnull TokenConfiguration reference) {
- tokenConfiguration.addConfiguration(initConfiguration(reference));
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void unbindTokenConfiguration(@Nonnull TokenConfiguration reference) {
- tokenConfiguration.removeConfiguration(reference);
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void bindAuthorizationConfiguration(@Nonnull AuthorizationConfiguration reference) {
- authorizationConfiguration = initConfiguration(reference);
- // TODO (OAK-1268): authorizationConfiguration.addConfiguration(initConfiguration(reference));
- }
-
- @SuppressWarnings("UnusedDeclaration")
- protected void unbindAuthorizationConfiguration(@Nonnull AuthorizationConfiguration reference) {
- authorizationConfiguration = new AuthorizationConfigurationImpl(this);
- // TODO (OAK-1268): authorizationConfiguration.removeConfiguration(reference);
- }
-
- //------------------------------------------------------------< private >---
- private void initializeConfigurations() {
- initConfiguration(authorizationConfiguration, ConfigurationParameters.of(
- AccessControlConstants.PARAM_RESTRICTION_PROVIDER, restrictionProvider)
- );
-
- Map userMap = ImmutableMap.of(
- UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, authorizableActionProvider,
- UserConstants.PARAM_AUTHORIZABLE_NODE_NAME, authorizableNodeName,
- UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, userAuthenticationFactory);
- initConfiguration(userConfiguration, ConfigurationParameters.of(userMap));
-
- initConfiguration(authenticationConfiguration);
- initConfiguration(privilegeConfiguration);
- }
-
- private T initConfiguration(@Nonnull T config) {
- if (config instanceof ConfigurationBase) {
- ConfigurationBase cfg = (ConfigurationBase) config;
- cfg.setSecurityProvider(this);
- cfg.setParameters(ConfigurationParameters.of(ConfigurationParameters.EMPTY, cfg.getParameters()));
- }
- return config;
- }
-
- private T initConfiguration(@Nonnull T config, @Nonnull ConfigurationParameters params) {
- if (config instanceof ConfigurationBase) {
- ConfigurationBase cfg = (ConfigurationBase) config;
- cfg.setSecurityProvider(this);
- cfg.setParameters(ConfigurationParameters.of(params, cfg.getParameters()));
- }
- return config;
- }
}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderRegistration.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderRegistration.java (revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderRegistration.java (working copy)
@@ -0,0 +1,937 @@
+/*
+ * 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;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.References;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.osgi.OsgiWhiteboard;
+import org.apache.jackrabbit.oak.security.user.UserConfigurationImpl;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.MoveTracker;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
+import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationBase;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.Context;
+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.LoginContextProvider;
+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.permission.PermissionProvider;
+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.principal.PrincipalProvider;
+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.apache.jackrabbit.oak.spi.whiteboard.Registration;
+import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableActionProvider;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardAuthorizableNodeName;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardRestrictionProvider;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUserAuthenticationFactory;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.security.AccessControlManager;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Lists.newCopyOnWriteArrayList;
+
+@Component(
+ immediate = true,
+ metatype = true,
+ label = "Apache Jackrabbit Oak SecurityProvider",
+ description = "The default SecurityProvider embedded in Apache Jackrabbit Oak"
+)
+@Properties({
+ @Property(
+ name = "requiredServicePids",
+ label = "Required service PIDs",
+ description = "The SecurityProvider will not register itself " +
+ "unless the services identified by these PIDs are " +
+ "registered first. Only the PIDs of implementations of " +
+ "the following interfaces are checked: " +
+ "PrincipalConfiguration, TokenConfiguration, " +
+ "AuthorizableNodeName, AuthorizableActionProvider, " +
+ "RestrictionProvider and UserAuthenticationFactory.",
+ value = {
+ "org.apache.jackrabbit.oak.security.principal.PrincipalConfigurationImpl",
+ "org.apache.jackrabbit.oak.security.authentication.token.TokenConfigurationImpl",
+ "org.apache.jackrabbit.oak.security.user.RandomAuthorizableNodeName",
+ "org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider",
+ "org.apache.jackrabbit.oak.security.authorization.restriction.RestrictionProviderImpl",
+ "org.apache.jackrabbit.oak.security.user.UserAuthenticationFactoryImpl"
+ },
+ unbounded = PropertyUnbounded.ARRAY
+ )
+})
+@References({
+ @Reference(
+ name = "principalConfiguration",
+ referenceInterface = PrincipalConfiguration.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ ),
+ @Reference(
+ name = "tokenConfiguration",
+ referenceInterface = TokenConfiguration.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ ),
+ @Reference(
+ name = "authorizableNodeName",
+ referenceInterface = AuthorizableNodeName.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ ),
+ @Reference(
+ name = "authorizableActionProvider",
+ referenceInterface = AuthorizableActionProvider.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ ),
+ @Reference(
+ name = "restrictionProvider",
+ referenceInterface = RestrictionProvider.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ ),
+ @Reference(
+ name = "userAuthenticationFactory",
+ referenceInterface = UserAuthenticationFactory.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC
+ )
+})
+public class SecurityProviderRegistration {
+
+ @Reference
+ private AuthorizationConfiguration authorizationConfiguration;
+
+ @Reference
+ private AuthenticationConfiguration authenticationConfiguration;
+
+ @Reference
+ private PrivilegeConfiguration privilegeConfiguration;
+
+ @Reference
+ private UserConfiguration userConfiguration;
+
+ private BundleContext context;
+
+ private ServiceRegistration registration;
+
+ private final Preconditions preconditions = new Preconditions();
+
+ private final List principalConfigurations = newCopyOnWriteArrayList();
+
+ private final List tokenConfigurations = newCopyOnWriteArrayList();
+
+ private final List authorizableNodeNames = newCopyOnWriteArrayList();
+
+ private final List authorizableActionProviders = newCopyOnWriteArrayList();
+
+ private final List restrictionProviders = newCopyOnWriteArrayList();
+
+ private final List userAuthenticationFactories = newCopyOnWriteArrayList();
+
+ private final SecurityProvider securityProvider = createSecurityProvider();
+
+ @Activate
+ public void activate(BundleContext context, Map configuration) {
+ String[] requiredServicePids = getRequiredServicePids(configuration);
+
+ synchronized (this) {
+ for (String pid : requiredServicePids) {
+ preconditions.addPrecondition(pid);
+ }
+
+ this.context = context;
+ }
+
+ maybeActivate();
+ }
+
+ @Modified
+ public void modified(Map configuration) {
+ String[] requiredServicePids = getRequiredServicePids(configuration);
+
+ synchronized (this) {
+ preconditions.clearPreconditions();
+
+ for (String pid : requiredServicePids) {
+ preconditions.addPrecondition(pid);
+ }
+ }
+
+ maybeDeactivate();
+ maybeActivate();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ ServiceRegistration r;
+
+ synchronized (this) {
+ r = registration;
+
+ registration = null;
+ context = null;
+
+ preconditions.clearPreconditions();
+ }
+
+ if (r != null) {
+ r.unregister();
+ }
+ }
+
+ public void bindAuthorizationConfiguration(AuthorizationConfiguration authorizationConfiguration) {
+ this.authorizationConfiguration = initializeConfiguration(authorizationConfiguration);
+ }
+
+ public void unbindAuthorizationConfiguration(AuthorizationConfiguration authorizationConfiguration) {
+ this.authorizationConfiguration = null;
+ }
+
+ public void bindAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
+ this.authenticationConfiguration = initializeConfiguration(authenticationConfiguration);
+ }
+
+ public void unbindAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
+ this.authenticationConfiguration = null;
+ }
+
+ public void bindPrivilegeConfiguration(PrivilegeConfiguration privilegeConfiguration) {
+ this.privilegeConfiguration = initializeConfiguration(privilegeConfiguration);
+ }
+
+ public void unbindPrivilegeConfiguration(PrivilegeConfiguration privilegeConfiguration) {
+ this.privilegeConfiguration = null;
+ }
+
+ public void bindUserConfiguration(UserConfiguration userConfiguration) {
+ this.userConfiguration = initializeConfiguration(userConfiguration);
+ }
+
+ public void unbindUserConfiguration(UserConfiguration userConfiguration) {
+ this.userConfiguration = null;
+ }
+
+ public void bindPrincipalConfiguration(PrincipalConfiguration principalConfiguration, Map properties) {
+ synchronized (this) {
+ principalConfigurations.add(initializeConfiguration(principalConfiguration));
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindPrincipalConfiguration(PrincipalConfiguration principalConfiguration, Map properties) {
+ synchronized (this) {
+ principalConfigurations.remove(principalConfiguration);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ public void bindTokenConfiguration(TokenConfiguration tokenConfiguration, Map properties) {
+ synchronized (this) {
+ tokenConfigurations.add(initializeConfiguration(tokenConfiguration));
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindTokenConfiguration(TokenConfiguration tokenConfiguration, Map properties) {
+ synchronized (this) {
+ tokenConfigurations.remove(tokenConfiguration);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ public void bindAuthorizableNodeName(AuthorizableNodeName authorizableNodeName, Map properties) {
+ synchronized (this) {
+ authorizableNodeNames.add(authorizableNodeName);
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindAuthorizableNodeName(AuthorizableNodeName authorizableNodeName, Map properties) {
+ synchronized (this) {
+ authorizableNodeNames.remove(authorizableNodeName);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ public void bindAuthorizableActionProvider(AuthorizableActionProvider authorizableActionProvider, Map properties) {
+ synchronized (this) {
+ authorizableActionProviders.add(authorizableActionProvider);
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindAuthorizableActionProvider(AuthorizableActionProvider authorizableActionProvider, Map properties) {
+ synchronized (this) {
+ authorizableActionProviders.remove(authorizableActionProvider);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ public void bindRestrictionProvider(RestrictionProvider restrictionProvider, Map properties) {
+ synchronized (this) {
+ restrictionProviders.add(restrictionProvider);
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindRestrictionProvider(RestrictionProvider restrictionProvider, Map properties) {
+ synchronized (this) {
+ restrictionProviders.remove(restrictionProvider);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ public void bindUserAuthenticationFactory(UserAuthenticationFactory userAuthenticationFactory, Map properties) {
+ synchronized (this) {
+ userAuthenticationFactories.add(userAuthenticationFactory);
+ addCandidate(properties);
+ }
+
+ maybeActivate();
+ }
+
+ public void unbindUserAuthenticationFactory(UserAuthenticationFactory userAuthenticationFactory, Map properties) {
+ synchronized (this) {
+ userAuthenticationFactories.remove(userAuthenticationFactory);
+ removeCandidate(properties);
+ }
+
+ maybeDeactivate();
+ }
+
+ /**
+ * Create a new instance of {@code SecurityProvider} and register it as a
+ * service if some conditions hold.
+ *
+ * A new service is registered if (1) this component is activated, (2) the
+ * preconditions specified via OSGi configuration are satisfied, and (3) a
+ * {@code SecurityProvider} service was not already registered by a previous
+ * invocation of this method.
+ */
+ private void maybeActivate() {
+ BundleContext bc;
+
+ 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 (context == null) {
+ 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()) {
+ 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) {
+ return;
+ }
+
+ bc = this.context;
+ }
+
+ // Register the SecurityProvider.
+
+ ServiceRegistration r = bc.registerService(SecurityProvider.class.getName(), createSecurityProvider(), null);
+
+ synchronized (this) {
+
+ // Check if another SecurityProvider was registered between the time
+ // we checked the state of the component and the end of the
+ // registration process.
+
+ if (registration == null) {
+ registration = r;
+ r = null;
+ }
+ }
+
+ // Unregister the newly registered SecurityProvider if it is
+ // out-of-date.
+
+ if (r != null) {
+ r.unregister();
+ }
+ }
+
+ /**
+ * Unregister a previously registered {@code SecurityProvider} service if
+ * the preconditions specified via OSGi configuration are satisfied.
+ */
+ private void maybeDeactivate() {
+ ServiceRegistration r;
+
+ synchronized (this) {
+
+ // If there is nothing to register, we obviously have nothing to do.
+
+ if (registration == null) {
+ return;
+ }
+
+ // The preconditions are not satisfied. This may happen when a
+ // dependency is unbound from the current component.
+
+ if (preconditions.areSatisfied()) {
+ return;
+ }
+
+ r = registration;
+ registration = null;
+ }
+
+ if (r != null) {
+ r.unregister();
+ }
+ }
+
+ /**
+ * Initialize a referenced {@code AuthorizationConfiguration} service. The
+ * service will be linked to the {@code SecurityProvider} managed by this
+ * component and to the {@code RestrictionProvider} services registered into
+ * the framework.
+ *
+ * @param authorizationConfiguration A {@code AuthorizationConfiguration}
+ * service referenced by this component.
+ * @return The same {@code AuthorizationConfiguration} service passed as a
+ * parameter after being initialized.
+ */
+ private AuthorizationConfiguration initializeConfiguration(AuthorizationConfiguration authorizationConfiguration) {
+ return initializeConfiguration(authorizationConfiguration, ConfigurationParameters.of(
+ AccessControlConstants.PARAM_RESTRICTION_PROVIDER, createDelegatingRestrictionProvider()
+ ));
+ }
+
+ /**
+ * Initialize a referenced {@code UserConfiguration} service. The service
+ * will be linked to the {@code SecurityProvider} managed by this component
+ * and to the {@code AuthorizableActionProvider}, {@code
+ * AuthorizableNodeName} and {@code UserAuthenticationFactory} registered
+ * into the framework.
+ *
+ * @param userConfiguration A {@code UserConfiguration} service referenced
+ * by this component.
+ * @return The same {@code UserConfiguration} service passed as a parameter
+ * after being initialized.
+ */
+ private UserConfiguration initializeConfiguration(UserConfiguration userConfiguration) {
+ return initializeConfiguration(userConfiguration, ConfigurationParameters.of(
+ ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_ACTION_PROVIDER, createDelegatingAuthorizableActionProvider()),
+ ConfigurationParameters.of(UserConstants.PARAM_AUTHORIZABLE_NODE_NAME, createDelegatingAuthorizableNodeName()),
+ ConfigurationParameters.of(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, createDelegatingUserAuthenticationFactory())
+ ));
+ }
+
+
+ private T initializeConfiguration(T configuration) {
+ return initializeConfiguration(configuration, ConfigurationParameters.EMPTY);
+ }
+
+ /**
+ * Initialize a referenced {@code SecurityConfiguration} services. The
+ * service will be linked to the {@code SecurityProvider} managed by this
+ * component, and its parameters will be merged with the parameters passed
+ * to this method.
+ *
+ * @param configuration An instance of {@code SecurityConfiguration} to
+ * initialize.
+ * @param parameters The parameters to merge with the ones already hold
+ * by the configuration.
+ * @param The type of the {@code SecurityConfiguration}
+ * instance.
+ * @return The same {@code SecurityConfiguration} after being initialized.
+ */
+ private T initializeConfiguration(T configuration, ConfigurationParameters parameters) {
+ if (configuration instanceof ConfigurationBase) {
+ ConfigurationBase base = (ConfigurationBase) configuration;
+ base.setSecurityProvider(createDelegatingSecurityProvider());
+ base.setParameters(ConfigurationParameters.of(parameters, base.getParameters()));
+ }
+
+ return configuration;
+ }
+
+ /**
+ * Create a {@code SecurityProvider} that references its configurations from
+ * the OSGi framework.
+ *
+ * @return An instance of {@code SecurityProvider}.
+ */
+ private SecurityProvider createSecurityProvider() {
+ SecurityProviderImpl securityProvider = new SecurityProviderImpl();
+
+ securityProvider.setAuthenticationConfiguration(createDelegatinAuthenticationConfiguration());
+ securityProvider.setAuthorizationConfiguration(createDelegatingAuthorizationConfiguration());
+ securityProvider.setUserConfiguration(createDelegatingUserConfiguration());
+ securityProvider.setPrivilegeConfiguration(createDelegatingPrivilegeConfiguration());
+ securityProvider.setPrincipalConfiguration(createDelegatingPrincipalConfiguration());
+ securityProvider.setTokenConfiguration(createDelegatingTokenConfiguration());
+ securityProvider.setWhiteboard(createDelegatingWhiteboard());
+
+ return securityProvider;
+ }
+
+ /**
+ * Create a {@code PrincipalConfiguration} that aggregates every {@code
+ * PrincipalConfiguration} service referenced by this component.
+ *
+ * @return An instance of {@code PrincipalConfiguration}.
+ */
+ private PrincipalConfiguration createDelegatingPrincipalConfiguration() {
+ return new CompositePrincipalConfiguration(createDelegatingSecurityProvider()) {
+
+ @Override
+ protected List getConfigurations() {
+ return newArrayList(principalConfigurations);
+ }
+
+ };
+ }
+
+ /**
+ * Create a {@code TokenConfiguration} that aggregates every {@code
+ * TokenConfiguration} service referenced by this component.
+ *
+ * @return An instance of {@code TokenConfiguration}.
+ */
+ private TokenConfiguration createDelegatingTokenConfiguration() {
+ return new CompositeTokenConfiguration(createDelegatingSecurityProvider()) {
+
+ @Override
+ protected List getConfigurations() {
+ return newArrayList(tokenConfigurations);
+ }
+
+ };
+ }
+
+ /**
+ * Create an instance of {@code SecurityProvider} that delegates its call to
+ * the {@code SecurityProvider} registered by this component. If no {@code
+ * SecurityProvider} is registered yet, the returned instance will just
+ * throw {@code IllegalStateException}.
+ *
+ * This delegate is necessary because some configurations require a {@code
+ * SecurityProvider} at construction time. The delegate can be considered a
+ * "promise" that a {@code SecurityProvider} will be registered when the
+ * right conditions are met.
+ *
+ * @return An instance of {@code SecurityProvider}
+ */
+ private SecurityProvider createDelegatingSecurityProvider() {
+ return new SecurityProvider() {
+
+ @Nonnull
+ @Override
+ public ConfigurationParameters getParameters(@Nullable String name) {
+ return securityProvider.getParameters(name);
+ }
+
+ @Nonnull
+ @Override
+ public Iterable extends SecurityConfiguration> getConfigurations() {
+ return securityProvider.getConfigurations();
+ }
+
+ @Nonnull
+ @Override
+ public T getConfiguration(@Nonnull Class configClass) {
+ return securityProvider.getConfiguration(configClass);
+ }
+
+ };
+ }
+
+ /**
+ * Create a {@code RestrictionProvider} that aggregates every {@code
+ * RestrictionProvider} service referenced by this component.
+ *
+ * @return An instance of {@code RestrictionProvider}.
+ */
+ private RestrictionProvider createDelegatingRestrictionProvider() {
+ return new WhiteboardRestrictionProvider() {
+
+ @Override
+ protected List getServices() {
+ return newArrayList(restrictionProviders);
+ }
+
+ };
+ }
+
+ /**
+ * Create a {@code AuthorizableActionProvider} that aggregates every {@code
+ * AuthorizableActionProvider} service referenced by this component.
+ *
+ * @return An instance of {@code RestrictionProvider}.
+ */
+ private AuthorizableActionProvider createDelegatingAuthorizableActionProvider() {
+ return new WhiteboardAuthorizableActionProvider() {
+
+ @Override
+ protected List getServices() {
+ return newArrayList(authorizableActionProviders);
+ }
+
+ };
+ }
+
+ /**
+ * Create a {@code AuthorizableNodeName} that aggregates every {@code
+ * AuthorizableNodeName} service referenced by this component.
+ *
+ * @return An instance of {@code AuthorizableNodeName}.
+ */
+ private AuthorizableNodeName createDelegatingAuthorizableNodeName() {
+ return new WhiteboardAuthorizableNodeName() {
+
+ @Override
+ protected List getServices() {
+ return newArrayList(authorizableNodeNames);
+ }
+
+ };
+ }
+
+ /**
+ * Create a {@code UserAuthenticationFactory} that aggregates every {@code
+ * UserAuthenticationFactory} service referenced by this component.
+ *
+ * @return An instance of {@code UserAuthenticationFactory}.
+ */
+ private UserAuthenticationFactory createDelegatingUserAuthenticationFactory() {
+ return new WhiteboardUserAuthenticationFactory(UserConfigurationImpl.getDefaultAuthenticationFactory()) {
+
+ @Override
+ protected List getServices() {
+ return newArrayList(userAuthenticationFactories);
+ }
+
+ };
+ }
+
+ private AuthenticationConfiguration createDelegatinAuthenticationConfiguration() {
+ return new DelegatingAuthenticationConfiguration() {
+
+ @Override
+ protected AuthenticationConfiguration getAuthenticationConfiguration() {
+ return authenticationConfiguration;
+ }
+
+ };
+ }
+
+ private AuthorizationConfiguration createDelegatingAuthorizationConfiguration() {
+ return new DelegatinAuthorizationConfiguration() {
+
+ @Override
+ protected AuthorizationConfiguration getAuthorizationConfiguration() {
+ return authorizationConfiguration;
+ }
+
+ };
+ }
+
+ private PrivilegeConfiguration createDelegatingPrivilegeConfiguration() {
+ return new DelegatingPrivilegeConfiguration() {
+
+ @Override
+ protected PrivilegeConfiguration getPrivilegeConfiguration() {
+ return privilegeConfiguration;
+ }
+
+ };
+ }
+
+ private UserConfiguration createDelegatingUserConfiguration() {
+ return new DelegatingUserConfiguration() {
+
+ @Override
+ protected UserConfiguration getUserConfiguration() {
+ return userConfiguration;
+ }
+
+ };
+ }
+
+ private Whiteboard createDelegatingWhiteboard() {
+ return new DelegatingWhiteboard() {
+
+ @Override
+ protected Whiteboard getWhiteboard() {
+ return new OsgiWhiteboard(context);
+ }
+
+ };
+ }
+
+ private void addCandidate(Map properties) {
+ String pid = getServicePid(properties);
+
+ if (pid == null) {
+ return;
+ }
+
+ preconditions.addCandidate(pid);
+ }
+
+ private void removeCandidate(Map properties) {
+ String pid = getServicePid(properties);
+
+ if (pid == null) {
+ return;
+ }
+
+ preconditions.removeCandidate(pid);
+ }
+
+ private String getServicePid(Map properties) {
+ return PropertiesUtil.toString(properties.get(Constants.SERVICE_PID), null);
+ }
+
+ private String[] getRequiredServicePids(Map configuration) {
+ return PropertiesUtil.toStringArray(configuration.get("requiredServicePids"), new String[]{});
+ }
+
+ private static abstract class DelegatingSecurityConfiguration implements SecurityConfiguration {
+
+ protected abstract SecurityConfiguration getSecurityConfiguration();
+
+ @Nonnull
+ @Override
+ public String getName() {
+ return getSecurityConfiguration().getName();
+ }
+
+ @Nonnull
+ @Override
+ public ConfigurationParameters getParameters() {
+ return getSecurityConfiguration().getParameters();
+ }
+
+ @Nonnull
+ @Override
+ public WorkspaceInitializer getWorkspaceInitializer() {
+ return getSecurityConfiguration().getWorkspaceInitializer();
+ }
+
+ @Nonnull
+ @Override
+ public RepositoryInitializer getRepositoryInitializer() {
+ return getSecurityConfiguration().getRepositoryInitializer();
+ }
+
+ @Nonnull
+ @Override
+ public List extends CommitHook> getCommitHooks(@Nonnull String workspaceName) {
+ return getSecurityConfiguration().getCommitHooks(workspaceName);
+ }
+
+ @Nonnull
+ @Override
+ public List extends ValidatorProvider> getValidators(@Nonnull String workspaceName, @Nonnull Set principals, @Nonnull MoveTracker moveTracker) {
+ return getSecurityConfiguration().getValidators(workspaceName, principals, moveTracker);
+ }
+
+ @Nonnull
+ @Override
+ public List getProtectedItemImporters() {
+ return getSecurityConfiguration().getProtectedItemImporters();
+ }
+
+ @Nonnull
+ @Override
+ public Context getContext() {
+ return getSecurityConfiguration().getContext();
+ }
+
+ }
+
+ private static abstract class DelegatinAuthorizationConfiguration extends DelegatingSecurityConfiguration implements AuthorizationConfiguration {
+
+ protected abstract AuthorizationConfiguration getAuthorizationConfiguration();
+
+ @Nonnull
+ @Override
+ public AccessControlManager getAccessControlManager(@Nonnull Root root, @Nonnull NamePathMapper namePathMapper) {
+ return getAuthorizationConfiguration().getAccessControlManager(root, namePathMapper);
+ }
+
+ @Nonnull
+ @Override
+ public RestrictionProvider getRestrictionProvider() {
+ return getAuthorizationConfiguration().getRestrictionProvider();
+ }
+
+ @Nonnull
+ @Override
+ public PermissionProvider getPermissionProvider(@Nonnull Root root, @Nonnull String workspaceName, @Nonnull Set principals) {
+ return getAuthorizationConfiguration().getPermissionProvider(root, workspaceName, principals);
+ }
+
+ @Override
+ protected SecurityConfiguration getSecurityConfiguration() {
+ return getAuthorizationConfiguration();
+ }
+
+ }
+
+ private static abstract class DelegatingAuthenticationConfiguration extends DelegatingSecurityConfiguration implements AuthenticationConfiguration {
+
+ protected abstract AuthenticationConfiguration getAuthenticationConfiguration();
+
+ @Nonnull
+ @Override
+ public LoginContextProvider getLoginContextProvider(ContentRepository contentRepository) {
+ return getAuthenticationConfiguration().getLoginContextProvider(contentRepository);
+ }
+
+ @Override
+ protected SecurityConfiguration getSecurityConfiguration() {
+ return getAuthenticationConfiguration();
+ }
+
+ }
+
+ private static abstract class DelegatingUserConfiguration extends DelegatingSecurityConfiguration implements UserConfiguration {
+
+ protected abstract UserConfiguration getUserConfiguration();
+
+ @Override
+ protected SecurityConfiguration getSecurityConfiguration() {
+ return getUserConfiguration();
+ }
+
+ @Nonnull
+ @Override
+ public UserManager getUserManager(Root root, NamePathMapper namePathMapper) {
+ return getUserConfiguration().getUserManager(root, namePathMapper);
+ }
+
+ @Nullable
+ @Override
+ public PrincipalProvider getUserPrincipalProvider(@Nonnull Root root, @Nonnull NamePathMapper namePathMapper) {
+ return getUserConfiguration().getUserPrincipalProvider(root, namePathMapper);
+ }
+
+ }
+
+ private static abstract class DelegatingPrivilegeConfiguration extends DelegatingSecurityConfiguration implements PrivilegeConfiguration {
+
+ protected abstract PrivilegeConfiguration getPrivilegeConfiguration();
+
+ @Override
+ protected SecurityConfiguration getSecurityConfiguration() {
+ return getPrivilegeConfiguration();
+ }
+
+ @Nonnull
+ @Override
+ public PrivilegeManager getPrivilegeManager(Root root, NamePathMapper namePathMapper) {
+ return getPrivilegeConfiguration().getPrivilegeManager(root, namePathMapper);
+ }
+
+ }
+
+ private static abstract class DelegatingWhiteboard implements Whiteboard {
+
+ protected abstract Whiteboard getWhiteboard();
+
+ @Override
+ public Registration register(Class type, T service, Map, ?> properties) {
+ return getWhiteboard().register(type, service, properties);
+ }
+
+ @Override
+ public Tracker track(Class type) {
+ return getWhiteboard().track(type);
+ }
+
+ }
+
+}
Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/security/SecurityProviderRegistration.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java (working copy)
@@ -44,7 +44,7 @@
/**
* Default implementation for the {@code TokenConfiguration} interface.
*/
-@Component(metatype = true, label = "Apache Jackrabbit Oak TokenConfiguration")
+@Component(metatype = true, label = "Apache Jackrabbit Oak TokenConfiguration", immediate = true)
@Service({TokenConfiguration.class, SecurityConfiguration.class})
@Properties({
@Property(name = TokenProvider.PARAM_TOKEN_EXPIRATION,
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/restriction/RestrictionProviderImpl.java (working copy)
@@ -56,7 +56,7 @@
* is {@link org.apache.jackrabbit.oak.api.Type#STRINGS}.
*
*/
-@Component
+@Component(immediate = true)
@Service(RestrictionProvider.class)
public class RestrictionProviderImpl extends AbstractRestrictionProvider {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/package-info.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/package-info.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/package-info.java (working copy)
@@ -20,7 +20,7 @@
*
* See README.md for more details.
*/
-@Version("1.0.1")
+@Version("2.0.0")
@Export(optional = "provide:=true")
package org.apache.jackrabbit.oak.security;
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/principal/PrincipalConfigurationImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/principal/PrincipalConfigurationImpl.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/principal/PrincipalConfigurationImpl.java (working copy)
@@ -38,7 +38,7 @@
/**
* Default implementation of the {@code PrincipalConfiguration}
*/
-@Component()
+@Component(immediate = true)
@Service({PrincipalConfiguration.class, SecurityConfiguration.class})
public class PrincipalConfigurationImpl extends ConfigurationBase implements PrincipalConfiguration {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RandomAuthorizableNodeName.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RandomAuthorizableNodeName.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/RandomAuthorizableNodeName.java (working copy)
@@ -33,7 +33,7 @@
* Implementation of the {@code AuthorizableNodeName} that generates a random
* node name that doesn't reveal the ID of the authorizable.
*/
-@Component(metatype = true, label = "Apache Jackrabbit Oak Random Authorizable Node Name", description = "Generates a random name for the authorizable node.", policy = ConfigurationPolicy.REQUIRE)
+@Component(metatype = true, label = "Apache Jackrabbit Oak Random Authorizable Node Name", description = "Generates a random name for the authorizable node.", immediate = true)
@Service(AuthorizableNodeName.class)
public class RandomAuthorizableNodeName implements AuthorizableNodeName {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationFactoryImpl.java (working copy)
@@ -26,7 +26,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-@Component
+@Component(immediate = true)
@Service
public class UserAuthenticationFactoryImpl implements UserAuthenticationFactory {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/CompositeTokenConfiguration.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/CompositeTokenConfiguration.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/CompositeTokenConfiguration.java (working copy)
@@ -28,7 +28,7 @@
/**
* {@link TokenConfiguration} that combines different token provider implementations.
*/
-public final class CompositeTokenConfiguration extends CompositeConfiguration implements TokenConfiguration {
+public class CompositeTokenConfiguration extends CompositeConfiguration implements TokenConfiguration {
public CompositeTokenConfiguration(@Nonnull SecurityProvider securityProvider) {
super(TokenConfiguration.NAME, securityProvider);
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/package-info.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/package-info.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/package-info.java (working copy)
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.1.0")
+@Version("1.2.0")
@Export(optional = "provide:=true")
package org.apache.jackrabbit.oak.spi.security.authentication.token;
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/CompositePrincipalConfiguration.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/CompositePrincipalConfiguration.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/CompositePrincipalConfiguration.java (working copy)
@@ -30,7 +30,7 @@
* {@link PrincipalConfiguration} that combines different principal provider
* implementations that share a common principal manager implementation.
*/
-public final class CompositePrincipalConfiguration extends CompositeConfiguration implements PrincipalConfiguration {
+public class CompositePrincipalConfiguration extends CompositeConfiguration implements PrincipalConfiguration {
public CompositePrincipalConfiguration(@Nonnull SecurityProvider securityProvider) {
super(PrincipalConfiguration.NAME, securityProvider);
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/package-info.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/package-info.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/principal/package-info.java (working copy)
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.0")
+@Version("1.1.0")
@Export(optional = "provide:=true")
package org.apache.jackrabbit.oak.spi.security.principal;
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/DefaultAuthorizableActionProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/DefaultAuthorizableActionProvider.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/DefaultAuthorizableActionProvider.java (working copy)
@@ -38,7 +38,7 @@
* Default implementation of the {@link AuthorizableActionProvider} interface
* that allows to config all actions provided by the OAK.
*/
-@Component(metatype = true, label = "Apache Jackrabbit Oak AuthorizableActionProvider")
+@Component(metatype = true, label = "Apache Jackrabbit Oak AuthorizableActionProvider", immediate = true)
@Service(AuthorizableActionProvider.class)
@Properties({
@Property(name = DefaultAuthorizableActionProvider.ENABLED_ACTIONS,
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java (revision 1703179)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/action/package-info.java (working copy)
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.0.2")
+@Version("1.0.3")
@Export(optional = "provide:=true")
package org.apache.jackrabbit.oak.spi.security.user.action;
Index: oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy
===================================================================
--- oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy (revision 0)
+++ oak-pojosr/src/test/groovy/org/apache/jackrabbit/oak/run/osgi/SecurityProviderRegistrationTest.groovy (working copy)
@@ -0,0 +1,424 @@
+/*
+ * 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.run.osgi
+
+import org.apache.felix.connect.launch.PojoServiceRegistry
+import org.apache.jackrabbit.api.security.principal.PrincipalManager
+import org.apache.jackrabbit.oak.api.Root
+import org.apache.jackrabbit.oak.api.Tree
+import org.apache.jackrabbit.oak.namepath.NamePathMapper
+import org.apache.jackrabbit.oak.spi.commit.CommitHook
+import org.apache.jackrabbit.oak.spi.commit.MoveTracker
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer
+import org.apache.jackrabbit.oak.spi.lifecycle.WorkspaceInitializer
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters
+import org.apache.jackrabbit.oak.spi.security.Context
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication
+import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration
+import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenProvider
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider
+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.action.AuthorizableAction
+import org.apache.jackrabbit.oak.spi.security.user.action.AuthorizableActionProvider
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter
+import org.junit.Before
+import org.junit.Test
+import org.osgi.framework.ServiceReference
+import org.osgi.service.cm.ConfigurationAdmin
+
+import javax.annotation.Nonnull
+import javax.annotation.Nullable
+import javax.jcr.RepositoryException
+import javax.jcr.Value
+import javax.jcr.security.AccessControlException
+import java.security.Principal
+import java.util.concurrent.TimeUnit
+
+class SecurityProviderRegistrationTest extends AbstractRepositoryFactoryTest {
+
+ private PojoServiceRegistry registry;
+
+ @Before
+ public void initializeRegistry() {
+ registry = repositoryFactory.initializeServiceRegistry(config)
+ }
+
+ /**
+ * Test that, without any additional configuration, a SecurityProvider
+ * service is registered by default.
+ */
+ @Test
+ public void testDefaultSetup() {
+ assert securityProviderServiceReferences != null
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required
+ * PrincipalConfiguration service.
+ */
+ @Test
+ public void testRequiredPrincipalConfigurationNotAvailable() {
+ testRequiredService(PrincipalConfiguration, newPrincipalConfiguration())
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required TokenConfiguration
+ * service.
+ */
+ @Test
+ public void testRequiredTokenConfigurationNotAvailable() {
+ testRequiredService(TokenConfiguration, newTokenConfiguration())
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required
+ * AuthorizableNodeName service.
+ */
+ @Test
+ public void testRequiredAuthorizableNodeNameNotAvailable() {
+ testRequiredService(AuthorizableNodeName, newAuthorizableNodeName())
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required
+ * AuthorizableActionProvider service.
+ */
+ @Test
+ public void testRequiredAuthorizableActionProviderNotAvailable() {
+ testRequiredService(AuthorizableActionProvider, newAuthorizableActionProvider())
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required RestrictionProvider
+ * service.
+ */
+ @Test
+ public void testRequiredRestrictionProviderNotAvailable() {
+ testRequiredService(RestrictionProvider, newRestrictionProvider())
+ }
+
+ /**
+ * A SecurityProvider shouldn't start without a required
+ * UserAuthenticationFactory service.
+ */
+ @Test
+ public void testRequiredUserAuthenticationFactoryNotAvailable() {
+ testRequiredService(UserAuthenticationFactory, newUserAuthenticationFactory())
+ }
+
+ /**
+ * A SecurityProvider should be registered only if every every prerequisite
+ * is satisfied.
+ */
+ @Test
+ public void testMultipleRequiredServices() {
+
+ // Set up the SecurityProvider to require three services
+
+ setRequiredServicePids("test.RequiredPrincipalConfiguration", "test.RequiredTokenConfiguration", "test.AuthorizableNodeName")
+ TimeUnit.MILLISECONDS.sleep(500)
+ assert securityProviderServiceReferences == null
+
+ // Start the services and verify that only at the end the
+ // SecurityProvider registers itself
+
+ registry.registerService(PrincipalConfiguration.class.name, newPrincipalConfiguration(), dict("service.pid": "test.RequiredPrincipalConfiguration"))
+ assert securityProviderServiceReferences == null
+
+ registry.registerService(TokenConfiguration.class.name, newTokenConfiguration(), dict("service.pid": "test.RequiredTokenConfiguration"))
+ assert securityProviderServiceReferences == null
+
+ registry.registerService(TokenConfiguration.class.name, newTokenConfiguration(), dict("service.pid": "test.AuthorizableNodeName"))
+ assert securityProviderServiceReferences != null
+ }
+
+ private void testRequiredService(Class serviceClass, T service) {
+
+ // Adding a new precondition on a missing service PID forces the
+ // SecurityProvider to unregister.
+
+ setRequiredServicePids("test.Required" + serviceClass.simpleName)
+ TimeUnit.MILLISECONDS.sleep(500)
+ assert securityProviderServiceReferences == null
+
+ // If a service is registered, and if the PID of the service matches the
+ // precondition, the SecurityProvider is registered again.
+
+ def registration = registry.registerService(serviceClass.name, service, dict("service.pid": "test.Required" + serviceClass.simpleName))
+ assert securityProviderServiceReferences != null
+
+ // If the service is unregistered, but the precondition is still in
+ // place, the SecurityProvider unregisters again.
+
+ registration.unregister()
+ assert securityProviderServiceReferences == null
+
+ // Removing the precondition allows the SecurityProvider to register.
+
+ setRequiredServicePids()
+ TimeUnit.MILLISECONDS.sleep(500)
+ assert securityProviderServiceReferences != null
+ }
+
+ private ServiceReference>[] getSecurityProviderServiceReferences() {
+ return registry.getServiceReferences(SecurityProvider.class.name, null)
+ }
+
+ private void setRequiredServicePids(String... pids) {
+ setConfiguration([
+ "org.apache.jackrabbit.oak.security.SecurityProviderRegistration": [
+ "requiredServicePids": pids
+ ]
+ ])
+ }
+
+ private void setConfiguration(Map> configuration) {
+ getConfigurationInstaller().installConfigs(configuration)
+ }
+
+ private ConfigInstaller getConfigurationInstaller() {
+ return new ConfigInstaller(getConfigurationAdmin(), registry.bundleContext)
+ }
+
+ private ConfigurationAdmin getConfigurationAdmin() {
+ return registry.getService(registry.getServiceReference(ConfigurationAdmin.class.name)) as ConfigurationAdmin
+ }
+
+ private static Dictionary dict(Map map) {
+ return new Hashtable(map);
+ }
+
+ private static PrincipalConfiguration newPrincipalConfiguration() {
+ return new PrincipalConfiguration() {
+
+ @Override
+ PrincipalManager getPrincipalManager(Root root, NamePathMapper namePathMapper) {
+ return null
+ }
+
+ @Override
+ PrincipalProvider getPrincipalProvider(Root root, NamePathMapper namePathMapper) {
+ return null
+ }
+
+ @Override
+ String getName() {
+ return null
+ }
+
+ @Override
+ ConfigurationParameters getParameters() {
+ return null
+ }
+
+ @Override
+ WorkspaceInitializer getWorkspaceInitializer() {
+ return null
+ }
+
+ @Override
+ RepositoryInitializer getRepositoryInitializer() {
+ return null
+ }
+
+ @Override
+ List extends CommitHook> getCommitHooks(
+ @Nonnull String workspaceName) {
+ return null
+ }
+
+ @Override
+ List extends ValidatorProvider> getValidators(
+ @Nonnull String workspaceName,
+ @Nonnull Set principals,
+ @Nonnull MoveTracker moveTracker) {
+ return null
+ }
+
+ @Override
+ List getProtectedItemImporters() {
+ return null
+ }
+
+ @Override
+ Context getContext() {
+ return null
+ }
+
+ }
+ }
+
+ private static TokenConfiguration newTokenConfiguration() {
+ return new TokenConfiguration() {
+
+ @Override
+ TokenProvider getTokenProvider(Root root) {
+ return null
+ }
+
+ @Override
+ String getName() {
+ return null
+ }
+
+ @Override
+ ConfigurationParameters getParameters() {
+ return null
+ }
+
+ @Override
+ WorkspaceInitializer getWorkspaceInitializer() {
+ return null
+ }
+
+ @Override
+ RepositoryInitializer getRepositoryInitializer() {
+ return null
+ }
+
+ @Override
+ List extends CommitHook> getCommitHooks(
+ @Nonnull String workspaceName) {
+ return null
+ }
+
+ @Override
+ List extends ValidatorProvider> getValidators(
+ @Nonnull String workspaceName,
+ @Nonnull Set principals,
+ @Nonnull MoveTracker moveTracker) {
+ return null
+ }
+
+ @Override
+ List getProtectedItemImporters() {
+ return null
+ }
+
+ @Override
+ Context getContext() {
+ return null
+ }
+
+ }
+ }
+
+ private static AuthorizableNodeName newAuthorizableNodeName() {
+ return new AuthorizableNodeName() {
+
+ @Override
+ String generateNodeName(@Nonnull String authorizableId) {
+ return null
+ }
+
+ }
+ }
+
+ private static AuthorizableActionProvider newAuthorizableActionProvider() {
+ return new AuthorizableActionProvider() {
+
+ @Override
+ List extends AuthorizableAction> getAuthorizableActions(
+ @Nonnull SecurityProvider securityProvider) {
+ return null
+ }
+
+ }
+ }
+
+ private static RestrictionProvider newRestrictionProvider() {
+ new RestrictionProvider() {
+
+ @Override
+ Set getSupportedRestrictions(
+ @Nullable String oakPath) {
+ return null
+ }
+
+ @Override
+ Restriction createRestriction(
+ @Nullable String oakPath,
+ @Nonnull String oakName,
+ @Nonnull Value value) throws AccessControlException, RepositoryException {
+ return null
+ }
+
+ @Override
+ Restriction createRestriction(
+ @Nullable String oakPath,
+ @Nonnull String oakName,
+ @Nonnull Value... values) throws AccessControlException, RepositoryException {
+ return null
+ }
+
+ @Override
+ Set readRestrictions(
+ @Nullable String oakPath, @Nonnull Tree aceTree) {
+ return null
+ }
+
+ @Override
+ void writeRestrictions(String oakPath, Tree aceTree, Set restrictions) throws RepositoryException {
+
+ }
+
+ @Override
+ void validateRestrictions(
+ @Nullable String oakPath,
+ @Nonnull Tree aceTree) throws AccessControlException, RepositoryException {
+
+ }
+
+ @Override
+ RestrictionPattern getPattern(
+ @Nullable String oakPath, @Nonnull Tree tree) {
+ return null
+ }
+
+ @Override
+ RestrictionPattern getPattern(
+ @Nullable String oakPath,
+ @Nonnull Set restrictions) {
+ return null
+ }
+
+ }
+ }
+
+ private static UserAuthenticationFactory newUserAuthenticationFactory() {
+ return new UserAuthenticationFactory() {
+
+ @Override
+ Authentication getAuthentication(
+ @Nonnull UserConfiguration configuration,
+ @Nonnull Root root, @Nullable String userId) {
+ return null
+ }
+
+ }
+ }
+
+}