OAK-11984 Support UserId Change for External Users (#2581)
* OAK-11984 Support UserId Change for External Users
* Removed unused change
* Update oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
Co-authored-by: Alejandro Moratinos <Amoratinos@users.noreply.github.com>
* Update oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
Co-authored-by: Alejandro Moratinos <Amoratinos@users.noreply.github.com>
* moving constants
* Added FF
* Added tests for FF
* Added debug log
---------
Co-authored-by: Alejandro Moratinos <Amoratinos@users.noreply.github.com>
Co-authored-by: angela <anchela@adobe.com>
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
index 4ce1746..89257f3 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalIdentityConstants.java
@@ -36,7 +36,12 @@
* @see DefaultSyncContext#REP_EXTERNAL_ID
*/
String REP_EXTERNAL_ID = DefaultSyncContext.REP_EXTERNAL_ID;
-
+
+ /**
+ * Name of the attribute storing the external identifier in Credentials
+ */
+ String EXTERNAL_ID_ATTRIBUTE = ":externalId";
+
/**
* Name of the property storing the date of the last synchronization of an
* external identity.
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
index ad9d9b7..447cb45 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModule.java
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.oak.spi.security.authentication.external.impl;
+import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.oak.api.AuthInfo;
import org.apache.jackrabbit.oak.api.CommitFailedException;
@@ -58,6 +59,7 @@
import javax.security.auth.login.LoginException;
import java.security.Principal;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -65,6 +67,7 @@
import java.util.stream.Stream;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID;
/**
* {@code ExternalLoginModule} implements a {@code LoginModule} that uses an
@@ -228,7 +231,25 @@
// before into the repository.
UserManager userManager = getUserManager();
SyncedIdentity sId = getSyncedIdentity(userId, userManager);
-
+ if (Boolean.parseBoolean(System.getProperty("FT_GRANITE-61684")) &&
+ sId ==null && userManager != null && creds != null) {
+ log.debug("FT_GRANITE-61684 is enabled and user is not found by userId. Trying to find external user by externalId attribute.");
+ // Check if the external user was registered with a different userId, and the same externalId
+ Object externalAttribute = credentialsSupport.getAttributes(creds).get(SHARED_ATTRIBUTE_EXTERNAL_ID);
+ if (externalAttribute != null ) {
+ @NotNull Iterator<Authorizable> authIterator = userManager.findAuthorizables(ExternalIdentityConstants.REP_EXTERNAL_ID, externalAttribute + ";" + idp.getName(), UserManager.SEARCH_TYPE_USER);
+ if (authIterator.hasNext()) {
+ log.debug("Found existing user by externalId attribute: {}", externalAttribute);
+ //modify credentials to reflect the login name stored in oak
+ Authorizable authorizable = authIterator.next();
+ sId = getSyncedIdentity(authorizable.getID(), userManager);
+ Map<String, ?> attributes = credentialsSupport.getAttributes(creds);
+ HashMap<String, Object> newAttributes = new HashMap<>(attributes);
+ newAttributes.put(SHARED_KEY_LOGIN_NAME, authorizable.getID());
+ credentialsSupport.setAttributes(creds, newAttributes);
+ }
+ }
+ }
// if there exists an authorizable with the given userid (syncedIdentity != null),
// ignore it if any of the following conditions is met:
// - identity is local (i.e. not an external identity)
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
index 112ef6a..ed2f9b3 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/package-info.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("2.4.0")
+@Version("2.5.0")
package org.apache.jackrabbit.oak.spi.security.authentication.external;
import org.osgi.annotation.versioning.Version;
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleTest.java
index 851bb7d..b6f3f81 100644
--- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleTest.java
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/ExternalLoginModuleTest.java
@@ -25,6 +25,8 @@
import org.apache.jackrabbit.oak.api.ContentRepository;
import org.apache.jackrabbit.oak.api.ContentSession;
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.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
@@ -69,9 +71,11 @@
import java.util.Map;
import java.util.Set;
+import static java.util.Map.of;
import static org.apache.jackrabbit.oak.api.CommitFailedException.OAK;
import static org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule.SHARED_KEY_ATTRIBUTES;
import static org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule.SHARED_KEY_PRE_AUTH_LOGIN;
+import static org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID;
import static org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider.DEFAULT_IDP_NAME;
import static org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider.ID_EXCEPTION;
import static org.apache.jackrabbit.oak.spi.security.authentication.external.TestIdentityProvider.ID_TEST_USER;
@@ -253,6 +257,173 @@
verifyNoInteractions(monitor);
}
+ private void createExternalIdIndex(Tree rootTree) {
+ // Navigate to or create the /oak:index node
+ Tree index = rootTree.getChild("oak:index");
+ if (!index.exists()) {
+ index = rootTree.addChild("oak:index");
+ index.setProperty("jcr:primaryType", "oak:Unstructured", Type.NAME);
+ }
+
+ Tree externalIdIndex = index.getChild("externalId");
+ if (!externalIdIndex.exists()) {
+ externalIdIndex = index.addChild("externalId");
+ externalIdIndex.setProperty("jcr:primaryType", "oak:QueryIndexDefinition", Type.NAME); // Correct node type
+ externalIdIndex.setProperty("type", "property", Type.STRING);
+ externalIdIndex.setProperty("propertyNames", Collections.singletonList("rep:externalId"), Type.NAMES);
+ externalIdIndex.setProperty("reindex", true, Type.BOOLEAN);
+ externalIdIndex.setProperty("unique", true, Type.BOOLEAN);
+ }
+
+ }
+ @Test
+ public void testLoginUserIdentifiedByExternalId() throws Exception {
+ System.setProperty("FT_GRANITE-61684", "true");
+
+ String idpName = "testExternalId";
+ // UserId (PrincipalName) already present in oak (old UserId for the user)
+ String oldPrincipalName = "test3OldValue";
+ // ExternalId present in oak and returned by the Idp, and already configured in Oak
+ String externalId = "extId123";
+ // New UserId returned by the ExternalIdp
+ String newPrincipalName = "newUserId";
+
+ TestExternalUserIdCredentials creds = new TestExternalUserIdCredentials(newPrincipalName);
+ creds.setAttribute(SHARED_ATTRIBUTE_EXTERNAL_ID, externalId);
+
+ // We need to create an index, or we have an exception with the search
+ createExternalIdIndex(root.getTree("/"));
+ UserManager userManager = getUserManager(root);
+ userManager.createUser(oldPrincipalName, null).setProperty(REP_EXTERNAL_ID, getValueFactory().createValue(externalId + ";" + idpName));
+ root.commit();
+
+ ExternalIdentityProvider idp = new TestExternalUserIdIdentityProvider(idpName);
+
+ when(extIPMgr.getProvider(DEFAULT_IDP_NAME)).thenReturn(idp);
+ when(syncManager.getSyncHandler("syncHandler")).thenReturn(new DefaultSyncHandler(new DefaultSyncConfigImpl().setName("syncHandler")));
+
+ wb.register(ExternalIdentityProviderManager.class, extIPMgr, Collections.emptyMap());
+ wb.register(SyncManager.class, syncManager, Collections.emptyMap());
+
+ CallbackHandler cbh = createCallbackHandler(wb, getContentRepository(), getSecurityProvider(), creds);
+
+ Map<String, Object> sharedState = new HashMap<>();
+
+ loginModule.initialize(new Subject(), cbh, sharedState, of(PARAM_IDP_NAME, DEFAULT_IDP_NAME, PARAM_SYNC_HANDLER_NAME, "syncHandler"));
+ assertTrue(loginModule.login());
+ assertTrue(loginModule.commit());
+ // The original PrincipalId is used, even if the Idp initially sent a new UderId.
+ assertEquals(creds.getUserId(), oldPrincipalName);
+ assertTrue(loginModule.logout());
+ }
+
+ @Test(expected = LoginException.class)
+ public void testLoginUserIdentifiedByExternalIdIssue() throws Exception {
+ System.setProperty("FT_GRANITE-61684", "false");
+
+ String idpName = "testExternalId";
+ // UserId (PrincipalName) already present in oak (old UserId for the user)
+ String oldPrincipalName = "test3OldValue";
+ // ExternalId present in oak and returned by the Idp, and already configured in Oak
+ String externalId = "extId123";
+ // New UserId returned by the ExternalIdp
+ String newPrincipalName = "newUserId";
+
+ TestExternalUserIdCredentials creds = new TestExternalUserIdCredentials(newPrincipalName);
+ creds.setAttribute(SHARED_ATTRIBUTE_EXTERNAL_ID, externalId);
+
+ // We need to create an index, or we have an exception with the search
+ createExternalIdIndex(root.getTree("/"));
+ UserManager userManager = getUserManager(root);
+ userManager.createUser(oldPrincipalName, null).setProperty(REP_EXTERNAL_ID, getValueFactory().createValue(externalId + ";" + idpName));
+ root.commit();
+
+ ExternalIdentityProvider idp = new TestExternalUserIdIdentityProvider(idpName);
+
+ when(extIPMgr.getProvider(DEFAULT_IDP_NAME)).thenReturn(idp);
+ when(syncManager.getSyncHandler("syncHandler")).thenReturn(new DefaultSyncHandler(new DefaultSyncConfigImpl().setName("syncHandler")));
+
+ wb.register(ExternalIdentityProviderManager.class, extIPMgr, Collections.emptyMap());
+ wb.register(SyncManager.class, syncManager, Collections.emptyMap());
+
+ CallbackHandler cbh = createCallbackHandler(wb, getContentRepository(), getSecurityProvider(), creds);
+
+ Map<String, Object> sharedState = new HashMap<>();
+
+ loginModule.initialize(new Subject(), cbh, sharedState, of(PARAM_IDP_NAME, DEFAULT_IDP_NAME, PARAM_SYNC_HANDLER_NAME, "syncHandler"));
+ assertTrue(loginModule.login());
+ assertTrue(loginModule.commit());
+ // The original PrincipalId is used, even if the Idp initially sent a new UderId.
+ assertEquals(creds.getUserId(), oldPrincipalName);
+ assertTrue(loginModule.logout());
+ }
+
+ // Test if the user is not found by the externalId, even if it is present in oak.
+ // This is the default case for users that did not modify his userId
+ @Test
+ public void testLoginUserIdentifiedByExternalIdNotFound() throws Exception {
+ System.setProperty("FT_GRANITE-61684", "true");
+
+ String idpName = "testExternalId";
+ // UserId (PrincipalName) already present in oak (UserId for the user)
+ String principalName = "test4";
+
+ // We need to create an index, or we have an exception with the search
+ createExternalIdIndex(root.getTree("/"));
+ root.commit();
+
+ TestExternalUserIdCredentials creds = new TestExternalUserIdCredentials(principalName);
+ creds.setAttribute(SHARED_ATTRIBUTE_EXTERNAL_ID, principalName);
+
+ ExternalIdentityProvider idp = new TestExternalUserIdIdentityProvider(idpName);
+
+ when(extIPMgr.getProvider(DEFAULT_IDP_NAME)).thenReturn(idp);
+ when(syncManager.getSyncHandler("syncHandler")).thenReturn(new DefaultSyncHandler(new DefaultSyncConfigImpl().setName("syncHandler")));
+
+ wb.register(ExternalIdentityProviderManager.class, extIPMgr, Collections.emptyMap());
+ wb.register(SyncManager.class, syncManager, Collections.emptyMap());
+
+ CallbackHandler cbh = createCallbackHandler(wb, getContentRepository(), getSecurityProvider(), creds);
+
+ Map<String,Object> sharedState = new HashMap<>();
+
+ loginModule.initialize(new Subject(), cbh, sharedState, of(PARAM_IDP_NAME, DEFAULT_IDP_NAME, PARAM_SYNC_HANDLER_NAME, "syncHandler"));
+ assertTrue(loginModule.login());
+ assertTrue(loginModule.commit());
+ // The original PrincipalId is used, even if the Idp initially sent a new UderId.
+ assertEquals(creds.getUserId(), principalName);
+ assertTrue(loginModule.logout());
+ }
+
+ @Test
+ public void testLoginUserIdentifiedByExternalIdMissingCredentials() throws Exception {
+ System.setProperty("FT_GRANITE-61684", "true");
+ String idpName = "testExternalId";
+
+ // We need to create an index, or we have an exception with the search
+ createExternalIdIndex(root.getTree("/"));
+ root.commit();
+
+ ExternalIdentityProvider idp = new TestExternalUserIdIdentityProvider(idpName);
+
+ when(extIPMgr.getProvider(DEFAULT_IDP_NAME)).thenReturn(idp);
+ when(syncManager.getSyncHandler("syncHandler")).thenReturn(new DefaultSyncHandler(new DefaultSyncConfigImpl().setName("syncHandler")));
+
+ wb.register(ExternalIdentityProviderManager.class, extIPMgr, Collections.emptyMap());
+ wb.register(SyncManager.class, syncManager, Collections.emptyMap());
+
+ CallbackHandler cbh = createCallbackHandler(wb, getContentRepository(), getSecurityProvider(), null);
+
+ Map<String,Object> sharedState = new HashMap<>();
+ sharedState.put(SHARED_KEY_PRE_AUTH_LOGIN, new PreAuthenticatedLogin(ID_TEST_USER));
+ sharedState.put(SHARED_KEY_ATTRIBUTES, Collections.singletonMap("att", "value"));
+
+ loginModule.initialize(new Subject(), cbh, sharedState, of(PARAM_IDP_NAME, DEFAULT_IDP_NAME, PARAM_SYNC_HANDLER_NAME, "syncHandler"));
+ assertFalse(loginModule.login());
+ assertFalse(loginModule.commit());
+ assertFalse(loginModule.logout());
+ }
+
@Test
public void testLoginCommitUpdatesSubject() throws Exception {
when(extIPMgr.getProvider(DEFAULT_IDP_NAME)).thenReturn(new TestIdentityProvider());
@@ -631,4 +802,5 @@
verify(monitor).loginError();
}
}
+
}
\ No newline at end of file
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdCredentials.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdCredentials.java
new file mode 100644
index 0000000..dcb88d5
--- /dev/null
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdCredentials.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule;
+import org.apache.jackrabbit.oak.spi.security.authentication.credentials.AbstractCredentials;
+import org.jetbrains.annotations.NotNull;
+
+class TestExternalUserIdCredentials extends AbstractCredentials {
+ public TestExternalUserIdCredentials(String originalUserId) {
+ super(originalUserId);
+ }
+
+ @Override
+ public @NotNull String getUserId() {
+ Object loginName = getAttribute(AbstractLoginModule.SHARED_KEY_LOGIN_NAME);
+ if ( loginName != null) {
+ return (String) loginName;
+ }
+ return userId;
+ }
+
+}
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdIdentityProvider.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdIdentityProvider.java
new file mode 100644
index 0000000..1065382
--- /dev/null
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/TestExternalUserIdIdentityProvider.java
@@ -0,0 +1,290 @@
+/*
+ * 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 org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConstants;
+import org.apache.jackrabbit.oak.spi.security.authentication.credentials.AbstractCredentials;
+import org.apache.jackrabbit.oak.spi.security.authentication.credentials.CredentialsSupport;
+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.token.TokenConstants;
+import org.jetbrains.annotations.NotNull;
+
+import javax.jcr.Credentials;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class TestExternalUserIdIdentityProvider implements ExternalIdentityProvider, CredentialsSupport {
+
+ private final String name;
+
+ public TestExternalUserIdIdentityProvider(final String name) {
+ this.name = name;
+ }
+
+
+ //-------------------------------------< ExternalIdentityProvider >---
+
+ @Override
+ public ExternalUser authenticate(@NotNull Credentials credentials) {
+ if (credentials instanceof TestExternalUserIdCredentials) {
+ TestExternalUserIdCredentials oAuthCredentials = (TestExternalUserIdCredentials) credentials;
+ return new TestExternalUser(oAuthCredentials);
+ }
+ return null;
+ }
+
+ @Override
+ public ExternalGroup getGroup(@NotNull String name) throws ExternalIdentityException {
+ return null;
+ }
+
+ @Override
+ public ExternalIdentity getIdentity(@NotNull ExternalIdentityRef ref) throws ExternalIdentityException {
+ if (isForeignRef(ref)) {
+ return null;
+ } else if (ref instanceof TestGroupExternalIdentityRef) {
+ return new TestExternalUserIdExternalGroup(ref);
+ }
+
+ return null;
+ }
+
+ @Override
+ public @NotNull String getName() {
+ return name;
+ }
+
+ @Override
+ public ExternalUser getUser(@NotNull String userId) throws ExternalIdentityException {
+ return null;
+ }
+
+ @Override
+ public @NotNull Iterator<ExternalUser> listUsers() throws ExternalIdentityException {
+ //return an empty iterator
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public @NotNull Iterator<ExternalGroup> listGroups() throws ExternalIdentityException {
+ //return an empty iterator
+ return Collections.emptyIterator();
+ }
+
+ //----------------------------------------- PRIVATE METHODS---
+
+ private boolean isForeignRef(ExternalIdentityRef ref) {
+ if (ref == null) {
+ return false;
+ }
+
+ String provider = ref.getProviderName();
+ // the part that supports null or empty provider strings is taken from the LDAP idp code
+ return !(provider == null || provider.isEmpty() || getName().equals(ref.getProviderName()));
+ }
+
+ //-----------------------------------------< CredentialsSupport >---
+
+ @Override
+ public @NotNull Set<Class> getCredentialClasses() {
+ return Collections.singleton(TestExternalUserIdCredentials.class);
+ }
+
+ @Override
+ public String getUserId(@NotNull Credentials credentials) {
+ if (credentials instanceof TestExternalUserIdCredentials) {
+ return ((TestExternalUserIdCredentials)credentials).getUserId();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public @NotNull Map<String, ?> getAttributes(@NotNull Credentials credentials) {
+ if (credentials instanceof TestExternalUserIdCredentials) {
+ HashMap<String, Object> attrs = new HashMap<>();
+ attrs.put(TokenConstants.TOKEN_ATTRIBUTE, "");
+ attrs.put(AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID, ((TestExternalUserIdCredentials) credentials).getAttribute(AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID));
+ attrs.put(AuthenticationConstants.SHARED_KEY_LOGIN_NAME, ((TestExternalUserIdCredentials) credentials).getAttribute(AuthenticationConstants.SHARED_KEY_LOGIN_NAME));
+ return attrs;
+ } else {
+ return Collections.emptyMap();
+ }
+ }
+
+ @Override
+ public boolean setAttributes(@NotNull Credentials credentials, @NotNull Map<String, ?> attributes) {
+ if (credentials instanceof TestExternalUserIdCredentials) {
+ ((TestExternalUserIdCredentials)credentials).setAttributes((Map<String, Object>) attributes);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ //-----------------------------------------< OAuthExternalUser >---
+
+ class TestExternalUser implements ExternalUser {
+
+ final AbstractCredentials externalCredentials;
+
+ TestExternalUser(AbstractCredentials externalCredentials) {
+ this.externalCredentials = externalCredentials;
+ }
+
+ @Override
+ public @NotNull ExternalIdentityRef getExternalId() {
+ return new ExternalIdentityRef((String) Objects.requireNonNull(externalCredentials.getAttribute(AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID)),
+ getName());
+ }
+
+ @Override
+ public @NotNull String getId() {
+ return externalCredentials.getUserId();
+ }
+
+ @Override
+ public @NotNull String getPrincipalName() {
+ return externalCredentials.getUserId();
+ }
+
+ /*
+ * The intermediate path is extracted
+ * from the id. It tries to be backward
+ * compatible with the previous AEM
+ * OAuth implementation
+ */
+ @Override
+ public String getIntermediatePath() {
+ String id = getId();
+
+ if (id.length() > 4) {
+ id = id.substring(id.indexOf("-")+1);
+ if (id.length() > 4) {
+ return id.substring(0,4);
+ } else {
+ return id;
+ }
+ }
+ return id;
+ }
+
+ @Override
+ public @NotNull Iterable<ExternalIdentityRef> getDeclaredGroups()
+ throws ExternalIdentityException {
+ Iterable<ExternalIdentityRef> groups = getGroups();
+ if (groups!= null) {
+ List<ExternalIdentityRef> list = new ArrayList<>();
+ for (ExternalIdentityRef ref:groups) {
+ list.add(new TestGroupExternalIdentityRef(ref.getId(), getName()));
+ }
+ return Collections.unmodifiableList(list);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public @NotNull Map<String, ?> getProperties() {
+ return externalCredentials.getAttributes();
+ }
+
+ Iterable<ExternalIdentityRef> getGroups() {
+ Collection<?> values = getProperties().values();
+ for (Object o: values) {
+ if (o instanceof ExternalUser) {
+ ExternalUser externalUser = (ExternalUser) o;
+ if (getExternalId().getId().equals(externalUser.getExternalId().getId())) {
+ try {
+ return externalUser.getDeclaredGroups();
+ } catch (ExternalIdentityException e) {
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ //-----------------------------------------< OAuthExternalGroup >---
+
+ static class TestExternalUserIdExternalGroup implements ExternalGroup {
+
+ final ExternalIdentityRef ref;
+
+ public TestExternalUserIdExternalGroup(ExternalIdentityRef ref) {
+ this.ref = ref;
+ }
+
+ @Override
+ public @NotNull ExternalIdentityRef getExternalId() {
+ return ref;
+ }
+
+ @Override
+ public @NotNull String getId() {
+ return ref.getId();
+ }
+
+ @Override
+ public @NotNull String getPrincipalName() {
+ return ref.getId();
+ }
+
+ @Override
+ public String getIntermediatePath() {
+ return null;
+ }
+
+ @Override
+ public @NotNull Iterable<ExternalIdentityRef> getDeclaredGroups()
+ throws ExternalIdentityException {
+ //not supporting nested groups for now
+ return Collections.emptyList();
+ }
+
+ @Override
+ public @NotNull Map<String, ?> getProperties() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public @NotNull Iterable<ExternalIdentityRef> getDeclaredMembers() {
+ return Collections.emptyList();
+ }
+ }
+
+ static class TestGroupExternalIdentityRef extends ExternalIdentityRef {
+ public TestGroupExternalIdentityRef(String id, String providerName) {
+ super(id, providerName);
+ }
+ }
+
+}
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java
index 4434d8c..952dabc 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java
@@ -150,25 +150,25 @@
* Key of the sharedState entry referring to validated Credentials that is
* shared between multiple login modules.
*/
- public static final String SHARED_KEY_CREDENTIALS = "org.apache.jackrabbit.credentials";
+ public static final String SHARED_KEY_CREDENTIALS = AuthenticationConstants.SHARED_KEY_CREDENTIALS;
/**
* Key of the sharedState entry referring to a valid login ID that is shared
* between multiple login modules.
*/
- public static final String SHARED_KEY_LOGIN_NAME = "javax.security.auth.login.name";
+ public static final String SHARED_KEY_LOGIN_NAME = AuthenticationConstants.SHARED_KEY_LOGIN_NAME;
/**
* Key of the sharedState entry referring to public attributes that are shared
* between multiple login modules.
*/
- public static final String SHARED_KEY_ATTRIBUTES = "javax.security.auth.login.attributes";
+ public static final String SHARED_KEY_ATTRIBUTES = AuthenticationConstants.SHARED_KEY_ATTRIBUTES;
/**
* Key of the sharedState entry referring to pre authenticated login information that is shared
* between multiple login modules.
*/
- public static final String SHARED_KEY_PRE_AUTH_LOGIN = PreAuthenticatedLogin.class.getName();
+ public static final String SHARED_KEY_PRE_AUTH_LOGIN = AuthenticationConstants.SHARED_KEY_PRE_AUTH_LOGIN;
protected Subject subject;
protected CallbackHandler callbackHandler;
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthenticationConstants.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthenticationConstants.java
new file mode 100644
index 0000000..eb7032b
--- /dev/null
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AuthenticationConstants.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Constants used by the authentication framework.
+ */
+public interface AuthenticationConstants {
+
+ /**
+ * Key of the sharedState entry referring to a valid login ID that is shared
+ * between multiple login modules.
+ */
+ String SHARED_KEY_LOGIN_NAME = "javax.security.auth.login.name";
+
+ /**
+ * Key of the sharedState entry referring to validated Credentials that is
+ * shared between multiple login modules.
+ */
+ String SHARED_KEY_CREDENTIALS = "org.apache.jackrabbit.credentials";
+
+ /**
+ * Key of the sharedState entry referring to public attributes that are shared
+ * between multiple login modules.
+ */
+ String SHARED_KEY_ATTRIBUTES = "javax.security.auth.login.attributes";
+
+ /**
+ * Key of the sharedState entry referring to pre authenticated login information that is shared
+ * between multiple login modules.
+ */
+ String SHARED_KEY_PRE_AUTH_LOGIN = PreAuthenticatedLogin.class.getName();
+
+ /**
+ * Name of the attribute storing the external identifier in Credentials
+ */
+ String SHARED_ATTRIBUTE_EXTERNAL_ID = ":externalId";
+
+}
\ No newline at end of file
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java
index 163f8ae..362747c 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@Version("1.6.0")
+@Version("1.7.0")
package org.apache.jackrabbit.oak.spi.security.authentication;
import org.osgi.annotation.versioning.Version;
diff --git a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstants.java b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstants.java
index 4aab444..bed1a20 100644
--- a/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstants.java
+++ b/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstants.java
@@ -19,6 +19,7 @@
import java.util.Set;
import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants;
+import org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConstants;
public interface TokenConstants {
@@ -34,11 +35,14 @@
String TOKENS_NT_NAME = NodeTypeConstants.NT_REP_UNSTRUCTURED;
String TOKEN_NT_NAME = "rep:Token";
+
Set<String> RESERVED_ATTRIBUTES = Set.of(
TOKEN_ATTRIBUTE,
TOKEN_ATTRIBUTE_EXPIRY,
- TOKEN_ATTRIBUTE_KEY);
+ TOKEN_ATTRIBUTE_KEY,
+ AuthenticationConstants.SHARED_KEY_LOGIN_NAME,
+ AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID);
Set<String> TOKEN_PROPERTY_NAMES = Set.of(TOKEN_ATTRIBUTE_EXPIRY, TOKEN_ATTRIBUTE_KEY);
diff --git a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstantsTest.java b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstantsTest.java
index c6f9497..fdf5045 100644
--- a/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstantsTest.java
+++ b/oak-security-spi/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/token/TokenConstantsTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.jackrabbit.oak.spi.security.authentication.token;
+import org.apache.jackrabbit.oak.spi.security.authentication.AuthenticationConstants;
import org.junit.Test;
import java.util.Set;
@@ -29,7 +30,7 @@
@Test
public void testReservedAttributes() {
- assertEquals(Set.of(TOKEN_ATTRIBUTE, TOKEN_ATTRIBUTE_EXPIRY, TOKEN_ATTRIBUTE_KEY), TokenConstants.RESERVED_ATTRIBUTES);
+ assertEquals(Set.of(TOKEN_ATTRIBUTE, TOKEN_ATTRIBUTE_EXPIRY, TOKEN_ATTRIBUTE_KEY, AuthenticationConstants.SHARED_ATTRIBUTE_EXTERNAL_ID, AuthenticationConstants.SHARED_KEY_LOGIN_NAME), TokenConstants.RESERVED_ATTRIBUTES);
}
@Test