Browse Source

[Improvement-16106] Add admin-user-filter in LDAP (#16826)

* improvement 16106

* fix ut

* fix comment

* fix spotless
dev
xiangzihao 5 days ago committed by GitHub
parent
commit
627c76b75a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/api-test.yml
  2. 37
      docs/docs/en/guide/security/authentication-type.md
  3. 34
      docs/docs/zh/guide/security/authentication-type.md
  4. 26
      dolphinscheduler-api-test/README.md
  5. 2
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java
  6. 112
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/LdapLoginAPITest.java
  7. 48
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/entity/GetUserInfoResponseData.java
  8. 37
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/security/UserPage.java
  9. 321
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/application.yaml
  10. 63
      dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/docker-compose.yaml
  11. 5
      dolphinscheduler-api-test/pom.xml
  12. 35
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/LdapLoginResult.java
  13. 8
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/AbstractAuthenticator.java
  14. 11
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticator.java
  15. 125
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapService.java
  16. 4
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/pwd/PasswordAuthenticator.java
  17. 6
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/sso/CasdoorAuthenticator.java
  18. 12
      dolphinscheduler-api/src/main/resources/application.yaml
  19. 1
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java
  20. 20
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticatorTest.java
  21. 107
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java
  22. 9
      dolphinscheduler-api/src/test/resources/application.yaml
  23. 12
      dolphinscheduler-standalone-server/src/main/resources/application.yaml

2
.github/workflows/api-test.yml

@ -115,6 +115,8 @@ jobs:
class: org.apache.dolphinscheduler.api.test.cases.ExecutorAPITest class: org.apache.dolphinscheduler.api.test.cases.ExecutorAPITest
- name: WorkflowInstanceAPITest - name: WorkflowInstanceAPITest
class: org.apache.dolphinscheduler.api.test.cases.WorkflowInstanceAPITest class: org.apache.dolphinscheduler.api.test.cases.WorkflowInstanceAPITest
- name: LdapLoginAPITest
class: org.apache.dolphinscheduler.api.test.cases.LdapLoginAPITest
env: env:
RECORDING_PATH: /tmp/recording-${{ matrix.case.name }} RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
steps: steps:

37
docs/docs/en/guide/security/authentication-type.md

@ -10,21 +10,23 @@
security: security:
authentication: authentication:
# Authentication types (supported types: PASSWORD,LDAP,CASDOOR_SSO) # Authentication types (supported types: PASSWORD,LDAP,CASDOOR_SSO)
type: PASSWORD type: LDAP
# IF you set type `LDAP`, below config will be effective # IF you set type `LDAP`, below config will be effective
ldap: ldap:
# ldap server config # ldap server config
urls: ldap://ldap.forumsys.com:389/ url: ldap://ldap.forumsys.com:389/
base-dn: dc=example,dc=com base-dn: dc=example,dc=com
username: cn=read-only-admin,dc=example,dc=com username: cn=admin,dc=example,dc=com
password: password password: password
user: user:
# admin userId when you use LDAP login # admin userId when you use LDAP login
admin: read-only-admin admin: ldap-admin
# user search filter to find admin user
admin-user-filter: (&(cn={0}))
identity-attribute: uid identity-attribute: uid
email-attribute: mail email-attribute: mail
# action when ldap user is not exist (supported types: CREATE,DENY) # action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE not-exist-action: DENY
ssl: ssl:
enable: false enable: false
# jks file absolute path && password # jks file absolute path && password
@ -71,31 +73,6 @@ casdoor:
redirect-url: "" redirect-url: ""
``` ```
For detailed explanation of specific fields, please see: [Api-server related configuration](../../architecture/configuration.md)
## LDAP Test
We offer you a unit-test class while you can test the integration of DolphinScheduler with LDAP without running the service.
> dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java
You can follow guide below:
- Change`TestPropertySource`configuration to your LDAP information.
- Change userId && userPwd to your information in the `ldapLogin` method.
- Change the expected email to the return value you expect in the `ldapLogin` method.
- Run`ldapLogin`method and determine whether the LDAP login result is expected.
If you want to enable ssl, please change configuration in `TestPropertySource` like below:
```
security.authentication.ldap.ssl.enable=false
// absolute path
security.authentication.ldap.ssl.trust-store=/ldapkeystore.jks
security.authentication.ldap.ssl.trust-store-password=yourpassword
```
Then run`ldapLoginSSL`method and determine whether the LDAP login result is expected.
## Casdoor SSO ## Casdoor SSO
[Casdoor](https://casdoor.org/) is a UI-first Identity Access Management (IAM) / Single-Sign-On (SSO) platform based on OAuth 2.0, OIDC, SAML and CAS. You can add SSO capability to Dolphinscheduler through Casdoor by following these steps: [Casdoor](https://casdoor.org/) is a UI-first Identity Access Management (IAM) / Single-Sign-On (SSO) platform based on OAuth 2.0, OIDC, SAML and CAS. You can add SSO capability to Dolphinscheduler through Casdoor by following these steps:

34
docs/docs/zh/guide/security/authentication-type.md

@ -14,17 +14,18 @@ security:
# IF you set type `LDAP`, below config will be effective # IF you set type `LDAP`, below config will be effective
ldap: ldap:
# ldap server config # ldap server config
urls: ldap://ldap.forumsys.com:389/ url: ldap://ldap.forumsys.com:389/
base-dn: dc=example,dc=com base-dn: dc=example,dc=com
username: cn=read-only-admin,dc=example,dc=com username: cn=admin,dc=example,dc=com
password: password password: password
user: user:
# admin userId when you use LDAP login # admin userId when you use LDAP login
admin: read-only-admin admin: ldap-admin
# user search filter to find admin user
identity-attribute: uid identity-attribute: uid
email-attribute: mail email-attribute: mail
# action when ldap user is not exist (supported types: CREATE,DENY) # action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE not-exist-action: DENY
ssl: ssl:
enable: false enable: false
# jks file absolute path && password # jks file absolute path && password
@ -71,31 +72,6 @@ casdoor:
redirect-url: "" redirect-url: ""
``` ```
具体字段解释详见:[Api-server相关配置](../../architecture/configuration.md)
## 开发者LDAP测试
我们提供了一个单元测试类,可以在不启动项目的情况下测试DolphinScheduler与LDAP的集成。
> dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java
使用步骤如下:
- 修改`TestPropertySource`配置参数为你的LDAP信息;
- 修改`ldapLogin`方法中的userId和userPwd为你的账号密码;
- 修改`ldapLogin`方法中的expected email为正常登陆的返回值;
- 执行`ldapLogin`方法,判断LDAP登陆结果是否为预期;
如果你要启用ssl,请修改`TestPropertySource`配置中ssl相关参数为:
```
security.authentication.ldap.ssl.enable=false
// absolute path
security.authentication.ldap.ssl.trust-store=/ldapkeystore.jks
security.authentication.ldap.ssl.trust-store-password=yourpassword
```
运行`ldapLoginSSL`方法,判断email是否为预期的返回值。
## 通过 Casdoor 实现 SSO 登录 ## 通过 Casdoor 实现 SSO 登录
Casdoor 是基于 OAuth 2.0、OIDC、SAML 和 CAS 的面向 UI 的身份访问管理(IAM)/单点登录(SSO)平台。您可以通过以下步骤通过 Casdoor 为 Dolphinscheduler 添加 SSO 功能: Casdoor 是基于 OAuth 2.0、OIDC、SAML 和 CAS 的面向 UI 的身份访问管理(IAM)/单点登录(SSO)平台。您可以通过以下步骤通过 Casdoor 为 Dolphinscheduler 添加 SSO 功能:

26
dolphinscheduler-api-test/README.md

@ -43,3 +43,29 @@ class TenantAPITest {
} }
``` ```
## Notes
## Local development
### Mac M1
Add VM options to the test configuration in IntelliJ IDEA:
```
# In this mode you need to install docker desktop for mac and run it with locally
-Dm1_chip=true
```
### Running locally(without Docker)
```
# In this mode you need to start frontend and backend services locally
-Dlocal=true
```
### Running locally(with Docker)
```
# In this mode you only need to install docker locally
```
- To run the tests locally, you need to have the DolphinScheduler running locally. You should add `dolphinscheduler-api-test/pom.xml` to the maven project
Since it does not participate in project compilation, it is not in the main project.
- Running run test class `org.apache.dolphinscheduler.api.test.cases.TenantAPITest` in the IDE.

2
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java

@ -47,10 +47,12 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DisableIfTestFails;
//TODO: Some test cases rely on WorkflowInstance APIs. Should complete remaining cases after WorkflowInstance related API tests done. //TODO: Some test cases rely on WorkflowInstance APIs. Should complete remaining cases after WorkflowInstance related API tests done.
@DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml") @DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml")
@Slf4j @Slf4j
@DisableIfTestFails
public class ExecutorAPITest { public class ExecutorAPITest {
private static final String username = "admin"; private static final String username = "admin";

112
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/LdapLoginAPITest.java

@ -0,0 +1,112 @@
/*
* 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.dolphinscheduler.api.test.cases;
import org.apache.dolphinscheduler.api.test.core.DolphinScheduler;
import org.apache.dolphinscheduler.api.test.entity.GetUserInfoResponseData;
import org.apache.dolphinscheduler.api.test.entity.HttpResponse;
import org.apache.dolphinscheduler.api.test.entity.LoginResponseData;
import org.apache.dolphinscheduler.api.test.pages.LoginPage;
import org.apache.dolphinscheduler.api.test.pages.security.UserPage;
import org.apache.dolphinscheduler.api.test.utils.JSONUtils;
import org.apache.dolphinscheduler.common.enums.UserType;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DisableIfTestFails;
@DolphinScheduler(composeFiles = "docker/ldap-login/docker-compose.yaml")
@Slf4j
@DisableIfTestFails
public class LdapLoginAPITest {
private static String sessionId;
@Test
@Order(10)
public void testAdminUserLoginSuccess() {
final String username = "admin_user01";
final String password = "123";
LoginPage loginPage = new LoginPage();
HttpResponse loginHttpResponse = loginPage.login(username, password);
sessionId =
JSONUtils.convertValue(loginHttpResponse.getBody().getData(), LoginResponseData.class).getSessionId();
UserPage userPage = new UserPage();
HttpResponse getUserInfoHttpResponse = userPage.getUserInfo(sessionId);
GetUserInfoResponseData getUserInfoResponseData =
JSONUtils.convertValue(getUserInfoHttpResponse.getBody().getData(), GetUserInfoResponseData.class);
Assertions.assertEquals(username, getUserInfoResponseData.getUserName());
Assertions.assertEquals(UserType.ADMIN_USER, getUserInfoResponseData.getUserType());
}
@Test
@Order(20)
public void testAdminUserFilterLoginSuccess() {
final String username = "admin_user03";
final String password = "123";
LoginPage loginPage = new LoginPage();
HttpResponse loginHttpResponse = loginPage.login(username, password);
sessionId =
JSONUtils.convertValue(loginHttpResponse.getBody().getData(), LoginResponseData.class).getSessionId();
UserPage userPage = new UserPage();
HttpResponse getUserInfoHttpResponse = userPage.getUserInfo(sessionId);
GetUserInfoResponseData getUserInfoResponseData =
JSONUtils.convertValue(getUserInfoHttpResponse.getBody().getData(), GetUserInfoResponseData.class);
Assertions.assertEquals(username, getUserInfoResponseData.getUserName());
Assertions.assertEquals(UserType.ADMIN_USER, getUserInfoResponseData.getUserType());
}
@Test
@Order(30)
public void testGeneralUserLoginSuccess() {
final String username = "general_user02";
final String password = "123";
LoginPage loginPage = new LoginPage();
HttpResponse loginHttpResponse = loginPage.login(username, password);
sessionId =
JSONUtils.convertValue(loginHttpResponse.getBody().getData(), LoginResponseData.class).getSessionId();
UserPage userPage = new UserPage();
HttpResponse getUserInfoHttpResponse = userPage.getUserInfo(sessionId);
GetUserInfoResponseData getUserInfoResponseData =
JSONUtils.convertValue(getUserInfoHttpResponse.getBody().getData(), GetUserInfoResponseData.class);
Assertions.assertEquals(username, getUserInfoResponseData.getUserName());
Assertions.assertEquals(UserType.GENERAL_USER, getUserInfoResponseData.getUserType());
}
@Test
@Order(40)
public void testGeneralUserLoginFailed() {
final String username = "general_user02";
final String password = "1";
LoginPage loginPage = new LoginPage();
HttpResponse loginHttpResponse = loginPage.login(username, password);
Boolean loginResult = loginHttpResponse.getBody().getSuccess();
Assertions.assertFalse(loginResult);
}
}

48
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/entity/GetUserInfoResponseData.java

@ -0,0 +1,48 @@
/*
* 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.dolphinscheduler.api.test.entity;
import org.apache.dolphinscheduler.common.enums.UserType;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GetUserInfoResponseData {
private Integer id;
private String userName;
private String userPassword;
private String email;
private Integer phone;
private UserType userType;
private Integer tenantId;
private Integer state;
private String tenantCode;
private String queueName;
private String alertGroup;
private String queue;
private String timeZone;
private Date createTime;
private Date updateTime;
}

37
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/security/UserPage.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.dolphinscheduler.api.test.pages.security;
import org.apache.dolphinscheduler.api.test.core.Constants;
import org.apache.dolphinscheduler.api.test.entity.HttpResponse;
import org.apache.dolphinscheduler.api.test.utils.RequestClient;
import java.util.HashMap;
import java.util.Map;
public class UserPage {
public HttpResponse getUserInfo(String sessionId) {
Map<String, String> headers = new HashMap<>();
headers.put(Constants.SESSION_ID_KEY, sessionId);
RequestClient requestClient = new RequestClient();
return requestClient.get("/users/get-user-info", headers, new HashMap<>());
}
}

321
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/application.yaml

@ -0,0 +1,321 @@
#
# 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.
#
spring:
profiles:
active: h2
jackson:
time-zone: UTC
date-format: "yyyy-MM-dd HH:mm:ss"
banner:
charset: UTF-8
location: classpath:standalone-banner.txt
sql:
init:
schema-locations: classpath:sql/dolphinscheduler_h2.sql
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:dolphinscheduler;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_LOWER=true
username: sa
password: ""
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: never
properties:
org.quartz.threadPool.threadPriority: 5
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.scheduler.instanceId: AUTO
org.quartz.jobStore.tablePrefix: QRTZ_
org.quartz.jobStore.acquireTriggersWithinLock: true
org.quartz.scheduler.instanceName: DolphinScheduler
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.jobStore.useProperties: false
org.quartz.threadPool.makeThreadsDaemons: true
org.quartz.threadPool.threadCount: 25
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.scheduler.makeSchedulerThreadDaemon: true
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.clusterCheckinInterval: 5000
org.quartz.scheduler.batchTriggerAcquisitionMaxCount: 1
servlet:
multipart:
max-file-size: 1024MB
max-request-size: 1024MB
messages:
basename: i18n/messages
jpa:
hibernate:
ddl-auto: none
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
cloud.discovery.client.composite-indicator.enabled: false
mybatis-plus:
mapper-locations: classpath:org/apache/dolphinscheduler/dao/mapper/*Mapper.xml
type-aliases-package: org.apache.dolphinscheduler.dao.entity
configuration:
cache-enabled: false
call-setters-on-nulls: true
map-underscore-to-camel-case: true
jdbc-type-for-null: NULL
global-config:
db-config:
id-type: auto
banner: false
registry:
type: jdbc
security:
authentication:
# Authentication types (supported types: PASSWORD,LDAP,CASDOOR_SSO)
type: LDAP
# IF you set type `LDAP`, below config will be effective
ldap:
# ldap server config
url: ldap://openldap:1389/
base-dn: ou=users,dc=example,dc=org
username: cn=admin,dc=example,dc=org
password: adminpassword
user:
# admin username when you use LDAP login
admin-username: admin_user01
admin-user-filter: (&(cn={0})(sn=Bar3))
identity-attribute: cn
email-attribute: uid
# action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE
ssl:
enable: false
# jks file absolute path && password
trust-store: "/ldapkeystore.jks"
trust-store-password: ""
casdoor:
user:
admin: admin
oauth2:
enable: false
provider:
github:
authorizationUri: "https://github.com/login/oauth/authorize"
redirectUri: "http://localhost:12345/dolphinscheduler/redirect/login/oauth2"
clientId: ""
clientSecret: ""
tokenUri: "https://github.com/login/oauth/access_token"
userInfoUri: "https://api.github.com/user"
callbackUrl: "http://localhost:5173/login"
iconUri: ""
provider: github
gitee:
authorizationUri: "https://gitee.com/oauth/authorize"
redirectUri: "http://127.0.0.1:12345/dolphinscheduler/redirect/login/oauth2"
clientId: ""
clientSecret: ""
tokenUri: "https://gitee.com/oauth/token?grant_type=authorization_code"
userInfoUri: "https://gitee.com/api/v5/user"
callbackUrl: "http://127.0.0.1:5173/login"
iconUri: ""
provider: gitee
casdoor:
# Your Casdoor server url
endpoint: http://localhost:8000
client-id: ""
client-secret: ""
# The certificate may be multi-line, you can use `|-` for ease
certificate: ""
# Your organization name added in Casdoor
organization-name: built-in
# Your application name added in Casdoor
application-name: dolphinscheduler
# Doplhinscheduler login url
redirect-url: http://localhost:5173/login
master:
listen-port: 5678
# master heartbeat interval
max-heartbeat-interval: 10s
server-load-protection:
enabled: true
# Master max system cpu usage, when the master's system cpu usage is smaller then this value, master server can execute workflow.
max-system-cpu-usage-percentage-thresholds: 1
# Master max jvm cpu usage, when the master's jvm cpu usage is smaller then this value, master server can execute workflow.
max-jvm-cpu-usage-percentage-thresholds: 0.9
# Master max System memory usage , when the master's system memory usage is smaller then this value, master server can execute workflow.
max-system-memory-usage-percentage-thresholds: 0.9
# Master max disk usage , when the master's disk usage is smaller then this value, master server can execute workflow.
max-disk-usage-percentage-thresholds: 0.9
worker-load-balancer-configuration-properties:
# RANDOM, ROUND_ROBIN, FIXED_WEIGHTED_ROUND_ROBIN, DYNAMIC_WEIGHTED_ROUND_ROBIN
type: DYNAMIC_WEIGHTED_ROUND_ROBIN
# dynamic-weight-config-properties only used in DYNAMIC_WEIGHTED_ROUND_ROBIN, the weight of memory-usage, cpu-usage, task-thread-pool-usage should sum to 100.
dynamic-weight-config-properties:
memory-usage-weight: 30
cpu-usage-weight: 30
task-thread-pool-usage-weight: 40
worker-group-refresh-interval: 10s
command-fetch-strategy:
type: ID_SLOT_BASED
config:
# The incremental id step
id-step: 1
# master fetch command num
fetch-size: 10
worker:
# worker listener port
listen-port: 1234
# worker execute thread number to limit task instances in parallel
exec-threads: 10
# worker heartbeat interval
max-heartbeat-interval: 10s
# worker host weight to dispatch tasks, default value 100
host-weight: 100
server-load-protection:
enabled: true
# Worker max system cpu usage, when the worker's system cpu usage is smaller then this value, worker server can be dispatched tasks.
max-system-cpu-usage-percentage-thresholds: 1
# Worker max jvm cpu usage, when the worker's jvm cpu usage is smaller then this value, worker server can be dispatched tasks.
max-jvm-cpu-usage-percentage-thresholds: 0.9
# Worker max System memory usage , when the worker's system memory usage is smaller then this value, worker server can be dispatched tasks.
max-system-memory-usage-percentage-thresholds: 0.9
# Worker max disk usage , when the worker's disk usage is smaller then this value, worker server can be dispatched tasks.
max-disk-usage-percentage-thresholds: 0.9
task-execute-threads-full-policy: REJECT
tenant-config:
# tenant corresponds to the user of the system, which is used by the worker to submit the job. If system does not have this user, it will be automatically created after the parameter worker.tenant.auto.create is true.
auto-create-tenant-enabled: true
# Scenes to be used for distributed users. For example, users created by FreeIpa are stored in LDAP. This parameter only applies to Linux, When this parameter is true, worker.tenant.auto.create has no effect and will not automatically create tenants.
distributed-tenant: false
# If set true, will use worker bootstrap user as the tenant to execute task when the tenant is `default`;
default-tenant-enabled: true
alert:
port: 50052
# Mark each alert of alert server if late after x milliseconds as failed.
# Define value is (0 = infinite), and alert server would be waiting alert result.
wait-timeout: 0
max-heartbeat-interval: 60s
# The maximum number of alerts that can be processed in parallel
sender-parallelism: 5
api:
audit-enable: false
# Traffic control, if you turn on this config, the maximum number of request/s will be limited.
# global max request number per second
# default tenant-level max request number
traffic-control:
global-switch: false
max-global-qps-rate: 300
tenant-switch: false
default-tenant-qps-rate: 10
#customize-tenant-qps-rate:
# eg.
#tenant1: 11
#tenant2: 20
python-gateway:
# Weather enable python gateway server or not. The default value is true.
enabled: true
# Authentication token for connection from python api to python gateway server. Should be changed the default value
# when you deploy in public network.
auth-token: jwUDzpLsNKEFER4*a8gruBH_GsAurNxU7A@Xc
# The address of Python gateway server start. Set its value to `0.0.0.0` if your Python API run in different
# between Python gateway server. It could be be specific to other address like `127.0.0.1` or `localhost`
gateway-server-address: 0.0.0.0
# The port of Python gateway server start. Define which port you could connect to Python gateway server from
# Python API side.
gateway-server-port: 25333
# The address of Python callback client.
python-address: 127.0.0.1
# The port of Python callback client.
python-port: 25334
# Close connection of socket server if no other request accept after x milliseconds. Define value is (0 = infinite),
# and socket server would never close even though no requests accept
connect-timeout: 0
# Close each active connection of socket server if python program not active after x milliseconds. Define value is
# (0 = infinite), and socket server would never close even though no requests accept
read-timeout: 0
server:
port: 12345
servlet:
session:
timeout: 120m
context-path: /dolphinscheduler/
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
jetty:
max-http-form-post-size: 5000000
accesslog:
enabled: true
custom-format: '%{client}a - %u %t "%r" %s %O %{ms}Tms'
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
endpoint:
health:
enabled: true
show-details: always
health:
db:
enabled: true
defaults:
enabled: false
metrics:
tags:
application: ${spring.application.name}
metrics:
enabled: true
# Override by profile
---
spring:
config:
activate:
on-profile: postgresql
quartz:
properties:
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://127.0.0.1:5432/dolphinscheduler
username: root
password: root
---
spring:
config:
activate:
on-profile: mysql
sql:
init:
schema-locations: classpath:sql/dolphinscheduler_mysql.sql
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/dolphinscheduler?useUnicode=true&characterEncoding=UTF-8
username: root
password: root

63
dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/docker-compose.yaml

@ -0,0 +1,63 @@
#
# 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.
#
version: "3.8"
services:
dolphinscheduler:
image: apache/dolphinscheduler-standalone-server:ci
environment:
WORKER_TENANT_AUTO_CREATE: 'true'
ports:
- "12345:12345"
volumes:
- ./application.yaml:/opt/dolphinscheduler/standalone-server/conf/application.yaml
networks:
- api-test
healthcheck:
test: [ "CMD", "curl", "http://localhost:12345/dolphinscheduler/actuator/health" ]
interval: 5s
timeout: 60s
retries: 120
depends_on:
- openldap
openldap:
hostname: openldap
image: bitnami/openldap:2.6
ports:
- '1389:1389'
- '1636:1636'
environment:
- LDAP_ADMIN_USERNAME=admin
- LDAP_ADMIN_PASSWORD=adminpassword
- LDAP_USERS=admin_user01,general_user02,admin_user03
- LDAP_PASSWORDS=123,123,123
- LDAP_ROOT=dc=example,dc=org
- LDAP_ADMIN_DN=cn=admin,dc=example,dc=org
networks:
- api-test
tty: true
stdin_open: true
restart: always
healthcheck:
test: ldapsearch -x -H 'ldap://127.0.0.1:1389' -D 'cn=admin,dc=example,dc=org' -w adminpassword -b 'ou=users,dc=example,dc=org' '(cn=admin_user01)'
interval: 5s
timeout: 60s
retries: 120
networks:
api-test:

5
dolphinscheduler-api-test/pom.xml

@ -36,7 +36,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.7.2</junit.version> <junit.version>5.7.2</junit.version>
<selenium.version>3.141.59</selenium.version> <selenium.version>4.21.0</selenium.version>
<testcontainers.version>1.19.8</testcontainers.version>
<lombok.version>1.18.24</lombok.version> <lombok.version>1.18.24</lombok.version>
<assertj-core.version>3.23.1</assertj-core.version> <assertj-core.version>3.23.1</assertj-core.version>
<awaitility.version>4.1.0</awaitility.version> <awaitility.version>4.1.0</awaitility.version>
@ -133,7 +134,7 @@
<dependency> <dependency>
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId> <artifactId>testcontainers-bom</artifactId>
<version>1.18.1</version> <version>${testcontainers.version}</version>
<scope>import</scope> <scope>import</scope>
<type>pom</type> <type>pom</type>
</dependency> </dependency>

35
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/LdapLoginResult.java

@ -0,0 +1,35 @@
/*
* 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.dolphinscheduler.api.dto;
import org.apache.dolphinscheduler.common.enums.UserType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LdapLoginResult {
private boolean success;
private String ldapEmail;
private UserType userType;
private String userName;
}

8
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/AbstractAuthenticator.java

@ -59,16 +59,16 @@ public abstract class AbstractAuthenticator implements Authenticator {
/** /**
* user login and return user in db * user login and return user in db
* *
* @param userId user identity field * @param userName user identity field
* @param password user login password * @param password user login password
* @return user object in databse * @return user object in databse
*/ */
public abstract User login(@NonNull String userId, String password); public abstract User login(@NonNull String userName, String password);
@Override @Override
public Result<Map<String, String>> authenticate(@NonNull String userId, String password, @NonNull String ip) { public Result<Map<String, String>> authenticate(@NonNull String userName, String password, @NonNull String ip) {
Result<Map<String, String>> result = new Result<>(); Result<Map<String, String>> result = new Result<>();
User user = login(userId, password); User user = login(userName, password);
if (user == null) { if (user == null) {
if (Objects.equals(securityConfig.getType(), AuthenticationType.CASDOOR_SSO.name())) { if (Objects.equals(securityConfig.getType(), AuthenticationType.CASDOOR_SSO.name())) {
log.error("State or code entered incorrectly."); log.error("State or code entered incorrectly.");

11
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticator.java

@ -17,6 +17,7 @@
package org.apache.dolphinscheduler.api.security.impl.ldap; package org.apache.dolphinscheduler.api.security.impl.ldap;
import org.apache.dolphinscheduler.api.dto.LdapLoginResult;
import org.apache.dolphinscheduler.api.security.impl.AbstractAuthenticator; import org.apache.dolphinscheduler.api.security.impl.AbstractAuthenticator;
import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.entity.User;
@ -30,14 +31,14 @@ public class LdapAuthenticator extends AbstractAuthenticator {
LdapService ldapService; LdapService ldapService;
@Override @Override
public User login(@NonNull String userId, String password) { public User login(@NonNull String userName, String password) {
User user = null; User user = null;
String ldapEmail = ldapService.ldapLogin(userId, password); LdapLoginResult ldapLoginResult = ldapService.ldapLogin(userName, password);
if (ldapEmail != null) { if (ldapLoginResult.isSuccess()) {
// check if user exist // check if user exist
user = userService.getUserByUserName(userId); user = userService.getUserByUserName(userName);
if (user == null && ldapService.createIfUserNotExists()) { if (user == null && ldapService.createIfUserNotExists()) {
user = userService.createUser(ldapService.getUserType(userId), userId, ldapEmail); user = userService.createUser(ldapLoginResult.getUserType(), userName, ldapLoginResult.getLdapEmail());
} }
} }
return user; return user;

125
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapService.java

@ -17,11 +17,13 @@
package org.apache.dolphinscheduler.api.security.impl.ldap; package org.apache.dolphinscheduler.api.security.impl.ldap;
import org.apache.dolphinscheduler.api.dto.LdapLoginResult;
import org.apache.dolphinscheduler.api.security.LdapUserNotExistActionType; import org.apache.dolphinscheduler.api.security.LdapUserNotExistActionType;
import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import javax.naming.Context; import javax.naming.Context;
@ -46,11 +48,14 @@ import org.springframework.stereotype.Component;
@Slf4j @Slf4j
public class LdapService { public class LdapService {
@Value("${security.authentication.ldap.user.admin:#{null}}") @Value("${security.authentication.ldap.user.admin-username:#{null}}")
private String adminUserId; private String ldapAdminUserName;
@Value("${security.authentication.ldap.urls:#{null}}") @Value("${security.authentication.ldap.user.admin-user-filter:#{null}}")
private String ldapUrls; private String ldapAdminUserFilter;
@Value("${security.authentication.ldap.url:#{null}}")
private String ldapUrl;
@Value("${security.authentication.ldap.base-dn:#{null}}") @Value("${security.authentication.ldap.base-dn:#{null}}")
private String ldapBaseDn; private String ldapBaseDn;
@ -67,7 +72,7 @@ public class LdapService {
@Value("${security.authentication.ldap.user.email-attribute:#{null}}") @Value("${security.authentication.ldap.user.email-attribute:#{null}}")
private String ldapEmailAttribute; private String ldapEmailAttribute;
@Value("${security.authentication.ldap.user.not-exist-action:CREATE}") @Value("${security.authentication.ldap.user.not-exist-action:DENY}")
private String ldapUserNotExistAction; private String ldapUserNotExistAction;
@Value("${security.authentication.ldap.ssl.enable:false}") @Value("${security.authentication.ldap.ssl.enable:false}")
@ -79,56 +84,58 @@ public class LdapService {
@Value("${security.authentication.ldap.ssl.trust-store-password:#{null}}") @Value("${security.authentication.ldap.ssl.trust-store-password:#{null}}")
private String trustStorePassword; private String trustStorePassword;
/***
* get user type by configured admin userId
* @param userId login userId
* @return user type
*/
public UserType getUserType(String userId) {
return adminUserId.equalsIgnoreCase(userId) ? UserType.ADMIN_USER : UserType.GENERAL_USER;
}
/** /**
* login by userId and return user email * login by userName and return LdapLoginResult
*
* @param userId user identity id
* @param userPwd user login password
* @return user email
*/ */
public String ldapLogin(String userId, String userPwd) { public LdapLoginResult ldapLogin(String userName, String userPwd) {
Properties searchEnv = getManagerLdapEnv(); Properties searchEnv = getManagerLdapEnv();
LdapContext ctx = null; LdapContext ctx = null;
LdapLoginResult ldapLoginResult = new LdapLoginResult();
ldapLoginResult.setSuccess(false);
if (StringUtils.isEmpty(ldapEmailAttribute)) {
log.warn("ldap email attribute is empty, skipping ldap authentication");
return ldapLoginResult;
}
try { try {
// Connect to the LDAP server and Authenticate with a service user of whom we know the DN and credentials // Connect to the LDAP server and Authenticate with a service user of whom we know the DN and credentials
ctx = new InitialLdapContext(searchEnv, null); ctx = new InitialLdapContext(searchEnv, null);
SearchControls sc = new SearchControls(); SearchControls sc = new SearchControls();
sc.setReturningAttributes(new String[]{ldapEmailAttribute}); sc.setReturningAttributes(new String[]{ldapEmailAttribute});
sc.setSearchScope(SearchControls.SUBTREE_SCOPE); sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
EqualsFilter filter = new EqualsFilter(ldapUserIdentifyingAttribute, userId); EqualsFilter userFilter = new EqualsFilter(ldapUserIdentifyingAttribute, userName);
NamingEnumeration<SearchResult> results = ctx.search(ldapBaseDn, filter.toString(), sc); String userSearchEmail = ldapSearch(ctx, null, userPwd, userFilter.toString(), sc, searchEnv);
if (results.hasMore()) {
// get the users DN (distinguishedName) from the result if (StringUtils.isNotEmpty(ldapAdminUserFilter)) {
SearchResult result = results.next(); String adminFilterSearchEmail = ldapSearch(ctx, userName, userPwd, ldapAdminUserFilter, sc, searchEnv);
NamingEnumeration<? extends Attribute> attrs = result.getAttributes().getAll(); if (adminFilterSearchEmail != null) {
while (attrs.hasMore()) { ldapLoginResult.setLdapEmail(adminFilterSearchEmail);
// Open another connection to the LDAP server with the found DN and the password ldapLoginResult.setUserType(UserType.ADMIN_USER);
searchEnv.put(Context.SECURITY_PRINCIPAL, result.getNameInNamespace()); ldapLoginResult.setUserName(userName);
searchEnv.put(Context.SECURITY_CREDENTIALS, userPwd); ldapLoginResult.setSuccess(true);
try { return ldapLoginResult;
new InitialDirContext(searchEnv);
} catch (Exception e) {
log.warn("invalid ldap credentials or ldap search error", e);
return null;
} }
Attribute attr = attrs.next(); } else {
if (attr.getID().equals(ldapEmailAttribute)) { log.debug("ldap admin user filter is empty, skipping admin user filter search");
return (String) attr.get();
} }
if (userSearchEmail != null) {
if (Objects.equals(ldapAdminUserName, userName)) {
ldapLoginResult.setUserType(UserType.ADMIN_USER);
} else {
ldapLoginResult.setUserType(UserType.GENERAL_USER);
} }
ldapLoginResult.setLdapEmail(userSearchEmail);
ldapLoginResult.setUserName(userName);
ldapLoginResult.setSuccess(true);
return ldapLoginResult;
} else {
log.debug("user email attribute {} not found in ldap", ldapEmailAttribute);
} }
} catch (NamingException e) { } catch (NamingException e) {
log.error("ldap search error", e); log.error("ldap search error", e);
return null; return ldapLoginResult;
} finally { } finally {
try { try {
if (ctx != null) { if (ctx != null) {
@ -139,7 +146,7 @@ public class LdapService {
} }
} }
return null; return ldapLoginResult;
} }
/*** /***
@ -152,7 +159,7 @@ public class LdapService {
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, ldapSecurityPrincipal); env.put(Context.SECURITY_PRINCIPAL, ldapSecurityPrincipal);
env.put(Context.SECURITY_CREDENTIALS, ldapPrincipalPassword); env.put(Context.SECURITY_CREDENTIALS, ldapPrincipalPassword);
env.put(Context.PROVIDER_URL, ldapUrls); env.put(Context.PROVIDER_URL, ldapUrl);
if (sslEnable) { if (sslEnable) {
env.put(Context.SECURITY_PROTOCOL, "ssl"); env.put(Context.SECURITY_PROTOCOL, "ssl");
@ -164,6 +171,42 @@ public class LdapService {
return env; return env;
} }
private String ldapSearch(LdapContext ctx,
String userName,
String userPwd,
String filter,
SearchControls sc,
Properties searchEnv) throws NamingException {
NamingEnumeration<SearchResult> results;
if (userName == null) {
results = ctx.search(ldapBaseDn, filter, sc);
} else {
results = ctx.search(ldapBaseDn, filter, new Object[]{userName}, sc);
}
if (results.hasMore()) {
// get the users DN (distinguishedName) from the result
SearchResult result = results.next();
NamingEnumeration<? extends Attribute> attrs = result.getAttributes().getAll();
while (attrs.hasMore()) {
// Open another connection to the LDAP server with the found DN and the password
searchEnv.put(Context.SECURITY_PRINCIPAL, result.getNameInNamespace());
searchEnv.put(Context.SECURITY_CREDENTIALS, userPwd);
try {
new InitialDirContext(searchEnv);
} catch (Exception e) {
log.warn("invalid ldap credentials or ldap search error", e);
return null;
}
Attribute attr = attrs.next();
if (attr.getID().equals(ldapEmailAttribute)) {
return (String) attr.get();
}
}
}
return null;
}
public LdapUserNotExistActionType getLdapUserNotExistAction() { public LdapUserNotExistActionType getLdapUserNotExistAction() {
if (StringUtils.isBlank(ldapUserNotExistAction)) { if (StringUtils.isBlank(ldapUserNotExistAction)) {
log.info( log.info(

4
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/pwd/PasswordAuthenticator.java

@ -23,7 +23,7 @@ import org.apache.dolphinscheduler.dao.entity.User;
public class PasswordAuthenticator extends AbstractAuthenticator { public class PasswordAuthenticator extends AbstractAuthenticator {
@Override @Override
public User login(String userId, String password) { public User login(String userName, String password) {
return userService.queryUser(userId, password); return userService.queryUser(userName, password);
} }
} }

6
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/sso/CasdoorAuthenticator.java

@ -48,7 +48,7 @@ public class CasdoorAuthenticator extends AbstractSsoAuthenticator {
private String adminUserName; private String adminUserName;
@Override @Override
public User login(@NonNull String state, String code) { public User login(@NonNull String userName, String code) {
ServletRequestAttributes servletRequestAttributes = ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes == null) { if (servletRequestAttributes == null) {
@ -59,11 +59,11 @@ public class CasdoorAuthenticator extends AbstractSsoAuthenticator {
// Invalid state // Invalid state
request.getSession().setAttribute(Constants.SSO_LOGIN_USER_STATE, null); request.getSession().setAttribute(Constants.SSO_LOGIN_USER_STATE, null);
// Check state to protect from CSRF attack // Check state to protect from CSRF attack
if (originalState == null || !MessageDigest.isEqual(originalState.getBytes(), state.getBytes())) { if (originalState == null || !MessageDigest.isEqual(originalState.getBytes(), userName.getBytes())) {
return null; return null;
} }
String token = casdoorAuthService.getOAuthToken(code, state); String token = casdoorAuthService.getOAuthToken(code, userName);
CasdoorUser casdoorUser = casdoorAuthService.parseJwtToken(token); CasdoorUser casdoorUser = casdoorAuthService.parseJwtToken(token);
User user = null; User user = null;
if (casdoorUser.getName() != null) { if (casdoorUser.getName() != null) {

12
dolphinscheduler-api/src/main/resources/application.yaml

@ -175,17 +175,19 @@ security:
# IF you set type `LDAP`, below config will be effective # IF you set type `LDAP`, below config will be effective
ldap: ldap:
# ldap server config # ldap server config
urls: ldap://ldap.forumsys.com:389/ url: ldap://ldap.forumsys.com:389/
base-dn: dc=example,dc=com base-dn: dc=example,dc=com
username: cn=read-only-admin,dc=example,dc=com username: cn=admin,dc=example,dc=com
password: password password: password
user: user:
# admin userId when you use LDAP login # admin username when you use LDAP login
admin: read-only-admin admin-username: ldap-admin
# user search filter to find admin user
admin-user-filter: (&(cn={0}))
identity-attribute: uid identity-attribute: uid
email-attribute: mail email-attribute: mail
# action when ldap user is not exist (supported types: CREATE,DENY) # action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE not-exist-action: DENY
ssl: ssl:
enable: false enable: false
# jks file absolute path && password # jks file absolute path && password

1
dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java

@ -27,6 +27,7 @@ import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"security.authentication.type=LDAP", "security.authentication.type=LDAP",
"security.authentication.ldap.user.not-exist-action=CREATE"
}) })
public class SecurityConfigLDAPTest extends AbstractControllerTest { public class SecurityConfigLDAPTest extends AbstractControllerTest {

20
dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticatorTest.java

@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.apache.dolphinscheduler.api.controller.AbstractControllerTest; import org.apache.dolphinscheduler.api.controller.AbstractControllerTest;
import org.apache.dolphinscheduler.api.dto.LdapLoginResult;
import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.security.LdapUserNotExistActionType; import org.apache.dolphinscheduler.api.security.LdapUserNotExistActionType;
import org.apache.dolphinscheduler.api.service.SessionService; import org.apache.dolphinscheduler.api.service.SessionService;
@ -81,11 +82,13 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
private User mockUser; private User mockUser;
private Session mockSession; private Session mockSession;
private String ldapUid = "test"; private final String ldapUid = "test";
private String ldapUserPwd = "password"; private final String ldapUserPwd = "password";
private String ldapEmail = "test@example.com"; private final String ldapEmail = "test@example.com";
private String ip = "127.0.0.1"; private final String ip = "127.0.0.1";
private UserType userType = UserType.GENERAL_USER; private final UserType userType = UserType.GENERAL_USER;
private final LdapLoginResult ldapLoginResultSuccess = new LdapLoginResult(true, ldapEmail, userType, ldapUid);
private final LdapLoginResult ldapLoginResultFailed = new LdapLoginResult(false, ldapEmail, userType, ldapUid);
@Override @Override
@BeforeEach @BeforeEach
@ -109,7 +112,7 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
@Test @Test
public void testAuthenticate() { public void testAuthenticate() {
when(ldapService.ldapLogin(ldapUid, ldapUserPwd)).thenReturn(ldapEmail); when(ldapService.ldapLogin(ldapUid, ldapUserPwd)).thenReturn(ldapLoginResultSuccess);
when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(mockSession); when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(mockSession);
// test username pwd correct and user not exist, config user not exist action deny, so login denied // test username pwd correct and user not exist, config user not exist action deny, so login denied
@ -131,8 +134,9 @@ public class LdapAuthenticatorTest extends AbstractControllerTest {
Assertions.assertEquals(Status.LOGIN_SESSION_FAILED.getCode(), (int) result.getCode()); Assertions.assertEquals(Status.LOGIN_SESSION_FAILED.getCode(), (int) result.getCode());
// test username pwd error, login failed // test username pwd error, login failed
when(ldapService.ldapLogin(ldapUid, ldapUserPwd)).thenReturn(null); when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(mockSession);
result = ldapAuthenticator.authenticate(ldapUid, ldapUserPwd, ip); when(ldapService.ldapLogin(ldapUid, "123")).thenReturn(ldapLoginResultFailed);
result = ldapAuthenticator.authenticate(ldapUid, "123", ip);
Assertions.assertEquals(Status.USER_NAME_PASSWD_ERROR.getCode(), (int) result.getCode()); Assertions.assertEquals(Status.USER_NAME_PASSWD_ERROR.getCode(), (int) result.getCode());
} }

107
dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java

@ -1,107 +0,0 @@
/*
* 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.dolphinscheduler.api.security.impl.ldap;
import org.apache.dolphinscheduler.api.ApiApplicationServer;
import org.apache.dolphinscheduler.common.enums.ProfileType;
import org.apache.dolphinscheduler.common.enums.UserType;
import java.lang.reflect.Field;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
@Disabled
@ActiveProfiles(ProfileType.H2)
@SpringBootTest(classes = ApiApplicationServer.class)
@TestPropertySource(properties = {
"security.authentication.type=LDAP",
"security.authentication.ldap.user.admin=read-only-admin",
"security.authentication.ldap.urls=ldap://ldap.forumsys.com:389/",
"security.authentication.ldap.base-dn=dc=example,dc=com",
"security.authentication.ldap.username=cn=read-only-admin,dc=example,dc=com",
"security.authentication.ldap.password=password",
"security.authentication.ldap.user.identity-attribute=uid",
"security.authentication.ldap.user.email-attribute=mail",
"security.authentication.ldap.user.not-exist-action=CREATE",
"security.authentication.ldap.ssl.enable=false",
"security.authentication.ldap.ssl.trust-store=",
"security.authentication.ldap.ssl.trust-store-password=",
})
public class LdapServiceTest {
@Autowired
protected AutowireCapableBeanFactory beanFactory;
private LdapService ldapService;
private final String username = "tesla";
private final String correctPassword = "password";
@BeforeEach
public void setUp() {
ldapService = new LdapService();
beanFactory.autowireBean(ldapService);
}
@Test
public void getUserType() {
UserType userType = ldapService.getUserType("read-only-admin");
Assertions.assertEquals(UserType.ADMIN_USER, userType);
}
@Test
public void ldapLogin() throws NoSuchFieldException, IllegalAccessException {
changeSslEnable(false);
String email = ldapService.ldapLogin(username, correctPassword);
Assertions.assertEquals("tesla@ldap.forumsys.com", email);
}
@Test
public void ldapLoginError() throws NoSuchFieldException, IllegalAccessException {
changeSslEnable(false);
String email2 = ldapService.ldapLogin(username, "error password");
Assertions.assertNull(email2);
}
@Test
public void ldapLoginSSL() throws NoSuchFieldException, IllegalAccessException {
changeSslEnable(true);
String email = ldapService.ldapLogin(username, correctPassword);
Assertions.assertNull(email);
}
private void changeSslEnable(boolean sslEnable) throws NoSuchFieldException, IllegalAccessException {
Class<LdapService> ldapServiceClass = LdapService.class;
Field sslEnableField = ldapServiceClass.getDeclaredField("sslEnable");
sslEnableField.setAccessible(true);
sslEnableField.set(ldapService, sslEnable);
if (sslEnable) {
Field trustStorePasswordField = ldapServiceClass.getDeclaredField("trustStorePassword");
trustStorePasswordField.setAccessible(true);
trustStorePasswordField.set(ldapService, "trustStorePassword");
}
}
}

9
dolphinscheduler-api/src/test/resources/application.yaml

@ -100,17 +100,18 @@ security:
# IF you set type `LDAP`, below config will be effective # IF you set type `LDAP`, below config will be effective
ldap: ldap:
# ldap server config # ldap server config
urls: ldap://ldap.forumsys.com:389/ url: ldap://ldap.forumsys.com:389/
base-dn: dc=example,dc=com base-dn: dc=example,dc=com
username: cn=read-only-admin,dc=example,dc=com username: cn=read-only-admin,dc=example,dc=com
password: password password: password
user: user:
# admin userId when you use LDAP login # admin username when you use LDAP login
admin: read-only-admin admin-username: admin
admin-user-filter: (&(cn={0}))
identity-attribute: uid identity-attribute: uid
email-attribute: mail email-attribute: mail
# action when ldap user is not exist (supported types: CREATE,DENY) # action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE not-exist-action: DENY
ssl: ssl:
enable: false enable: false
# jks file absolute path && password # jks file absolute path && password

12
dolphinscheduler-standalone-server/src/main/resources/application.yaml

@ -90,17 +90,19 @@ security:
# IF you set type `LDAP`, below config will be effective # IF you set type `LDAP`, below config will be effective
ldap: ldap:
# ldap server config # ldap server config
urls: ldap://ldap.forumsys.com:389/ url: ldap://ldap.forumsys.com:389/
base-dn: dc=example,dc=com base-dn: dc=example,dc=com
username: cn=read-only-admin,dc=example,dc=com username: admin,dc=example,dc=com
password: password password: password
user: user:
# admin userId when you use LDAP login # admin username when you use LDAP login
admin: read-only-admin admin-username: ldap-admin
# user search filter to find admin user
admin-user-filter: (&(cn={0}))
identity-attribute: uid identity-attribute: uid
email-attribute: mail email-attribute: mail
# action when ldap user is not exist (supported types: CREATE,DENY) # action when ldap user is not exist (supported types: CREATE,DENY)
not-exist-action: CREATE not-exist-action: DENY
ssl: ssl:
enable: false enable: false
# jks file absolute path && password # jks file absolute path && password

Loading…
Cancel
Save