| /* |
| * 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.basic; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.InputStream; |
| import java.math.BigDecimal; |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.jcr.Binary; |
| 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.User; |
| import org.apache.jackrabbit.api.security.user.UserManager; |
| import org.apache.jackrabbit.oak.commons.DebugTimer; |
| import org.apache.jackrabbit.oak.commons.PathUtils; |
| 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.SyncContext; |
| 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.principal.PrincipalImpl; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static java.text.Normalizer.Form.NFKC; |
| import static java.text.Normalizer.normalize; |
| |
| /** |
| * Internal implementation of the sync context |
| */ |
| public class DefaultSyncContext implements SyncContext { |
| |
| /** |
| * default logger |
| */ |
| private static final Logger log = LoggerFactory.getLogger(DefaultSyncContext.class); |
| |
| /** |
| * Name of the {@link ExternalIdentity#getExternalId()} property of a synchronized identity. |
| */ |
| public static final String REP_EXTERNAL_ID = "rep:externalId"; |
| |
| /** |
| * Name of the property that stores the time when an identity was synced. |
| */ |
| public static final String REP_LAST_SYNCED = "rep:lastSynced"; |
| |
| protected final DefaultSyncConfig config; |
| |
| protected final ExternalIdentityProvider idp; |
| |
| protected final UserManager userManager; |
| |
| protected final ValueFactory valueFactory; |
| |
| protected boolean keepMissing; |
| |
| protected boolean forceUserSync; |
| |
| protected boolean forceGroupSync; |
| |
| // we use the same wall clock for the entire context |
| protected final long now; |
| |
| protected final Value nowValue; |
| |
| public DefaultSyncContext(@NotNull DefaultSyncConfig config, @NotNull ExternalIdentityProvider idp, @NotNull UserManager userManager, @NotNull ValueFactory valueFactory) { |
| this.config = config; |
| this.idp = idp; |
| this.userManager = userManager; |
| this.valueFactory = valueFactory; |
| |
| // initialize 'now' |
| final Calendar nowCal = Calendar.getInstance(); |
| this.nowValue = valueFactory.createValue(nowCal); |
| this.now = nowCal.getTimeInMillis(); |
| } |
| |
| /** |
| * Creates a synced identity from the given authorizable. |
| * @param auth the authorizable |
| * @return the id |
| * @throws RepositoryException if an error occurs |
| */ |
| @Nullable |
| public static DefaultSyncedIdentity createSyncedIdentity(@Nullable Authorizable auth) throws RepositoryException { |
| if (auth == null) { |
| return null; |
| } |
| ExternalIdentityRef ref = getIdentityRef(auth); |
| Value[] lmValues = auth.getProperty(REP_LAST_SYNCED); |
| long lastModified = -1; |
| if (lmValues != null && lmValues.length > 0) { |
| lastModified = lmValues[0].getLong(); |
| } |
| return new DefaultSyncedIdentity(auth.getID(), ref, auth.isGroup(), lastModified); |
| } |
| |
| /** |
| * Retrieves the external identity ref from the authorizable |
| * @param auth the authorizable |
| * @return the ref |
| * @throws RepositoryException if an error occurs |
| */ |
| @Nullable |
| public static ExternalIdentityRef getIdentityRef(@Nullable Authorizable auth) throws RepositoryException { |
| if (auth == null) { |
| return null; |
| } |
| Value[] v = auth.getProperty(REP_EXTERNAL_ID); |
| if (v == null || v.length == 0) { |
| return null; |
| } |
| return ExternalIdentityRef.fromString(v[0].getString()); |
| } |
| |
| /** |
| * Robust relative path concatenation. |
| * @param paths relative paths |
| * @return the concatenated path |
| * @deprecated Since Oak 1.3.10. Please use {@link PathUtils#concatRelativePaths(String...)} instead. |
| */ |
| public static String joinPaths(String... paths) { |
| return PathUtils.concatRelativePaths(paths); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void close() { |
| // nothing to do |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isKeepMissing() { |
| return keepMissing; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @NotNull |
| @Override |
| public SyncContext setKeepMissing(boolean keepMissing) { |
| this.keepMissing = keepMissing; |
| return this; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isForceUserSync() { |
| return forceUserSync; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @NotNull |
| @Override |
| public SyncContext setForceUserSync(boolean forceUserSync) { |
| this.forceUserSync = forceUserSync; |
| return this; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isForceGroupSync() { |
| return forceGroupSync; |
| } |
| |
| @Override |
| @NotNull |
| public SyncContext setForceGroupSync(boolean forceGroupSync) { |
| this.forceGroupSync = forceGroupSync; |
| return this; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @NotNull |
| @Override |
| public SyncResult sync(@NotNull ExternalIdentity identity) throws SyncException { |
| ExternalIdentityRef ref = identity.getExternalId(); |
| if (!isSameIDP(ref)) { |
| // create result in accordance with sync(String) where status is FOREIGN |
| boolean isGroup = (identity instanceof ExternalGroup); |
| return new DefaultSyncResultImpl(new DefaultSyncedIdentity(identity.getId(), ref, isGroup, -1), SyncResult.Status.FOREIGN); |
| } |
| try { |
| DebugTimer timer = new DebugTimer(); |
| DefaultSyncResultImpl ret; |
| boolean created = false; |
| if (identity instanceof ExternalUser) { |
| User user = getAuthorizable(identity, User.class); |
| timer.mark("find"); |
| if (user == null) { |
| user = createUser((ExternalUser) identity); |
| timer.mark("create"); |
| created = true; |
| } |
| ret = syncUser((ExternalUser) identity, user); |
| timer.mark("sync"); |
| } else if (identity instanceof ExternalGroup) { |
| Group group = getAuthorizable(identity, Group.class); |
| timer.mark("find"); |
| if (group == null) { |
| group = createGroup((ExternalGroup) identity); |
| timer.mark("create"); |
| created = true; |
| } |
| ret = syncGroup((ExternalGroup) identity, group); |
| timer.mark("sync"); |
| } else { |
| throw new IllegalArgumentException("identity must be user or group but was: " + identity); |
| } |
| debug("sync({}) -> {} {}", ref.getString(), identity.getId(), timer.getString()); |
| if (created) { |
| ret.setStatus(SyncResult.Status.ADD); |
| } |
| return ret; |
| } catch (RepositoryException e) { |
| throw new SyncException(e); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @NotNull |
| @Override |
| public SyncResult sync(@NotNull String id) throws SyncException { |
| try { |
| DebugTimer timer = new DebugTimer(); |
| DefaultSyncResultImpl ret; |
| // find authorizable |
| Authorizable auth = userManager.getAuthorizable(id); |
| if (auth == null) { |
| return new DefaultSyncResultImpl(new DefaultSyncedIdentity(id, null, false, -1), SyncResult.Status.NO_SUCH_AUTHORIZABLE); |
| } |
| // check if we need to deal with this authorizable |
| ExternalIdentityRef ref = getIdentityRef(auth); |
| if (ref == null || !isSameIDP(ref)) { |
| return new DefaultSyncResultImpl(new DefaultSyncedIdentity(id, ref, auth.isGroup(), -1), SyncResult.Status.FOREIGN); |
| } |
| |
| if (auth.isGroup()) { |
| ExternalGroup external = idp.getGroup(id); |
| timer.mark("retrieve"); |
| if (external == null) { |
| ret = handleMissingIdentity(id, auth, timer); |
| } else { |
| ret = syncGroup(external, (Group) auth); |
| timer.mark("sync"); |
| } |
| } else { |
| ExternalUser external = idp.getUser(id); |
| timer.mark("retrieve"); |
| if (external == null) { |
| ret = handleMissingIdentity(id, auth, timer); |
| } else { |
| ret = syncUser(external, (User) auth); |
| timer.mark("sync"); |
| } |
| } |
| debug("sync({}) -> {} {}", id, ref.getString(), timer.getString()); |
| return ret; |
| } catch (RepositoryException | ExternalIdentityException e) { |
| throw new SyncException(e); |
| } |
| } |
| |
| private DefaultSyncResultImpl handleMissingIdentity(@NotNull String id, |
| @NotNull Authorizable authorizable, |
| @NotNull DebugTimer timer) throws RepositoryException { |
| DefaultSyncedIdentity syncId = createSyncedIdentity(authorizable); |
| SyncResult.Status status; |
| if (authorizable.isGroup() && ((Group) authorizable).getDeclaredMembers().hasNext()) { |
| log.info("won't remove local group with members: {}", id); |
| status = SyncResult.Status.NOP; |
| } else if (!keepMissing) { |
| if (config.user().getDisableMissing() && !authorizable.isGroup()) { |
| ((User) authorizable).disable("No longer exists on external identity provider '" + idp.getName() + "'"); |
| log.debug("disabling user '{}' that no longer exists on IDP {}", id, idp.getName()); |
| status = SyncResult.Status.DISABLE; |
| } else { |
| authorizable.remove(); |
| log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName()); |
| status = SyncResult.Status.DELETE; |
| } |
| timer.mark("remove"); |
| } else { |
| status = SyncResult.Status.MISSING; |
| log.info("external identity missing for {}, but purge == false.", id); |
| } |
| return new DefaultSyncResultImpl(syncId, status); |
| } |
| |
| /** |
| * Retrieves the repository authorizable that corresponds to the given external identity |
| * @param external the external identity |
| * @param type the authorizable type |
| * @return the repository authorizable or {@code null} if not found. |
| * @throws RepositoryException if an error occurs. |
| * @throws SyncException if the repository contains a colliding authorizable with the same name. |
| */ |
| @Nullable |
| protected <T extends Authorizable> T getAuthorizable(@NotNull ExternalIdentity external, @NotNull Class<T> type) |
| throws RepositoryException, SyncException { |
| Authorizable authorizable = userManager.getAuthorizable(external.getId()); |
| if (authorizable == null) { |
| authorizable = userManager.getAuthorizable(external.getPrincipalName()); |
| } |
| if (authorizable == null) { |
| return null; |
| } else if (type.isInstance(authorizable)) { |
| //noinspection unchecked |
| return (T) authorizable; |
| } else { |
| log.error("Unable to process external {}: {}. Colliding authorizable exists in repository.", type.getSimpleName(), external.getId()); |
| throw new SyncException("Unexpected authorizable: " + authorizable); |
| } |
| } |
| |
| /** |
| * Creates a new repository user for the given external one. |
| * Note that this method only creates the authorizable but does not perform any synchronization. |
| * |
| * @param externalUser the external user |
| * @return the repository user |
| * @throws RepositoryException if an error occurs |
| */ |
| @NotNull |
| protected User createUser(@NotNull ExternalUser externalUser) throws RepositoryException { |
| Principal principal = new PrincipalImpl(externalUser.getPrincipalName()); |
| String authId = config.user().isApplyRFC7613UsernameCaseMapped() ? |
| normalize(externalUser.getId().toLowerCase(), NFKC) : externalUser.getId(); |
| User user = userManager.createUser( |
| authId, |
| null, |
| principal, |
| PathUtils.concatRelativePaths(config.user().getPathPrefix(), externalUser.getIntermediatePath()) |
| ); |
| setExternalId(user, externalUser); |
| return user; |
| } |
| |
| /** |
| * Creates a new repository group for the given external one. |
| * Note that this method only creates the authorizable but does not perform any synchronization. |
| * |
| * @param externalGroup the external group |
| * @return the repository group |
| * @throws RepositoryException if an error occurs |
| */ |
| @NotNull |
| protected Group createGroup(@NotNull ExternalGroup externalGroup) throws RepositoryException { |
| Principal principal = new PrincipalImpl(externalGroup.getPrincipalName()); |
| Group group = userManager.createGroup( |
| externalGroup.getId(), |
| principal, |
| PathUtils.concatRelativePaths(config.group().getPathPrefix(), externalGroup.getIntermediatePath()) |
| ); |
| 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(@NotNull Authorizable authorizable, @NotNull 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())); |
| } |
| |
| @NotNull |
| protected DefaultSyncResultImpl syncUser(@NotNull ExternalUser external, @NotNull User user) throws RepositoryException { |
| // make also sure the local user to be synced belongs to the same IDP. Note: 'external' has been verified before. |
| if (!isSameIDP(user)) { |
| return new DefaultSyncResultImpl(new DefaultSyncedIdentity(external.getId(), external.getExternalId(), false, -1), SyncResult.Status.FOREIGN); |
| } |
| |
| SyncResult.Status status; |
| // check if user is expired |
| if (!forceUserSync && !isExpired(user)) { |
| status = SyncResult.Status.NOP; |
| } else { |
| syncExternalIdentity(external, user, config.user()); |
| if (isExpired(user, config.user().getMembershipExpirationTime(), "Membership")) { |
| // synchronize external memberships |
| syncMembership(external, user, config.user().getMembershipNestingDepth()); |
| } |
| if (this.config.user().getDisableMissing() && user.isDisabled()) { |
| status = SyncResult.Status.ENABLE; |
| user.disable(null); |
| } else { |
| status = SyncResult.Status.UPDATE; |
| } |
| // finally "touch" the sync property |
| user.setProperty(REP_LAST_SYNCED, nowValue); |
| } |
| return new DefaultSyncResultImpl(createSyncedIdentity(user), status); |
| } |
| |
| @NotNull |
| protected DefaultSyncResultImpl syncGroup(@NotNull ExternalGroup external, @NotNull Group group) throws RepositoryException { |
| // make also sure the local user to be synced belongs to the same IDP. Note: 'external' has been verified before. |
| if (!isSameIDP(group)) { |
| return new DefaultSyncResultImpl(new DefaultSyncedIdentity(external.getId(), external.getExternalId(), false, -1), SyncResult.Status.FOREIGN); |
| } |
| |
| SyncResult.Status status; |
| // first check if group is expired |
| if (!forceGroupSync && !isExpired(group)) { |
| status = SyncResult.Status.NOP; |
| } else { |
| syncExternalIdentity(external, group, config.group()); |
| // finally "touch" the sync property |
| group.setProperty(REP_LAST_SYNCED, nowValue); |
| status = SyncResult.Status.UPDATE; |
| } |
| return new DefaultSyncResultImpl(createSyncedIdentity(group), status); |
| } |
| |
| /** |
| * Synchronize content common to both external users and external groups: |
| * - properties |
| * - auto-group membership |
| * |
| * @param external The external identity |
| * @param authorizable The corresponding repository user/group |
| * @param config The sync configuration |
| * @throws RepositoryException If an error occurs. |
| */ |
| private void syncExternalIdentity(@NotNull ExternalIdentity external, |
| @NotNull Authorizable authorizable, |
| @NotNull DefaultSyncConfig.Authorizable config) throws RepositoryException { |
| syncProperties(external, authorizable, config.getPropertyMapping()); |
| applyMembership(authorizable, config.getAutoMembership()); |
| } |
| |
| /** |
| * Recursively sync the memberships of an authorizable up-to the specified depth. If the given depth |
| * is equal or less than 0, no syncing is performed. |
| * |
| * @param external the external identity |
| * @param auth the authorizable |
| * @param depth recursion depth. |
| * @throws RepositoryException |
| */ |
| protected void syncMembership(@NotNull ExternalIdentity external, @NotNull Authorizable auth, long depth) |
| throws RepositoryException { |
| if (depth <= 0) { |
| return; |
| } |
| debug("Syncing membership '{}' -> '{}'", external.getExternalId().getString(), auth.getID()); |
| |
| final DebugTimer timer = new DebugTimer(); |
| Iterable<ExternalIdentityRef> externalGroups; |
| try { |
| externalGroups = external.getDeclaredGroups(); |
| } catch (ExternalIdentityException e) { |
| log.error("Error while retrieving external declared groups for '{}'", external.getId(), e); |
| return; |
| } |
| timer.mark("fetching"); |
| |
| // first get the set of the existing groups that are synced ones |
| Map<String, Group> declaredExternalGroups = new HashMap<>(); |
| Iterator<Group> grpIter = auth.declaredMemberOf(); |
| while (grpIter.hasNext()) { |
| Group grp = grpIter.next(); |
| if (isSameIDP(grp)) { |
| declaredExternalGroups.put(grp.getID(), grp); |
| } |
| } |
| timer.mark("reading"); |
| |
| for (ExternalIdentityRef ref : externalGroups) { |
| log.debug("- processing membership {}", ref.getId()); |
| // get group |
| ExternalGroup extGroup; |
| try { |
| ExternalIdentity extId = idp.getIdentity(ref); |
| if (extId instanceof ExternalGroup) { |
| extGroup = (ExternalGroup) extId; |
| } else { |
| log.warn("No external group found for ref '{}'.", ref.getString()); |
| continue; |
| } |
| } catch (ExternalIdentityException e) { |
| log.warn("Unable to retrieve external group '{}' from provider.", ref.getString(), e); |
| continue; |
| } |
| log.debug("- idp returned '{}'", extGroup.getId()); |
| |
| // mark group as processed |
| Group grp = declaredExternalGroups.remove(extGroup.getId()); |
| boolean exists = grp != null; |
| |
| if (!exists) { |
| Authorizable a = userManager.getAuthorizable(extGroup.getId()); |
| if (a == null) { |
| grp = createGroup(extGroup); |
| log.debug("- created new group"); |
| } else if (a.isGroup() && isSameIDP(a)) { |
| grp = (Group) a; |
| } else { |
| log.warn("Existing authorizable '{}' is not a group from this IDP '{}'.", extGroup.getId(), idp.getName()); |
| continue; |
| } |
| log.debug("- user manager returned '{}'", grp); |
| } |
| |
| syncGroup(extGroup, grp); |
| |
| if (!exists) { |
| // ensure membership |
| grp.addMember(auth); |
| log.debug("- added '{}' as member to '{}'", auth, grp); |
| } |
| |
| // recursively apply further membership |
| if (depth > 1) { |
| log.debug("- recursively sync group membership of '{}' (depth = {}).", grp.getID(), depth); |
| syncMembership(extGroup, grp, depth - 1); |
| } else { |
| log.debug("- group nesting level for '{}' reached", grp.getID()); |
| } |
| } |
| timer.mark("adding"); |
| |
| // remove us from the lost membership groups |
| for (Group grp : declaredExternalGroups.values()) { |
| grp.removeMember(auth); |
| log.debug("- removing member '{}' for group '{}'", auth.getID(), grp.getID()); |
| } |
| timer.mark("removing"); |
| debug("syncMembership({}) {}", external.getId(), timer.getString()); |
| } |
| |
| /** |
| * Ensures that the given authorizable is member of the specific groups. Note that it does not create groups |
| * if missing, nor remove memberships of groups not in the given set. |
| * @param member the authorizable |
| * @param groups set of groups. |
| */ |
| protected void applyMembership(@NotNull Authorizable member, @NotNull Set<String> groups) throws RepositoryException { |
| for (String groupName : groups) { |
| Authorizable group = userManager.getAuthorizable(groupName); |
| if (group == null) { |
| log.warn("Unable to apply auto-membership to {}. No such group: {}", member.getID(), groupName); |
| } else if (group instanceof Group) { |
| ((Group) group).addMember(member); |
| } else { |
| log.warn("Unable to apply auto-membership to {}. Authorizable '{}' is not a group.", member.getID(), groupName); |
| } |
| } |
| } |
| |
| /** |
| * Syncs the properties specified in the {@code mapping} from the external identity to the given authorizable. |
| * Note that this method does not check for value equality and just blindly copies or deletes the properties. |
| * |
| * @param ext external identity |
| * @param auth the authorizable |
| * @param mapping the property mapping |
| * @throws RepositoryException if an error occurs |
| */ |
| protected void syncProperties(@NotNull ExternalIdentity ext, @NotNull Authorizable auth, @NotNull Map<String, String> mapping) |
| throws RepositoryException { |
| Map<String, ?> properties = ext.getProperties(); |
| for (Map.Entry<String, String> entry : mapping.entrySet()) { |
| String relPath = entry.getKey(); |
| String name = entry.getValue(); |
| Object obj = properties.get(name); |
| if (obj == null) { |
| int nameLen = name.length(); |
| if (nameLen > 1 && name.charAt(0) == '"' && name.charAt(nameLen-1) == '"') { |
| auth.setProperty(relPath, valueFactory.createValue(name.substring(1, nameLen - 1))); |
| } else { |
| auth.removeProperty(relPath); |
| } |
| } else { |
| if (obj instanceof Collection) { |
| auth.setProperty(relPath, createValues((Collection) obj)); |
| } else if (obj instanceof byte[] || obj instanceof char[]) { |
| auth.setProperty(relPath, createValue(obj)); |
| } else if (obj instanceof Object[]) { |
| auth.setProperty(relPath, createValues(Arrays.asList((Object[]) obj))); |
| } else { |
| auth.setProperty(relPath, createValue(obj)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks if the given authorizable needs syncing based on the {@link #REP_LAST_SYNCED} property. |
| * |
| * @param authorizable the authorizable to check |
| * @return {@code true} if the authorizable needs sync |
| */ |
| private boolean isExpired(@NotNull Authorizable authorizable) throws RepositoryException { |
| long expTime = (authorizable.isGroup()) ? config.group().getExpirationTime() : config.user().getExpirationTime(); |
| return isExpired(authorizable, expTime, "Properties"); |
| } |
| |
| /** |
| * Checks if the given authorizable needs syncing based on the {@link #REP_LAST_SYNCED} property. |
| * @param auth the authorizable to check |
| * @param expirationTime the expiration time to compare to. |
| * @param type debug message type |
| * @return {@code true} if the authorizable needs sync |
| */ |
| protected boolean isExpired(@NotNull Authorizable auth, long expirationTime, @NotNull String type) throws RepositoryException { |
| Value[] values = auth.getProperty(REP_LAST_SYNCED); |
| if (values == null || values.length == 0) { |
| debug("{} of {} '{}' need sync. {} not set.", type, auth.isGroup() ? "group" : "user", auth.getID(), REP_LAST_SYNCED); |
| return true; |
| } else if (now - values[0].getLong() > expirationTime) { |
| debug("{} of {} '{}' need sync. {} expired ({} > {})", type, auth.isGroup() ? "group" : "user", auth.getID(), now - values[0].getLong(), expirationTime, REP_LAST_SYNCED); |
| return true; |
| } else { |
| debug("{} of {} '{}' do not need sync.", type, auth.isGroup() ? "group" : "user", auth.getID()); |
| return false; |
| } |
| } |
| |
| /** |
| * Creates a new JCR value of the given object, checking the internal type. |
| * @param v the value |
| * @return the JCR value or null |
| * @throws RepositoryException if an error occurs |
| */ |
| @Nullable |
| protected Value createValue(@Nullable Object v) throws RepositoryException { |
| if (v == null) { |
| return null; |
| } else if (v instanceof Boolean) { |
| return valueFactory.createValue((Boolean) v); |
| } else if (v instanceof Byte || v instanceof Short || v instanceof Integer || v instanceof Long) { |
| return valueFactory.createValue(((Number) v).longValue()); |
| } else if (v instanceof Float || v instanceof Double) { |
| return valueFactory.createValue(((Number) v).doubleValue()); |
| } else if (v instanceof BigDecimal) { |
| return valueFactory.createValue((BigDecimal) v); |
| } else if (v instanceof Calendar) { |
| return valueFactory.createValue((Calendar) v); |
| } else if (v instanceof Date) { |
| Calendar cal = Calendar.getInstance(); |
| cal.setTime((Date) v); |
| return valueFactory.createValue(cal); |
| } else if (v instanceof byte[]) { |
| Binary bin = valueFactory.createBinary(new ByteArrayInputStream((byte[])v)); |
| return valueFactory.createValue(bin); |
| } else if (v instanceof Binary) { |
| return valueFactory.createValue((Binary) v); |
| } else if (v instanceof InputStream) { |
| return valueFactory.createValue((InputStream) v); |
| } else if (v instanceof char[]) { |
| return valueFactory.createValue(new String((char[]) v)); |
| } else { |
| return valueFactory.createValue(String.valueOf(v)); |
| } |
| } |
| |
| /** |
| * Creates an array of JCR values based on the type. |
| * @param propValues the given values |
| * @return and array of JCR values |
| * @throws RepositoryException if an error occurs |
| */ |
| @Nullable |
| protected Value[] createValues(@NotNull Collection<?> propValues) throws RepositoryException { |
| List<Value> values = new ArrayList<>(); |
| for (Object obj : propValues) { |
| Value v = createValue(obj); |
| if (v != null) { |
| values.add(v); |
| } |
| } |
| return values.toArray(new Value[0]); |
| } |
| |
| /** |
| * Checks if the given authorizable was synced from the same IDP by comparing the IDP name of the |
| * {@value #REP_EXTERNAL_ID} property. |
| * |
| * @param auth the authorizable. |
| * @return {@code true} if same IDP. |
| */ |
| protected boolean isSameIDP(@Nullable Authorizable auth) throws RepositoryException { |
| ExternalIdentityRef ref = getIdentityRef(auth); |
| return ref != null && idp.getName().equals(ref.getProviderName()); |
| } |
| |
| /** |
| * Tests if the given {@link ExternalIdentityRef} refers to the same IDP |
| * as associated with this context instance. |
| * |
| * @param ref The {@link ExternalIdentityRef} to be tested. |
| * @return {@code true} if {@link ExternalIdentityRef#getProviderName()} refers |
| * to the IDP associated with this context instance. |
| */ |
| protected boolean isSameIDP(@NotNull ExternalIdentityRef ref) { |
| return idp.getName().equals(ref.getProviderName()); |
| } |
| |
| private static void debug(@NotNull String mgs, @NotNull Object... objects) { |
| if (log.isDebugEnabled()) { |
| log.debug(mgs, objects); |
| } |
| } |
| } |