You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2016/05/17 16:18:30 UTC
svn commit: r1744292 [1/3] - in /jackrabbit/oak/trunk:
oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/
oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/...
Author: angela
Date: Tue May 17 16:18:29 2016
New Revision: 1744292
URL: http://svn.apache.org/viewvc?rev=1744292&view=rev
Log:
OAK-4101 : Consider separate external (group) principal management
Added:
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleDynamicMembershipTest.java
- copied, changed from r1742077, jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestSecurityProvider.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContextTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/AbstractPrincipalTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/EmptyPrincipalProviderTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProviderTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporterTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializerTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorTest.java (with props)
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfigurationTest.java (with props)
Modified:
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java
jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/ExternalLoginModuleTest.java
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java
jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/AbstractSecurityTest.java
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/defaultusersync.md
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/principal/principalprovider.md
jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/authentication/external/AbstractExternalTest.java
Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java Tue May 17 16:18:29 2016
@@ -204,6 +204,8 @@ public class DefaultSyncConfig {
private long membershipNestingDepth;
+ private boolean dynamicMembership;
+
/**
* Returns the duration in milliseconds until the group membership of a user is expired. If the
* membership information is expired it is re-synced according to the maximum nesting depth.
@@ -251,6 +253,40 @@ public class DefaultSyncConfig {
return this;
}
+ /**
+ * Returns {@code true} if a dynamic group membership is enabled.
+ *
+ * Turning this option on may alter the behavior of other configuration
+ * options dealing with synchronization of group accounts and group membership.
+ * In particular it's an implementation detail if external groups may
+ * no longer be synchronized into the repository.
+ *
+ * @return {@code true} if dynamic group membership for external
+ * user identities is turn on; {@code false} otherwise.
+ */
+ @Nonnull
+ public boolean getDynamicMembership() {
+ return dynamicMembership;
+ }
+
+ /**
+ * Enable or disable the dynamic group membership. If turned external
+ * identities and their group membership will be synchronized such that the
+ * membership information is generated dynamically. External groups may
+ * or may not be synchronized into the repository if this option is turned
+ * on.
+ *
+ * @param dynamicMembership Boolean flag to enable or disable a dedicated
+ * dynamic group management.
+ * @return {@code this}
+ * @see #getDynamicMembership() for details.
+ */
+ @Nonnull
+ public User setDynamicMembership(boolean dynamicMembership) {
+ this.dynamicMembership = dynamicMembership;
+ return this;
+ }
+
}
/**
Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java Tue May 17 16:18:29 2016
@@ -380,7 +380,7 @@ public class DefaultSyncContext implemen
principal,
PathUtils.concatRelativePaths(config.user().getPathPrefix(), externalUser.getIntermediatePath())
);
- user.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalUser.getExternalId().getString()));
+ setExternalId(user, externalUser);
return user;
}
@@ -400,10 +400,24 @@ public class DefaultSyncContext implemen
principal,
PathUtils.concatRelativePaths(config.group().getPathPrefix(), externalGroup.getIntermediatePath())
);
- group.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalGroup.getExternalId().getString()));
+ setExternalId(group, externalGroup);
return group;
}
+ /**
+ * Sets the {@link #REP_EXTERNAL_ID} as obtained from {@code externalIdentity}
+ * to the specified {@code authorizable} (user or group). The property is
+ * a single value of type {@link javax.jcr.PropertyType#STRING STRING}.
+ *
+ * @param authorizable The user or group that needs to get the {@link #REP_EXTERNAL_ID} property set.
+ * @param externalIdentity The {@link ExternalIdentity} from which to retrieve the value of the property.
+ * @throws RepositoryException If setting the property using {@link Authorizable#setProperty(String, Value)} fails.
+ */
+ private void setExternalId(@Nonnull Authorizable authorizable, @Nonnull ExternalIdentity externalIdentity) throws RepositoryException {
+ log.debug("Fallback: setting rep:externalId without adding the corresponding mixin type");
+ authorizable.setProperty(REP_EXTERNAL_ID, valueFactory.createValue(externalIdentity.getExternalId().getString()));
+ }
+
@Nonnull
protected DefaultSyncResultImpl syncUser(@Nonnull ExternalUser external, @Nonnull User user) throws RepositoryException {
SyncResult.Status status;
@@ -728,7 +742,7 @@ public class DefaultSyncContext implemen
* @return {@code true} if {@link ExternalIdentityRef#getProviderName()} refers
* to the IDP associated with this context instance.
*/
- private boolean isSameIDP(@Nonnull ExternalIdentityRef ref) {
+ protected boolean isSameIDP(@Nonnull ExternalIdentityRef ref) {
return idp.getName().equals(ref.getProviderName());
}
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/package-info.java Tue May 17 16:18:29 2016
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.0.1")
+@Version("1.1.0")
@Export
package org.apache.jackrabbit.oak.spi.security.authentication.external.basic;
Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java Tue May 17 16:18:29 2016
@@ -152,6 +152,32 @@ public class DefaultSyncConfigImpl exten
public static final String PARAM_USER_MEMBERSHIP_NESTING_DEPTH = "user.membershipNestingDepth";
/**
+ * @see DefaultSyncConfig.User#getDynamicMembership()
+ */
+ public static final boolean PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT = false;
+
+ /**
+ * Configuration option to enable dynamic group membership. If enabled the
+ * implementation will no longer synchronized group accounts into the repository
+ * but instead will enable a dedicated principal management: This results in
+ * external users having their complete principal set as defined external IDP
+ * synchronized to the repository asserting proper population of the
+ * {@link javax.security.auth.Subject} upon login. Please note that the external
+ * groups are reflected through the built-in principal management and thus can
+ * be retrieved for authorization purposes. However, the information is no
+ * longer reflected through the Jackrabbit user management API.
+ *
+ * @see DefaultSyncConfig.User#getDynamicMembership()
+ */
+ @Property(
+ label = "User Dynamic Membership",
+ description = "If enabled membership of external identities (user) is no longer fully reflected " +
+ "within the repositories user management.",
+ boolValue = PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT
+ )
+ public static final String PARAM_USER_DYNAMIC_MEMBERSHIP = "user.dynamicMembership";
+
+ /**
* @see DefaultSyncConfig.Group#getExpirationTime()
*/
public static final String PARAM_GROUP_EXPIRATION_TIME_DEFAULT = "1d";
@@ -229,6 +255,7 @@ public class DefaultSyncConfigImpl exten
cfg.user()
.setMembershipExpirationTime(getMilliSeconds(params, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT, ONE_HOUR))
.setMembershipNestingDepth(params.getConfigValue(PARAM_USER_MEMBERSHIP_NESTING_DEPTH, PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT))
+ .setDynamicMembership(params.getConfigValue(PARAM_USER_DYNAMIC_MEMBERSHIP, PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT))
.setExpirationTime(getMilliSeconds(params, PARAM_USER_EXPIRATION_TIME, PARAM_USER_EXPIRATION_TIME_DEFAULT, ONE_HOUR))
.setPathPrefix(params.getConfigValue(PARAM_USER_PATH_PREFIX, PARAM_USER_PATH_PREFIX_DEFAULT))
.setAutoMembership(params.getConfigValue(PARAM_USER_AUTO_MEMBERSHIP, PARAM_USER_AUTO_MEMBERSHIP_DEFAULT))
Modified: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncHandler.java Tue May 17 16:18:29 2016
@@ -103,7 +103,11 @@ public class DefaultSyncHandler implemen
@Override
public SyncContext createContext(@Nonnull ExternalIdentityProvider idp, @Nonnull UserManager userManager,
@Nonnull ValueFactory valueFactory) throws SyncException {
- return new DefaultSyncContext(config, idp, userManager, valueFactory);
+ if (config.user().getDynamicMembership()) {
+ return new DynamicSyncContext(config, idp, userManager, valueFactory);
+ } else {
+ return new DefaultSyncContext(config, idp, userManager, valueFactory);
+ }
}
/**
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java Tue May 17 16:18:29 2016
@@ -0,0 +1,162 @@
+/*
+ * 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.spi.security.authentication.external.impl;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalGroup;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityRef;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalUser;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncException;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.SyncResult;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncResultImpl;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncedIdentity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extension of the {@code DefaultSyncContext} that doesn't synchronize group
+ * membership of new external users into the user management of the repository.
+ * Instead it will only synchronize the principal names up to the configured depths.
+ * In combination with the a dedicated {@code PrincipalConfiguration} this allows
+ * to benefit from the repository's authorization model (which is solely
+ * based on principals) i.e. full compatibility with the default approach without
+ * the complication of synchronizing user management information into the repository,
+ * when user management is effectively take care of by the third party system.
+ *
+ * With the {@link org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncHandler}
+ * this feature can be turned on using
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig.User#setDynamicMembership(boolean)}
+ *
+ * Note: users and groups that have been synchronized before the dynamic membership
+ * feature has been enabled will continue to be synchronized in the default way
+ * and this context doesn't take effect.
+ *
+ * @since Oak 1.5.3
+ */
+public class DynamicSyncContext extends DefaultSyncContext {
+
+ private static final Logger log = LoggerFactory.getLogger(DynamicSyncContext.class);
+
+ public DynamicSyncContext(@Nonnull DefaultSyncConfig config,
+ @Nonnull ExternalIdentityProvider idp,
+ @Nonnull UserManager userManager,
+ @Nonnull ValueFactory valueFactory) {
+ super(config, idp, userManager, valueFactory);
+ }
+
+ //--------------------------------------------------------< SyncContext >---
+ @Nonnull
+ @Override
+ public SyncResult sync(@Nonnull ExternalIdentity identity) throws SyncException {
+ if (identity instanceof ExternalUser) {
+ return super.sync(identity);
+ } else if (identity instanceof ExternalGroup) {
+ try {
+ Group group = getAuthorizable(identity, Group.class);
+ if (group != null) {
+ // group has been synchronized before -> continue updating for consistency.
+ return syncGroup((ExternalGroup) identity, group);
+ } else {
+ // external group has never been synchronized before:
+ // don't sync external groups into the repository internal user management
+ // but limit synchronized information to group-principals stored
+ // separately with each external user such that the subject gets
+ // properly populated upon login
+ ExternalIdentityRef ref = identity.getExternalId();
+
+ log.debug("ExternalGroup {}: Not synchronized as authorizable Group into the repository.", ref.getString());
+
+ SyncResult.Status status = (isSameIDP(ref)) ? SyncResult.Status.NOP : SyncResult.Status.FOREIGN;
+ return new DefaultSyncResultImpl(new DefaultSyncedIdentity(identity.getId(), ref, true, -1), status);
+ }
+ } catch (RepositoryException e) {
+ throw new SyncException(e);
+ }
+ } else {
+ throw new IllegalArgumentException("identity must be user or group but was: " + identity);
+ }
+ }
+
+ //-------------------------------------------------< DefaultSyncContext >---
+ @Override
+ protected void syncMembership(@Nonnull ExternalIdentity external, @Nonnull Authorizable auth, long depth) throws RepositoryException {
+ if (auth.isGroup()) {
+ return;
+ }
+
+ if (auth.hasProperty(REP_LAST_SYNCED) && !auth.hasProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES)) {
+ // user has been synchronized before dynamic membership has been turned on
+ super.syncMembership(external, auth, depth);
+ } else {
+ // retrieve membership of the given external user (up to the configured
+ // depth) and add (or replace) the rep:externalPrincipalNames property
+ // with the accurate collection of principal names.
+ try {
+ Value[] vs;
+ if (depth <= 0) {
+ vs = new Value[0];
+ } else {
+ Set<String> principalsNames = new HashSet<String>();
+ collectPrincipalNames(principalsNames, external.getDeclaredGroups(), depth);
+ vs = createValues(principalsNames);
+ }
+ auth.setProperty(ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES, vs);
+ } catch (ExternalIdentityException e) {
+ log.error("Failed to synchronize membership information for external identity " + external.getId(), e);
+ }
+ }
+ }
+
+ /**
+ * Recursively collect the principal names of the given declared group
+ * references up to the given depth.
+ *
+ * @param principalNames The set used to collect the names of the group principals.
+ * @param declaredGroupIdRefs The declared group references for a user or a group.
+ * @param depth Configured membership nesting; the recursion will be stopped once depths is < 1.
+ * @throws ExternalIdentityException If an error occurs while resolving the the external group references.
+ */
+ private void collectPrincipalNames(@Nonnull Set<String> principalNames, @Nonnull Iterable<ExternalIdentityRef> declaredGroupIdRefs, long depth) throws ExternalIdentityException {
+ for (ExternalIdentityRef ref : declaredGroupIdRefs) {
+ // get group
+ ExternalIdentity extId = idp.getIdentity(ref);
+ if (extId instanceof ExternalGroup) {
+ principalNames.add(extId.getPrincipalName());
+ // recursively apply further membership until the configured depth is reached
+ if (depth > 1) {
+ collectPrincipalNames(principalNames, extId.getDeclaredGroups(), depth - 1);
+ }
+ } else {
+ log.debug("Not an external group ({}) => ignore.", extId);
+ }
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncContext.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java Tue May 17 16:18:29 2016
@@ -0,0 +1,70 @@
+/*
+ * 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.spi.security.authentication.external.impl;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncContext;
+
+/**
+ * Constants used by the external identity management.
+ *
+ * @since Oak 1.5.3
+ */
+public interface ExternalIdentityConstants {
+
+ /**
+ * Name of the property storing the external identifier.
+ * This property is of type {@link org.apache.jackrabbit.oak.api.Type#STRING}
+ * and mandatory for external identities that have been synchronized into
+ * the repository.
+ *
+ * @see DefaultSyncContext#REP_EXTERNAL_ID
+ */
+ String REP_EXTERNAL_ID = DefaultSyncContext.REP_EXTERNAL_ID;
+
+ /**
+ * Name of the property storing the date of the last synchronization of an
+ * external identity.
+ * This property is of type {@link org.apache.jackrabbit.oak.api.Type#DATE}
+ *
+ * @see DefaultSyncContext#REP_LAST_SYNCED
+ */
+ String REP_LAST_SYNCED = DefaultSyncContext.REP_LAST_SYNCED;
+
+ /**
+ * Name of the property storing the principal names of the external groups
+ * a given external identity (user) is member. Not that the set depends on
+ * the configured nesting {@link DefaultSyncConfig.User#getMembershipNestingDepth() depth}.
+ * The existence of this property is optional and will only be created if
+ * {@link DefaultSyncConfig.User#getDynamicMembership()} is turned on.
+ *
+ * This property is of type {@link org.apache.jackrabbit.oak.api.Type#STRINGS}.
+ * Please note, that for security reasons is system maintained and protected
+ * on the Oak level and cannot be manipulated by regular {@code ContentSession}
+ * objects irrespective of the effective permissions.
+ */
+ String REP_EXTERNAL_PRINCIPAL_NAMES = "rep:externalPrincipalNames";
+
+ /**
+ * The set of served property names defined by this interface.
+ */
+ Set<String> RESERVED_PROPERTY_NAMES = ImmutableSet.of(REP_EXTERNAL_ID, REP_EXTERNAL_PRINCIPAL_NAMES);
+
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java Tue May 17 16:18:29 2016
@@ -0,0 +1,412 @@
+/*
+ * 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.spi.security.authentication.external.impl.principal;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.QueryEngine;
+import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the {@code PrincipalProvider} interface that exposes
+ * 'external' principals of type {@link java.security.acl.Group}. 'External'
+ * refers to the fact that these principals are defined and managed by an
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}.
+ *
+ * For performance reasons this implementation doesn't lookup principals on the IDP
+ * but relies on a persisted cache inside the repository where the names of these
+ * external principals are synchronized to based on a configurable expiration time.
+ *
+ * Currently, the implementation respects the {@code rep:externalPrincipalNames}
+ * properties, where group membership of external users gets synchronized if
+ * {@link DefaultSyncConfig.User#getDynamicMembership() dynamic membership} has
+ * been enabled.
+ *
+ * Please note that in contrast to the default principal provider implementation
+ * shipped with Oak the group principals known and exposed by this provider are
+ * not backed by an authorizable group and thus cannot be retrieved using
+ * Jackrabbit user management API.
+ *
+ * @since Oak 1.5.3
+ * @see org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DynamicSyncContext
+ */
+class ExternalGroupPrincipalProvider implements PrincipalProvider, ExternalIdentityConstants {
+
+ private static final Logger log = LoggerFactory.getLogger(ExternalGroupPrincipalProvider.class);
+
+ private static final String BINDING_PRINCIPAL_NAMES = "principalNames";
+
+ private final Root root;
+ private final NamePathMapper namePathMapper;
+ private final UserManager userManager;
+
+ ExternalGroupPrincipalProvider(Root root, UserConfiguration uc, NamePathMapper namePathMapper) {
+ this.root = root;
+ this.namePathMapper = namePathMapper;
+ userManager = uc.getUserManager(root, namePathMapper);
+ }
+
+ //--------------------------------------------------< PrincipalProvider >---
+ @Override
+ public Principal getPrincipal(@Nonnull String principalName) {
+ Result result = findPrincipals(principalName, true);
+ if (result != null && result.getRows().iterator().hasNext()) {
+ return new ExternalGroupPrincipal(principalName);
+ } else {
+ return null;
+ }
+ }
+
+ @Nonnull
+ @Override
+ public Set<Group> getGroupMembership(@Nonnull Principal principal) {
+ if (!(principal instanceof Group)) {
+ try {
+ if (principal instanceof ItemBasedPrincipal) {
+ Tree t = root.getTree(((ItemBasedPrincipal) principal).getPath());
+ return getGroupPrincipals(t);
+ } else {
+ return getGroupPrincipals(userManager.getAuthorizable(principal));
+ }
+ } catch (RepositoryException e) {
+ log.debug(e.getMessage());
+ }
+
+ }
+ return ImmutableSet.of();
+ }
+
+ @Nonnull
+ @Override
+ public Set<? extends Principal> getPrincipals(@Nonnull String userID) {
+ try {
+ return getGroupPrincipals(userManager.getAuthorizable(userID));
+ } catch (RepositoryException e) {
+ log.debug(e.getMessage());
+ return ImmutableSet.of();
+ }
+ }
+
+ @Nonnull
+ @Override
+ public Iterator<? extends Principal> findPrincipals(@Nullable String nameHint, int searchType) {
+ if (PrincipalManager.SEARCH_TYPE_NOT_GROUP != searchType) {
+ Result result = findPrincipals(Strings.nullToEmpty(nameHint), false);
+ if (result != null) {
+ return Iterators.filter(new GroupPrincipalIterator(nameHint, result), Predicates.notNull());
+ }
+ }
+
+ return Iterators.emptyIterator();
+ }
+
+ @Nonnull
+ @Override
+ public Iterator<? extends Principal> findPrincipals(int searchType) {
+ return findPrincipals(null, searchType);
+ }
+
+ //------------------------------------------------------------< private >---
+
+ private Set<Group> getGroupPrincipals(@CheckForNull Authorizable authorizable) throws RepositoryException {
+ if (authorizable != null && !authorizable.isGroup()) {
+ Tree userTree = root.getTree(authorizable.getPath());
+ return getGroupPrincipals(userTree);
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+
+ private Set<Group> getGroupPrincipals(@Nonnull Tree userTree) {
+ if (userTree.exists() && UserUtil.isType(userTree, AuthorizableType.USER) && userTree.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+ PropertyState ps = userTree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+ if (ps != null) {
+ Set<Group> groupPrincipals = Sets.newHashSet();
+ for (String principalName : ps.getValue(Type.STRINGS)) {
+ groupPrincipals.add(new ExternalGroupPrincipal(principalName));
+ }
+ return groupPrincipals;
+ }
+ }
+ // group principals cannot be retrieved
+ return ImmutableSet.of();
+ }
+
+ /**
+ * Runs an Oak {@link org.apache.jackrabbit.oak.query.Query} searching for
+ * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} properties that match the given
+ * name or name hint.
+ *
+ * @param nameHint The principal name or name hint to be searched for.
+ * @param exactMatch boolean flag indicating if the query should search for
+ * exact matching.
+ * @return The query result.
+ */
+ @CheckForNull
+ private Result findPrincipals(@Nonnull String nameHint, boolean exactMatch) {
+ try {
+ Map<String, ? extends PropertyValue> bindings = buildBinding(nameHint, exactMatch);
+ String op = (exactMatch) ? " = " : " LIKE ";
+ String statement = "SELECT '" + REP_EXTERNAL_PRINCIPAL_NAMES + "' FROM [rep:User] WHERE PROPERTY(["
+ + REP_EXTERNAL_PRINCIPAL_NAMES + "], '" + PropertyType.TYPENAME_STRING + "')"
+ + op + "$" + BINDING_PRINCIPAL_NAMES + QueryEngine.INTERNAL_SQL2_QUERY;
+ return root.getQueryEngine().executeQuery(statement, Query.JCR_SQL2, bindings, namePathMapper.getSessionLocalMappings());
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Build the map used for the query bindings.
+ *
+ * @param nameHint The name hint
+ * @param exactMatch boolean flag indicating if the query should search for exact matching.
+ * @return the bindings
+ */
+ @Nonnull
+ private static Map<String, ? extends PropertyValue> buildBinding(@Nonnull String nameHint, boolean exactMatch) {
+ String val = nameHint;
+ if (!exactMatch) {
+ // not-exact query matching required => add leading and trailing %
+ if (nameHint.isEmpty()) {
+ val = "%";
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append('%');
+ sb.append(nameHint.replace("%", "\\%").replace("_", "\\_"));
+ sb.append('%');
+ val = sb.toString();
+ }
+ }
+ return Collections.singletonMap(BINDING_PRINCIPAL_NAMES, PropertyValues.newString(val));
+ }
+
+ //------------------------------------------------------< inner classes >---
+
+ /**
+ * Implementation of the {@link Group} interface representing external group
+ * identities that are <strong>not</strong> represented as authorizable group
+ * in the repository's user management.
+ */
+ private final class ExternalGroupPrincipal extends PrincipalImpl implements java.security.acl.Group {
+
+ public ExternalGroupPrincipal(String principalName) {
+ super(principalName);
+
+ }
+
+ @Override
+ public boolean addMember(Principal user) {
+ if (isMember(user)) {
+ return false;
+ } else {
+ throw new UnsupportedOperationException("Adding members to external group principals is not supported.");
+ }
+ }
+
+ @Override
+ public boolean removeMember(Principal user) {
+ if (!isMember(user)) {
+ return false;
+ } else {
+ throw new UnsupportedOperationException("Removing members from external group principals is not supported.");
+ }
+ }
+
+ @Override
+ public boolean isMember(Principal member) {
+ if (member instanceof Group) {
+ return false;
+ }
+ try {
+ String name = getName();
+ if (member instanceof ItemBasedPrincipal) {
+ Tree tree = root.getTree(((ItemBasedPrincipal) member).getPath());
+ if (UserUtil.isType(tree, AuthorizableType.USER)) {
+ PropertyState ps = tree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+ return (ps != null && Iterables.contains(ps.getValue(Type.STRINGS), name));
+ }
+ } else {
+ Authorizable a = userManager.getAuthorizable(member);
+ if (a != null && !a.isGroup()) {
+ Value[] vs = a.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+ if (vs != null) {
+ for (Value v : vs) {
+ if (name.equals(v.getString())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ log.debug(e.getMessage());
+ }
+ return false;
+ }
+
+ @Override
+ public Enumeration<? extends Principal> members() {
+ Result result = findPrincipals(getName(), true);
+ if (result != null) {
+ return Iterators.asEnumeration(new MemberIterator(result));
+ } else {
+ return Iterators.asEnumeration(Iterators.<Principal>emptyIterator());
+ }
+ }
+ }
+
+ /**
+ * {@link Group} principal iterator converting the query results of
+ * {@link #findPrincipals(String, int)} and {@link #findPrincipals(int)}.
+ * Since each result row provides the values of the {@code PropertyState},
+ * which matched the query, this iterator needs to filter the individual
+ * property values.
+ *
+ * Additional the iterator keeps track of principal names that have already
+ * been served and will not return duplicates.
+ *
+ * @see #findPrincipals(String, int)
+ * @see #findPrincipals(int)
+ */
+ private final class GroupPrincipalIterator extends AbstractLazyIterator<Principal> {
+
+ private final Set<String> processed = new HashSet<String>();
+
+ private final String queryString;
+ private final Iterator<? extends ResultRow> rows;
+
+ private Iterator<String> propValues = Iterators.emptyIterator();
+
+ private GroupPrincipalIterator(@Nullable String queryString, @Nonnull Result queryResult) {
+ this.queryString = queryString;
+ rows = queryResult.getRows().iterator();
+ }
+
+ @Override
+ protected Principal getNext() {
+ if (!propValues.hasNext()) {
+ if (rows.hasNext()) {
+ propValues = rows.next().getValue(REP_EXTERNAL_PRINCIPAL_NAMES).getValue(Type.STRINGS).iterator();
+ } else {
+ propValues = Iterators.emptyIterator();
+ }
+ }
+ while (propValues.hasNext()) {
+ String principalName = propValues.next();
+ if (principalName != null && !processed.contains(principalName) && matchesQuery(principalName) ) {
+ processed.add(principalName);
+ return new ExternalGroupPrincipal(principalName);
+ }
+ }
+ return null;
+ }
+
+ private boolean matchesQuery(@Nonnull String principalName) {
+ if (queryString == null) {
+ return true;
+ } else {
+ return principalName.contains(queryString);
+ }
+ }
+ }
+
+ /**
+ * {@code Principal} iterator representing the members of a given
+ * {@link ExternalGroupPrincipal}. The members are collected through an
+ * Oak {@link org.apache.jackrabbit.oak.query.Query Query}.
+ *
+ * Note that the query result is subject to permission evaluation for
+ * the editing {@link Root} based on the accessibility of the individual
+ * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} properties that contain the
+ * exact name of the external group principal.
+ *
+ * @see ExternalGroupPrincipal#members()
+ */
+ private final class MemberIterator extends AbstractLazyIterator<Principal> {
+
+ /**
+ * The query results containing the path of the user accounts
+ * (i.e. members) that contain the target group principal in the
+ * {@link #REP_EXTERNAL_PRINCIPAL_NAMES} property values.
+ */
+ private final Iterator<? extends ResultRow> rows;
+
+ private MemberIterator(@Nonnull Result queryResult) {
+ rows = queryResult.getRows().iterator();
+ }
+
+ @Override
+ protected Principal getNext() {
+ while (rows.hasNext()) {
+ String userPath = rows.next().getPath();
+ try {
+ Authorizable authorizable = userManager.getAuthorizableByPath(userPath);
+ if (authorizable != null) {
+ return authorizable.getPrincipal();
+ }
+ } catch (RepositoryException e) {
+ log.debug("{}", e.getMessage());
+ }
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java Tue May 17 16:18:29 2016
@@ -0,0 +1,96 @@
+/*
+ * 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.spi.security.authentication.external.impl.principal;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Session;
+import javax.jcr.nodetype.PropertyDefinition;
+
+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.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;
+import org.apache.jackrabbit.oak.spi.xml.PropInfo;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedPropertyImporter;
+import org.apache.jackrabbit.oak.spi.xml.ReferenceChangeTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implementation of the {@link ProtectedPropertyImporter} interface to properly
+ * handle reserved system maintained properties defined by this module.
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityImporter implements ProtectedPropertyImporter, ExternalIdentityConstants {
+
+ private static final Logger log = LoggerFactory.getLogger(ExternalIdentityImporter.class);
+
+ private boolean isSystemSession;
+
+ //----------------------------------------------< ProtectedItemImporter >---
+ @Override
+ public boolean init(@Nonnull Session session, @Nonnull Root root, @Nonnull NamePathMapper namePathMapper, boolean isWorkspaceImport, int uuidBehavior, @Nonnull ReferenceChangeTracker referenceTracker, @Nonnull SecurityProvider securityProvider) {
+ isSystemSession = root.getContentSession().getAuthInfo().getPrincipals().contains(SystemPrincipal.INSTANCE);
+ return true;
+ }
+
+ @Override
+ public void processReferences() {
+ // nothing to
+ }
+
+ //------------------------------------------< ProtectedPropertyImporter >---
+ /**
+ * Due to the fact that the reserved external-identity properties are
+ * not protected from a JCR (item definition) point of view, the handling
+ * of the system maintained properties needs to be postpone to the {@link #propertiesCompleted} call.
+ *
+ * @param parent The affected parent node.
+ * @param protectedPropInfo The {@code PropInfo} to be imported.
+ * @param def The property definition determined by the importer that
+ * calls this method.
+ * @return Always returns false.
+ */
+ @Override
+ public boolean handlePropInfo(@Nonnull Tree parent, @Nonnull PropInfo protectedPropInfo, @Nonnull PropertyDefinition def) {
+ return false;
+ }
+
+ /**
+ * Prevent 'rep:externalPrincipalNames' properties from being imported with a
+ * non-system session.
+ * Note: in order to make sure those properties are synchronized again upon
+ * the next login, 'rep:lastSynced' property gets removed as well.
+ *
+ * @param protectedParent The protected parent tree.
+ */
+ @Override
+ public void propertiesCompleted(@Nonnull Tree protectedParent) {
+ if (!isSystemSession) {
+ if (protectedParent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+ log.debug("Found reserved property " + REP_EXTERNAL_PRINCIPAL_NAMES + " managed by the system => Removed from imported scope.");
+ protectedParent.removeProperty(REP_EXTERNAL_PRINCIPAL_NAMES);
+ // force creation of rep:externalPrincipalNames by removing the
+ // rep:lastSynced property as well.
+ protectedParent.removeProperty(REP_LAST_SYNCED);
+ }
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityImporter.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java Tue May 17 16:18:29 2016
@@ -0,0 +1,102 @@
+/*
+ * 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.spi.security.authentication.external.impl.principal;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
+import org.apache.jackrabbit.oak.plugins.tree.RootFactory;
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.lifecycle.RepositoryInitializer;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of the {@code RepositoryInitializer} interface responsible for
+ * setting up query indices for the system maintained, protected properties defined
+ * by this module:
+ *
+ * <ul>
+ * <li>Index Definition <i>externalPrincipalNames</i>: Indexing
+ * {@link ExternalIdentityConstants#REP_EXTERNAL_PRINCIPAL_NAMES} properties.
+ * This index is used by the {@link ExternalGroupPrincipalProvider} to lookup
+ * and find principals stored in this property.</li>
+ * </ul>
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityRepositoryInitializer implements RepositoryInitializer {
+
+ private static final Logger log = LoggerFactory.getLogger(ExternalIdentityRepositoryInitializer.class);
+
+ ExternalIdentityRepositoryInitializer() {
+ }
+
+ @Override
+ public void initialize(@Nonnull NodeBuilder builder) {
+ NodeState base = builder.getNodeState();
+ NodeStore store = new MemoryNodeStore(base);
+
+ String errorMsg = "Failed to initialize external identity content.";
+ try {
+
+ Root root = RootFactory.createSystemRoot(store,
+ new EditorHook(new CompositeEditorProvider(new NamespaceEditorProvider(), new TypeEditorProvider())),
+ null, null, null, null);
+
+ // create index definition for "rep:externalId" and "rep:externalPrincipalNames"
+ NodeUtil rootTree = checkNotNull(new NodeUtil(root.getTree("/")));
+ NodeUtil index = rootTree.getOrAddChild(IndexConstants.INDEX_DEFINITIONS_NAME, JcrConstants.NT_UNSTRUCTURED);
+
+ if (!index.hasChild("externalPrincipalNames")) {
+ NodeUtil definition = IndexUtils.createIndexDefinition(index, "externalPrincipalNames", false,
+ new String[]{ExternalIdentityConstants.REP_EXTERNAL_PRINCIPAL_NAMES}, null);
+ definition.setString("info", "Oak index used by the principal management provided by the external authentication module.");
+ }
+
+ if (root.hasPendingChanges()) {
+ root.commit();
+ }
+ } catch (RepositoryException e) {
+ log.error(errorMsg, e);
+ throw new RuntimeException(e);
+ } catch (CommitFailedException e) {
+ log.error(errorMsg, e);
+ throw new RuntimeException(e);
+ }
+
+ NodeState target = store.getRoot();
+ target.compareAgainstBaseState(base, new ApplyDiff(builder));
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityRepositoryInitializer.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java Tue May 17 16:18:29 2016
@@ -0,0 +1,121 @@
+/*
+ * 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.spi.security.authentication.external.impl.principal;
+
+import java.security.Principal;
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.ExternalIdentityConstants;
+import org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+/**
+ * {@code ValidatorProvider} used to assure that the system maintained properties
+ * associated with external identities are only written by system sessions and
+ * are consistent.
+ *
+ * @since Oak 1.5.3
+ */
+class ExternalIdentityValidatorProvider extends ValidatorProvider implements ExternalIdentityConstants {
+
+ private final boolean isSystem;
+
+ ExternalIdentityValidatorProvider(@Nonnull Set<Principal> principals) {
+ isSystem = principals.contains(SystemPrincipal.INSTANCE);
+
+ }
+
+ private void checkAddModifyProperties(@Nonnull NodeState parent, @Nonnull String name, @Nonnull PropertyState propertyState) throws CommitFailedException {
+ if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name)) {
+ if (!isSystem) {
+ throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
+ }
+ Type<?> type = propertyState.getType();
+ if (!Type.STRINGS.equals(type) || !propertyState.isArray()) {
+ throw new CommitFailedException(CommitFailedException.CONSTRAINT, 71, "Property rep:externalPrincipalNames must be multi-valued of type STRING.");
+ }
+ if (!parent.hasProperty(REP_EXTERNAL_ID)) {
+ throw new CommitFailedException(CommitFailedException.CONSTRAINT, 72, "Property rep:externalPrincipalNames requires rep:externalId to be present on the Node.");
+ }
+ }
+ }
+
+ private void checkRemoveProperties(@Nonnull NodeState parent, @Nonnull String name) throws CommitFailedException {
+ if (RESERVED_PROPERTY_NAMES.contains(name)) {
+ if (REP_EXTERNAL_ID.equals(name) && parent.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {
+ throw new CommitFailedException(CommitFailedException.CONSTRAINT, 73, "Property rep:externalId cannot be removed as long as rep:externalPrincipalNames is present.");
+ }
+ if (REP_EXTERNAL_PRINCIPAL_NAMES.equals(name) && !isSystem) {
+ throw new CommitFailedException(CommitFailedException.CONSTRAINT, 70, "Attempt to create, modify or remove the system property " + name);
+ }
+ }
+ }
+
+ @Override
+ protected Validator getRootValidator(@Nonnull NodeState before, @Nonnull NodeState after,
+ @Nonnull CommitInfo info) {
+ return new ExternalIdentityValidator(after);
+ }
+
+ private final class ExternalIdentityValidator extends DefaultValidator {
+
+ private final NodeState parent;
+
+ private ExternalIdentityValidator(@Nonnull NodeState parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public void propertyAdded(PropertyState after) throws CommitFailedException {
+ checkAddModifyProperties(parent, after.getName(), after);
+ }
+
+ @Override
+ public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException {
+ checkAddModifyProperties(parent, before.getName(), after);
+ }
+
+ @Override
+ public void propertyDeleted(PropertyState before) throws CommitFailedException {
+ checkRemoveProperties(parent, before.getName());
+ }
+
+ @Override
+ public Validator childNodeAdded(String name, NodeState after) throws CommitFailedException {
+ return new ExternalIdentityValidator(after);
+ }
+
+ @Override
+ public Validator childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
+ return new ExternalIdentityValidator(after);
+ }
+
+ @Override
+ public Validator childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+ // removal of the parent node containing a reserved property must be possible
+ return null;
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalIdentityValidatorProvider.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java?rev=1744292&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java (added)
+++ jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java Tue May 17 16:18:29 2016
@@ -0,0 +1,238 @@
+/*
+ * 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.spi.security.authentication.external.impl.principal;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+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.Service;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+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.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.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.external.SyncHandler;
+import org.apache.jackrabbit.oak.spi.security.authentication.external.impl.DefaultSyncConfigImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalManagerImpl;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Implementation of the {@code PrincipalConfiguration} interface that provides
+ * principal management for {@link Group principals} associated with
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentity external identities}
+ * managed outside of the scope of the repository by an
+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}.
+ *
+ * @since Oak 1.5.3
+ * @see <a href="https://issues.apache.org/jira/browse/OAK-4101">OAK-4101</a>
+ */
+@Component(
+ metatype = true,
+ label = "Apache Jackrabbit Oak External PrincipalConfiguration",
+ immediate = true
+)
+@Service({PrincipalConfiguration.class, SecurityConfiguration.class})
+public class ExternalPrincipalConfiguration extends ConfigurationBase implements PrincipalConfiguration {
+
+ private SyncConfigTracker syncConfigTracker;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public ExternalPrincipalConfiguration() {
+ super();
+ }
+
+ public ExternalPrincipalConfiguration(SecurityProvider securityProvider) {
+ super(securityProvider, securityProvider.getParameters(NAME));
+ }
+
+ //---------------------------------------------< PrincipalConfiguration >---
+ @Nonnull
+ @Override
+ public PrincipalManager getPrincipalManager(Root root, NamePathMapper namePathMapper) {
+ return new PrincipalManagerImpl(getPrincipalProvider(root, namePathMapper));
+ }
+
+ @Nonnull
+ @Override
+ public PrincipalProvider getPrincipalProvider(Root root, NamePathMapper namePathMapper) {
+ if (dynamicMembershipEnabled()) {
+ UserConfiguration uc = getSecurityProvider().getConfiguration(UserConfiguration.class);
+ return new ExternalGroupPrincipalProvider(root, uc, namePathMapper);
+ } else {
+ return EmptyPrincipalProvider.INSTANCE;
+ }
+ }
+
+ //----------------------------------------------< SecurityConfiguration >---
+ @Nonnull
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Nonnull
+ @Override
+ public RepositoryInitializer getRepositoryInitializer() {
+ return new ExternalIdentityRepositoryInitializer();
+ }
+
+ @Nonnull
+ @Override
+ public List<? extends ValidatorProvider> getValidators(@Nonnull String workspaceName, @Nonnull Set<Principal> principals, @Nonnull MoveTracker moveTracker) {
+ return ImmutableList.of(new ExternalIdentityValidatorProvider(principals));
+ }
+
+ @Nonnull
+ @Override
+ public List<ProtectedItemImporter> getProtectedItemImporters() {
+ return ImmutableList.<ProtectedItemImporter>of(new ExternalIdentityImporter());
+ }
+
+ //----------------------------------------------------< SCR integration >---
+ @SuppressWarnings("UnusedDeclaration")
+ @Activate
+ private void activate(BundleContext bundleContext, Map<String, Object> properties) {
+ setParameters(ConfigurationParameters.of(properties));
+ syncConfigTracker = new SyncConfigTracker(bundleContext);
+ syncConfigTracker.open();
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ @Deactivate
+ private void deactivate() {
+ if (syncConfigTracker != null) {
+ syncConfigTracker.close();
+ }
+ }
+
+ //------------------------------------------------------------< private >---
+
+ private boolean dynamicMembershipEnabled() {
+ return syncConfigTracker != null && syncConfigTracker.isEnabled;
+ }
+
+ /**
+ * Implementation of the {@code PrincipalProvider} interface that never
+ * returns any principals.
+ */
+ private final static class EmptyPrincipalProvider implements PrincipalProvider {
+
+ private static final PrincipalProvider INSTANCE = new EmptyPrincipalProvider();
+
+ private EmptyPrincipalProvider() {}
+
+ @Override
+ public Principal getPrincipal(@Nonnull String principalName) {
+ return null;
+ }
+
+ @Nonnull
+ @Override
+ public Set<Group> getGroupMembership(@Nonnull Principal principal) {
+ return ImmutableSet.of();
+ }
+
+ @Nonnull
+ @Override
+ public Set<? extends Principal> getPrincipals(@Nonnull String userID) {
+ return ImmutableSet.of();
+ }
+
+ @Nonnull
+ @Override
+ public Iterator<? extends Principal> findPrincipals(@Nullable String nameHint, int searchType) {
+ return Iterators.emptyIterator();
+ }
+
+ @Nonnull
+ @Override
+ public Iterator<? extends Principal> findPrincipals(int searchType) {
+ return Iterators.emptyIterator();
+ }
+ }
+
+ /**
+ * {@code ServiceTracker} to detect any {@link SyncHandler} that has
+ * dynamic membership enabled.
+ */
+ private static final class SyncConfigTracker extends ServiceTracker {
+
+ private Set<ServiceReference> enablingRefs = new HashSet<ServiceReference>();
+ private boolean isEnabled = false;
+
+ public SyncConfigTracker(BundleContext context) {
+ super(context, SyncHandler.class.getName(), null);
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference) {
+ if (hasDynamicMembership(reference)) {
+ enablingRefs.add(reference);
+ isEnabled = true;
+ }
+ return super.addingService(reference);
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service) {
+ if (hasDynamicMembership(reference)) {
+ enablingRefs.add(reference);
+ isEnabled = true;
+ } else {
+ enablingRefs.remove(reference);
+ isEnabled = !enablingRefs.isEmpty();
+ }
+ super.modifiedService(reference, service);
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ enablingRefs.remove(reference);
+ isEnabled = !enablingRefs.isEmpty();
+ super.removedService(reference, service);
+ }
+
+ private static boolean hasDynamicMembership(ServiceReference reference) {
+ return PropertiesUtil.toBoolean(reference.getProperty(DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP), DefaultSyncConfigImpl.PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT);
+ }
+ }
+}
\ No newline at end of file
Propchange: jackrabbit/oak/trunk/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalPrincipalConfiguration.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java?rev=1744292&r1=1744291&r2=1744292&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java (original)
+++ jackrabbit/oak/trunk/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/AbstractExternalAuthTest.java Tue May 17 16:18:29 2016
@@ -16,13 +16,17 @@
*/
package org.apache.jackrabbit.oak.spi.security.authentication.external;
+import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.RepositoryException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
import com.google.common.base.Function;
import com.google.common.base.Predicates;
@@ -31,6 +35,10 @@ import com.google.common.collect.Sets;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.SystemSubject;
import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;
import org.junit.After;
import org.junit.Before;
@@ -50,6 +58,9 @@ public abstract class AbstractExternalAu
private Set<String> ids;
+ private ContentSession systemSession;
+ private Root systemRoot;
+
@Before
public void before() throws Exception {
super.before();
@@ -67,6 +78,10 @@ public abstract class AbstractExternalAu
destroyIDP();
idp = null;
+ if (systemSession != null) {
+ systemSession.close();
+ }
+
// discard any pending changes
root.refresh();
@@ -106,6 +121,13 @@ public abstract class AbstractExternalAu
}), Predicates.notNull());
}
+ @Override
+ protected SecurityProvider getSecurityProvider() {
+ if (securityProvider == null) {
+ securityProvider = new TestSecurityProvider(getSecurityConfigParameters());
+ }
+ return securityProvider;
+ }
protected ExternalIdentityProvider createIDP() {
return new TestIdentityProvider();
@@ -127,4 +149,17 @@ public abstract class AbstractExternalAu
syncConfig.user().setMembershipNestingDepth(1);
return syncConfig;
}
+
+ protected Root getSystemRoot() throws Exception {
+ if (systemRoot == null) {
+ systemSession = Subject.doAs(SystemSubject.INSTANCE, new PrivilegedExceptionAction<ContentSession>() {
+ @Override
+ public ContentSession run() throws LoginException, NoSuchWorkspaceException {
+ return getContentRepository().login(null, null);
+ }
+ });
+ systemRoot = systemSession.getLatestRoot();
+ }
+ return systemRoot;
+ }
}
\ No newline at end of file