diff --git a/.github/workflows/api-test.yml b/.github/workflows/api-test.yml index fffebe801a..b31d431c28 100644 --- a/.github/workflows/api-test.yml +++ b/.github/workflows/api-test.yml @@ -115,6 +115,8 @@ jobs: class: org.apache.dolphinscheduler.api.test.cases.ExecutorAPITest - name: WorkflowInstanceAPITest class: org.apache.dolphinscheduler.api.test.cases.WorkflowInstanceAPITest + - name: LdapLoginAPITest + class: org.apache.dolphinscheduler.api.test.cases.LdapLoginAPITest env: RECORDING_PATH: /tmp/recording-${{ matrix.case.name }} steps: diff --git a/docs/docs/en/guide/security/authentication-type.md b/docs/docs/en/guide/security/authentication-type.md index 31c8d05de3..ae82f54976 100644 --- a/docs/docs/en/guide/security/authentication-type.md +++ b/docs/docs/en/guide/security/authentication-type.md @@ -10,21 +10,23 @@ security: authentication: # Authentication types (supported types: PASSWORD,LDAP,CASDOOR_SSO) - type: PASSWORD + type: LDAP # IF you set type `LDAP`, below config will be effective ldap: # ldap server config - urls: ldap://ldap.forumsys.com:389/ + url: ldap://ldap.forumsys.com:389/ base-dn: dc=example,dc=com - username: cn=read-only-admin,dc=example,dc=com + username: cn=admin,dc=example,dc=com password: password user: # 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 email-attribute: mail # action when ldap user is not exist (supported types: CREATE,DENY) - not-exist-action: CREATE + not-exist-action: DENY ssl: enable: false # jks file absolute path && password @@ -71,31 +73,6 @@ casdoor: 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](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: diff --git a/docs/docs/zh/guide/security/authentication-type.md b/docs/docs/zh/guide/security/authentication-type.md index b32e13b46b..3a7b5a4772 100644 --- a/docs/docs/zh/guide/security/authentication-type.md +++ b/docs/docs/zh/guide/security/authentication-type.md @@ -14,22 +14,23 @@ security: # IF you set type `LDAP`, below config will be effective ldap: # ldap server config - urls: ldap://ldap.forumsys.com:389/ + url: ldap://ldap.forumsys.com:389/ base-dn: dc=example,dc=com - username: cn=read-only-admin,dc=example,dc=com + username: cn=admin,dc=example,dc=com password: password user: - # admin userId when you use LDAP login - admin: read-only-admin - identity-attribute: uid - email-attribute: mail - # action when ldap user is not exist (supported types: CREATE,DENY) - not-exist-action: CREATE + # admin userId when you use LDAP login + admin: ldap-admin + # user search filter to find admin user + identity-attribute: uid + email-attribute: mail + # action when ldap user is not exist (supported types: CREATE,DENY) + not-exist-action: DENY ssl: - enable: false - # jks file absolute path && password - trust-store: "/ldapkeystore.jks" - trust-store-password: "password" + enable: false + # jks file absolute path && password + trust-store: "/ldapkeystore.jks" + trust-store-password: "password" casdoor: user: admin: "" @@ -71,31 +72,6 @@ casdoor: 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 是基于 OAuth 2.0、OIDC、SAML 和 CAS 的面向 UI 的身份访问管理(IAM)/单点登录(SSO)平台。您可以通过以下步骤通过 Casdoor 为 Dolphinscheduler 添加 SSO 功能: diff --git a/dolphinscheduler-api-test/README.md b/dolphinscheduler-api-test/README.md index 9e48bc6430..1db1196841 100644 --- a/dolphinscheduler-api-test/README.md +++ b/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. + diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java index b9430546b0..bf1d05691a 100644 --- a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/ExecutorAPITest.java +++ b/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.Order; 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. @DolphinScheduler(composeFiles = "docker/basic/docker-compose.yaml") @Slf4j +@DisableIfTestFails public class ExecutorAPITest { private static final String username = "admin"; diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/LdapLoginAPITest.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/cases/LdapLoginAPITest.java new file mode 100644 index 0000000000..f2b9e4cadc --- /dev/null +++ b/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); + } +} diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/entity/GetUserInfoResponseData.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/entity/GetUserInfoResponseData.java new file mode 100644 index 0000000000..7b5db1ab96 --- /dev/null +++ b/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; +} diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/security/UserPage.java b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/java/org/apache/dolphinscheduler/api/test/pages/security/UserPage.java new file mode 100644 index 0000000000..f667bcfcd0 --- /dev/null +++ b/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 headers = new HashMap<>(); + headers.put(Constants.SESSION_ID_KEY, sessionId); + + RequestClient requestClient = new RequestClient(); + + return requestClient.get("/users/get-user-info", headers, new HashMap<>()); + } +} diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/application.yaml b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/application.yaml new file mode 100644 index 0000000000..51d8e23c2f --- /dev/null +++ b/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 diff --git a/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/docker-compose.yaml b/dolphinscheduler-api-test/dolphinscheduler-api-test-case/src/test/resources/docker/ldap-login/docker-compose.yaml new file mode 100644 index 0000000000..28d91849c1 --- /dev/null +++ b/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: diff --git a/dolphinscheduler-api-test/pom.xml b/dolphinscheduler-api-test/pom.xml index 095cefd64c..c16c6c6a6b 100644 --- a/dolphinscheduler-api-test/pom.xml +++ b/dolphinscheduler-api-test/pom.xml @@ -36,7 +36,8 @@ UTF-8 5.7.2 - 3.141.59 + 4.21.0 + 1.19.8 1.18.24 3.23.1 4.1.0 @@ -133,7 +134,7 @@ org.testcontainers testcontainers-bom - 1.18.1 + ${testcontainers.version} import pom diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/LdapLoginResult.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/dto/LdapLoginResult.java new file mode 100644 index 0000000000..47c458ac77 --- /dev/null +++ b/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; +} diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/AbstractAuthenticator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/AbstractAuthenticator.java index 43b7e57402..7b9c3f5a43 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/AbstractAuthenticator.java +++ b/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 * - * @param userId user identity field + * @param userName user identity field * @param password user login password * @return user object in databse */ - public abstract User login(@NonNull String userId, String password); + public abstract User login(@NonNull String userName, String password); @Override - public Result> authenticate(@NonNull String userId, String password, @NonNull String ip) { + public Result> authenticate(@NonNull String userName, String password, @NonNull String ip) { Result> result = new Result<>(); - User user = login(userId, password); + User user = login(userName, password); if (user == null) { if (Objects.equals(securityConfig.getType(), AuthenticationType.CASDOOR_SSO.name())) { log.error("State or code entered incorrectly."); diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticator.java index 64f15eeeb2..030bc09fd7 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticator.java +++ b/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; +import org.apache.dolphinscheduler.api.dto.LdapLoginResult; import org.apache.dolphinscheduler.api.security.impl.AbstractAuthenticator; import org.apache.dolphinscheduler.dao.entity.User; @@ -30,14 +31,14 @@ public class LdapAuthenticator extends AbstractAuthenticator { LdapService ldapService; @Override - public User login(@NonNull String userId, String password) { + public User login(@NonNull String userName, String password) { User user = null; - String ldapEmail = ldapService.ldapLogin(userId, password); - if (ldapEmail != null) { + LdapLoginResult ldapLoginResult = ldapService.ldapLogin(userName, password); + if (ldapLoginResult.isSuccess()) { // check if user exist - user = userService.getUserByUserName(userId); + user = userService.getUserByUserName(userName); if (user == null && ldapService.createIfUserNotExists()) { - user = userService.createUser(ldapService.getUserType(userId), userId, ldapEmail); + user = userService.createUser(ldapLoginResult.getUserType(), userName, ldapLoginResult.getLdapEmail()); } } return user; diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapService.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapService.java index 058280b81f..3a394809b4 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapService.java +++ b/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; +import org.apache.dolphinscheduler.api.dto.LdapLoginResult; import org.apache.dolphinscheduler.api.security.LdapUserNotExistActionType; import org.apache.dolphinscheduler.common.enums.UserType; import org.apache.commons.lang3.StringUtils; +import java.util.Objects; import java.util.Properties; import javax.naming.Context; @@ -46,11 +48,14 @@ import org.springframework.stereotype.Component; @Slf4j public class LdapService { - @Value("${security.authentication.ldap.user.admin:#{null}}") - private String adminUserId; + @Value("${security.authentication.ldap.user.admin-username:#{null}}") + private String ldapAdminUserName; - @Value("${security.authentication.ldap.urls:#{null}}") - private String ldapUrls; + @Value("${security.authentication.ldap.user.admin-user-filter:#{null}}") + private String ldapAdminUserFilter; + + @Value("${security.authentication.ldap.url:#{null}}") + private String ldapUrl; @Value("${security.authentication.ldap.base-dn:#{null}}") private String ldapBaseDn; @@ -67,7 +72,7 @@ public class LdapService { @Value("${security.authentication.ldap.user.email-attribute:#{null}}") private String ldapEmailAttribute; - @Value("${security.authentication.ldap.user.not-exist-action:CREATE}") + @Value("${security.authentication.ldap.user.not-exist-action:DENY}") private String ldapUserNotExistAction; @Value("${security.authentication.ldap.ssl.enable:false}") @@ -79,56 +84,58 @@ public class LdapService { @Value("${security.authentication.ldap.ssl.trust-store-password:#{null}}") 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 - * - * @param userId user identity id - * @param userPwd user login password - * @return user email + * login by userName and return LdapLoginResult */ - public String ldapLogin(String userId, String userPwd) { + public LdapLoginResult ldapLogin(String userName, String userPwd) { Properties searchEnv = getManagerLdapEnv(); 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 { // Connect to the LDAP server and Authenticate with a service user of whom we know the DN and credentials ctx = new InitialLdapContext(searchEnv, null); SearchControls sc = new SearchControls(); sc.setReturningAttributes(new String[]{ldapEmailAttribute}); sc.setSearchScope(SearchControls.SUBTREE_SCOPE); - EqualsFilter filter = new EqualsFilter(ldapUserIdentifyingAttribute, userId); - NamingEnumeration results = ctx.search(ldapBaseDn, filter.toString(), sc); - if (results.hasMore()) { - // get the users DN (distinguishedName) from the result - SearchResult result = results.next(); - NamingEnumeration 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(); - } + EqualsFilter userFilter = new EqualsFilter(ldapUserIdentifyingAttribute, userName); + String userSearchEmail = ldapSearch(ctx, null, userPwd, userFilter.toString(), sc, searchEnv); + + if (StringUtils.isNotEmpty(ldapAdminUserFilter)) { + String adminFilterSearchEmail = ldapSearch(ctx, userName, userPwd, ldapAdminUserFilter, sc, searchEnv); + if (adminFilterSearchEmail != null) { + ldapLoginResult.setLdapEmail(adminFilterSearchEmail); + ldapLoginResult.setUserType(UserType.ADMIN_USER); + ldapLoginResult.setUserName(userName); + ldapLoginResult.setSuccess(true); + return ldapLoginResult; } + } else { + log.debug("ldap admin user filter is empty, skipping admin user filter search"); + } + + 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) { log.error("ldap search error", e); - return null; + return ldapLoginResult; } finally { try { 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_PRINCIPAL, ldapSecurityPrincipal); env.put(Context.SECURITY_CREDENTIALS, ldapPrincipalPassword); - env.put(Context.PROVIDER_URL, ldapUrls); + env.put(Context.PROVIDER_URL, ldapUrl); if (sslEnable) { env.put(Context.SECURITY_PROTOCOL, "ssl"); @@ -164,6 +171,42 @@ public class LdapService { return env; } + private String ldapSearch(LdapContext ctx, + String userName, + String userPwd, + String filter, + SearchControls sc, + Properties searchEnv) throws NamingException { + NamingEnumeration 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 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() { if (StringUtils.isBlank(ldapUserNotExistAction)) { log.info( diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/pwd/PasswordAuthenticator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/pwd/PasswordAuthenticator.java index a7ae0dd9f1..86c419a605 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/pwd/PasswordAuthenticator.java +++ b/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 { @Override - public User login(String userId, String password) { - return userService.queryUser(userId, password); + public User login(String userName, String password) { + return userService.queryUser(userName, password); } } diff --git a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/sso/CasdoorAuthenticator.java b/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/sso/CasdoorAuthenticator.java index 4d8cf93ada..5713696f8e 100644 --- a/dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/security/impl/sso/CasdoorAuthenticator.java +++ b/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; @Override - public User login(@NonNull String state, String code) { + public User login(@NonNull String userName, String code) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (servletRequestAttributes == null) { @@ -59,11 +59,11 @@ public class CasdoorAuthenticator extends AbstractSsoAuthenticator { // Invalid state request.getSession().setAttribute(Constants.SSO_LOGIN_USER_STATE, null); // 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; } - String token = casdoorAuthService.getOAuthToken(code, state); + String token = casdoorAuthService.getOAuthToken(code, userName); CasdoorUser casdoorUser = casdoorAuthService.parseJwtToken(token); User user = null; if (casdoorUser.getName() != null) { diff --git a/dolphinscheduler-api/src/main/resources/application.yaml b/dolphinscheduler-api/src/main/resources/application.yaml index 9b0e94d644..4578aa08ac 100644 --- a/dolphinscheduler-api/src/main/resources/application.yaml +++ b/dolphinscheduler-api/src/main/resources/application.yaml @@ -175,17 +175,19 @@ security: # IF you set type `LDAP`, below config will be effective ldap: # ldap server config - urls: ldap://ldap.forumsys.com:389/ + url: ldap://ldap.forumsys.com:389/ base-dn: dc=example,dc=com - username: cn=read-only-admin,dc=example,dc=com + username: cn=admin,dc=example,dc=com password: password user: - # admin userId when you use LDAP login - admin: read-only-admin + # admin username when you use LDAP login + admin-username: ldap-admin + # user search filter to find admin user + admin-user-filter: (&(cn={0})) identity-attribute: uid email-attribute: mail # action when ldap user is not exist (supported types: CREATE,DENY) - not-exist-action: CREATE + not-exist-action: DENY ssl: enable: false # jks file absolute path && password diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java index 88662273ea..ea09ec5a22 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java +++ b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/SecurityConfigLDAPTest.java @@ -27,6 +27,7 @@ import org.springframework.test.context.TestPropertySource; @TestPropertySource(properties = { "security.authentication.type=LDAP", + "security.authentication.ldap.user.not-exist-action=CREATE" }) public class SecurityConfigLDAPTest extends AbstractControllerTest { diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticatorTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticatorTest.java index 26f086a907..9e75634bcc 100644 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapAuthenticatorTest.java +++ b/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 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.security.LdapUserNotExistActionType; import org.apache.dolphinscheduler.api.service.SessionService; @@ -81,11 +82,13 @@ public class LdapAuthenticatorTest extends AbstractControllerTest { private User mockUser; private Session mockSession; - private String ldapUid = "test"; - private String ldapUserPwd = "password"; - private String ldapEmail = "test@example.com"; - private String ip = "127.0.0.1"; - private UserType userType = UserType.GENERAL_USER; + private final String ldapUid = "test"; + private final String ldapUserPwd = "password"; + private final String ldapEmail = "test@example.com"; + private final String ip = "127.0.0.1"; + 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 @BeforeEach @@ -109,7 +112,7 @@ public class LdapAuthenticatorTest extends AbstractControllerTest { @Test 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); // 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()); // test username pwd error, login failed - when(ldapService.ldapLogin(ldapUid, ldapUserPwd)).thenReturn(null); - result = ldapAuthenticator.authenticate(ldapUid, ldapUserPwd, ip); + when(sessionService.createSessionIfAbsent(Mockito.any(User.class))).thenReturn(mockSession); + when(ldapService.ldapLogin(ldapUid, "123")).thenReturn(ldapLoginResultFailed); + result = ldapAuthenticator.authenticate(ldapUid, "123", ip); Assertions.assertEquals(Status.USER_NAME_PASSWD_ERROR.getCode(), (int) result.getCode()); } diff --git a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java b/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java deleted file mode 100644 index 30dbd80479..0000000000 --- a/dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/security/impl/ldap/LdapServiceTest.java +++ /dev/null @@ -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 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"); - } - } -} diff --git a/dolphinscheduler-api/src/test/resources/application.yaml b/dolphinscheduler-api/src/test/resources/application.yaml index 5eb7e1f8d7..b5c3373d65 100644 --- a/dolphinscheduler-api/src/test/resources/application.yaml +++ b/dolphinscheduler-api/src/test/resources/application.yaml @@ -100,17 +100,18 @@ security: # IF you set type `LDAP`, below config will be effective ldap: # ldap server config - urls: ldap://ldap.forumsys.com:389/ + url: ldap://ldap.forumsys.com:389/ base-dn: dc=example,dc=com username: cn=read-only-admin,dc=example,dc=com password: password user: - # admin userId when you use LDAP login - admin: read-only-admin + # admin username when you use LDAP login + admin-username: admin + admin-user-filter: (&(cn={0})) identity-attribute: uid email-attribute: mail # action when ldap user is not exist (supported types: CREATE,DENY) - not-exist-action: CREATE + not-exist-action: DENY ssl: enable: false # jks file absolute path && password diff --git a/dolphinscheduler-standalone-server/src/main/resources/application.yaml b/dolphinscheduler-standalone-server/src/main/resources/application.yaml index 97b9b6d22d..5f83729bad 100644 --- a/dolphinscheduler-standalone-server/src/main/resources/application.yaml +++ b/dolphinscheduler-standalone-server/src/main/resources/application.yaml @@ -90,17 +90,19 @@ security: # IF you set type `LDAP`, below config will be effective ldap: # ldap server config - urls: ldap://ldap.forumsys.com:389/ + url: ldap://ldap.forumsys.com:389/ base-dn: dc=example,dc=com - username: cn=read-only-admin,dc=example,dc=com + username: admin,dc=example,dc=com password: password user: - # admin userId when you use LDAP login - admin: read-only-admin + # admin username when you use LDAP login + admin-username: ldap-admin + # user search filter to find admin user + admin-user-filter: (&(cn={0})) identity-attribute: uid email-attribute: mail # action when ldap user is not exist (supported types: CREATE,DENY) - not-exist-action: CREATE + not-exist-action: DENY ssl: enable: false # jks file absolute path && password