Jetty 12 merge from 11 and fix JAAS ldap login test (#10857)
* Merge from jetty-10.0.x to jetty-11.0.x * Fixes #10812 - Correct awaitility dependency scope (#10813) * Bump org.apache.logging.log4j:log4j-api from 2.20.0 to 2.21.1 Bumps org.apache.logging.log4j:log4j-api from 2.20.0 to 2.21.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump maven.plugin-tools.version from 3.9.0 to 3.10.1 Bumps `maven.plugin-tools.version` from 3.9.0 to 3.10.1. Updates `org.apache.maven.plugin-tools:maven-plugin-annotations` from 3.9.0 to 3.10.1 - [Release notes](https://github.com/apache/maven-plugin-tools/releases) - [Commits](https://github.com/apache/maven-plugin-tools/compare/maven-plugin-tools-3.9.0...maven-plugin-tools-3.10.1) Updates `org.apache.maven.plugin-tools:maven-plugin-tools-api` from 3.9.0 to 3.10.1 - [Release notes](https://github.com/apache/maven-plugin-tools/releases) - [Commits](https://github.com/apache/maven-plugin-tools/compare/maven-plugin-tools-3.9.0...maven-plugin-tools-3.10.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugin-tools:maven-plugin-annotations dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.maven.plugin-tools:maven-plugin-tools-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump org.apache.maven.plugins:maven-clean-plugin from 3.3.1 to 3.3.2 Bumps [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/apache/maven-clean-plugin/releases) - [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.3.1...maven-clean-plugin-3.3.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-clean-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump org.apache.maven.plugins:maven-dependency-plugin Bumps [org.apache.maven.plugins:maven-dependency-plugin](https://github.com/apache/maven-dependency-plugin) from 3.6.0 to 3.6.1. - [Commits](https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.6.0...maven-dependency-plugin-3.6.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-dependency-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump commons-io:commons-io from 2.14.0 to 2.15.0 Bumps commons-io:commons-io from 2.14.0 to 2.15.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump org.apache.maven.plugins:maven-plugin-plugin from 3.9.0 to 3.10.1 Bumps [org.apache.maven.plugins:maven-plugin-plugin](https://github.com/apache/maven-plugin-tools) from 3.9.0 to 3.10.1. - [Release notes](https://github.com/apache/maven-plugin-tools/releases) - [Commits](https://github.com/apache/maven-plugin-tools/compare/maven-plugin-tools-3.9.0...maven-plugin-tools-3.10.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-plugin-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump maven.surefire.plugin.version from 3.1.2 to 3.2.1 Bumps `maven.surefire.plugin.version` from 3.1.2 to 3.2.1. Updates `org.apache.maven.plugins:maven-failsafe-plugin` from 3.1.2 to 3.2.1 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.2...surefire-3.2.1) Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.1.2 to 3.2.1 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.2...surefire-3.2.1) Updates `org.apache.maven.surefire:surefire-junit47` from 3.1.2 to 3.2.1 --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.apache.maven.surefire:surefire-junit47 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Bump org.apache.maven.plugins:maven-checkstyle-plugin Bumps [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) from 3.3.0 to 3.3.1. - [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.3.0...maven-checkstyle-plugin-3.3.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump apacheds.version from 2.0.0.AM26 to 2.0.0.AM27 (#10836) * Bump apacheds.version from 2.0.0.AM26 to 2.0.0.AM27 Bumps `apacheds.version` from 2.0.0.AM26 to 2.0.0.AM27. Updates `org.apache.directory.server:apacheds-test-framework` from 2.0.0.AM26 to 2.0.0.AM27 - [Commits](https://github.com/apache/directory-server/compare/2.0.0.AM26...2.0.0.AM27) Updates `org.apache.directory.server:apacheds-server-integ` from 2.0.0.AM26 to 2.0.0.AM27 - [Commits](https://github.com/apache/directory-server/compare/2.0.0.AM26...2.0.0.AM27) Updates `org.apache.directory.server:apacheds-core-integ` from 2.0.0.AM26 to 2.0.0.AM27 - [Commits](https://github.com/apache/directory-server/compare/2.0.0.AM26...2.0.0.AM27) --- updated-dependencies: - dependency-name: org.apache.directory.server:apacheds-test-framework dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.apache.directory.server:apacheds-server-integ dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.apache.directory.server:apacheds-core-integ dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * get this work Signed-off-by: Olivier Lamy <olamy@apache.org> --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Olivier Lamy <olamy@apache.org> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Olivier Lamy <olamy@apache.org> * Bump org.infinispan:infinispan-bom from 11.0.17.Final to 11.0.18.Final (#10724) Bumps [org.infinispan:infinispan-bom](https://github.com/infinispan/infinispan) from 11.0.17.Final to 11.0.18.Final. - [Release notes](https://github.com/infinispan/infinispan/releases) - [Changelog](https://github.com/infinispan/infinispan/blob/main/Jenkinsfile-release) - [Commits](https://github.com/infinispan/infinispan/compare/11.0.17.Final...11.0.18.Final) --- updated-dependencies: - dependency-name: org.infinispan:infinispan-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * not needed, bad merge Signed-off-by: Olivier Lamy <olamy@apache.org> * fix version Signed-off-by: Olivier Lamy <olamy@apache.org> --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Olivier Lamy <olamy@apache.org> Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com> Co-authored-by: Chad Wilson <chadw@thoughtworks.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
81814dc226
commit
91d11941b3
|
@ -90,6 +90,7 @@
|
|||
<reconcile propertyName="skipExec" skipValue="true"/>
|
||||
<reconcile propertyName="skipTests" skipValue="true"/>
|
||||
<reconcile propertyName="testFailureIgnore" skipValue="true"/>
|
||||
<reconcile propertyName="test" skipValue="true"/>
|
||||
</reconciles>
|
||||
<nologs>
|
||||
<nolog propertyName="systemPropertyVariables"/>
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
<name>Build :: Resources</name>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
<!-- versions for these plugins are not based on parent pom -->
|
||||
<maven.deploy.plugin.version>3.1.1</maven.deploy.plugin.version>
|
||||
<maven.javadoc.plugin.version>3.4.0</maven.javadoc.plugin.version>
|
||||
<maven.remote-resources.plugin.version>3.1.0</maven.remote-resources.plugin.version>
|
||||
<maven.surefire.plugin.version>3.1.2</maven.surefire.plugin.version>
|
||||
<maven.surefire.plugin.version>3.2.1</maven.surefire.plugin.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
|
|
|
@ -175,7 +175,7 @@ To allow the greatest degree of flexibility in using JAAS with web applications,
|
|||
Note that you don't ordinarily need to set these explicitly, as Jetty has defaults which will work in 99% of cases.
|
||||
However, should you need to, you can configure:
|
||||
|
||||
* a CallbackHandler (Default: `org.eclipse.jetty.jaas.callback.DefaultCallbackHandler`)
|
||||
* a CallbackHandler (Default: `org.eclipse.jetty.security.jaas.callback.DefaultCallbackHandler`)
|
||||
* a list of classnames for the Principal implementation that equate to a user role (Default: `org.eclipse.jetty.jaas.JAASRole`)
|
||||
|
||||
Here's an example of setting each of these (to their default values):
|
||||
|
@ -186,7 +186,7 @@ Here's an example of setting each of these (to their default values):
|
|||
<Set name="Name">Test JAAS Realm</Set>
|
||||
<Set name="LoginModuleName">xyz</Set>
|
||||
<Set name="CallbackHandlerClass">
|
||||
org.eclipse.jetty.jaas.callback.DefaultCallbackHandler
|
||||
org.eclipse.jetty.security.jaas.callback.DefaultCallbackHandler
|
||||
</Set>
|
||||
<Set name="roleClassNames">
|
||||
<Array type="java.lang.String">
|
||||
|
|
|
@ -12,11 +12,28 @@
|
|||
<description>The common Jetty security implementation</description>
|
||||
|
||||
<properties>
|
||||
<apache.directory.api.version>2.1.5</apache.directory.api.version>
|
||||
<apacheds.version>2.0.0.AM27</apacheds.version>
|
||||
<bundle-symbolic-name>${project.groupId}.security</bundle-symbolic-name>
|
||||
<spotbugs.onlyAnalyze>org.eclipse.jetty.security.*</spotbugs.onlyAnalyze>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-asn1-api</artifactId>
|
||||
<version>${apache.directory.api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-model</artifactId>
|
||||
<version>${apache.directory.api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-util</artifactId>
|
||||
<version>${apache.directory.api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
|
@ -25,6 +42,70 @@
|
|||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-schema-data</artifactId>
|
||||
<version>${apache.directory.api.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-core-integ</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-schema-data</artifactId>
|
||||
</exclusion>
|
||||
<!-- exclude additional LDIF schema files to avoid conflicts through
|
||||
multiple copies -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.shared</groupId>
|
||||
<artifactId>shared-ldap-schema</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-server-integ</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-schema-data</artifactId>
|
||||
</exclusion>
|
||||
<!-- exclude additional LDIF schema files to avoid conflicts through
|
||||
multiple copies -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.shared</groupId>
|
||||
<artifactId>shared-ldap-schema</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-test-framework</artifactId>
|
||||
<version>${apacheds.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-schema-data</artifactId>
|
||||
</exclusion>
|
||||
<!-- exclude additional LDIF schema files to avoid conflicts through
|
||||
multiple copies -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.directory.shared</groupId>
|
||||
<artifactId>shared-ldap-schema</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http-tools</artifactId>
|
||||
|
@ -55,7 +136,8 @@
|
|||
<configuration>
|
||||
<argLine>@{argLine}
|
||||
${jetty.surefire.argLine}
|
||||
--add-reads org.eclipse.jetty.security=org.eclipse.jetty.logging</argLine>
|
||||
--add-reads org.eclipse.jetty.security=org.eclipse.jetty.logging
|
||||
--add-exports org.eclipse.jetty.security/org.eclipse.jetty.security.jaas.spi=java.base</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
|
|
@ -1,319 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.jaas;
|
||||
|
||||
/* TODO
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
|
||||
import javax.security.auth.login.Configuration;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||
import org.apache.directory.server.annotations.CreateTransport;
|
||||
import org.apache.directory.server.core.annotations.ApplyLdifs;
|
||||
import org.apache.directory.server.core.annotations.CreateDS;
|
||||
import org.apache.directory.server.core.annotations.CreatePartition;
|
||||
import org.apache.directory.server.core.integ.FrameworkRunner;
|
||||
import org.apache.directory.server.ldap.LdapServer;
|
||||
import org.eclipse.jetty.ee10.jaas.spi.LdapLoginModule;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.ee10.servlet.security.DefaultIdentityService;
|
||||
import org.eclipse.jetty.ee10.servlet.security.authentication.BasicAuthenticator;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
@EnabledForJreRange(max = JRE.JAVA_17, disabledReason = "sun.security.x509.X509CertInfo.set not present in Java 21, needs a Java 21 compatible version of Apache Directory Server")
|
||||
@RunWith(FrameworkRunner.class)
|
||||
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP")})
|
||||
@CreateDS(allowAnonAccess = false, partitions = {
|
||||
@CreatePartition(name = "Users Partition", suffix = "ou=people,dc=jetty,dc=org"),
|
||||
@CreatePartition(name = "Groups Partition", suffix = "ou=groups,dc=jetty,dc=org")
|
||||
})
|
||||
@ApplyLdifs({
|
||||
// Entry 1
|
||||
"dn: ou=people,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: people",
|
||||
// Entry # 2
|
||||
"dn:uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: someone",
|
||||
"sn: sn test",
|
||||
"userPassword: complicatedpassword",
|
||||
// Entry # 3
|
||||
"dn:uid=someoneelse,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: someoneelse",
|
||||
"sn: sn test",
|
||||
"userPassword: verycomplicatedpassword",
|
||||
// Entry 4
|
||||
"dn: ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
// Entry 5
|
||||
"dn: ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: subdir",
|
||||
// Entry # 6
|
||||
"dn:uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: uniqueuser",
|
||||
"sn: unique user",
|
||||
"userPassword: hello123",
|
||||
// Entry # 7
|
||||
"dn:uid=ambiguousone,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: ambiguous1",
|
||||
"sn: ambiguous user",
|
||||
"userPassword: foobar",
|
||||
// Entry # 8
|
||||
"dn:uid=ambiguousone,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: ambiguous2",
|
||||
"sn: ambiguous subdir user",
|
||||
"userPassword: barfoo",
|
||||
// Entry 9
|
||||
"dn: cn=developers,ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: groupOfUniqueNames",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
"description: People who try to build good software",
|
||||
"uniquemember: uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"cn: developers",
|
||||
// Entry 10
|
||||
"dn: cn=admin,ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: groupOfUniqueNames",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
"description: People who try to run software build by developers",
|
||||
"uniquemember: uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=someoneelse,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"cn: admin"
|
||||
})
|
||||
*/
|
||||
|
||||
public class JAASLdapLoginServiceTest
|
||||
{
|
||||
/* TODO restore this test
|
||||
public static class TestConfiguration extends Configuration
|
||||
{
|
||||
private boolean forceBindingLogin;
|
||||
|
||||
public TestConfiguration(boolean forceBindingLogin)
|
||||
{
|
||||
this.forceBindingLogin = forceBindingLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
|
||||
{
|
||||
Map<String, String> options = new HashMap<>();
|
||||
options.put("hostname", "localhost");
|
||||
options.put("port", Integer.toString(_ldapServer.getTransports()[0].getPort()));
|
||||
options.put("contextFactory", "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
options.put("bindDn", "uid=admin,ou=system");
|
||||
options.put("bindPassword", "secret");
|
||||
options.put("userBaseDn", "ou=people,dc=jetty,dc=org");
|
||||
options.put("roleBaseDn", "ou=groups,dc=jetty,dc=org");
|
||||
options.put("roleNameAttribute", "cn");
|
||||
options.put("forceBindingLogin", Boolean.toString(forceBindingLogin));
|
||||
AppConfigurationEntry entry = new AppConfigurationEntry(LdapLoginModule.class.getCanonicalName(), LoginModuleControlFlag.REQUIRED, options);
|
||||
|
||||
return new AppConfigurationEntry[]{entry};
|
||||
}
|
||||
}
|
||||
|
||||
public static LdapServer getLdapServer()
|
||||
{
|
||||
return _ldapServer;
|
||||
}
|
||||
|
||||
public static void setLdapServer(LdapServer ldapServer)
|
||||
{
|
||||
_ldapServer = ldapServer;
|
||||
}
|
||||
|
||||
private static LdapServer _ldapServer;
|
||||
private Server _server;
|
||||
private LocalConnector _connector;
|
||||
private ServletContextHandler _context;
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
_connector = new LocalConnector(_server);
|
||||
_server.addConnector(_connector);
|
||||
|
||||
_context = new ServletContextHandler();
|
||||
_context.setContextPath("/ctx");
|
||||
_server.setHandler(_context);
|
||||
ConstraintSecurityHandler security = new ConstraintSecurityHandler();
|
||||
security.setAuthenticator(new BasicAuthenticator());
|
||||
_context.setSecurityHandler(security);
|
||||
}
|
||||
|
||||
private JAASLoginService jaasLoginService(String name)
|
||||
{
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.ee10.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(true));
|
||||
return ls;
|
||||
}
|
||||
|
||||
private String doLogin(String username, String password, List<String> hasRoles, List<String> hasntRoles) throws Exception
|
||||
{
|
||||
JAASLoginService ls = jaasLoginService("foo");
|
||||
_server.addBean(ls, true);
|
||||
|
||||
_context.setServletHandler(new ServletHandler());
|
||||
ServletHolder holder = new ServletHolder();
|
||||
holder.setServlet(new TestServlet(hasRoles, hasntRoles));
|
||||
_context.getServletHandler().addServletWithMapping(holder, "/");
|
||||
|
||||
_server.start();
|
||||
|
||||
return _connector.getResponse("GET /ctx/test HTTP/1.0\n" + "Authorization: Basic " +
|
||||
Base64.getEncoder().encodeToString((username + ":" + password).getBytes(ISO_8859_1)) + "\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapUserIdentity() throws Exception
|
||||
{
|
||||
setUp();
|
||||
_context.setServletHandler(new ServletHandler());
|
||||
ServletHolder holder = new ServletHolder();
|
||||
holder.setServlet(new TestServlet(Arrays.asList("developers", "admin"), Arrays.asList("blabla")));
|
||||
_context.getServletHandler().addServletWithMapping(holder, "/");
|
||||
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.ee10.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(false));
|
||||
|
||||
_server.addBean(ls, true);
|
||||
_server.start();
|
||||
|
||||
String response = _connector.getResponse("GET /ctx/test HTTP/1.0\n" + "Authorization: Basic " +
|
||||
Base64.getEncoder().encodeToString("someone:complicatedpassword".getBytes(ISO_8859_1)) + "\n\n");
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
_server.stop();
|
||||
|
||||
_context.setServletHandler(new ServletHandler());
|
||||
holder = new ServletHolder();
|
||||
holder.setServlet(new TestServlet(Arrays.asList("admin"), Arrays.asList("developers, blabla")));
|
||||
_context.getServletHandler().addServletWithMapping(holder, "/");
|
||||
|
||||
_server.start();
|
||||
|
||||
response = _connector.getResponse("GET /ctx/test HTTP/1.0\n" + "Authorization: Basic " +
|
||||
Base64.getEncoder().encodeToString("someoneelse:verycomplicatedpassword".getBytes(ISO_8859_1)) + "\n\n");
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapUserIdentityBindingLogin() throws Exception
|
||||
{
|
||||
setUp();
|
||||
_context.setServletHandler(new ServletHandler());
|
||||
ServletHolder holder = new ServletHolder();
|
||||
holder.setServlet(new TestServlet(Arrays.asList("developers", "admin"), Arrays.asList("blabla")));
|
||||
_context.getServletHandler().addServletWithMapping(holder, "/");
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.ee10.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(true));
|
||||
_server.addBean(ls, true);
|
||||
_server.start();
|
||||
|
||||
|
||||
String response = _connector.getResponse("GET /ctx/test HTTP/1.0\n" + "Authorization: Basic " +
|
||||
Base64.getEncoder().encodeToString("someone:complicatedpassword".getBytes(ISO_8859_1)) + "\n\n");
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
|
||||
_server.stop();
|
||||
_context.setServletHandler(new ServletHandler());
|
||||
_context.addServlet(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
//check authentication status
|
||||
if (req.getUserPrincipal() == null)
|
||||
req.authenticate(resp);
|
||||
}
|
||||
|
||||
}, "/");
|
||||
_server.start();
|
||||
|
||||
//TODO this test shows response already committed!
|
||||
response = _connector.getResponse("GET /ctx/test HTTP/1.0\n" + "Authorization: Basic " +
|
||||
Base64.getEncoder().encodeToString("someone:wrongpassword".getBytes(ISO_8859_1)) + "\n\n");
|
||||
System.err.println(response);
|
||||
assertThat(response, startsWith("HTTP/1.1 " + HttpServletResponse.SC_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapBindingSubdirUniqueUserName() throws Exception
|
||||
{
|
||||
setUp();
|
||||
String response = doLogin("uniqueuser", "hello123", Arrays.asList("developers", "admin"), Arrays.asList("blabla"));
|
||||
assertThat(response, startsWith("HTTP/1.1 200 OK"));
|
||||
}
|
||||
|
||||
//TODO test is failing, needs more work
|
||||
@Test
|
||||
public void testLdapBindingAmbiguousUserName() throws Exception
|
||||
{
|
||||
setUp();
|
||||
String response = doLogin("ambiguousone", "foobar", null, null);
|
||||
assertThat(response, startsWith("HTTP/1.1 " + HttpServletResponse.SC_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
//TODO test is failing, needs more work
|
||||
@Test
|
||||
public void testLdapBindingSubdirAmbiguousUserName() throws Exception
|
||||
{
|
||||
setUp();
|
||||
String response = doLogin("ambiguousone", "barfoo", null, null);
|
||||
assertThat(response, startsWith("HTTP/1.1 " + HttpServletResponse.SC_UNAUTHORIZED));
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,400 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.jaas;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
|
||||
import javax.security.auth.login.Configuration;
|
||||
|
||||
import org.apache.directory.server.annotations.CreateLdapServer;
|
||||
import org.apache.directory.server.annotations.CreateTransport;
|
||||
import org.apache.directory.server.core.annotations.ApplyLdifs;
|
||||
import org.apache.directory.server.core.annotations.CreateDS;
|
||||
import org.apache.directory.server.core.annotations.CreatePartition;
|
||||
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
|
||||
import org.apache.directory.server.core.integ.ApacheDSTestExtension;
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.security.DefaultIdentityService;
|
||||
import org.eclipse.jetty.security.UserIdentity;
|
||||
import org.eclipse.jetty.security.jaas.spi.LdapLoginModule;
|
||||
import org.eclipse.jetty.server.Components;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.HttpStream;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.server.TunnelSupport;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* JAASLdapLoginServiceTest
|
||||
*/
|
||||
@ExtendWith(ApacheDSTestExtension.class)
|
||||
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP")})
|
||||
@CreateDS(name = "JAASLdapLoginServiceTest-class", partitions = {
|
||||
@CreatePartition(name = "Users Partition", suffix = "ou=people,dc=jetty,dc=org"),
|
||||
@CreatePartition(name = "Groups Partition", suffix = "ou=groups,dc=jetty,dc=org")
|
||||
})
|
||||
@ApplyLdifs({
|
||||
// Entry 1
|
||||
"dn: ou=people,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: people",
|
||||
// Entry # 2
|
||||
"dn:uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: someone",
|
||||
"sn: sn test",
|
||||
"userPassword: complicatedpassword",
|
||||
// Entry # 3
|
||||
"dn:uid=someoneelse,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: someoneelse",
|
||||
"sn: sn test",
|
||||
"userPassword: verycomplicatedpassword",
|
||||
// Entry 4
|
||||
"dn: ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
// Entry 5
|
||||
"dn: ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: organizationalunit",
|
||||
"objectClass: top",
|
||||
"ou: subdir",
|
||||
// Entry # 6
|
||||
"dn:uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: uniqueuser",
|
||||
"sn: unique user",
|
||||
"userPassword: hello123",
|
||||
// Entry # 7
|
||||
"dn:uid=ambiguousone,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: ambiguous1",
|
||||
"sn: ambiguous user",
|
||||
"userPassword: foobar",
|
||||
// Entry # 8
|
||||
"dn:uid=ambiguousone,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"objectClass: inetOrgPerson",
|
||||
"cn: ambiguous2",
|
||||
"sn: ambiguous subdir user",
|
||||
"userPassword: barfoo",
|
||||
// Entry 9
|
||||
"dn: cn=developers,ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: groupOfUniqueNames",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
"description: People who try to build good software",
|
||||
"uniquemember: uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"cn: developers",
|
||||
// Entry 10
|
||||
"dn: cn=admin,ou=groups,dc=jetty,dc=org",
|
||||
"objectClass: groupOfUniqueNames",
|
||||
"objectClass: top",
|
||||
"ou: groups",
|
||||
"description: People who try to run software build by developers",
|
||||
"uniquemember: uid=someone,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=someoneelse,ou=people,dc=jetty,dc=org",
|
||||
"uniquemember: uid=uniqueuser,ou=subdir,ou=people,dc=jetty,dc=org",
|
||||
"cn: admin"
|
||||
})
|
||||
public class JAASLdapLoginServiceTest extends AbstractLdapTestUnit
|
||||
{
|
||||
private JAASLoginService jaasLoginService(String name)
|
||||
{
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.security.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(true));
|
||||
return ls;
|
||||
}
|
||||
|
||||
private UserIdentity doLogin(String username, String password) throws Exception
|
||||
{
|
||||
JAASLoginService ls = jaasLoginService("foo");
|
||||
Request request = new MockCoreRequest();
|
||||
return ls.login(username, password, request, null);
|
||||
}
|
||||
|
||||
public static class TestConfiguration extends Configuration
|
||||
{
|
||||
private boolean forceBindingLogin;
|
||||
|
||||
public TestConfiguration(boolean forceBindingLogin)
|
||||
{
|
||||
this.forceBindingLogin = forceBindingLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
|
||||
{
|
||||
Map<String, String> options = new HashMap<>();
|
||||
options.put("hostname", "localhost");
|
||||
options.put("port", Integer.toString(ldapServer.getTransports()[0].getPort()));
|
||||
options.put("contextFactory", "com.sun.jndi.ldap.LdapCtxFactory");
|
||||
options.put("bindDn", "uid=admin,ou=system");
|
||||
options.put("bindPassword", "secret");
|
||||
options.put("userBaseDn", "ou=people,dc=jetty,dc=org");
|
||||
options.put("roleBaseDn", "ou=groups,dc=jetty,dc=org");
|
||||
options.put("roleNameAttribute", "cn");
|
||||
options.put("forceBindingLogin", Boolean.toString(forceBindingLogin));
|
||||
AppConfigurationEntry entry = new AppConfigurationEntry(LdapLoginModule.class.getCanonicalName(), LoginModuleControlFlag.REQUIRED, options);
|
||||
|
||||
return new AppConfigurationEntry[]{entry};
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapUserIdentity() throws Exception
|
||||
{
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.security.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(false));
|
||||
Request request = new MockCoreRequest();
|
||||
UserIdentity userIdentity = ls.login("someone", "complicatedpassword", request, null);
|
||||
assertNotNull(userIdentity);
|
||||
assertTrue(userIdentity.isUserInRole("developers"));
|
||||
assertTrue(userIdentity.isUserInRole("admin"));
|
||||
assertFalse(userIdentity.isUserInRole("blabla"));
|
||||
|
||||
userIdentity = ls.login("someoneelse", "verycomplicatedpassword", request, null);
|
||||
assertNotNull(userIdentity);
|
||||
assertFalse(userIdentity.isUserInRole("developers"));
|
||||
assertTrue(userIdentity.isUserInRole("admin"));
|
||||
assertFalse(userIdentity.isUserInRole("blabla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapUserIdentityBindingLogin() throws Exception
|
||||
{
|
||||
JAASLoginService ls = new JAASLoginService("foo");
|
||||
ls.setCallbackHandlerClass("org.eclipse.jetty.security.jaas.callback.DefaultCallbackHandler");
|
||||
ls.setIdentityService(new DefaultIdentityService());
|
||||
ls.setConfiguration(new TestConfiguration(true));
|
||||
Request request = new MockCoreRequest();
|
||||
UserIdentity userIdentity = ls.login("someone", "complicatedpassword", request, null);
|
||||
assertNotNull(userIdentity);
|
||||
assertTrue(userIdentity.isUserInRole("developers"));
|
||||
assertTrue(userIdentity.isUserInRole("admin"));
|
||||
assertFalse(userIdentity.isUserInRole("blabla"));
|
||||
|
||||
userIdentity = ls.login("someone", "wrongpassword", request, null);
|
||||
assertNull(userIdentity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapBindingSubdirUniqueUserName() throws Exception
|
||||
{
|
||||
UserIdentity userIdentity = doLogin("uniqueuser", "hello123");
|
||||
assertNotNull(userIdentity);
|
||||
assertTrue(userIdentity.isUserInRole("developers"));
|
||||
assertTrue(userIdentity.isUserInRole("admin"));
|
||||
assertFalse(userIdentity.isUserInRole("blabla"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapBindingAmbiguousUserName() throws Exception
|
||||
{
|
||||
UserIdentity userIdentity = doLogin("ambiguousone", "foobar");
|
||||
assertNull(userIdentity);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLdapBindingSubdirAmbiguousUserName() throws Exception
|
||||
{
|
||||
UserIdentity userIdentity = doLogin("ambiguousone", "barfoo");
|
||||
assertNull(userIdentity);
|
||||
}
|
||||
|
||||
private static class MockCoreRequest implements Request
|
||||
{
|
||||
|
||||
@Override
|
||||
public Object removeAttribute(String name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setAttribute(String name, Object attribute)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getAttributeNameSet()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Components getComponents()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionMetaData getConnectionMetaData()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpURI getHttpURI()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getHeaders()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpFields getTrailers()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<HttpCookie> getCookies()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBeginNanoTime()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeadersNanoTime()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void demand(Runnable demandCallback)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable failure)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIdleTimeoutListener(Predicate<TimeoutException> onIdleTimeout)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFailureListener(Consumer<Throwable> onFailure)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public TunnelSupport getTunnelSupport()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHttpStreamWrapper(Function<HttpStream, HttpStream> wrapper)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session getSession(boolean create)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue