diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index 41341bbfe1..95bd6b24d3 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -519,6 +519,16 @@
api-ldap-client-api
provided
+
+ org.apache.directory.api
+ api-ldap-codec-core
+ provided
+
+
+ org.apache.directory.api
+ api-asn1-api
+ provided
+
org.apache.mina
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
index 2cee216260..197a45a410 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -19,6 +19,8 @@
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
import org.apache.directory.api.ldap.model.cursor.Cursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
@@ -42,6 +44,7 @@
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControlFactory;
import org.apache.knox.gateway.services.ldap.interceptor.InterceptorFactory;
import java.io.File;
@@ -135,6 +138,14 @@ public void start() throws Exception {
directoryService = new DefaultDirectoryService();
directoryService.setInstanceLayout(new InstanceLayout(workDir));
+ // Add RolesLookupBypassControlFactory
+ LdapApiService apiService = directoryService.getLdapCodecService();
+ if (apiService == null) {
+ apiService = LdapApiServiceFactory.getSingleton();
+ }
+ RolesLookupBypassControlFactory rolesLookupBypassControlFactory = new RolesLookupBypassControlFactory(apiService);
+ apiService.registerRequestControl(rolesLookupBypassControlFactory);
+
// Create SchemaManager
SchemaManager schemaManager = SchemaManagerFactory.createSchemaManager();
directoryService.setSchemaManager(schemaManager);
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControl.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControl.java
new file mode 100644
index 0000000000..78712fc38f
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControl.java
@@ -0,0 +1,25 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import org.apache.directory.api.ldap.model.message.Control;
+
+public interface RolesLookupBypassControl extends Control {
+ boolean isBypassRolesLookup();
+ void setBypassRolesLookup(boolean bypassRolesLookup);
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecorator.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecorator.java
new file mode 100644
index 0000000000..4821efc57a
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecorator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import org.apache.directory.api.asn1.Asn1Object;
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.asn1.EncoderException;
+import org.apache.directory.api.asn1.util.Asn1Buffer;
+import org.apache.directory.api.ldap.codec.api.ControlDecorator;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+
+import java.nio.ByteBuffer;
+
+public class RolesLookupBypassControlDecorator extends ControlDecorator implements RolesLookupBypassControl {
+
+ private final RolesLookupBypassControlFactory rolesLookupBypassControlFactory;
+
+ public RolesLookupBypassControlDecorator(LdapApiService codec, RolesLookupBypassControl decoratedControl, RolesLookupBypassControlFactory rolesLookupBypassControlFactory) {
+ super(codec, decoratedControl);
+ this.rolesLookupBypassControlFactory = rolesLookupBypassControlFactory;
+ }
+
+ @Override
+ public Asn1Object decode(byte[] bytes) throws DecoderException {
+ rolesLookupBypassControlFactory.decodeValue(getDecorated(), bytes);
+ return this;
+ }
+
+ @Override
+ public int computeLength() {
+ return 3; // Tag, Length, Value
+ }
+
+ @Override
+ public ByteBuffer encode(ByteBuffer byteBuffer) throws EncoderException {
+ Asn1Buffer asn1Buffer = new Asn1Buffer();
+ rolesLookupBypassControlFactory.encodeValue(asn1Buffer, getDecorated());
+
+ // reverse the byte ordering because Asn1Buffers store bytes in reverse
+ ByteBuffer factoryBuffer = asn1Buffer.getBytes();
+ int totalBytes = factoryBuffer.remaining();
+ byte[] factoryBytes = factoryBuffer.array();
+ for (int i = totalBytes - 1; i >= 0; i-- ) {
+ byteBuffer.put(factoryBytes[i]);
+ }
+
+ return byteBuffer;
+ }
+
+ @Override
+ public boolean isBypassRolesLookup() {
+ return getDecorated().isBypassRolesLookup();
+ }
+
+ @Override
+ public void setBypassRolesLookup(boolean bypassRolesLookup) {
+ getDecorated().setBypassRolesLookup(bypassRolesLookup);
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactory.java
new file mode 100644
index 0000000000..b700664bbf
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.asn1.util.Asn1Buffer;
+import org.apache.directory.api.ldap.codec.api.AbstractControlFactory;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.model.message.Control;
+import org.apache.knox.gateway.services.ldap.model.constants.SchemaConstants;
+
+public class RolesLookupBypassControlFactory extends AbstractControlFactory {
+ public static final byte BOOLEAN_TAG_BYTE = 0x01;
+
+ public RolesLookupBypassControlFactory(LdapApiService codec) {
+ super(codec, SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID);
+ }
+
+ @Override
+ public Control newControl() {
+ return new RolesLookupBypassControlDecorator(codec, new RolesLookupBypassControlImpl(), this);
+ }
+
+ @Override
+ public void decodeValue(Control control, byte[] controlBytes) throws DecoderException {
+ if (control instanceof RolesLookupBypassControl) {
+ RolesLookupBypassControl rolesLookupBypassControl = (RolesLookupBypassControl) control;
+ if (controlBytes == null || controlBytes.length < 3) {
+ throw new DecoderException("Invalid BER encoding for Boolean Control");
+ }
+
+ if (controlBytes[0] != BOOLEAN_TAG_BYTE) {
+ throw new DecoderException("Expected Boolean Tag (0x01), found: " + controlBytes[0]);
+ }
+
+ boolean value = (controlBytes[2] != 0x00);
+ rolesLookupBypassControl.setBypassRolesLookup(value);
+ } else {
+ throw new DecoderException("Cannot decode into " + control.getClass().getSimpleName() + ". Control must be instance of RolesLookupBypassControl.");
+ }
+ }
+
+ @Override
+ public void encodeValue(Asn1Buffer buffer, Control control) {
+ if (control instanceof RolesLookupBypassControl) {
+ RolesLookupBypassControl rolesLookupBypassControl = (RolesLookupBypassControl) control;
+
+ buffer.put(BOOLEAN_TAG_BYTE);
+ buffer.put((byte) 1); // Value is one byte long
+ buffer.put((byte) (rolesLookupBypassControl.isBypassRolesLookup() ? 0xFF : 0x00));
+ }
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlImpl.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlImpl.java
new file mode 100644
index 0000000000..403ad85d0a
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import org.apache.directory.api.ldap.model.message.controls.AbstractControl;
+import org.apache.knox.gateway.services.ldap.model.constants.SchemaConstants;
+
+public class RolesLookupBypassControlImpl extends AbstractControl implements RolesLookupBypassControl {
+ private boolean bypassRolesLookup;
+
+ public RolesLookupBypassControlImpl() {
+ super(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID);
+ }
+
+ @Override
+ public boolean isBypassRolesLookup() {
+ return bypassRolesLookup;
+ }
+
+ @Override
+ public void setBypassRolesLookup(boolean bypassRolesLookup) {
+ this.bypassRolesLookup = bypassRolesLookup;
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptor.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptor.java
index a860c2a4e0..c5a5a6abe8 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptor.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptor.java
@@ -23,6 +23,7 @@
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
+import org.apache.directory.api.ldap.model.message.Control;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
@@ -33,6 +34,8 @@
import org.apache.knox.gateway.services.ldap.LDAPRolesLookupService;
import org.apache.knox.gateway.services.ldap.LdapMessages;
import org.apache.knox.gateway.services.ldap.LdapUtils;
+import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControl;
+import org.apache.knox.gateway.services.ldap.model.constants.SchemaConstants;
import java.util.ArrayList;
import java.util.Collection;
@@ -46,6 +49,7 @@
*/
public class LDAPRolesLookupInterceptor extends BaseInterceptor {
private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class);
+
private final LDAPRolesLookupService rolesLookupService;
public LDAPRolesLookupInterceptor(LDAPRolesLookupService rolesLookupService) {
@@ -54,6 +58,16 @@ public LDAPRolesLookupInterceptor(LDAPRolesLookupService rolesLookupService) {
@Override
public EntryFilteringCursor search(SearchOperationContext ctx) throws LdapException {
+ if (ctx.hasRequestControl(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID)) {
+ Control control = ctx.getRequestControl(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID);
+ if (control instanceof RolesLookupBypassControl) {
+ RolesLookupBypassControl rolesLookupBypassControl = (RolesLookupBypassControl) control;
+ if (rolesLookupBypassControl.isBypassRolesLookup()) {
+ return next(ctx);
+ }
+ }
+ }
+
final List entries = new ArrayList<>();
try (EntryFilteringCursor cursor = next(ctx)) {
while (cursor.next()) {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
index 0f67f25753..03743d959c 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
@@ -22,7 +22,6 @@
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.server.core.api.CoreSession;
-import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.LdapPrincipal;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
@@ -63,11 +62,6 @@ public LdapBackend getBackend() {
return backend;
}
- @Override
- public void init(DirectoryService directoryService) throws LdapException {
- super.init(directoryService);
- }
-
@Override
public Entry lookup(LookupOperationContext ctx) throws LdapException {
Entry entry = next(ctx);
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
index b9ffdcda16..77229054e6 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -17,8 +17,12 @@
*/
package org.apache.knox.gateway.services.ldap;
+import org.apache.directory.api.ldap.codec.api.ControlFactory;
+import org.apache.directory.api.ldap.model.message.Control;
import org.apache.directory.server.core.api.interceptor.Interceptor;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControlFactory;
+import org.apache.knox.gateway.services.ldap.model.constants.SchemaConstants;
import org.easymock.EasyMock;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.knox.gateway.services.ldap.interceptor.UserSearchInterceptor;
@@ -374,8 +378,21 @@ public void testStartWithMultipleBackendsIdCollision() throws Exception {
}
@Test
- public void testGetUserGroups() {
+ public void testStartRegistersRolesLookupBypassControl() throws Exception {
+ GatewayConfig mockConfig = EasyMock.createNiceMock(GatewayConfig.class);
+ expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPPort()).andReturn(port).anyTimes();
+ expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com").anyTimes();
+ expect(mockConfig.getLDAPInterceptorNames()).andReturn(List.of()).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
+
+ serverManager.start();
+ Map> controlFactoryMap = serverManager.directoryService.getLdapCodecService().getRequestControlFactories();
+ assertTrue(controlFactoryMap.containsKey(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID));
+ assertTrue(controlFactoryMap.get(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID) instanceof RolesLookupBypassControlFactory);
}
private Map createFileBackendInterceptorConfig() {
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecoratorTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecoratorTest.java
new file mode 100644
index 0000000000..2b6c3f44b3
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlDecoratorTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import static org.easymock.EasyMock.mock;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class RolesLookupBypassControlDecoratorTest {
+
+ private RolesLookupBypassControl rolesLookupBypassControl;
+ private LdapApiService mockLdapApiService;
+ private RolesLookupBypassControlFactory rolesLookupBypassControlFactory;
+
+ private RolesLookupBypassControlDecorator rolesLookupBypassControlDecorator;
+
+ @Before
+ public void setUp() throws Exception {
+ mockLdapApiService = mock(LdapApiService.class);
+ replay(mockLdapApiService);
+
+ rolesLookupBypassControl = new RolesLookupBypassControlImpl();
+ rolesLookupBypassControlFactory = new RolesLookupBypassControlFactory(mockLdapApiService);
+
+ rolesLookupBypassControlDecorator = new RolesLookupBypassControlDecorator(mockLdapApiService, rolesLookupBypassControl, rolesLookupBypassControlFactory);
+ }
+
+ @Test
+ public void testDecodeFalseValue() throws Exception {
+ byte[] bytes = new byte[]{0x01, 0x01, 0x00};
+ rolesLookupBypassControlDecorator.decode(bytes);
+
+ assertFalse(rolesLookupBypassControl.isBypassRolesLookup());
+ }
+
+ @Test
+ public void testDecodeTrueValue() throws Exception {
+ byte[] bytes = new byte[]{0x01, 0x01, (byte) 0xff};
+ rolesLookupBypassControlDecorator.decode(bytes);
+
+ assertTrue(rolesLookupBypassControl.isBypassRolesLookup());
+ }
+
+ @Test(expected = DecoderException.class)
+ public void testDecodeWrongTag() throws Exception {
+ byte[] bytes = new byte[]{0x02, 0x01, 0x00};
+ rolesLookupBypassControlDecorator.decode(bytes);
+ }
+
+ @Test(expected = DecoderException.class)
+ public void testDecodeWrongLength() throws Exception {
+ byte[] bytes = new byte[]{0x02, 0x02, 0x00, 0x00};
+ rolesLookupBypassControlDecorator.decode(bytes);
+ }
+
+
+ @Test
+ public void testComputeLength() {
+ assertEquals("Length must always be 3", 3, rolesLookupBypassControlDecorator.computeLength());
+ }
+
+ @Test
+ public void testEncodeTrueValue() throws Exception {
+ testEncode(true);
+ }
+
+ @Test
+ public void testEncodeFalseValue() throws Exception {
+ testEncode(false);
+ }
+
+ private void testEncode(boolean encodeValue) throws Exception {
+ byte byteValue = encodeValue ? (byte) 0xff : 0x00;
+ rolesLookupBypassControlDecorator.setBypassRolesLookup(encodeValue);
+
+ byte[] expectedBytes = new byte[] {0x01, 0x01, byteValue};
+ ByteBuffer byteBuffer = ByteBuffer.allocate(3);
+ ByteBuffer encodedBuffer = rolesLookupBypassControlDecorator.encode(byteBuffer);
+ // transition from write mode to read mode
+ encodedBuffer.flip();
+ byte[] encodedBytes = new byte[encodedBuffer.remaining()];
+ encodedBuffer.get(encodedBytes);
+ assertArrayEquals(expectedBytes, encodedBytes);
+ }
+
+ @Test
+ public void testIsBypassRolesLookup() {
+ // Set value on the decorated instance and check that the decorator matches.
+ rolesLookupBypassControl.setBypassRolesLookup(true);
+ assertEquals("isBypassRolesLookup should match the value from the decorated Impl", true, rolesLookupBypassControlDecorator.isBypassRolesLookup());
+ rolesLookupBypassControl.setBypassRolesLookup(false);
+ assertEquals("isBypassRolesLookup should match the value from the decorated Impl", false, rolesLookupBypassControlDecorator.isBypassRolesLookup());
+ }
+
+ @Test
+ public void testSetBypassRolesLookup() {
+ // Set value on the decorator and check that the decorated instance matches.
+ rolesLookupBypassControlDecorator.setBypassRolesLookup(true);
+ assertEquals("Decorated instance value should be updated by the decorator", true, rolesLookupBypassControl.isBypassRolesLookup());
+ rolesLookupBypassControlDecorator.setBypassRolesLookup(false);
+ assertEquals("Decorated instance value should be updated by the decorator", false, rolesLookupBypassControl.isBypassRolesLookup());
+ }
+}
\ No newline at end of file
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactoryTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactoryTest.java
new file mode 100644
index 0000000000..7e26224dcf
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/control/RolesLookupBypassControlFactoryTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.knox.gateway.services.ldap.control;
+
+import static org.easymock.EasyMock.mock;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.directory.api.asn1.util.Asn1Buffer;
+import org.apache.directory.api.ldap.codec.api.LdapApiService;
+import org.apache.directory.api.ldap.model.message.Control;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class RolesLookupBypassControlFactoryTest {
+
+ private LdapApiService mockLdapApiService;
+ private RolesLookupBypassControlFactory rolesLookupBypassControlFactory;
+
+ @Before
+ public void setUp() throws Exception {
+ mockLdapApiService = mock(LdapApiService.class);
+ replay(mockLdapApiService);
+ rolesLookupBypassControlFactory = new RolesLookupBypassControlFactory(mockLdapApiService);
+ }
+
+ @Test
+ public void testNewControl() {
+ Control control = rolesLookupBypassControlFactory.newControl();
+ assertTrue("Control must be a RolesLookupBypassControlDecorator", control instanceof RolesLookupBypassControlDecorator);
+ }
+
+ @Test
+ public void testDecodeFalseValue() throws Exception {
+ RolesLookupBypassControl control = new RolesLookupBypassControlImpl();
+ byte[] bytes = new byte[]{0x01, 0x01, 0x00};
+
+ rolesLookupBypassControlFactory.decodeValue(control, bytes);
+
+ assertFalse(control.isBypassRolesLookup());
+ }
+
+ @Test
+ public void testDecodeTrueValue() throws Exception {
+ RolesLookupBypassControl control = new RolesLookupBypassControlImpl();
+ byte[] bytes = new byte[]{0x01, 0x01, (byte) 0xff};
+
+ rolesLookupBypassControlFactory.decodeValue(control, bytes);
+
+ assertTrue(control.isBypassRolesLookup());
+ }
+
+ @Test
+ public void testEncodeTrueValue() {
+ testEncode(true);
+ }
+
+ @Test
+ public void testEncodeFalseValue() {
+ testEncode(false);
+ }
+
+ private void testEncode(boolean encodeValue) {
+ byte byteValue = encodeValue ? (byte) 0xff : 0x00;
+
+ Asn1Buffer asn1Buffer = new Asn1Buffer();
+ RolesLookupBypassControl control = new RolesLookupBypassControlImpl();
+ control.setBypassRolesLookup(encodeValue);
+
+ rolesLookupBypassControlFactory.encodeValue(asn1Buffer, control);
+
+ // expectedBytes in reverse because Asn1Buffer stores bytes in reverse order
+ byte[] expectedBytes = new byte[]{byteValue, 0x01, 0x01};
+ System.out.println(asn1Buffer.toString());
+ ByteBuffer encodedBuffer = asn1Buffer.getBytes();
+ byte[] encodedBytes = new byte[encodedBuffer.remaining()];
+ encodedBuffer.get(encodedBytes);
+ assertArrayEquals(expectedBytes, encodedBytes);
+ }
+}
\ No newline at end of file
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/ConfigurableEntriesTestInterceptor.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/ConfigurableEntriesTestInterceptor.java
new file mode 100644
index 0000000000..27ac17f86b
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/ConfigurableEntriesTestInterceptor.java
@@ -0,0 +1,55 @@
+/*
+ * 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.knox.gateway.services.ldap.interceptor;
+
+import org.apache.directory.api.ldap.model.cursor.ListCursor;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
+import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+
+import java.util.List;
+
+/**
+ * Interceptor for testing. This interceptor will return a Cursor of a List
+ * of configured Entries.
+ */
+public class ConfigurableEntriesTestInterceptor extends BaseInterceptor {
+ private List entries;
+ private EntryFilteringCursor cursor;
+
+ ConfigurableEntriesTestInterceptor(String name) {
+ super(name);
+ }
+
+ public void setEntries(List entries) {
+ this.entries = entries;
+ }
+
+ public EntryFilteringCursor getCursor() {
+ return cursor;
+ }
+
+ @Override
+ public EntryFilteringCursor search(SearchOperationContext searchContext) throws LdapException {
+ cursor = new EntryFilteringCursorImpl(new ListCursor<>(entries), searchContext, schemaManager);
+ return cursor;
+ }
+}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorTest.java
index 828937074e..d4088a15b1 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorTest.java
@@ -21,18 +21,14 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import org.apache.directory.api.ldap.model.cursor.ListCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
-import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.core.api.CoreSession;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
-import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
-import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
import org.apache.knox.gateway.security.ldap.SimpleDirectoryService;
import org.apache.knox.gateway.services.ldap.SchemaManagerFactory;
@@ -140,27 +136,4 @@ private void assertNextEntryUid(EntryFilteringCursor cursor, String uid) throws
Value value = uidAttr.get();
assertEquals("Uid should match " + uid, uid, value.getString());
}
-
- private static class ConfigurableEntriesTestInterceptor extends BaseInterceptor {
- private List entries;
- private EntryFilteringCursor cursor;
-
- ConfigurableEntriesTestInterceptor(String name) {
- super(name);
- }
-
- public void setEntries(List entries) {
- this.entries = entries;
- }
-
- public EntryFilteringCursor getCursor() {
- return cursor;
- }
-
- @Override
- public EntryFilteringCursor search(SearchOperationContext searchContext) throws LdapException {
- cursor = new EntryFilteringCursorImpl(new ListCursor<>(entries), searchContext, schemaManager);
- return cursor;
- }
- }
}
\ No newline at end of file
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptorTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptorTest.java
index 637e1fc0f9..52b37d99ce 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptorTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/interceptor/LDAPRolesLookupInterceptorTest.java
@@ -20,20 +20,40 @@
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.knox.gateway.security.ldap.SimpleDirectoryService;
import org.apache.knox.gateway.services.ldap.LDAPRolesLookupService;
+import org.apache.knox.gateway.services.ldap.SchemaManagerFactory;
+import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControl;
+import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControlImpl;
import org.easymock.EasyMock;
+import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class LDAPRolesLookupInterceptorTest {
+ private SchemaManager schemaManager;
+
+ @Before
+ public void setUp() throws Exception {
+ schemaManager = SchemaManagerFactory.createSchemaManager();
+ }
@Test
public void testModifyEntryWithRoles() throws Exception {
@@ -68,14 +88,89 @@ public void testModifyEntryNoMemberOfNoRoles() throws Exception {
assertNull(modifiedEntry.get("memberOf"));
}
- private LDAPRolesLookupInterceptor createInterceptor() {
+ @Test
+ public void testRolesLookupNoBypass() throws Exception {
final LDAPRolesLookupService mockRolesService = EasyMock.createMock(LDAPRolesLookupService.class);
+
+ final Collection roles = Arrays.asList("roleA", "roleG");
+ expect(mockRolesService.lookupRoles(anyString(), anyObject()))
+ .andReturn(roles)
+ .atLeastOnce();
replay(mockRolesService);
- return new LDAPRolesLookupInterceptor(mockRolesService);
+
+ TestContext testContext = createTestContext(false, mockRolesService);
+
+ // Set up test to with group and role mapping
+ final Entry userEntry = createUserEntry("alice", "cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org");
+ testContext.nextInterceptor.setEntries(List.of(userEntry));
+
+ final EntryFilteringCursor entries = testContext.interceptor.search(testContext.ctx);
+
+ assertTrue(entries.next());
+ Entry modifiedEntry = entries.get();
+ assertMemberOf(modifiedEntry,
+ "cn=roleA,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=roleG,ou=groups,dc=hadoop,dc=apache,dc=org");
+ assertFalse(entries.next());
+ }
+
+ @Test
+ public void testRolesLookupWithBypass() throws Exception {
+ final LDAPRolesLookupService mockRolesService = EasyMock.createMock(LDAPRolesLookupService.class);
+
+ TestContext testContext = createTestContext(true, mockRolesService);
+
+ // Set up test to with group and role mapping
+ final Entry userEntry = createUserEntry("alice", "cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org");
+ testContext.nextInterceptor.setEntries(List.of(userEntry));
+
+ final EntryFilteringCursor entries = testContext.interceptor.search(testContext.ctx);
+
+ assertTrue(entries.next());
+ Entry modifiedEntry = entries.get();
+ assertMemberOf(modifiedEntry, "cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org");
+ assertFalse(entries.next());
+ }
+
+ private TestContext createTestContext(boolean bypass, LDAPRolesLookupService rolesService) throws Exception {
+ DirectoryService directoryService = new SimpleDirectoryService();
+ directoryService.setShutdownHookEnabled(false);
+ directoryService.setSchemaManager(SchemaManagerFactory.createSchemaManager());
+
+ LDAPRolesLookupInterceptor interceptor =
+ new LDAPRolesLookupInterceptor(rolesService);
+ interceptor.init(directoryService);
+ directoryService.addLast(interceptor);
+
+ ConfigurableEntriesTestInterceptor nextInterceptor =
+ new ConfigurableEntriesTestInterceptor("NEXT");
+ nextInterceptor.init(directoryService);
+ directoryService.addLast(nextInterceptor);
+
+ SearchOperationContext ctx =
+ new SearchOperationContext(directoryService.getSession());
+ ctx.setInterceptors(List.of(interceptor.getName(), "NEXT"));
+
+ RolesLookupBypassControl control =
+ new RolesLookupBypassControlImpl();
+ control.setBypassRolesLookup(bypass);
+ ctx.addRequestControl(control);
+
+ return new TestContext(interceptor, nextInterceptor, ctx);
+ }
+
+ private LDAPRolesLookupService createMockRolesService() throws Exception {
+ final LDAPRolesLookupService mockRolesService = EasyMock.createMock(LDAPRolesLookupService.class);
+ replay(mockRolesService);
+ return mockRolesService;
+ }
+
+ private LDAPRolesLookupInterceptor createInterceptor() throws Exception {
+ return new LDAPRolesLookupInterceptor(createMockRolesService());
}
private Entry createUserEntry(final String username, final String... memberOfDns) throws Exception {
- final Entry entry = new DefaultEntry();
+ final Entry entry = new DefaultEntry(schemaManager);
entry.add("uid", username);
for (final String dn : memberOfDns) {
entry.add("memberOf", dn);
@@ -90,4 +185,10 @@ private void assertMemberOf(final Entry entry, final String... expectedDns) {
assertTrue("Missing expected role DN: " + expected, memberOf.contains(expected));
}
}
+
+ private record TestContext(
+ LDAPRolesLookupInterceptor interceptor,
+ ConfigurableEntriesTestInterceptor nextInterceptor,
+ SearchOperationContext ctx) {
+ }
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ldap/model/constants/SchemaConstants.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ldap/model/constants/SchemaConstants.java
new file mode 100644
index 0000000000..ef34744d76
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ldap/model/constants/SchemaConstants.java
@@ -0,0 +1,29 @@
+/*
+ * 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.knox.gateway.services.ldap.model.constants;
+
+/**
+ * A utility class where we declare the schema objects being used by the Knox LDAP Server.
+ * Apache Knox OID Base = 1.3.6.1.4.1.18060.18
+ */
+public final class SchemaConstants {
+
+ // Apache Knox LDAP Controls 1.3.6.1.4.1.18060.18.0
+ public static final String ROLES_LOOKUP_BYPASS_CONTROL_OID = "1.3.6.1.4.1.18060.18.0.1";
+
+}
diff --git a/knox-site/docs/service_ldap_server.md b/knox-site/docs/service_ldap_server.md
index 6e0d1a56be..c6e6e91285 100644
--- a/knox-site/docs/service_ldap_server.md
+++ b/knox-site/docs/service_ldap_server.md
@@ -60,6 +60,24 @@ The duplicate user filter interceptor ensures that each `Entry` has a unique `ui
The user search interceptor is created if the `interceptorType` configuration is set to `backend`. This interceptor forwards search queries to its configured backend.
+#### Roles Lookup Interceptor (`rolesLookup`)
+
+The rolesLookup interceptor is created if the `interceptorType` configuration is set to `rolesLookup`. This interceptor transforms the response entities based on the mappings provided by the Role Lookup Service. For each entity, a request will be made to lookup roles based on the user's name and group membership. These roles will replace the values in the `memberOf` attribute.
+
+The interceptor will skip role mapping for a search request if the RolesLookupBypassControl is set to true. The control is specified using it's OID, `1.3.6.1.4.1.18060.18.0.1`. The value is a 3 byte array. This value must be base64 encoded for `ldapsearch`.
+
+| Byte | Value | Description |
+| :--- | :--- | :--- |
+| Tag | 0x01 | The Boolean Tag value |
+| Length | 0x03 | The length of the value in bytes |
+| Bypass | 0x00 or Oxff | 0x00 corresponds to `false` and 0xff corresponds to `true |
+
+
+For example, the control can be added to the `ldapsearch` cli using the `-e` option.
+```shell script
+ldapsearch -v -x -H ldap://localhost:3890 -b 'ou=people,DC=proxy,DC=com' -e "1.3.6.1.4.1.18060.18.0.1=AQH/" '(uid=sam*)' '*'
+```
+
### Backend Types
#### Common Backend Properties
@@ -259,13 +277,18 @@ Alternative: Use host and port instead of URL
-->
-
gateway.ldap.interceptor.duplicatefilter.interceptorType
duplicateuserfilter
+
+
+ gateway.ldap.interceptor.rolesLookup.interceptorType
+ rolesLookup
+
+
```
diff --git a/pom.xml b/pom.xml
index 3e89796af0..fad6fc3dcd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2227,6 +2227,16 @@
api-util
${apacheds.directory.api.version}
+
+ org.apache.directory.api
+ api-ldap-codec-core
+ ${apacheds.directory.api.version}
+
+
+ org.apache.directory.api
+ api-asn1-api
+ ${apacheds.directory.api.version}
+
org.apache.mina