diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 00000000000..b1872d166bd
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,53 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+github:
+ description: "Apache RocketMQ is a cloud native messaging and streaming platform, making it simple to build event-driven applications."
+ homepage: https://rocketmq.apache.org/
+ labels:
+ - messaging
+ - streaming
+ - eventing
+ - cloud-native
+ - rocketmq
+ - java
+ - hacktoberfest
+ enabled_merge_buttons:
+ # Enable squash button
+ squash: true
+ # Disable merge button
+ merge: false
+ # Disable rebase button
+ rebase: false
+ protected_branches:
+ master: {}
+ develop:
+ required_pull_request_reviews:
+ dismiss_stale_reviews: true
+ require_code_owner_reviews: false
+ required_approving_review_count: 1
+ required_status_checks:
+ contexts:
+ - misspell-check
+ - check-license
+ - maven-compile (ubuntu-latest, JDK-8)
+ - maven-compile (windows-latest, JDK-8)
+ - maven-compile (macos-latest, JDK-8)
+notifications:
+ commits: commits@rocketmq.apache.org
+ issues: commits@rocketmq.apache.org
+ pullrequests: commits@rocketmq.apache.org
+ jobs: commits@rocketmq.apache.org
+ discussions: dev@rocketmq.apache.org
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 00000000000..6ebc08d0dc3
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,69 @@
+#
+# 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.
+#
+startup --host_jvm_args=-Xmx2g
+
+run --color=yes
+
+build --color=yes
+build --enable_platform_specific_config
+
+test --action_env=TEST_TMPDIR=/tmp
+
+test --experimental_strict_java_deps=warn
+test --experimental_ui_max_stdouterr_bytes=10485760
+build --experimental_strict_java_deps=warn
+
+test --test_output=errors
+
+
+# This .bazelrc file contains all of the flags required for the provided
+# toolchain with Remote Build Execution.
+# Note your WORKSPACE must contain an rbe_autoconfig target with
+# name="rbe_default" to use these flags as-is.
+
+# Depending on how many machines are in the remote execution instance, setting
+# this higher can make builds faster by allowing more jobs to run in parallel.
+# Setting it too high can result in jobs that timeout, however, while waiting
+# for a remote machine to execute them.
+build:remote --jobs=150
+
+build:remote --remote_executor=grpcs://remote.buildbuddy.io
+build:remote --host_platform=@buildbuddy_toolchain//:platform
+build:remote --platforms=@buildbuddy_toolchain//:platform
+build:remote --extra_execution_platforms=@buildbuddy_toolchain//:platform
+build:remote --crosstool_top=@buildbuddy_toolchain//:toolchain
+build:remote --extra_toolchains=@buildbuddy_toolchain//:cc_toolchain
+build:remote --javabase=@buildbuddy_toolchain//:javabase_jdk8
+build:remote --host_javabase=@buildbuddy_toolchain//:javabase_jdk8
+build:remote --java_toolchain=@buildbuddy_toolchain//:toolchain_jdk8
+build:remote --host_java_toolchain=@buildbuddy_toolchain//:toolchain_jdk8
+build:remote --define=EXECUTOR=remote
+
+# Enable remote execution so actions are performed on the remote systems.
+build:remote --remote_executor=grpcs://remote.buildbuddy.io
+
+# Enforce stricter environment rules, which eliminates some non-hermetic
+# behavior and therefore improves both the remote cache hit rate and the
+# correctness and repeatability of the build.
+build:remote --incompatible_strict_action_env=true
+
+# Set a higher timeout value, just in case.
+build:remote --remote_timeout=3600
+
+# Use a pre-configured account, such that we may have pull-request replacing pull-request-target
+build:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU
+test:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU
diff --git a/.bazelversion b/.bazelversion
new file mode 100644
index 00000000000..7cbea073bea
--- /dev/null
+++ b/.bazelversion
@@ -0,0 +1 @@
+5.2.0
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000000..8f1cc71457d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -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.
+#
+
+name: Bug Report
+title: "[Bug] Bug title "
+description: Create a report to help us identify any unintended flaws, errors, or faults.
+body:
+ - type: checkboxes
+ attributes:
+ label: Before Creating the Bug Report
+ options:
+ - label: >
+ I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions).
+ required: true
+ - label: >
+ I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate.
+ required: true
+ - label: >
+ I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Runtime platform environment
+ description: Describe the runtime platform environment.
+ placeholder: >
+ OS: (e.g., "Ubuntu 20.04")
+ OS: (e.g., "Windows Server 2019")
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: RocketMQ version
+ description: Describe the RocketMQ version.
+ placeholder: >
+ branch: (e.g develop|4.9.x)
+ version: (e.g. 5.1.0|4.9.5)
+ Git commit id: (e.g. c88b5cfa72e204962929eea105687647146112c6)
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: JDK Version
+ description: Run or Compiler version.
+ placeholder: >
+ Compiler: (e.g., "Oracle JDK 11.0.17")
+ OS: (e.g., "Ubuntu 20.04")
+ Runtime (if different from JDK above): (e.g., "Oracle JRE 8u251")
+ OS (if different from OS compiled on): (e.g., "Windows Server 2019")
+ validations:
+ required: false
+
+ - type: textarea
+ attributes:
+ label: Describe the Bug
+ description: Describe what happened.
+ placeholder: >
+ A clear and concise description of what the bug is.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Steps to Reproduce
+ description: Describe the steps to reproduce the bug here.
+ placeholder: >
+ If possible, provide a recipe for reproducing the error.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: What Did You Expect to See?
+ description: You expect to see result.
+ placeholder: >
+ A clear and concise description of what you expected to see.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: What Did You See Instead?
+ description: You instead to see result.
+ placeholder: >
+ A clear and concise description of what you saw instead.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Additional Context
+ description: Additional context.
+ placeholder: >
+ Add any other context about the problem here.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000000..870c2b1d086
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+
+blank_issues_enabled: false
+contact_links:
+ - name: Ask Question
+ url: https://github.com/apache/rocketmq/discussions
+ about: Please go to GitHub Disccusions to ask questions
diff --git a/.github/ISSUE_TEMPLATE/doc.yml b/.github/ISSUE_TEMPLATE/doc.yml
new file mode 100644
index 00000000000..e68928464a7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/doc.yml
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+name: Documentation Related
+title: "[Doc] Documentation Related "
+description: I find some issues related to the documentation.
+labels: [ "module/doc" ]
+body:
+ - type: checkboxes
+ attributes:
+ label: Search before creation
+ description: >
+ Please make sure to search in the [issues](https://github.com/apache/rocketmq/issues)
+ first to see whether the same issue was reported already.
+ options:
+ - label: >
+ I had searched in the [issues](https://github.com/apache/rocketmq/issues) and found
+ no similar issues.
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Documentation Related
+ description: Describe the suggestion about document.
+ placeholder: >
+ e.g There is a typo
+ validations:
+ required: true
+
+ - type: checkboxes
+ attributes:
+ label: Are you willing to submit PR?
+ description: >
+ This is absolutely not required, but we are happy to guide you in the contribution process
+ especially if you already have a good understanding of how to implement the fix.
+ options:
+ - label: Yes I am willing to submit a PR!
+
+ - type: markdown
+ attributes:
+ value: "Thanks for completing our form!"
diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.yml b/.github/ISSUE_TEMPLATE/enhancement_request.yml
new file mode 100644
index 00000000000..cac503d17bd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement_request.yml
@@ -0,0 +1,75 @@
+#
+# 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.
+#
+
+
+name: Enhancement Request
+title: "[Enhancement] Enhancement title"
+description: Suggest an enhancement for this project
+labels: [ "type/enhancement" ]
+body:
+ - type: checkboxes
+ attributes:
+ label: Before Creating the Enhancement Request
+ description: >
+ Most of issues should be classified as bug or feature request. An issue should be considered as an enhancement when it proposes improvements to
+ existing functionality or user experience, without necessarily introducing new features or fixing existing bugs.
+ options:
+ - label: >
+ I have confirmed that this should be classified as an enhancement rather than a bug/feature.
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Summary
+ placeholder: >
+ A clear and concise description of the enhancement you would like to see in the project.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Motivation
+ placeholder: >
+ Explain why you believe this enhancement is necessary, and how it benefits the project and community.
+ Include any specific use cases that you have in mind.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Describe the Solution You'd Like
+ placeholder: >
+ Describe the enhancement you propose, detailing the change and implementation steps involved.
+ If you have multiple solutions, please list them separately.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Describe Alternatives You've Considered
+ placeholder: >
+ List any alternative enhancements or implementations you have considered, and explain why they may not be as effective or appropriate.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Additional Context
+ placeholder: >
+ Add any relevant context, screenshots, prototypes, or other supplementary information to help illustrate the enhancement.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 00000000000..8361b8aee92
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,58 @@
+#
+# 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.
+#
+
+
+name: Feature Request
+title: "[Feature] New feature title"
+description: Suggest an idea for this project.
+labels: [ "type/new feature" ]
+body:
+ - type: textarea
+ attributes:
+ label: Is Your Feature Request Related to a Problem?
+ description: Please Describe It.
+ placeholder: >
+ A clear and concise description of what the problem is.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Describe the Solution You'd Like
+ description: Describe how you solved it.
+ placeholder: >
+ A clear and concise description of what you want to happen.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Describe Alternatives You've Considered
+ description: Describe your solution
+ placeholder: >
+ A clear and concise description of any alternative solutions or features you've considered.
+ validations:
+ required: true
+
+ - type: textarea
+ attributes:
+ label: Additional Context
+ description: Additional context.
+ placeholder: >
+ Add any other context about the problem here.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md
deleted file mode 100644
index 1c8fa94aac2..00000000000
--- a/.github/ISSUE_TEMPLATE/issue_template.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-name: ISSUE_TEMPLATE
-about: Describe this issue template's purpose here.
-
----
-
-The issue tracker is **ONLY** used for bug report(feature request need to follow [RIP process](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal)). Keep in mind, please check whether there is an existing same report before your raise a new one.
-
-Alternately (especially if your communication is not a bug report), you can send mail to our [mailing lists](http://rocketmq.apache.org/about/contact/). We welcome any friendly suggestions, bug fixes, collaboration and other improvements.
-
-Please ensure that your bug report is clear and that it is complete. Otherwise, we may be unable to understand it or to reproduce it, either of which would prevent us from fixing the bug. We strongly recommend the report(bug report or feature request) could include some hints as the following:
-
-**BUG REPORT**
-
-1. Please describe the issue you observed:
-
-- What did you do (The steps to reproduce)?
-
-- What did you expect to see?
-
-- What did you see instead?
-
-2. Please tell us about your environment:
-
-3. Other information (e.g. detailed explanation, logs, related issues, suggestions how to fix, etc):
-
-**FEATURE REQUEST**
-
-1. Please describe the feature you are requesting.
-
-2. Provide any additional detail on your proposed use case for this feature.
-
-2. Indicate the importance of this issue to you (blocker, must-have, should-have, nice-to-have). Are you currently using any workarounds to address this issue?
-
-4. If there are some sub-tasks using -[] for each subtask and create a corresponding issue to map to the sub task:
-
-- [sub-task1-issue-number](example_sub_issue1_link_here): sub-task1 description here,
-- [sub-task2-issue-number](example_sub_issue2_link_here): sub-task2 description here,
-- ...
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 143dac89a96..96bffa55a3f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,20 +1,15 @@
-## What is the purpose of the change
+
-XXXXX
+### Which Issue(s) This PR Fixes
-## Brief changelog
+
-XX
+Fixes #issue_id
-## Verifying this change
+### Brief Description
-XXXX
+
-Follow this checklist to help us incorporate your contribution quickly and easily. Notice, `it would be helpful if you could finish the following 5 checklist(the last one is not necessary)before request the community to review your PR`.
+### How Did You Test This Change?
-- [x] Make sure there is a [Github issue](https://github.com/apache/rocketmq/issues) filed for the change (usually before you start working on it). Trivial changes like typos do not require a Github issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue.
-- [x] Format the pull request title like `[ISSUE #123] Fix UnknownException when host config not exist`. Each commit in the pull request should have a meaningful subject line and body.
-- [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
-- [x] Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when cross module dependency exist. If the new feature or significant change is committed, please remember to add integration-test in [test module](https://github.com/apache/rocketmq/tree/master/test).
-- [x] Run `mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyle` to make sure basic checks pass. Run `mvn clean install -DskipITs` to make sure unit-test pass. Run `mvn clean test-compile failsafe:integration-test` to make sure integration-test pass.
-- [ ] If this contribution is large, please file an [Apache Individual Contributor License Agreement](http://www.apache.org/licenses/#clas).
+
diff --git a/.github/asf-deploy-settings.xml b/.github/asf-deploy-settings.xml
new file mode 100644
index 00000000000..246bf0973f7
--- /dev/null
+++ b/.github/asf-deploy-settings.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+ apache.snapshots.https
+ ${env.NEXUS_DEPLOY_USERNAME}
+ ${env.NEXUS_DEPLOY_PASSWORD}
+
+
+ 60
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml
new file mode 100644
index 00000000000..510457ca46e
--- /dev/null
+++ b/.github/workflows/bazel.yml
@@ -0,0 +1,23 @@
+name: Build and Run Tests by Bazel
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches:
+ - master
+ - develop
+ - bazel
+
+jobs:
+ build:
+ name: "bazel-compile (${{ matrix.os }})"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Build
+ run: bazel build --config=remote //...
+ - name: Run Tests
+ run: bazel test --config=remote //...
\ No newline at end of file
diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml
new file mode 100644
index 00000000000..e5e8d323a29
--- /dev/null
+++ b/.github/workflows/codeql_analysis.yml
@@ -0,0 +1,32 @@
+name: CodeQL Analysis
+
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches:
+ - master
+
+jobs:
+ CodeQL-Build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Cache Maven packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: java
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 00000000000..b249072f8db
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,25 @@
+name: Coverage
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches: [master, develop]
+jobs:
+ calculate-coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - name: Set up JDK 8
+ uses: actions/setup-java@v4
+ with:
+ java-version: "8"
+ distribution: "corretto"
+ cache: "maven"
+ - name: Generate coverage report
+ run: mvn -B test -T 2C --file pom.xml
+ - name: Upload to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ fail_ci_if_error: true
+ verbose: true
+ token: cf0cba0a-22f8-4580-89ab-4f1dec3bda6f
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
new file mode 100644
index 00000000000..ad5bcbd6c25
--- /dev/null
+++ b/.github/workflows/integration-test.yml
@@ -0,0 +1,45 @@
+name: Run Integration Tests
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches: [master, develop]
+
+jobs:
+ it-test:
+ name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ jdk: [8]
+ steps:
+ - name: Cache Maven Repos
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Set up JDK ${{ matrix.jdk }}
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.jdk }}
+ distribution: "corretto"
+ cache: "maven"
+
+ - name: Run integration tests with Maven
+ run: mvn clean verify -Pit-test -Pskip-unit-tests
+
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always()
+ with:
+ report_paths: 'test/target/failsafe-reports/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
diff --git a/.github/workflows/license-checker.yaml b/.github/workflows/license-checker.yaml
new file mode 100644
index 00000000000..259fdd7ffa7
--- /dev/null
+++ b/.github/workflows/license-checker.yaml
@@ -0,0 +1,34 @@
+# 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.
+
+name: License checker
+
+on:
+ pull_request:
+ branches:
+ - develop
+ - master
+
+jobs:
+ check-license:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Check License Header
+ uses: apache/skywalking-eyes@v0.2.0
+ with:
+ log: info
+ config: .licenserc.yaml
diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml
new file mode 100644
index 00000000000..4eacd65b846
--- /dev/null
+++ b/.github/workflows/maven.yaml
@@ -0,0 +1,46 @@
+name: Build and Run Tests by Maven
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches: [master, develop, bazel]
+
+jobs:
+ java_build:
+ name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ jdk: [8]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Set up JDK ${{ matrix.jdk }}
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.jdk }}
+ # See https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions
+ # AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore.
+ distribution: "corretto"
+ cache: "maven"
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+
+ - name: Upload Auth JVM crash logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: jvm-crash-logs
+ path: /Users/runner/work/rocketmq/rocketmq/auth/hs_err_pid*.log
+ retention-days: 1
+
+ - name: Upload broker JVM crash logs
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: jvm-crash-logs
+ path: /Users/runner/work/rocketmq/rocketmq/broker/hs_err_pid*.log
+ retention-days: 1
diff --git a/.github/workflows/misspell_check.yml b/.github/workflows/misspell_check.yml
new file mode 100644
index 00000000000..81729e42a41
--- /dev/null
+++ b/.github/workflows/misspell_check.yml
@@ -0,0 +1,17 @@
+name: Misspell Check
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+ push:
+ branches: [master, develop]
+jobs:
+ misspell-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install misspell
+ run: |
+ curl -L -o ./install-misspell.sh https://git.io/misspell
+ sh ./install-misspell.sh
+ - name: Run misspell
+ run: find . -type f -print0 | xargs -0 bin/misspell -error -i transfered,derivate
diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml
new file mode 100644
index 00000000000..99d7309fd0c
--- /dev/null
+++ b/.github/workflows/pr-ci.yml
@@ -0,0 +1,36 @@
+name: PR-CI
+
+on:
+ pull_request:
+ types: [opened, reopened, synchronize]
+
+jobs:
+ dist-tar:
+ name: Build distribution tar
+ runs-on: ubuntu-latest
+ timeout-minutes: 120
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+ - uses: actions/setup-java@v3
+ with:
+ distribution: "temurin"
+ java-version: "8"
+ cache: "maven"
+ - name: Build distribution tar
+ run: |
+ mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: rocketmq
+ path: distribution/target/rocketmq*/rocketmq*
+ - name: Save PR number
+ run: |
+ mkdir -p ./pr
+ echo ${{ github.event.number }} > ./pr/NR
+ - uses: actions/upload-artifact@v4
+ with:
+ name: pr
+ path: pr/
diff --git a/.github/workflows/pr-e2e-test.yml b/.github/workflows/pr-e2e-test.yml
new file mode 100644
index 00000000000..0bc74a65ad5
--- /dev/null
+++ b/.github/workflows/pr-e2e-test.yml
@@ -0,0 +1,263 @@
+name: E2E test for pull request
+
+# read-write repo token
+# access to secrets
+on:
+ workflow_run:
+ workflows: ["PR-CI"]
+ types:
+ - completed
+
+env:
+ DOCKER_REPO: apache/rocketmq-ci
+
+jobs:
+ docker:
+ if: >
+ github.repository == 'apache/rocketmq' &&
+ github.event.workflow_run.event == 'pull_request' &&
+ github.event.workflow_run.conclusion == 'success'
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ base-image: ["ubuntu"]
+ java-version: ["8"]
+ steps:
+ - name: 'Download artifact'
+ uses: actions/github-script@v6
+ with:
+ script: |
+ let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{github.event.workflow_run.id }},
+ });
+ let matchArtifactRmq = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "rocketmq"
+ })[0];
+ let download = await github.rest.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifactRmq.id,
+ archive_format: 'zip',
+ });
+ var fs = require('fs');
+ fs.writeFileSync('${{github.workspace}}/rocketmq.zip', Buffer.from(download.data));
+ - run: |
+ unzip rocketmq.zip
+ mkdir rocketmq
+ cp -r rocketmq-* rocketmq/
+ ls
+ - uses: actions/checkout@v3
+ with:
+ repository: apache/rocketmq-docker.git
+ ref: master
+ path: rocketmq-docker
+ - name: docker-login
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and save docker images
+ id: build-images
+ run: |
+ cd rocketmq-docker/image-build-ci
+ version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
+ mkdir versionlist
+ touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
+ sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: versionlist
+ path: rocketmq-docker/image-build-ci/versionlist/*
+
+ list-version:
+ if: >
+ github.repository == 'apache/rocketmq' &&
+ always()
+ name: List version
+ needs: [docker]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ outputs:
+ version-json: ${{ steps.show_versions.outputs.version-json }}
+ steps:
+ - uses: actions/download-artifact@v4
+ name: Download versionlist
+ with:
+ name: versionlist
+ path: versionlist
+ - name: Show versions
+ id: show_versions
+ run: |
+ a=(`ls versionlist`)
+ printf '%s\n' "${a[@]}" | jq -R . | jq -s .
+ echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
+
+ deploy:
+ if: ${{ success() }}
+ name: Deploy RocketMQ
+ needs: [list-version,docker]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: Deploy rocketmq
+ with:
+ action: "deploy"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
+ chart-branch: "master"
+ chart-path: "./rocketmq-k8s-helm"
+ job-id: ${{ strategy.job-index }}
+ helm-values: |
+ nameserver:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ broker:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ proxy:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ test-e2e-grpc-java:
+ if: ${{ success() }}
+ name: Test E2E grpc java
+ needs: [list-version, deploy]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: java/e2e
+ test-cmd: "mvn -B test"
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-grpc-java-log.txt
+ path: testlog.txt
+
+ test-e2e-golang:
+ if: ${{ success() }}
+ name: Test E2E golang
+ needs: [list-version, deploy]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: golang
+ test-cmd: |
+ cd ../common && mvn -Prelease -DskipTests clean package -U
+ cd ../rocketmq-admintools && source bin/env.sh
+ LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1')
+ wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \
+ rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz
+ cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-golang-log.txt
+ path: testlog.txt
+
+ test-e2e-remoting-java:
+ if: ${{ success() }}
+ name: Test E2E remoting java
+ needs: [ list-version, deploy ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: java/e2e-v4
+ test-cmd: "mvn -B test"
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-remoting-java-log.txt
+ path: testlog.txt
+
+ clean:
+ if: always()
+ name: Clean
+ needs: [list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: clean
+ with:
+ action: "clean"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ job-id: ${{ strategy.job-index }}
\ No newline at end of file
diff --git a/.github/workflows/push-ci.yml b/.github/workflows/push-ci.yml
new file mode 100644
index 00000000000..6de8595724f
--- /dev/null
+++ b/.github/workflows/push-ci.yml
@@ -0,0 +1,348 @@
+name: PUSH-CI
+
+on:
+ push:
+ branches: [master, develop]
+ #schedule:
+ # - cron: "0 18 * * *" # TimeZone: UTC 0
+
+concurrency:
+ group: rocketmq-${{ github.ref }}
+
+env:
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
+ DOCKER_REPO: apache/rocketmq-ci
+
+jobs:
+ dist-tar:
+ if: github.repository == 'apache/rocketmq'
+ name: Build dist tar
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+ - uses: actions/setup-java@v3
+ with:
+ distribution: "temurin"
+ java-version: "8"
+ cache: "maven"
+ - name: Build distribution tar
+ run: |
+ mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: rocketmq
+ path: distribution/target/rocketmq*/rocketmq*
+
+ docker:
+ if: ${{ success() }}
+ name: Docker images
+ needs: [dist-tar]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ base-image: ["ubuntu"]
+ java-version: ["8"]
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ repository: apache/rocketmq-docker.git
+ ref: master
+ path: rocketmq-docker
+ - uses: actions/download-artifact@v4
+ name: Download distribution tar
+ with:
+ name: rocketmq
+ path: rocketmq
+ - name: docker-login
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and save docker images
+ id: build-images
+ run: |
+ cd rocketmq-docker/image-build-ci
+ version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
+ mkdir versionlist
+ touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
+ sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: versionlist
+ path: rocketmq-docker/image-build-ci/versionlist/*
+
+ list-version:
+ if: >
+ github.repository == 'apache/rocketmq' &&
+ always()
+ name: List version
+ needs: [docker]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ outputs:
+ version-json: ${{ steps.show_versions.outputs.version-json }}
+ steps:
+ - uses: actions/download-artifact@v4
+ name: Download versionlist
+ with:
+ name: versionlist
+ path: versionlist
+ - name: Show versions
+ id: show_versions
+ run: |
+ a=(`ls versionlist`)
+ printf '%s\n' "${a[@]}" | jq -R . | jq -s .
+ echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
+
+ deploy-e2e:
+ if: ${{ success() }}
+ name: Deploy RocketMQ For E2E
+ needs: [list-version,docker]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: Deploy rocketmq
+ with:
+ action: "deploy"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
+ chart-branch: "master"
+ chart-path: "./rocketmq-k8s-helm"
+ job-id: ${{ strategy.job-index }}
+ helm-values: |
+ nameserver:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ broker:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ proxy:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+
+ deploy-benchmark:
+ if: ${{ success() }}
+ name: Deploy RocketMQ For Benchmarking
+ needs: [list-version,docker]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: Deploy rocketmq
+ with:
+ action: "deploy"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
+ chart-branch: "master"
+ chart-path: "./rocketmq-k8s-helm"
+ job-id: "001-${{ strategy.job-index }}"
+ helm-values: |
+ nameserver:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ broker:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ proxy:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+
+ test-e2e-grpc-java:
+ if: ${{ success() }}
+ name: Test E2E grpc java
+ needs: [list-version, deploy-e2e]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: java/e2e
+ test-cmd: "mvn -B test"
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-grpc-java-log.txt
+ path: testlog.txt
+
+ test-e2e-golang:
+ if: ${{ success() }}
+ name: Test E2E golang
+ needs: [list-version, deploy-e2e]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: golang
+ test-cmd: |
+ cd ../common && mvn -Prelease -DskipTests clean package -U
+ cd ../rocketmq-admintools && source bin/env.sh
+ LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1')
+ wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \
+ rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz
+ cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-golang-log.txt
+ path: testlog.txt
+
+ test-e2e-remoting-java:
+ if: ${{ success() }}
+ name: Test E2E remoting java
+ needs: [ list-version, deploy-e2e ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
+ test-code-branch: "master"
+ test-code-path: java/e2e-v4
+ test-cmd: "mvn -B test"
+ job-id: 0
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: test-e2e-remoting-java-log.txt
+ path: testlog.txt
+
+ benchmark-test:
+ if: ${{ success() }}
+ runs-on: ubuntu-latest
+ name: Performance benchmark test
+ needs: [ list-version, deploy-benchmark ]
+ timeout-minutes: 60
+ steps:
+ - uses: apache/rocketmq-test-tool/benchmark-runner@ce372e5f3906ca1891e4918b05be14608eae608e
+ name: Performance benchmark
+ with:
+ action: "performance-benchmark"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ job-id: "001-${{ strategy.job-index }}"
+ # The time to run the test, 15 minutes
+ test-time: "900"
+ # Some thresholds set in advance
+ min-send-tps-threshold: "12000"
+ max-rt-ms-threshold: "500"
+ avg-rt-ms-threshold: "10"
+ max-2c-rt-ms-threshold: "150"
+ avg-2c-rt-ms-threshold: "10"
+ - name: Upload test report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: benchmark-report
+ path: benchmark/
+
+ clean-e2e:
+ if: always()
+ name: Clean E2E
+ needs: [ list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: clean
+ with:
+ action: "clean"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ job-id: ${{ strategy.job-index }}
+
+ clean-benchmark:
+ if: always()
+ name: Clean Benchmarking
+ needs: [ list-version, benchmark-test ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
+ name: clean
+ with:
+ action: "clean"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ job-id: "001-${{ strategy.job-index }}"
\ No newline at end of file
diff --git a/.github/workflows/rerun-workflow.yml b/.github/workflows/rerun-workflow.yml
new file mode 100644
index 00000000000..6c319505d2c
--- /dev/null
+++ b/.github/workflows/rerun-workflow.yml
@@ -0,0 +1,22 @@
+name: Rerun workflow
+on:
+ workflow_run:
+ workflows: ["Build and Run Tests by Maven" , "Build and Run Tests by Bazel"]
+ types:
+ - completed
+
+permissions:
+ actions: write
+
+jobs:
+ rerun:
+ if: github.event.workflow_run.conclusion == 'failure' && fromJSON(github.event.workflow_run.run_attempt) < 3
+ runs-on: ubuntu-latest
+ steps:
+ - name: rerun ${{ github.event.workflow_run.id }}
+ env:
+ GH_REPO: ${{ github.repository }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh run watch ${{ github.event.workflow_run.id }} > /dev/null 2>&1
+ gh run rerun ${{ github.event.workflow_run.id }} --failed
\ No newline at end of file
diff --git a/.github/workflows/snapshot-automation.yml b/.github/workflows/snapshot-automation.yml
new file mode 100644
index 00000000000..9b297583b12
--- /dev/null
+++ b/.github/workflows/snapshot-automation.yml
@@ -0,0 +1,261 @@
+# 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.
+
+name: Snapshot Daily Release Automation
+on:
+ schedule: # schedule the job to run at 12 a.m. daily
+ - cron: "0 0 * * *"
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: 'The branch to trigger the workflow, The default branch is "develop" when both branch and commit_id are empty'
+ required: false
+ commit_id:
+ description: 'The commit id to trigger the workflow. Do not set branch and commit_id together'
+ required: false
+ rocketmq_version:
+ description: 'Name of the SNAPSHOT version to be generated. The default version is "$VERSION-stable-SNAPSHOT"'
+ required: false
+
+env:
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
+ DOCKER_REPO: apache/rocketmq-ci
+
+jobs:
+ dist-tar:
+ if: github.repository == 'apache/rocketmq'
+ name: Build dist tar
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout develop
+ if: github.event.inputs.branch == '' && github.event.inputs.commit_id == ''
+ uses: actions/checkout@v3
+ with:
+ ref: develop
+
+ - name: Checkout specific commit
+ if: github.event.inputs.branch == '' && github.event.inputs.commit_id != ''
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.inputs.commit_id }}
+
+ - name: Checkout specific branch
+ if: github.event.inputs.branch != '' && github.event.inputs.commit_id == ''
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.inputs.branch }}
+
+ - uses: actions/setup-java@v4
+ with:
+ distribution: "corretto"
+ java-version: "8"
+ cache: "maven"
+ - name: Build distribution tar
+ env:
+ MAVEN_SETTINGS: ${{ github.workspace }}/.github/asf-deploy-settings.xml
+ run: |
+ mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: rocketmq
+ path: distribution/target/rocketmq*/rocketmq*
+
+ docker-build:
+ if: ${{ success() }}
+ name: Docker images
+ needs: [ dist-tar ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ base-image: [ "ubuntu" ]
+ java-version: [ "8" ]
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ repository: apache/rocketmq-docker.git
+ ref: master
+ path: rocketmq-docker
+ - uses: actions/download-artifact@v4
+ name: Download distribution tar
+ with:
+ name: rocketmq
+ path: rocketmq
+ - name: docker-login
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and save docker images
+ id: build-images
+ run: |
+ cd rocketmq-docker/image-build-ci
+ version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
+ mkdir versionlist
+ touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
+ sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
+ - uses: actions/upload-artifact@v4
+ name: Upload distribution tar
+ with:
+ name: versionlist
+ path: rocketmq-docker/image-build-ci/versionlist/*
+
+ list-version:
+ if: >
+ github.repository == 'apache/rocketmq' &&
+ always()
+ name: List version
+ needs: [ docker-build ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ outputs:
+ version-json: ${{ steps.show_versions.outputs.version-json }}
+ steps:
+ - uses: actions/download-artifact@v4
+ name: Download versionlist
+ with:
+ name: versionlist
+ path: versionlist
+ - name: Show versions
+ id: show_versions
+ run: |
+ a=(`ls versionlist`)
+ printf '%s\n' "${a[@]}" | jq -R . | jq -s .
+ echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
+
+ deploy-rocketmq:
+ if: ${{ success() }}
+ name: Deploy RocketMQ
+ needs: [ list-version,docker-build ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
+ name: Deploy rocketmq
+ with:
+ action: "deploy"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ chart-git: "https://github.com/apache/rocketmq-docker.git"
+ chart-branch: "master"
+ chart-path: "./rocketmq-k8s-helm"
+ job-id: ${{ strategy.job-index }}
+ helm-values: |
+ nameserver:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ broker:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+ proxy:
+ image:
+ repository: ${{env.DOCKER_REPO}}
+ tag: ${{ matrix.version }}
+
+ java-grpc-e2e-test:
+ if: ${{ success() }}
+ name: E2E Test
+ needs: [ list-version, deploy-rocketmq ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
+ name: e2e test
+ with:
+ action: "test"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ test-code-git: "https://github.com/apache/rocketmq-e2e.git"
+ test-code-branch: "master"
+ test-code-path: java/e2e
+ test-cmd: "mvn -B test"
+ job-id: ${{ strategy.job-index }}
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: always() # always run even if the previous step fails
+ with:
+ report_paths: '**/test_report/TEST-*.xml'
+ annotate_only: true
+ include_passed: true
+ detailed_summary: true
+ - uses: actions/upload-artifact@v4
+ if: always()
+ name: Upload test log
+ with:
+ name: testlog.txt
+ path: testlog.txt
+
+ clean:
+ if: always()
+ name: Clean
+ needs: [ list-version, java-grpc-e2e-test ]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ matrix:
+ version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
+ steps:
+ - uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
+ name: clean
+ with:
+ action: "clean"
+ ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
+ test-version: "${{ matrix.version }}"
+ job-id: ${{ strategy.job-index }}
+
+ snapshot:
+ runs-on: ubuntu-latest
+ needs: [ java-grpc-e2e-test ]
+ env:
+ NEXUS_DEPLOY_USERNAME: ${{ secrets.NEXUS_USER }}
+ NEXUS_DEPLOY_PASSWORD: ${{ secrets.NEXUS_PW }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ref: develop
+ persist-credentials: false
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: 8
+ distribution: "corretto"
+ cache: "maven"
+ - name: Update default pom version
+ if: github.event.inputs.rocketmq_version == ''
+ run: |
+ VERSION=$(mvn -q -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec)
+ VERSION=$(echo $VERSION | awk -F '-SNAPSHOT' '{print $1}')
+ VERSION=$VERSION-stable-SNAPSHOT
+ mvn versions:set -DnewVersion=$VERSION
+ - name: Update User-defined pom version
+ if: github.event.inputs.rocketmq_version != ''
+ run: |
+ mvn versions:set -DnewVersion=${{ github.event.inputs.rocketmq_version }}
+ - name: Deploy to ASF Snapshots Repository
+ timeout-minutes: 40
+ run: mvn clean deploy -DskipTests=true --settings .github/asf-deploy-settings.xml
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..ca1a153e76f
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,32 @@
+name: Close Stale Issues/PRs
+
+permissions:
+ issues: write
+ pull-requests: write
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: "0 0 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v5
+ with:
+ operations-per-run: 128
+ days-before-issue-stale: 365
+ days-before-issue-close: 3
+ exempt-issue-labels: "no stale"
+ stale-issue-label: "stale"
+ stale-issue-message: "This issue is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs."
+ close-issue-message: "This issue was closed because it has been inactive for 3 days since being marked as stale."
+ remove-issue-stale-when-updated: true
+ days-before-pr-stale: 365
+ days-before-pr-close: 3
+ exempt-pr-labels: "no stale"
+ stale-pr-label: "stale"
+ stale-pr-message: "This PR is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs. If you wish not to mark it as stale, please leave a comment in this PR."
+ close-pr-message: "This PR was closed because it has been inactive for 3 days since being marked as stale."
+ remove-pr-stale-when-updated: true
diff --git a/.gitignore b/.gitignore
index 8abdfd8fd6e..4ee76210738 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,12 +4,18 @@
.settings/
target/
devenv
-*.log*
+*.log.*
*.iml
.idea/
*.versionsBackup
!NOTICE-BIN
!LICENSE-BIN
-.DS_Store
-localbin
-nohup.out
+.DS_Store
+localbin
+nohup.out
+bazel-out
+bazel-bin
+bazel-rocketmq
+bazel-testlogs
+.vscode
+MODULE.bazel.lock
\ No newline at end of file
diff --git a/.licenserc.yaml b/.licenserc.yaml
new file mode 100644
index 00000000000..f74fb8c5dec
--- /dev/null
+++ b/.licenserc.yaml
@@ -0,0 +1,50 @@
+#
+# 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.
+#
+header:
+ license:
+ spdx-id: Apache-2.0
+ copyright-owner: Apache Software Foundation
+
+ paths-ignore:
+ - '.gitignore'
+ - '.travis.yml'
+ - 'CONTRIBUTING.md'
+ - 'LICENSE'
+ - 'NOTICE'
+ - '**/*.md'
+ - 'BUILDING'
+ - '.github/**'
+ - '*/src/test/resources/certs/*'
+ - 'src/test/**/*.log'
+ - '*/src/test/resources/META-INF/service/*'
+ - '*/src/main/resources/META-INF/service/*'
+ - '*/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json'
+ - '*/src/test/resources/mockito-extensions/*'
+ - '**/target/**'
+ - '**/*.iml'
+ - 'docs/**'
+ - 'localbin/**'
+ - 'distribution/LICENSE-BIN'
+ - 'distribution/NOTICE-BIN'
+ - 'distribution/conf/rmq-proxy.json'
+ - '.bazelversion'
+ - 'common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator'
+
+
+ comment: on-failure
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8f65c72f7d0..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-notifications:
- email:
- recipients:
- - dev@rocketmq.apache.org
- on_success: change
- on_failure: always
-
-language: java
-
-matrix:
- include:
- # On OSX, run with default JDK only.
- # - os: osx
- # On Linux, run with specific JDKs only.
- - os: linux
- env: CUSTOM_JDK="oraclejdk8"
-
-before_install:
- - echo 'MAVEN_OPTS="$MAVEN_OPTS -Xmx1024m -XX:MaxPermSize=512m -XX:+BytecodeVerificationLocal"' >> ~/.mavenrc
- - cat ~/.mavenrc
- - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export JAVA_HOME=$(/usr/libexec/java_home); fi
- - if [ "$TRAVIS_OS_NAME" == "linux" ]; then jdk_switcher use "$CUSTOM_JDK"; fi
-
-script:
- - travis_retry mvn -B clean apache-rat:check
- - travis_retry mvn -B package jacoco:report coveralls:report
-
-after_success:
- - mvn clean install -Pit-test
- - mvn sonar:sonar -Psonar-apache
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 00000000000..ba33a9e6123
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,49 @@
+#
+# 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.
+#
+load("@bazel_toolchains//rules/exec_properties:exec_properties.bzl", "create_rbe_exec_properties_dict")
+
+platform(
+ name = "custom_platform",
+ # Inherit from the platform target generated by 'rbe_configs_gen' assuming the generated configs
+ # were imported as a Bazel external repository named 'rbe_default'. If you extracted the
+ # generated configs elsewhere in your source repository, replace the following with the label
+ # to the 'platform' target in the generated configs.
+ parents = ["@rbe_default//config:platform"],
+ # Example custom execution property instructing RBE to use e2-standard-2 GCE VMs.
+ exec_properties = create_rbe_exec_properties_dict(
+ container_image = "ubuntu:latest",
+ ),
+)
+
+java_library(
+ name = "test_deps",
+ visibility = ["//visibility:public"],
+ exports = [
+ "@maven//:junit_junit",
+ "@maven//:org_assertj_assertj_core",
+ "@maven//:org_hamcrest_hamcrest_library",
+ "@maven//:org_mockito_mockito_core",
+ "@maven//:org_powermock_powermock_module_junit4",
+ "@maven//:org_powermock_powermock_api_mockito2",
+ "@maven//:org_hamcrest_hamcrest_core",
+ "@maven//:ch_qos_logback_logback_classic",
+ "@maven//:org_awaitility_awaitility",
+ "@maven//:org_openjdk_jmh_jmh_core",
+ "@maven//:org_openjdk_jmh_jmh_generator_annprocess",
+ "@maven//:org_mockito_mockito_junit_jupiter",
+ ],
+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b7b133472c4..f3386e8c9d0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,28 +5,44 @@ We want to have high quality, well documented codes for each programming languag
Nor is code the only way to contribute to the project. We strongly value documentation, integration with other project, and gladly accept improvements for these aspects.
+Recommend reading:
+ * [Contributors Tech Guide](http://www.apache.org/dev/contributors)
+ * [Get involved!](http://www.apache.org/foundation/getinvolved.html)
+
## Contributing code
To submit a change for inclusion, please do the following:
#### If the change is non-trivial please include some unit tests that cover the new functionality.
-#### If you are introducing a completely new feature or API it is a good idea to start a wiki and get consensus on the basic design first.
+#### If you are introducing a completely new feature or API it is a good idea to start a [RIP](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) and get consensus on the basic design first.
#### It is our job to follow up on patches in a timely fashion. Nag us if we aren't doing our job (sometimes we drop things).
+### Squash commits
+
+If your have a pull request on GitHub, and updated more than once, it's better to squash all commits.
+
+1. Identify how many commits you made since you began: ``git log``.
+2. Squash these commits by N: ``git rebase -i HEAD~N`` .
+3. Leave "pick" tag in the first line.
+4. Change all other commits from "pick" to "fixup".
+5. Then do "force push" to overwrite remote history: ``git push -u origin ROCKETMQ-9999 --force``
+6. All your changes are now in a single commit, that would be much better for review.
+
+More details of squash can be found at [stackoverflow](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git).
+
## Becoming a Committer
We are always interested in adding new contributors. What we look for are series of contributions, good taste and ongoing interest in the project. If you are interested in becoming a committer, please let one of the existing committers know and they can help you walk through the process.
Nowadays,we have several important contribution points:
#### Wiki & JavaDoc
-#### RocketMQ Console
#### RocketMQ SDK(C++\.Net\Php\Python\Go\Node.js)
-#### RocketMQ MySQL(Oracle\PostgreSQL\Redis\MongoDB\HBase\MSSQL) Replicator
+#### RocketMQ Connectors
##### Prerequisite
If you want to contribute the above listing points, you must abide our some prerequisites:
-###### Readability - API must have Javadoc,some very important methods also must have javadoc
+###### Readability - API must have Javadoc, some very important methods also must have javadoc
###### Testability - 80% above unit test coverage about main process
###### Maintainability - Comply with our [checkstyle spec](style/rmq_checkstyle.xml), and at least 3 month update frequency
###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/)
diff --git a/LICENSE b/LICENSE
index 7f77f44e739..f49a4e16e68 100644
--- a/LICENSE
+++ b/LICENSE
@@ -15,7 +15,7 @@
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
- "control" means (properties) the power, direct or indirect, to cause the
+ "control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 00000000000..15fc5c6e3a6
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+###############################################################################
+# Bazel now uses Bzlmod by default to manage external dependencies.
+# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
+#
+# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
+###############################################################################
diff --git a/NOTICE b/NOTICE
index 85e2dc3f511..6e7ed4a0f20 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
Apache RocketMQ
-Copyright 2016-2019 The Apache Software Foundation
+Copyright 2016-2025 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
index 35817421d82..1c82b34f92d 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,194 @@
-## Apache RocketMQ [](https://travis-ci.org/apache/rocketmq) [](https://coveralls.io/github/apache/rocketmq?branch=master)
-[](http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq)
-[](https://rocketmq.apache.org/dowloading/releases)
-[](https://www.apache.org/licenses/LICENSE-2.0.html)
+## Apache RocketMQ
+
+[![Build Status][maven-build-image]][maven-build-url]
+[![CodeCov][codecov-image]][codecov-url]
+[![Maven Central][maven-central-image]][maven-central-url]
+[![Release][release-image]][release-url]
+[![License][license-image]][license-url]
+[![Average Time to Resolve An Issue][percentage-of-issues-still-open-image]][percentage-of-issues-still-open-url]
+[![Percentage of Issues Still Open][average-time-to-resolve-an-issue-image]][average-time-to-resolve-an-issue-url]
+[![Twitter Follow][twitter-follow-image]][twitter-follow-url]
**[Apache RocketMQ](https://rocketmq.apache.org) is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability.**
+
It offers a variety of features:
-* Pub/Sub messaging model
-* Scheduled message delivery
+* Messaging patterns including publish/subscribe, request/reply and streaming
+* Financial grade transactional message
+* Built-in fault tolerance and high availability configuration options base on [DLedger Controller](docs/en/controller/quick_start.md)
+* Built-in message tracing capability, also support opentracing
+* Versatile big-data and streaming ecosystem integration
* Message retroactivity by time or offset
-* Log hub for streaming
-* Big data integration
* Reliable FIFO and strict ordered messaging in the same queue
-* Efficient pull&push consumption model
+* Efficient pull and push consumption model
* Million-level message accumulation capacity in a single queue
-* Multiple messaging protocols like JMS and OpenMessaging
+* Multiple messaging protocols like gRPC, MQTT, JMS and OpenMessaging
* Flexible distributed scale-out deployment architecture
* Lightning-fast batch message exchange system
* Various message filter mechanics such as SQL and Tag
* Docker images for isolated testing and cloud isolated clusters
* Feature-rich administrative dashboard for configuration, metrics and monitoring
-* Access control list
-* Message trace
+* Authentication and authorization
+* Free open source connectors, for both sources and sinks
+* Lightweight real-time computing
+----------
-----------
+## Quick Start
+
+This paragraph guides you through steps of installing RocketMQ in different ways.
+For local development and testing, only one instance will be created for each component.
+
+### Run RocketMQ locally
+
+RocketMQ runs on all major operating systems and requires only a Java JDK version 8 or higher to be installed.
+To check, run `java -version`:
+```shell
+$ java -version
+java version "1.8.0_121"
+```
+
+For Windows users, click [here](https://dist.apache.org/repos/dist/release/rocketmq/5.3.2/rocketmq-all-5.3.2-bin-release.zip) to download the 5.3.2 RocketMQ binary release,
+unpack it to your local disk, such as `D:\rocketmq`.
+For macOS and Linux users, execute following commands:
+
+```shell
+# Download release from the Apache mirror
+$ wget https://dist.apache.org/repos/dist/release/rocketmq/5.3.2/rocketmq-all-5.3.2-bin-release.zip
+
+# Unpack the release
+$ unzip rocketmq-all-5.3.2-bin-release.zip
+```
+
+Prepare a terminal and change to the extracted `bin` directory:
+```shell
+$ cd rocketmq-all-5.3.2-bin-release/bin
+```
+
+**1) Start NameServer**
+
+NameServer will be listening at `0.0.0.0:9876`, make sure that the port is not used by others on the local machine, and then do as follows.
+
+For macOS and Linux users:
+```shell
+### start Name Server
+$ nohup sh mqnamesrv &
+
+### check whether Name Server is successfully started
+$ tail -f ~/logs/rocketmqlogs/namesrv.log
+The Name Server boot success...
+```
+
+For Windows users, you need set environment variables first:
+- From the desktop, right click the Computer icon.
+- Choose Properties from the context menu.
+- Click the Advanced system settings link.
+- Click Environment Variables.
+- Add Environment `ROCKETMQ_HOME="D:\rocketmq"`.
+
+Then change directory to rocketmq, type and run:
+```shell
+$ mqnamesrv.cmd
+The Name Server boot success...
+```
+
+**2) Start Broker**
+
+For macOS and Linux users:
+```shell
+### start Broker
+$ nohup sh bin/mqbroker -n localhost:9876 &
+
+### check whether Broker is successfully started, eg: Broker's IP is 192.168.1.2, Broker's name is broker-a
+$ tail -f ~/logs/rocketmqlogs/broker.log
+The broker[broker-a, 192.169.1.2:10911] boot success...
+```
+
+For Windows users:
+```shell
+$ mqbroker.cmd -n localhost:9876
+The broker[broker-a, 192.169.1.2:10911] boot success...
+```
+
+### Run RocketMQ in Docker
+
+You can run RocketMQ on your own machine within Docker containers,
+`host` network will be used to expose listening port in the container.
+
+**1) Start NameServer**
+
+```shell
+$ docker run -it --net=host apache/rocketmq ./mqnamesrv
+```
+**2) Start Broker**
+
+```shell
+$ docker run -it --net=host --mount type=bind,source=/tmp/store,target=/home/rocketmq/store apache/rocketmq ./mqbroker -n localhost:9876
+```
+
+### Run RocketMQ in Kubernetes
+
+You can also run a RocketMQ cluster within a Kubernetes cluster using [RocketMQ Operator](https://github.com/apache/rocketmq-operator).
+Before your operations, make sure that `kubectl` and related kubeconfig file installed on your machine.
+
+**1) Install CRDs**
+```shell
+### install CRDs
+$ git clone https://github.com/apache/rocketmq-operator
+$ cd rocketmq-operator && make deploy
+
+### check whether CRDs is successfully installed
+$ kubectl get crd | grep rocketmq.apache.org
+brokers.rocketmq.apache.org 2022-05-12T09:23:18Z
+consoles.rocketmq.apache.org 2022-05-12T09:23:19Z
+nameservices.rocketmq.apache.org 2022-05-12T09:23:18Z
+topictransfers.rocketmq.apache.org 2022-05-12T09:23:19Z
+
+### check whether operator is running
+$ kubectl get pods | grep rocketmq-operator
+rocketmq-operator-6f65c77c49-8hwmj 1/1 Running 0 93s
+```
+
+**2) Create Cluster Instance**
+```shell
+### create RocketMQ cluster resource
+$ cd example && kubectl create -f rocketmq_v1alpha1_rocketmq_cluster.yaml
+
+### check whether cluster resources is running
+$ kubectl get sts
+NAME READY AGE
+broker-0-master 1/1 107m
+broker-0-replica-1 1/1 107m
+name-service 1/1 107m
+```
+
+---
+## Apache RocketMQ Community
+* [RocketMQ Streams](https://github.com/apache/rocketmq-streams): A lightweight stream computing engine based on Apache RocketMQ.
+* [RocketMQ Flink](https://github.com/apache/rocketmq-flink): The Apache RocketMQ connector of Apache Flink that supports source and sink connector in data stream and Table.
+* [RocketMQ APIs](https://github.com/apache/rocketmq-apis): RocketMQ protobuf protocol.
+* [RocketMQ Clients](https://github.com/apache/rocketmq-clients): gRPC/protobuf-based RocketMQ clients.
+* RocketMQ Remoting-based Clients
+ - [RocketMQ Client CPP](https://github.com/apache/rocketmq-client-cpp)
+ - [RocketMQ Client Go](https://github.com/apache/rocketmq-client-go)
+ - [RocketMQ Client Python](https://github.com/apache/rocketmq-client-python)
+ - [RocketMQ Client Nodejs](https://github.com/apache/rocketmq-client-nodejs)
+* [RocketMQ Spring](https://github.com/apache/rocketmq-spring): A project which helps developers quickly integrate Apache RocketMQ with Spring Boot.
+* [RocketMQ Exporter](https://github.com/apache/rocketmq-exporter): An Apache RocketMQ exporter for Prometheus.
+* [RocketMQ Operator](https://github.com/apache/rocketmq-operator): Providing a way to run an Apache RocketMQ cluster on Kubernetes.
+* [RocketMQ Docker](https://github.com/apache/rocketmq-docker): The Git repo of the Docker Image for Apache RocketMQ.
+* [RocketMQ Dashboard](https://github.com/apache/rocketmq-dashboard): Operation and maintenance console of Apache RocketMQ.
+* [RocketMQ Connect](https://github.com/apache/rocketmq-connect): A tool for scalably and reliably streaming data between Apache RocketMQ and other systems.
+* [RocketMQ MQTT](https://github.com/apache/rocketmq-mqtt): A new MQTT protocol architecture model, based on which Apache RocketMQ can better support messages from terminals such as IoT devices and Mobile APP.
+* [RocketMQ EventBridge](https://github.com/apache/rocketmq-eventbridge): EventBridge make it easier to build a event-driven application.
+* [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals): Incubator community projects of Apache RocketMQ, including [logappender](https://github.com/apache/rocketmq-externals/tree/master/logappender), [rocketmq-ansible](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-ansible), [rocketmq-beats-integration](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-beats-integration), [rocketmq-cloudevents-binding](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-cloudevents-binding), etc.
+* [RocketMQ Site](https://github.com/apache/rocketmq-site): The repository for Apache RocketMQ website.
+* [RocketMQ E2E](https://github.com/apache/rocketmq-e2e): A project for testing Apache RocketMQ, including end-to-end, performance, compatibility tests.
+
+
+----------
## Learn it & Contact us
* Mailing Lists:
* Home:
@@ -35,17 +197,54 @@ It offers a variety of features:
* Rips:
* Ask:
* Slack:
-
-----------
-## Apache RocketMQ Community
-* [RocketMQ Community Projects](https://github.com/apache/rocketmq-externals)
----------
+
+
## Contributing
We always welcome new contributions, whether for trivial cleanups, [big new features](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) or other material rewards, more details see [here](http://rocketmq.apache.org/docs/how-to-contribute/).
-
+
----------
## License
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation
+
+
+----------
+## Export Control Notice
+This distribution includes cryptographic software. The country in which you currently reside may have
+restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
+BEFORE using any encryption software, please check your country's laws, regulations and policies concerning
+the import, possession, or use, and re-export of encryption software, to see if this is permitted. See
+ for more information.
+
+The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this
+software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software
+using or performing cryptographic functions with asymmetric algorithms. The form and manner of this Apache
+Software Foundation distribution makes it eligible for export under the License Exception ENC Technology
+Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for
+both object code and source code.
+
+The following provides more details on the included cryptographic software:
+
+This software uses Apache Commons Crypto (https://commons.apache.org/proper/commons-crypto/) to
+support authentication, and encryption and decryption of data sent across the network between
+services.
+
+[maven-build-image]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml/badge.svg
+[maven-build-url]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml
+[codecov-image]: https://codecov.io/gh/apache/rocketmq/branch/master/graph/badge.svg
+[codecov-url]: https://codecov.io/gh/apache/rocketmq
+[maven-central-image]: https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-all/badge.svg
+[maven-central-url]: http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq
+[release-image]: https://img.shields.io/badge/release-download-orange.svg
+[release-url]: https://www.apache.org/licenses/LICENSE-2.0.html
+[license-image]: https://img.shields.io/badge/license-Apache%202-4EB1BA.svg
+[license-url]: https://www.apache.org/licenses/LICENSE-2.0.html
+[average-time-to-resolve-an-issue-image]: http://isitmaintained.com/badge/resolution/apache/rocketmq.svg
+[average-time-to-resolve-an-issue-url]: http://isitmaintained.com/project/apache/rocketmq
+[percentage-of-issues-still-open-image]: http://isitmaintained.com/badge/open/apache/rocketmq.svg
+[percentage-of-issues-still-open-url]: http://isitmaintained.com/project/apache/rocketmq
+[twitter-follow-image]: https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social
+[twitter-follow-url]: https://twitter.com/intent/follow?screen_name=ApacheRocketMQ
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 00000000000..9125a67f88b
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,144 @@
+#
+# 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.
+#
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+RULES_JVM_EXTERNAL_TAG = "4.2"
+
+RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca"
+
+http_archive(
+ name = "rules_jvm_external",
+ sha256 = RULES_JVM_EXTERNAL_SHA,
+ strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
+ url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
+)
+
+load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
+
+rules_jvm_external_deps()
+
+load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
+
+rules_jvm_external_setup()
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+
+maven_install(
+ artifacts = [
+ "junit:junit:4.13.2",
+ "com.alibaba:fastjson:1.2.76",
+ "com.alibaba.fastjson2:fastjson2:2.0.43",
+ "org.hamcrest:hamcrest-library:1.3",
+ "io.netty:netty-all:4.1.65.Final",
+ "org.assertj:assertj-core:3.22.0",
+ "org.mockito:mockito-core:3.10.0",
+ "org.powermock:powermock-module-junit4:2.0.9",
+ "org.powermock:powermock-api-mockito2:2.0.9",
+ "org.powermock:powermock-core:2.0.9",
+ "com.github.luben:zstd-jni:1.5.2-2",
+ "org.lz4:lz4-java:1.8.0",
+ "commons-validator:commons-validator:1.7",
+ "org.apache.commons:commons-lang3:3.12.0",
+ "org.hamcrest:hamcrest-core:1.3",
+ "io.openmessaging.storage:dledger:0.3.1",
+ "net.java.dev.jna:jna:4.2.2",
+ "ch.qos.logback:logback-classic:1.2.10",
+ "ch.qos.logback:logback-core:1.2.10",
+ "io.opentracing:opentracing-api:0.33.0",
+ "io.opentracing:opentracing-mock:0.33.0",
+ "commons-collections:commons-collections:3.2.2",
+ "org.awaitility:awaitility:4.1.0",
+ "commons-cli:commons-cli:1.5.0",
+ "com.google.guava:guava:31.0.1-jre",
+ "org.yaml:snakeyaml:1.30",
+ "commons-codec:commons-codec:1.13",
+ "commons-io:commons-io:2.7",
+ "com.google.truth:truth:0.30",
+ "org.bouncycastle:bcpkix-jdk15on:1.69",
+ "com.google.code.gson:gson:2.8.9",
+ "com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2",
+ "org.apache.rocketmq:rocketmq-proto:2.0.4",
+ "com.google.protobuf:protobuf-java:3.20.1",
+ "com.google.protobuf:protobuf-java-util:3.20.1",
+ "com.conversantmedia:disruptor:1.2.10",
+ "org.apache.tomcat:annotations-api:6.0.53",
+ "com.google.code.findbugs:jsr305:3.0.2",
+ "org.checkerframework:checker-qual:3.12.0",
+ "org.reflections:reflections:0.9.11",
+ "org.openjdk.jmh:jmh-core:1.19",
+ "org.openjdk.jmh:jmh-generator-annprocess:1.19",
+ "com.github.ben-manes.caffeine:caffeine:2.9.3",
+ "io.grpc:grpc-services:1.47.0",
+ "io.grpc:grpc-netty-shaded:1.47.0",
+ "io.grpc:grpc-context:1.47.0",
+ "io.grpc:grpc-stub:1.47.0",
+ "io.grpc:grpc-api:1.47.0",
+ "io.grpc:grpc-testing:1.47.0",
+ "org.springframework:spring-core:5.3.26",
+ "io.opentelemetry:opentelemetry-exporter-otlp:1.29.0",
+ "io.opentelemetry:opentelemetry-exporter-prometheus:1.29.0-alpha",
+ "io.opentelemetry:opentelemetry-exporter-logging:1.29.0",
+ "io.opentelemetry:opentelemetry-sdk:1.29.0",
+ "io.opentelemetry:opentelemetry-exporter-logging-otlp:1.29.0",
+ "com.squareup.okio:okio-jvm:3.0.0",
+ "io.opentelemetry:opentelemetry-api:1.29.0",
+ "io.opentelemetry:opentelemetry-sdk-metrics:1.29.0",
+ "io.opentelemetry:opentelemetry-sdk-common:1.29.0",
+ "io.github.aliyunmq:rocketmq-slf4j-api:1.0.0",
+ "io.github.aliyunmq:rocketmq-logback-classic:1.0.0",
+ "org.slf4j:jul-to-slf4j:2.0.6",
+ "org.jetbrains:annotations:23.1.0",
+ "io.github.aliyunmq:rocketmq-shaded-slf4j-api-bridge:1.0.0",
+ "software.amazon.awssdk:s3:2.20.29",
+ "com.fasterxml.jackson.core:jackson-databind:2.13.4.2",
+ "com.adobe.testing:s3mock-junit4:2.11.0",
+ "io.github.aliyunmq:rocketmq-grpc-netty-codec-haproxy:1.0.0",
+ "org.apache.rocketmq:rocketmq-rocksdb:1.0.2",
+ "com.alipay.sofa:jraft-core:1.3.14",
+ "com.alipay.sofa:hessian:3.3.6",
+ "io.netty:netty-tcnative-boringssl-static:2.0.48.Final",
+ "org.mockito:mockito-junit-jupiter:4.11.0",
+ "com.alibaba.fastjson2:fastjson2:2.0.43",
+ "org.junit.jupiter:junit-jupiter-api:5.9.1",
+ ],
+ fetch_sources = True,
+ repositories = [
+ # Private repositories are supported through HTTP Basic auth
+ "https://repo1.maven.org/maven2",
+ ],
+)
+
+http_archive(
+ name = "io_buildbuddy_buildbuddy_toolchain",
+ sha256 = "b12273608db627eb14051eb75f8a2134590172cd69392086d392e25f3954ea6e",
+ strip_prefix = "buildbuddy-toolchain-8d5d18373adfca9d8e33b4812915abc9b132f1ee",
+ urls = ["https://github.com/buildbuddy-io/buildbuddy-toolchain/archive/8d5d18373adfca9d8e33b4812915abc9b132f1ee.tar.gz"],
+)
+load("@io_buildbuddy_buildbuddy_toolchain//:deps.bzl", "buildbuddy_deps")
+buildbuddy_deps()
+load("@io_buildbuddy_buildbuddy_toolchain//:rules.bzl", "buildbuddy")
+buildbuddy(name = "buildbuddy_toolchain")
+
+http_archive(
+ name = "bazel_toolchains",
+ sha256 = "1adf5db506a7e3c465a26988514cfc3971af6d5b3c2218925cd6e71ee443fc3f",
+ strip_prefix = "bazel-toolchains-4.0.0",
+ urls = [
+ "https://github.com/bazelbuild/bazel-toolchains/releases/download/4.0.0/bazel-toolchains-4.0.0.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/4.0.0/bazel-toolchains-4.0.0.tar.gz",
+ ],
+)
diff --git a/acl/pom.xml b/acl/pom.xml
deleted file mode 100644
index 4482cd1e7cc..00000000000
--- a/acl/pom.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
- 4.0.0
-
- org.apache.rocketmq
- rocketmq-all
- 4.4.1-SNAPSHOT
-
- rocketmq-acl
- rocketmq-acl ${project.version}
-
- http://maven.apache.org
-
- UTF-8
-
-
-
- ${project.groupId}
- rocketmq-remoting
-
-
- ${project.groupId}
- rocketmq-logging
-
-
- ${project.groupId}
- rocketmq-common
-
-
- ${project.groupId}
- rocketmq-srvutil
-
-
- org.yaml
- snakeyaml
-
-
- commons-codec
- commons-codec
-
-
- org.apache.commons
- commons-lang3
-
-
-
- org.slf4j
- slf4j-api
- test
-
-
- ch.qos.logback
- logback-classic
- test
-
-
- ch.qos.logback
- logback-core
- test
-
-
-
-
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java b/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java
deleted file mode 100644
index c915cf35d1c..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/AccessValidator.java
+++ /dev/null
@@ -1,38 +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.rocketmq.acl;
-
-import org.apache.rocketmq.remoting.protocol.RemotingCommand;
-
-public interface AccessValidator {
- /**
- * Parse to get the AccessResource(user, resource, needed permission)
- *
- * @param request
- * @param remoteAddr
- * @return Plain access resource result,include access key,signature and some other access attributes.
- */
- AccessResource parse(RemotingCommand request, String remoteAddr);
-
- /**
- * Validate the access resource.
- *
- * @param accessResource
- */
- void validate(AccessResource accessResource);
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java
deleted file mode 100644
index 9e5bf1fb5d9..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclClientRPCHook.java
+++ /dev/null
@@ -1,98 +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.rocketmq.acl.common;
-
-import java.lang.reflect.Field;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-import org.apache.rocketmq.remoting.CommandCustomHeader;
-import org.apache.rocketmq.remoting.RPCHook;
-import org.apache.rocketmq.remoting.protocol.RemotingCommand;
-
-import static org.apache.rocketmq.acl.common.SessionCredentials.ACCESS_KEY;
-import static org.apache.rocketmq.acl.common.SessionCredentials.SECURITY_TOKEN;
-import static org.apache.rocketmq.acl.common.SessionCredentials.SIGNATURE;
-
-public class AclClientRPCHook implements RPCHook {
- private final SessionCredentials sessionCredentials;
- protected ConcurrentHashMap, Field[]> fieldCache =
- new ConcurrentHashMap, Field[]>();
-
- public AclClientRPCHook(SessionCredentials sessionCredentials) {
- this.sessionCredentials = sessionCredentials;
- }
-
- @Override
- public void doBeforeRequest(String remoteAddr, RemotingCommand request) {
- byte[] total = AclUtils.combineRequestContent(request,
- parseRequestContent(request, sessionCredentials.getAccessKey(), sessionCredentials.getSecurityToken()));
- String signature = AclUtils.calSignature(total, sessionCredentials.getSecretKey());
- request.addExtField(SIGNATURE, signature);
- request.addExtField(ACCESS_KEY, sessionCredentials.getAccessKey());
-
- // The SecurityToken value is unneccessary,user can choose this one.
- if (sessionCredentials.getSecurityToken() != null) {
- request.addExtField(SECURITY_TOKEN, sessionCredentials.getSecurityToken());
- }
- }
-
- @Override
- public void doAfterResponse(String remoteAddr, RemotingCommand request, RemotingCommand response) {
-
- }
-
- protected SortedMap parseRequestContent(RemotingCommand request, String ak, String securityToken) {
- CommandCustomHeader header = request.readCustomHeader();
- // Sort property
- SortedMap map = new TreeMap();
- map.put(ACCESS_KEY, ak);
- if (securityToken != null) {
- map.put(SECURITY_TOKEN, securityToken);
- }
- try {
- // Add header properties
- if (null != header) {
- Field[] fields = fieldCache.get(header.getClass());
- if (null == fields) {
- fields = header.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- }
- Field[] tmp = fieldCache.putIfAbsent(header.getClass(), fields);
- if (null != tmp) {
- fields = tmp;
- }
- }
-
- for (Field field : fields) {
- Object value = field.get(header);
- if (null != value && !field.isSynthetic()) {
- map.put(field.getName(), value.toString());
- }
- }
- }
- return map;
- } catch (Exception e) {
- throw new RuntimeException("incompatible exception.", e);
- }
- }
-
- public SessionCredentials getSessionCredentials() {
- return sessionCredentials;
- }
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java
deleted file mode 100644
index 1a618456f40..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclUtils.java
+++ /dev/null
@@ -1,140 +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.rocketmq.acl.common;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Map;
-import java.util.SortedMap;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.rocketmq.remoting.protocol.RemotingCommand;
-import org.yaml.snakeyaml.Yaml;
-
-import static org.apache.rocketmq.acl.common.SessionCredentials.CHARSET;
-
-public class AclUtils {
-
- public static byte[] combineRequestContent(RemotingCommand request, SortedMap fieldsMap) {
- try {
- StringBuilder sb = new StringBuilder("");
- for (Map.Entry entry : fieldsMap.entrySet()) {
- if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) {
- sb.append(entry.getValue());
- }
- }
-
- return AclUtils.combineBytes(sb.toString().getBytes(CHARSET), request.getBody());
- } catch (Exception e) {
- throw new RuntimeException("incompatible exception.", e);
- }
- }
-
- public static byte[] combineBytes(byte[] b1, byte[] b2) {
- int size = (null != b1 ? b1.length : 0) + (null != b2 ? b2.length : 0);
- byte[] total = new byte[size];
- if (null != b1)
- System.arraycopy(b1, 0, total, 0, b1.length);
- if (null != b2)
- System.arraycopy(b2, 0, total, b1.length, b2.length);
- return total;
- }
-
- public static String calSignature(byte[] data, String secretKey) {
- String signature = AclSigner.calSignature(data, secretKey);
- return signature;
- }
-
- public static void verify(String netaddress, int index) {
- if (!AclUtils.isScope(netaddress, index)) {
- throw new AclException(String.format("netaddress examine scope Exception netaddress is %s", netaddress));
- }
- }
-
- public static String[] getAddreeStrArray(String netaddress, String four) {
- String[] fourStrArray = StringUtils.split(four.substring(1, four.length() - 1), ",");
- String address = netaddress.substring(0, netaddress.indexOf("{"));
- String[] addreeStrArray = new String[fourStrArray.length];
- for (int i = 0; i < fourStrArray.length; i++) {
- addreeStrArray[i] = address + fourStrArray[i];
- }
- return addreeStrArray;
- }
-
- public static boolean isScope(String num, int index) {
- String[] strArray = StringUtils.split(num, ".");
- if (strArray.length != 4) {
- return false;
- }
- return isScope(strArray, index);
-
- }
-
- public static boolean isScope(String[] num, int index) {
- if (num.length <= index) {
-
- }
- for (int i = 0; i < index; i++) {
- if (!isScope(num[i])) {
- return false;
- }
- }
- return true;
-
- }
-
- public static boolean isScope(String num) {
- return isScope(Integer.valueOf(num.trim()));
- }
-
- public static boolean isScope(int num) {
- return num >= 0 && num <= 255;
- }
-
- public static boolean isAsterisk(String asterisk) {
- return asterisk.indexOf('*') > -1;
- }
-
- public static boolean isColon(String colon) {
- return colon.indexOf(',') > -1;
- }
-
- public static boolean isMinus(String minus) {
- return minus.indexOf('-') > -1;
-
- }
-
- public static T getYamlDataObject(String path, Class clazz) {
- Yaml ymal = new Yaml();
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(new File(path));
- return ymal.loadAs(fis, clazz);
- } catch (Exception e) {
- throw new AclException(String.format("The file for Plain mode was not found , paths %s", path), e);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- throw new AclException("close transport fileInputStream Exception", e);
- }
- }
- }
- }
-
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java b/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java
deleted file mode 100644
index 0acc8e95081..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java
+++ /dev/null
@@ -1,96 +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.rocketmq.acl.common;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.rocketmq.acl.plain.PlainAccessResource;
-import org.apache.rocketmq.common.protocol.RequestCode;
-
-public class Permission {
-
- public static final byte DENY = 1;
- public static final byte ANY = 1 << 1;
- public static final byte PUB = 1 << 2;
- public static final byte SUB = 1 << 3;
-
- public static final Set ADMIN_CODE = new HashSet();
-
- static {
- // UPDATE_AND_CREATE_TOPIC
- ADMIN_CODE.add(RequestCode.UPDATE_AND_CREATE_TOPIC);
- // UPDATE_BROKER_CONFIG
- ADMIN_CODE.add(RequestCode.UPDATE_BROKER_CONFIG);
- // DELETE_TOPIC_IN_BROKER
- ADMIN_CODE.add(RequestCode.DELETE_TOPIC_IN_BROKER);
- // UPDATE_AND_CREATE_SUBSCRIPTIONGROUP
- ADMIN_CODE.add(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP);
- // DELETE_SUBSCRIPTIONGROUP
- ADMIN_CODE.add(RequestCode.DELETE_SUBSCRIPTIONGROUP);
- }
-
- public static boolean checkPermission(byte neededPerm, byte ownedPerm) {
- if ((ownedPerm & DENY) > 0) {
- return false;
- }
- if ((neededPerm & ANY) > 0) {
- return ((ownedPerm & PUB) > 0) || ((ownedPerm & SUB) > 0);
- }
- return (neededPerm & ownedPerm) > 0;
- }
-
- public static byte parsePermFromString(String permString) {
- if (permString == null) {
- return Permission.DENY;
- }
- switch (permString.trim()) {
- case "PUB":
- return Permission.PUB;
- case "SUB":
- return Permission.SUB;
- case "PUB|SUB":
- return Permission.PUB | Permission.SUB;
- case "SUB|PUB":
- return Permission.PUB | Permission.SUB;
- case "DENY":
- return Permission.DENY;
- default:
- return Permission.DENY;
- }
- }
-
- public static void parseResourcePerms(PlainAccessResource plainAccessResource, Boolean isTopic,
- List resources) {
- if (resources == null || resources.isEmpty()) {
- return;
- }
- for (String resource : resources) {
- String[] items = StringUtils.split(resource, "=");
- if (items.length == 2) {
- plainAccessResource.addResourceAndPerm(isTopic ? items[0].trim() : PlainAccessResource.getRetryTopic(items[0].trim()), parsePermFromString(items[1].trim()));
- } else {
- throw new AclException(String.format("Parse resource permission failed for %s:%s", isTopic ? "topic" : "group", resource));
- }
- }
- }
-
- public static boolean needAdminPerm(Integer code) {
- return ADMIN_CODE.contains(code);
- }
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java
deleted file mode 100644
index bd50e1292ad..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainAccessValidator.java
+++ /dev/null
@@ -1,135 +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.rocketmq.acl.plain;
-
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import org.apache.rocketmq.acl.AccessResource;
-import org.apache.rocketmq.acl.AccessValidator;
-import org.apache.rocketmq.acl.common.AclException;
-import org.apache.rocketmq.acl.common.AclUtils;
-import org.apache.rocketmq.acl.common.Permission;
-import org.apache.rocketmq.acl.common.SessionCredentials;
-import org.apache.rocketmq.common.protocol.RequestCode;
-import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader;
-import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader;
-import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader;
-import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
-import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
-import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
-import org.apache.rocketmq.remoting.protocol.RemotingCommand;
-
-import static org.apache.rocketmq.acl.plain.PlainAccessResource.getRetryTopic;
-
-public class PlainAccessValidator implements AccessValidator {
-
- private PlainPermissionLoader aclPlugEngine;
-
- public PlainAccessValidator() {
- aclPlugEngine = new PlainPermissionLoader();
- }
-
- @Override
- public AccessResource parse(RemotingCommand request, String remoteAddr) {
- PlainAccessResource accessResource = new PlainAccessResource();
- if (remoteAddr != null && remoteAddr.contains(":")) {
- accessResource.setWhiteRemoteAddress(remoteAddr.split(":")[0]);
- } else {
- accessResource.setWhiteRemoteAddress(remoteAddr);
- }
-
- if (request.getExtFields() == null) {
- throw new AclException("request's extFields value is null");
- }
-
- accessResource.setRequestCode(request.getCode());
- accessResource.setAccessKey(request.getExtFields().get(SessionCredentials.ACCESS_KEY));
- accessResource.setSignature(request.getExtFields().get(SessionCredentials.SIGNATURE));
- accessResource.setSecretToken(request.getExtFields().get(SessionCredentials.SECURITY_TOKEN));
-
- try {
- switch (request.getCode()) {
- case RequestCode.SEND_MESSAGE:
- accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.PUB);
- break;
- case RequestCode.SEND_MESSAGE_V2:
- accessResource.addResourceAndPerm(request.getExtFields().get("b"), Permission.PUB);
- break;
- case RequestCode.CONSUMER_SEND_MSG_BACK:
- accessResource.addResourceAndPerm(request.getExtFields().get("originTopic"), Permission.PUB);
- accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("group")), Permission.SUB);
- break;
- case RequestCode.PULL_MESSAGE:
- accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB);
- accessResource.addResourceAndPerm(getRetryTopic(request.getExtFields().get("consumerGroup")), Permission.SUB);
- break;
- case RequestCode.QUERY_MESSAGE:
- accessResource.addResourceAndPerm(request.getExtFields().get("topic"), Permission.SUB);
- break;
- case RequestCode.HEART_BEAT:
- HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class);
- for (ConsumerData data : heartbeatData.getConsumerDataSet()) {
- accessResource.addResourceAndPerm(getRetryTopic(data.getGroupName()), Permission.SUB);
- for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) {
- accessResource.addResourceAndPerm(subscriptionData.getTopic(), Permission.SUB);
- }
- }
- break;
- case RequestCode.UNREGISTER_CLIENT:
- final UnregisterClientRequestHeader unregisterClientRequestHeader =
- (UnregisterClientRequestHeader) request
- .decodeCommandCustomHeader(UnregisterClientRequestHeader.class);
- accessResource.addResourceAndPerm(getRetryTopic(unregisterClientRequestHeader.getConsumerGroup()), Permission.SUB);
- break;
- case RequestCode.GET_CONSUMER_LIST_BY_GROUP:
- final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader =
- (GetConsumerListByGroupRequestHeader) request
- .decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class);
- accessResource.addResourceAndPerm(getRetryTopic(getConsumerListByGroupRequestHeader.getConsumerGroup()), Permission.SUB);
- break;
- case RequestCode.UPDATE_CONSUMER_OFFSET:
- final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader =
- (UpdateConsumerOffsetRequestHeader) request
- .decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class);
- accessResource.addResourceAndPerm(getRetryTopic(updateConsumerOffsetRequestHeader.getConsumerGroup()), Permission.SUB);
- accessResource.addResourceAndPerm(updateConsumerOffsetRequestHeader.getTopic(), Permission.SUB);
- break;
- default:
- break;
-
- }
- } catch (Throwable t) {
- throw new AclException(t.getMessage(), t);
- }
- // Content
- SortedMap map = new TreeMap();
- for (Map.Entry entry : request.getExtFields().entrySet()) {
- if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) {
- map.put(entry.getKey(), entry.getValue());
- }
- }
- accessResource.setContent(AclUtils.combineRequestContent(request, map));
- return accessResource;
- }
-
- @Override
- public void validate(AccessResource accessResource) {
- aclPlugEngine.validate((PlainAccessResource) accessResource);
- }
-
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionLoader.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionLoader.java
deleted file mode 100644
index 9148422ff10..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionLoader.java
+++ /dev/null
@@ -1,300 +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.rocketmq.acl.plain;
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.apache.rocketmq.acl.common.AclException;
-import org.apache.rocketmq.acl.common.AclUtils;
-import org.apache.rocketmq.acl.common.Permission;
-import org.apache.rocketmq.common.MixAll;
-import org.apache.rocketmq.common.constant.LoggerName;
-import org.apache.rocketmq.logging.InternalLogger;
-import org.apache.rocketmq.logging.InternalLoggerFactory;
-import org.apache.rocketmq.srvutil.FileWatchService;
-
-public class PlainPermissionLoader {
-
- private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME);
-
- private static final String DEFAULT_PLAIN_ACL_FILE = "/conf/plain_acl.yml";
-
- private final ReadWriteLock lock = new ReentrantReadWriteLock();
-
- private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
- System.getenv(MixAll.ROCKETMQ_HOME_ENV));
-
- private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE);
-
- private Map plainAccessResourceMap = new HashMap<>();
-
- private List globalWhiteRemoteAddressStrategy = new ArrayList<>();
-
- private RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory();
-
- private boolean isWatchStart;
-
- public PlainPermissionLoader() {
- load();
- watch();
- }
-
- public void load() {
-
- Map plainAccessResourceMap = new HashMap<>();
- List globalWhiteRemoteAddressStrategy = new ArrayList<>();
-
- JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileHome + File.separator + fileName,
- JSONObject.class);
-
- if (plainAclConfData == null || plainAclConfData.isEmpty()) {
- throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName));
- }
- log.info("Broker plain acl conf data is : ", plainAclConfData.toString());
- JSONArray globalWhiteRemoteAddressesList = plainAclConfData.getJSONArray("globalWhiteRemoteAddresses");
- if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) {
- for (int i = 0; i < globalWhiteRemoteAddressesList.size(); i++) {
- globalWhiteRemoteAddressStrategy.add(remoteAddressStrategyFactory.
- getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i)));
- }
- }
-
- JSONArray accounts = plainAclConfData.getJSONArray("accounts");
- if (accounts != null && !accounts.isEmpty()) {
- List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class);
- for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) {
- PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig);
- plainAccessResourceMap.put(plainAccessResource.getAccessKey(),plainAccessResource);
- }
- }
-
- this.globalWhiteRemoteAddressStrategy = globalWhiteRemoteAddressStrategy;
- this.plainAccessResourceMap = plainAccessResourceMap;
- }
-
- private void watch() {
- try {
- String watchFilePath = fileHome + fileName;
- FileWatchService fileWatchService = new FileWatchService(new String[] {watchFilePath}, new FileWatchService.Listener() {
- @Override
- public void onChanged(String path) {
- log.info("The plain acl yml changed, reload the context");
- load();
- }
- });
- fileWatchService.start();
- log.info("Succeed to start AclWatcherService");
- this.isWatchStart = true;
- } catch (Exception e) {
- log.error("Failed to start AclWatcherService", e);
- }
- }
-
- void checkPerm(PlainAccessResource needCheckedAccess, PlainAccessResource ownedAccess) {
- if (Permission.needAdminPerm(needCheckedAccess.getRequestCode()) && !ownedAccess.isAdmin()) {
- throw new AclException(String.format("Need admin permission for request code=%d, but accessKey=%s is not", needCheckedAccess.getRequestCode(), ownedAccess.getAccessKey()));
- }
- Map needCheckedPermMap = needCheckedAccess.getResourcePermMap();
- Map ownedPermMap = ownedAccess.getResourcePermMap();
-
- if (needCheckedPermMap == null) {
- // If the needCheckedPermMap is null,then return
- return;
- }
-
- for (Map.Entry needCheckedEntry : needCheckedPermMap.entrySet()) {
- String resource = needCheckedEntry.getKey();
- Byte neededPerm = needCheckedEntry.getValue();
- boolean isGroup = PlainAccessResource.isRetryTopic(resource);
-
- if (!ownedPermMap.containsKey(resource)) {
- // Check the default perm
- byte ownedPerm = isGroup ? ownedAccess.getDefaultGroupPerm() :
- ownedAccess.getDefaultTopicPerm();
- if (!Permission.checkPermission(neededPerm, ownedPerm)) {
- throw new AclException(String.format("No default permission for %s", PlainAccessResource.printStr(resource, isGroup)));
- }
- continue;
- }
- if (!Permission.checkPermission(neededPerm, ownedPermMap.get(resource))) {
- throw new AclException(String.format("No default permission for %s", PlainAccessResource.printStr(resource, isGroup)));
- }
- }
- }
-
- void clearPermissionInfo() {
- this.plainAccessResourceMap.clear();
- this.globalWhiteRemoteAddressStrategy.clear();
- }
-
- public PlainAccessResource buildPlainAccessResource(PlainAccessConfig plainAccessConfig) throws AclException {
- if (plainAccessConfig.getAccessKey() == null
- || plainAccessConfig.getSecretKey() == null
- || plainAccessConfig.getAccessKey().length() <= 6
- || plainAccessConfig.getSecretKey().length() <= 6) {
- throw new AclException(String.format(
- "The accessKey=%s and secretKey=%s cannot be null and length should longer than 6",
- plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey()));
- }
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey());
- plainAccessResource.setSecretKey(plainAccessConfig.getSecretKey());
- plainAccessResource.setWhiteRemoteAddress(plainAccessConfig.getWhiteRemoteAddress());
-
- plainAccessResource.setAdmin(plainAccessConfig.isAdmin());
-
- plainAccessResource.setDefaultGroupPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultGroupPerm()));
- plainAccessResource.setDefaultTopicPerm(Permission.parsePermFromString(plainAccessConfig.getDefaultTopicPerm()));
-
- Permission.parseResourcePerms(plainAccessResource, false, plainAccessConfig.getGroupPerms());
- Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms());
-
- plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategyFactory.
- getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress()));
-
- return plainAccessResource;
- }
-
- public void validate(PlainAccessResource plainAccessResource) {
-
- // Check the global white remote addr
- for (RemoteAddressStrategy remoteAddressStrategy : globalWhiteRemoteAddressStrategy) {
- if (remoteAddressStrategy.match(plainAccessResource)) {
- return;
- }
- }
-
- if (plainAccessResource.getAccessKey() == null) {
- throw new AclException(String.format("No accessKey is configured"));
- }
-
- if (!plainAccessResourceMap.containsKey(plainAccessResource.getAccessKey())) {
- throw new AclException(String.format("No acl config for %s", plainAccessResource.getAccessKey()));
- }
-
- // Check the white addr for accesskey
- PlainAccessResource ownedAccess = plainAccessResourceMap.get(plainAccessResource.getAccessKey());
- if (ownedAccess.getRemoteAddressStrategy().match(plainAccessResource)) {
- return;
- }
-
- // Check the signature
- String signature = AclUtils.calSignature(plainAccessResource.getContent(), ownedAccess.getSecretKey());
- if (!signature.equals(plainAccessResource.getSignature())) {
- throw new AclException(String.format("Check signature failed for accessKey=%s", plainAccessResource.getAccessKey()));
- }
- // Check perm of each resource
-
- checkPerm(plainAccessResource, ownedAccess);
- }
-
- public boolean isWatchStart() {
- return isWatchStart;
- }
-
- static class PlainAccessConfig {
-
- private String accessKey;
-
- private String secretKey;
-
- private String whiteRemoteAddress;
-
- private boolean admin;
-
- private String defaultTopicPerm;
-
- private String defaultGroupPerm;
-
- private List topicPerms;
-
- private List groupPerms;
-
- public String getAccessKey() {
- return accessKey;
- }
-
- public void setAccessKey(String accessKey) {
- this.accessKey = accessKey;
- }
-
- public String getSecretKey() {
- return secretKey;
- }
-
- public void setSecretKey(String secretKey) {
- this.secretKey = secretKey;
- }
-
- public String getWhiteRemoteAddress() {
- return whiteRemoteAddress;
- }
-
- public void setWhiteRemoteAddress(String whiteRemoteAddress) {
- this.whiteRemoteAddress = whiteRemoteAddress;
- }
-
- public boolean isAdmin() {
- return admin;
- }
-
- public void setAdmin(boolean admin) {
- this.admin = admin;
- }
-
- public String getDefaultTopicPerm() {
- return defaultTopicPerm;
- }
-
- public void setDefaultTopicPerm(String defaultTopicPerm) {
- this.defaultTopicPerm = defaultTopicPerm;
- }
-
- public String getDefaultGroupPerm() {
- return defaultGroupPerm;
- }
-
- public void setDefaultGroupPerm(String defaultGroupPerm) {
- this.defaultGroupPerm = defaultGroupPerm;
- }
-
- public List getTopicPerms() {
- return topicPerms;
- }
-
- public void setTopicPerms(List topicPerms) {
- this.topicPerms = topicPerms;
- }
-
- public List getGroupPerms() {
- return groupPerms;
- }
-
- public void setGroupPerms(List groupPerms) {
- this.groupPerms = groupPerms;
- }
-
- }
-
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategy.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategy.java
deleted file mode 100644
index 8eab40c954b..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategy.java
+++ /dev/null
@@ -1,22 +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.rocketmq.acl.plain;
-
-public interface RemoteAddressStrategy {
-
- boolean match(PlainAccessResource plainAccessResource);
-}
diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java
deleted file mode 100644
index de29e92fd55..00000000000
--- a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java
+++ /dev/null
@@ -1,180 +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.rocketmq.acl.plain;
-
-import java.util.HashSet;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.rocketmq.acl.common.AclException;
-import org.apache.rocketmq.acl.common.AclUtils;
-import org.apache.rocketmq.common.constant.LoggerName;
-import org.apache.rocketmq.logging.InternalLogger;
-import org.apache.rocketmq.logging.InternalLoggerFactory;
-
-public class RemoteAddressStrategyFactory {
-
- private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME);
-
- public static final NullRemoteAddressStrategy NULL_NET_ADDRESS_STRATEGY = new NullRemoteAddressStrategy();
-
- public static final BlankRemoteAddressStrategy BLANK_NET_ADDRESS_STRATEGY = new BlankRemoteAddressStrategy();
-
- public RemoteAddressStrategy getRemoteAddressStrategy(PlainAccessResource plainAccessResource) {
- return getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress());
- }
-
- public RemoteAddressStrategy getRemoteAddressStrategy(String remoteAddr) {
- if (StringUtils.isBlank(remoteAddr)) {
- return BLANK_NET_ADDRESS_STRATEGY;
- }
- if ("*".equals(remoteAddr)) {
- return NULL_NET_ADDRESS_STRATEGY;
- }
- if (remoteAddr.endsWith("}")) {
- String[] strArray = StringUtils.split(remoteAddr, ".");
- String four = strArray[3];
- if (!four.startsWith("{")) {
- throw new AclException(String.format("MultipleRemoteAddressStrategy netaddress examine scope Exception netaddress", remoteAddr));
- }
- return new MultipleRemoteAddressStrategy(AclUtils.getAddreeStrArray(remoteAddr, four));
- } else if (AclUtils.isColon(remoteAddr)) {
- return new MultipleRemoteAddressStrategy(StringUtils.split(remoteAddr, ","));
- } else if (AclUtils.isAsterisk(remoteAddr) || AclUtils.isMinus(remoteAddr)) {
- return new RangeRemoteAddressStrategy(remoteAddr);
- }
- return new OneRemoteAddressStrategy(remoteAddr);
-
- }
-
- public static class NullRemoteAddressStrategy implements RemoteAddressStrategy {
- @Override
- public boolean match(PlainAccessResource plainAccessResource) {
- return true;
- }
-
- }
-
- public static class BlankRemoteAddressStrategy implements RemoteAddressStrategy {
- @Override
- public boolean match(PlainAccessResource plainAccessResource) {
- return false;
- }
-
- }
-
- public static class MultipleRemoteAddressStrategy implements RemoteAddressStrategy {
-
- private final Set multipleSet = new HashSet<>();
-
- public MultipleRemoteAddressStrategy(String[] strArray) {
- for (String netaddress : strArray) {
- AclUtils.verify(netaddress, 4);
- multipleSet.add(netaddress);
- }
- }
-
- @Override
- public boolean match(PlainAccessResource plainAccessResource) {
- return multipleSet.contains(plainAccessResource.getWhiteRemoteAddress());
- }
-
- }
-
- public static class OneRemoteAddressStrategy implements RemoteAddressStrategy {
-
- private String netaddress;
-
- public OneRemoteAddressStrategy(String netaddress) {
- this.netaddress = netaddress;
- AclUtils.verify(netaddress, 4);
- }
-
- @Override
- public boolean match(PlainAccessResource plainAccessResource) {
- return netaddress.equals(plainAccessResource.getWhiteRemoteAddress());
- }
-
- }
-
- public static class RangeRemoteAddressStrategy implements RemoteAddressStrategy {
-
- private String head;
-
- private int start;
-
- private int end;
-
- private int index;
-
- public RangeRemoteAddressStrategy(String remoteAddr) {
- String[] strArray = StringUtils.split(remoteAddr, ".");
- if (analysis(strArray, 2) || analysis(strArray, 3)) {
- AclUtils.verify(remoteAddr, index - 1);
- StringBuffer sb = new StringBuffer().append(strArray[0].trim()).append(".").append(strArray[1].trim()).append(".");
- if (index == 3) {
- sb.append(strArray[2].trim()).append(".");
- }
- this.head = sb.toString();
- }
- }
-
- private boolean analysis(String[] strArray, int index) {
- String value = strArray[index].trim();
- this.index = index;
- if ("*".equals(value)) {
- setValue(0, 255);
- } else if (AclUtils.isMinus(value)) {
- if (value.indexOf("-") == 0) {
- throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception value %s ", value));
-
- }
- String[] valueArray = StringUtils.split(value, "-");
- this.start = Integer.valueOf(valueArray[0]);
- this.end = Integer.valueOf(valueArray[1]);
- if (!(AclUtils.isScope(end) && AclUtils.isScope(start) && start <= end)) {
- throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception start is %s , end is %s", start, end));
- }
- }
- return this.end > 0 ? true : false;
- }
-
- private void setValue(int start, int end) {
- this.start = start;
- this.end = end;
- }
-
- @Override
- public boolean match(PlainAccessResource plainAccessResource) {
- String netAddress = plainAccessResource.getWhiteRemoteAddress();
- if (netAddress.startsWith(this.head)) {
- String value;
- if (index == 3) {
- value = netAddress.substring(this.head.length());
- } else {
- value = netAddress.substring(this.head.length(), netAddress.lastIndexOf('.'));
- }
- Integer address = Integer.valueOf(value);
- if (address >= this.start && address <= this.end) {
- return true;
- }
- }
- return false;
- }
-
- }
-
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java
deleted file mode 100644
index eec626357fc..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/common/AclSignerTest.java
+++ /dev/null
@@ -1,34 +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.rocketmq.acl.common;
-
-import org.junit.Test;
-
-public class AclSignerTest {
-
- @Test(expected = Exception.class)
- public void calSignatureExceptionTest(){
- AclSigner.calSignature(new byte[]{},"");
- }
-
- @Test
- public void calSignatureTest(){
- AclSigner.calSignature("RocketMQ","12345678");
- AclSigner.calSignature("RocketMQ".getBytes(),"12345678");
- }
-
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java
deleted file mode 100644
index 72bcda6bb3a..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/common/AclUtilsTest.java
+++ /dev/null
@@ -1,141 +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.rocketmq.acl.common;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class AclUtilsTest {
-
- @Test
- public void getAddreeStrArray() {
- String address = "1.1.1.{1,2,3,4}";
- String[] addressArray = AclUtils.getAddreeStrArray(address, "{1,2,3,4}");
- List newAddressList = new ArrayList<>();
- for (String a : addressArray) {
- newAddressList.add(a);
- }
-
- List addressList = new ArrayList<>();
- addressList.add("1.1.1.1");
- addressList.add("1.1.1.2");
- addressList.add("1.1.1.3");
- addressList.add("1.1.1.4");
- Assert.assertEquals(newAddressList, addressList);
- }
-
- @Test
- public void isScopeStringArray() {
- String adderss = "12";
-
- for (int i = 0; i < 6; i++) {
- boolean isScope = AclUtils.isScope(adderss, 4);
- if (i == 3) {
- Assert.assertTrue(isScope);
- } else {
- Assert.assertFalse(isScope);
- }
- adderss = adderss + ".12";
- }
- }
-
- @Test
- public void isScopeArray() {
- String[] adderss = StringUtils.split("12.12.12.12", ".");
- boolean isScope = AclUtils.isScope(adderss, 4);
- Assert.assertTrue(isScope);
- isScope = AclUtils.isScope(adderss, 3);
- Assert.assertTrue(isScope);
-
- adderss = StringUtils.split("12.12.1222.1222", ".");
- isScope = AclUtils.isScope(adderss, 4);
- Assert.assertFalse(isScope);
- isScope = AclUtils.isScope(adderss, 3);
- Assert.assertFalse(isScope);
-
- }
-
- @Test
- public void isScopeStringTest() {
- for (int i = 0; i < 256; i++) {
- boolean isScope = AclUtils.isScope(i + "");
- Assert.assertTrue(isScope);
- }
- boolean isScope = AclUtils.isScope("-1");
- Assert.assertFalse(isScope);
- isScope = AclUtils.isScope("256");
- Assert.assertFalse(isScope);
- }
-
- @Test
- public void isScopeTest() {
- for (int i = 0; i < 256; i++) {
- boolean isScope = AclUtils.isScope(i);
- Assert.assertTrue(isScope);
- }
- boolean isScope = AclUtils.isScope(-1);
- Assert.assertFalse(isScope);
- isScope = AclUtils.isScope(256);
- Assert.assertFalse(isScope);
-
- }
-
- @Test
- public void isAsteriskTest() {
- boolean isAsterisk = AclUtils.isAsterisk("*");
- Assert.assertTrue(isAsterisk);
-
- isAsterisk = AclUtils.isAsterisk(",");
- Assert.assertFalse(isAsterisk);
- }
-
- @Test
- public void isColonTest() {
- boolean isColon = AclUtils.isColon(",");
- Assert.assertTrue(isColon);
-
- isColon = AclUtils.isColon("-");
- Assert.assertFalse(isColon);
- }
-
- @Test
- public void isMinusTest() {
- boolean isMinus = AclUtils.isMinus("-");
- Assert.assertTrue(isMinus);
-
- isMinus = AclUtils.isMinus("*");
- Assert.assertFalse(isMinus);
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void getYamlDataObjectTest() {
-
- Map map = AclUtils.getYamlDataObject("src/test/resources/conf/plain_acl.yml", Map.class);
- Assert.assertFalse(map.isEmpty());
- }
-
- @Test(expected = Exception.class)
- public void getYamlDataObjectExceptionTest() {
-
- AclUtils.getYamlDataObject("plain_acl.yml", Map.class);
- }
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java
deleted file mode 100644
index 253b5b241e0..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java
+++ /dev/null
@@ -1,168 +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.rocketmq.acl.common;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.rocketmq.acl.plain.PlainAccessResource;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class PermissionTest {
-
- @Test
- public void fromStringGetPermissionTest() {
- byte perm = Permission.parsePermFromString("PUB");
- Assert.assertEquals(perm, Permission.PUB);
-
- perm = Permission.parsePermFromString("SUB");
- Assert.assertEquals(perm, Permission.SUB);
-
- perm = Permission.parsePermFromString("PUB|SUB");
- Assert.assertEquals(perm, Permission.PUB|Permission.SUB);
-
- perm = Permission.parsePermFromString("SUB|PUB");
- Assert.assertEquals(perm, Permission.PUB|Permission.SUB);
-
- perm = Permission.parsePermFromString("DENY");
- Assert.assertEquals(perm, Permission.DENY);
-
- perm = Permission.parsePermFromString("1");
- Assert.assertEquals(perm, Permission.DENY);
-
- perm = Permission.parsePermFromString(null);
- Assert.assertEquals(perm, Permission.DENY);
-
- }
-
- @Test
- public void checkPermissionTest() {
- boolean boo = Permission.checkPermission(Permission.DENY, Permission.DENY);
- Assert.assertFalse(boo);
-
- boo = Permission.checkPermission(Permission.PUB, Permission.PUB);
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.SUB, Permission.SUB);
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.PUB, (byte) (Permission.PUB|Permission.SUB));
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.SUB, (byte) (Permission.PUB|Permission.SUB));
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.ANY, (byte) (Permission.PUB|Permission.SUB));
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.ANY, Permission.SUB);
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.ANY, Permission.PUB);
- Assert.assertTrue(boo);
-
- boo = Permission.checkPermission(Permission.DENY, Permission.ANY);
- Assert.assertFalse(boo);
-
- boo = Permission.checkPermission(Permission.DENY, Permission.PUB);
- Assert.assertFalse(boo);
-
- boo = Permission.checkPermission(Permission.DENY, Permission.SUB);
- Assert.assertFalse(boo);
-
- }
-
- @Test(expected = AclException.class)
- public void setTopicPermTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- Map resourcePermMap = plainAccessResource.getResourcePermMap();
-
- Permission.parseResourcePerms(plainAccessResource, false, null);
- Assert.assertNull(resourcePermMap);
-
- List groups = new ArrayList<>();
- Permission.parseResourcePerms(plainAccessResource, false, groups);
- Assert.assertNull(resourcePermMap);
-
- groups.add("groupA=DENY");
- groups.add("groupB=PUB|SUB");
- groups.add("groupC=PUB");
- Permission.parseResourcePerms(plainAccessResource, false, groups);
- resourcePermMap = plainAccessResource.getResourcePermMap();
-
- byte perm = resourcePermMap.get(PlainAccessResource.getRetryTopic("groupA"));
- Assert.assertEquals(perm, Permission.DENY);
-
- perm = resourcePermMap.get(PlainAccessResource.getRetryTopic("groupB"));
- Assert.assertEquals(perm,Permission.PUB|Permission.SUB);
-
- perm = resourcePermMap.get(PlainAccessResource.getRetryTopic("groupC"));
- Assert.assertEquals(perm, Permission.PUB);
-
- List topics = new ArrayList<>();
- topics.add("topicA=DENY");
- topics.add("topicB=PUB|SUB");
- topics.add("topicC=PUB");
-
- Permission.parseResourcePerms(plainAccessResource, true, topics);
-
- perm = resourcePermMap.get("topicA");
- Assert.assertEquals(perm, Permission.DENY);
-
- perm = resourcePermMap.get("topicB");
- Assert.assertEquals(perm, Permission.PUB|Permission.SUB);
-
- perm = resourcePermMap.get("topicC");
- Assert.assertEquals(perm, Permission.PUB);
-
- List erron = new ArrayList<>();
- erron.add("");
- Permission.parseResourcePerms(plainAccessResource, false, erron);
- }
-
- @Test
- public void checkAdminCodeTest() {
- Set code = new HashSet<>();
- code.add(17);
- code.add(25);
- code.add(215);
- code.add(200);
- code.add(207);
-
- for (int i = 0; i < 400; i++) {
- boolean boo = Permission.needAdminPerm(i);
- if (boo) {
- Assert.assertTrue(code.contains(i));
- }
- }
- }
-
- @Test
- public void AclExceptionTest(){
- AclException aclException = new AclException("CAL_SIGNATURE_FAILED",10015);
- AclException aclExceptionWithMessage = new AclException("CAL_SIGNATURE_FAILED",10015,"CAL_SIGNATURE_FAILED Exception");
- Assert.assertEquals(aclException.getCode(),10015);
- Assert.assertEquals(aclExceptionWithMessage.getStatus(),"CAL_SIGNATURE_FAILED");
- aclException.setCode(10016);
- Assert.assertEquals(aclException.getCode(),10016);
- aclException.setStatus("netaddress examine scope Exception netaddress");
- Assert.assertEquals(aclException.getStatus(),"netaddress examine scope Exception netaddress");
- }
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java
deleted file mode 100644
index a1a4bde4f87..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/common/SessionCredentialsTest.java
+++ /dev/null
@@ -1,91 +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.rocketmq.acl.common;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.Properties;
-
-public class SessionCredentialsTest {
-
- @Test
- public void equalsTest(){
- SessionCredentials sessionCredentials=new SessionCredentials("RocketMQ","12345678");
- sessionCredentials.setSecurityToken("abcd");
- SessionCredentials other=new SessionCredentials("RocketMQ","12345678","abcd");
- Assert.assertTrue(sessionCredentials.equals(other));
- }
-
- @Test
- public void updateContentTest(){
- SessionCredentials sessionCredentials=new SessionCredentials();
- Properties properties=new Properties();
- properties.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ");
- properties.setProperty(SessionCredentials.SECRET_KEY,"12345678");
- properties.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd");
- sessionCredentials.updateContent(properties);
- }
-
- @Test
- public void SessionCredentialHashCodeTest(){
- SessionCredentials sessionCredentials=new SessionCredentials();
- Properties properties=new Properties();
- properties.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ");
- properties.setProperty(SessionCredentials.SECRET_KEY,"12345678");
- properties.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd");
- sessionCredentials.updateContent(properties);
- Assert.assertEquals(sessionCredentials.hashCode(),353652211);
- }
-
- @Test
- public void SessionCredentialEqualsTest(){
- SessionCredentials sessionCredential1 =new SessionCredentials();
- Properties properties1=new Properties();
- properties1.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ");
- properties1.setProperty(SessionCredentials.SECRET_KEY,"12345678");
- properties1.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd");
- sessionCredential1.updateContent(properties1);
-
- SessionCredentials sessionCredential2 =new SessionCredentials();
- Properties properties2=new Properties();
- properties2.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ");
- properties2.setProperty(SessionCredentials.SECRET_KEY,"12345678");
- properties2.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd");
- sessionCredential2.updateContent(properties2);
-
- Assert.assertTrue(sessionCredential2.equals(sessionCredential1));
- sessionCredential2.setSecretKey("1234567899");
- sessionCredential2.setSignature("1234567899");
- Assert.assertFalse(sessionCredential2.equals(sessionCredential1));
- }
-
- @Test
- public void SessionCredentialToStringTest(){
- SessionCredentials sessionCredential1 =new SessionCredentials();
- Properties properties1=new Properties();
- properties1.setProperty(SessionCredentials.ACCESS_KEY,"RocketMQ");
- properties1.setProperty(SessionCredentials.SECRET_KEY,"12345678");
- properties1.setProperty(SessionCredentials.SECURITY_TOKEN,"abcd");
- sessionCredential1.updateContent(properties1);
-
- Assert.assertEquals(sessionCredential1.toString(),
- "SessionCredentials [accessKey=RocketMQ, secretKey=12345678, signature=null, SecurityToken=abcd]");
- }
-
-
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java
deleted file mode 100644
index e7b6f2d68af..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java
+++ /dev/null
@@ -1,287 +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.rocketmq.acl.plain;
-
-import java.nio.ByteBuffer;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.rocketmq.acl.common.AclClientRPCHook;
-import org.apache.rocketmq.acl.common.AclException;
-import org.apache.rocketmq.acl.common.AclUtils;
-import org.apache.rocketmq.acl.common.SessionCredentials;
-import org.apache.rocketmq.common.protocol.RequestCode;
-import org.apache.rocketmq.common.protocol.header.*;
-import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
-import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
-import org.apache.rocketmq.common.protocol.heartbeat.ProducerData;
-import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
-import org.apache.rocketmq.remoting.protocol.RemotingCommand;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class PlainAccessValidatorTest {
-
- private PlainAccessValidator plainAccessValidator;
- private AclClientRPCHook aclClient;
- private SessionCredentials sessionCredentials;
- @Before
- public void init() {
- System.setProperty("rocketmq.home.dir", "src/test/resources");
- System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml");
- plainAccessValidator = new PlainAccessValidator();
- sessionCredentials = new SessionCredentials();
- sessionCredentials.setAccessKey("RocketMQ");
- sessionCredentials.setSecretKey("12345678");
- sessionCredentials.setSecurityToken("87654321");
- aclClient = new AclClientRPCHook(sessionCredentials);
- }
-
- @Test
- public void contentTest() {
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "127.0.0.1");
- String signature = AclUtils.calSignature(accessResource.getContent(), sessionCredentials.getSecretKey());
-
- Assert.assertEquals(accessResource.getSignature(), signature);
-
- }
-
- @Test
- public void validateTest() {
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicB");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1");
- plainAccessValidator.validate(accessResource);
-
- }
-
- @Test
- public void validateSendMessageTest() {
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicB");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateSendMessageV2Test() {
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicC");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(messageRequestHeader));
- aclClient.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test(expected = AclException.class)
- public void validateForAdminCommandWithOutAclRPCHook() {
- RemotingCommand consumerOffsetAdminRequest = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_CONSUMER_OFFSET, null);
- plainAccessValidator.parse(consumerOffsetAdminRequest, "192.168.0.1:9876");
-
- RemotingCommand subscriptionGroupAdminRequest = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG, null);
- plainAccessValidator.parse(subscriptionGroupAdminRequest, "192.168.0.1:9876");
-
- RemotingCommand delayOffsetAdminRequest = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_DELAY_OFFSET, null);
- plainAccessValidator.parse(delayOffsetAdminRequest, "192.168.0.1:9876");
-
- RemotingCommand allTopicConfigAdminRequest = RemotingCommand.createRequestCommand(RequestCode.GET_ALL_TOPIC_CONFIG, null);
- plainAccessValidator.parse(allTopicConfigAdminRequest, "192.168.0.1:9876");
-
- }
-
- @Test
- public void validatePullMessageTest() {
- PullMessageRequestHeader pullMessageRequestHeader=new PullMessageRequestHeader();
- pullMessageRequestHeader.setTopic("topicC");
- pullMessageRequestHeader.setConsumerGroup("consumerGroupA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE,pullMessageRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateConsumeMessageBackTest() {
- ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader=new ConsumerSendMsgBackRequestHeader();
- consumerSendMsgBackRequestHeader.setOriginTopic("topicC");
- consumerSendMsgBackRequestHeader.setGroup("consumerGroupA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK,consumerSendMsgBackRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateQueryMessageTest() {
- QueryMessageRequestHeader queryMessageRequestHeader=new QueryMessageRequestHeader();
- queryMessageRequestHeader.setTopic("topicC");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE,queryMessageRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateHeartBeatTest() {
- HeartbeatData heartbeatData=new HeartbeatData();
- Set producerDataSet=new HashSet<>();
- Set consumerDataSet=new HashSet<>();
- Set subscriptionDataSet=new HashSet<>();
- ProducerData producerData=new ProducerData();
- producerData.setGroupName("producerGroupA");
- ConsumerData consumerData=new ConsumerData();
- consumerData.setGroupName("consumerGroupA");
- SubscriptionData subscriptionData=new SubscriptionData();
- subscriptionData.setTopic("topicC");
- producerDataSet.add(producerData);
- consumerDataSet.add(consumerData);
- subscriptionDataSet.add(subscriptionData);
- consumerData.setSubscriptionDataSet(subscriptionDataSet);
- heartbeatData.setProducerDataSet(producerDataSet);
- heartbeatData.setConsumerDataSet(consumerDataSet);
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT,null);
- remotingCommand.setBody(heartbeatData.encode());
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encode();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateUnRegisterClientTest() {
- UnregisterClientRequestHeader unregisterClientRequestHeader=new UnregisterClientRequestHeader();
- unregisterClientRequestHeader.setConsumerGroup("consumerGroupA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT,unregisterClientRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateGetConsumerListByGroupTest() {
- GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader=new GetConsumerListByGroupRequestHeader();
- getConsumerListByGroupRequestHeader.setConsumerGroup("consumerGroupA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP,getConsumerListByGroupRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test
- public void validateUpdateConsumerOffSetTest() {
- UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader=new UpdateConsumerOffsetRequestHeader();
- updateConsumerOffsetRequestHeader.setConsumerGroup("consumerGroupA");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET,updateConsumerOffsetRequestHeader);
- aclClient.doBeforeRequest("", remotingCommand);
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.0.1:9876");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test(expected = AclException.class)
- public void validateNullAccessKeyTest() {
- SessionCredentials sessionCredentials=new SessionCredentials();
- sessionCredentials.setAccessKey("RocketMQ1");
- sessionCredentials.setSecretKey("1234");
- AclClientRPCHook aclClientRPCHook=new AclClientRPCHook(sessionCredentials);
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicB");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
- aclClientRPCHook.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.1.1");
- plainAccessValidator.validate(accessResource);
- }
-
- @Test(expected = AclException.class)
- public void validateErrorSecretKeyTest() {
- SessionCredentials sessionCredentials=new SessionCredentials();
- sessionCredentials.setAccessKey("RocketMQ");
- sessionCredentials.setSecretKey("1234");
- AclClientRPCHook aclClientRPCHook=new AclClientRPCHook(sessionCredentials);
- SendMessageRequestHeader messageRequestHeader = new SendMessageRequestHeader();
- messageRequestHeader.setTopic("topicB");
- RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, messageRequestHeader);
- aclClientRPCHook.doBeforeRequest("", remotingCommand);
-
- ByteBuffer buf = remotingCommand.encodeHeader();
- buf.getInt();
- buf = ByteBuffer.allocate(buf.limit() - buf.position()).put(buf);
- buf.position(0);
- PlainAccessResource accessResource = (PlainAccessResource) plainAccessValidator.parse(RemotingCommand.decode(buf), "192.168.1.1");
- plainAccessValidator.validate(accessResource);
- }
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionLoaderTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionLoaderTest.java
deleted file mode 100644
index 575c9018743..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionLoaderTest.java
+++ /dev/null
@@ -1,275 +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.rocketmq.acl.plain;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.commons.lang3.reflect.FieldUtils;
-import org.apache.rocketmq.acl.common.AclException;
-import org.apache.rocketmq.acl.common.Permission;
-import org.apache.rocketmq.acl.plain.PlainPermissionLoader.PlainAccessConfig;
-import org.apache.rocketmq.common.UtilAll;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class PlainPermissionLoaderTest {
-
- PlainPermissionLoader plainPermissionLoader;
- PlainAccessResource PUBPlainAccessResource;
- PlainAccessResource SUBPlainAccessResource;
- PlainAccessResource ANYPlainAccessResource;
- PlainAccessResource DENYPlainAccessResource;
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
- PlainAccessResource plainAccessResourceTwo = new PlainAccessResource();
- Set adminCode = new HashSet<>();
-
- @Before
- public void init() throws NoSuchFieldException, SecurityException, IOException {
- // UPDATE_AND_CREATE_TOPIC
- adminCode.add(17);
- // UPDATE_BROKER_CONFIG
- adminCode.add(25);
- // DELETE_TOPIC_IN_BROKER
- adminCode.add(215);
- // UPDATE_AND_CREATE_SUBSCRIPTIONGROUP
- adminCode.add(200);
- // DELETE_SUBSCRIPTIONGROUP
- adminCode.add(207);
-
- PUBPlainAccessResource = clonePlainAccessResource(Permission.PUB);
- SUBPlainAccessResource = clonePlainAccessResource(Permission.SUB);
- ANYPlainAccessResource = clonePlainAccessResource(Permission.ANY);
- DENYPlainAccessResource = clonePlainAccessResource(Permission.DENY);
-
- System.setProperty("rocketmq.home.dir", "src/test/resources");
- System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml");
- plainPermissionLoader = new PlainPermissionLoader();
-
- }
-
- public PlainAccessResource clonePlainAccessResource(byte perm) {
- PlainAccessResource painAccessResource = new PlainAccessResource();
- painAccessResource.setAccessKey("RocketMQ");
- painAccessResource.setSecretKey("12345678");
- painAccessResource.setWhiteRemoteAddress("127.0." + perm + ".*");
- painAccessResource.setDefaultGroupPerm(perm);
- painAccessResource.setDefaultTopicPerm(perm);
- painAccessResource.addResourceAndPerm(PlainAccessResource.getRetryTopic("groupA"), Permission.PUB);
- painAccessResource.addResourceAndPerm(PlainAccessResource.getRetryTopic("groupB"), Permission.SUB);
- painAccessResource.addResourceAndPerm(PlainAccessResource.getRetryTopic("groupC"), Permission.ANY);
- painAccessResource.addResourceAndPerm(PlainAccessResource.getRetryTopic("groupD"), Permission.DENY);
-
- painAccessResource.addResourceAndPerm("topicA", Permission.PUB);
- painAccessResource.addResourceAndPerm("topicB", Permission.SUB);
- painAccessResource.addResourceAndPerm("topicC", Permission.ANY);
- painAccessResource.addResourceAndPerm("topicD", Permission.DENY);
- return painAccessResource;
- }
-
- @Test
- public void buildPlainAccessResourceTest() {
- PlainAccessResource plainAccessResource = null;
- PlainAccessConfig plainAccess = new PlainAccessConfig();
-
- plainAccess.setAccessKey("RocketMQ");
- plainAccess.setSecretKey("12345678");
- plainAccessResource = plainPermissionLoader.buildPlainAccessResource(plainAccess);
- Assert.assertEquals(plainAccessResource.getAccessKey(), "RocketMQ");
- Assert.assertEquals(plainAccessResource.getSecretKey(), "12345678");
-
- plainAccess.setWhiteRemoteAddress("127.0.0.1");
- plainAccessResource = plainPermissionLoader.buildPlainAccessResource(plainAccess);
- Assert.assertEquals(plainAccessResource.getWhiteRemoteAddress(), "127.0.0.1");
-
- plainAccess.setAdmin(true);
- plainAccessResource = plainPermissionLoader.buildPlainAccessResource(plainAccess);
- Assert.assertEquals(plainAccessResource.isAdmin(), true);
-
- List groups = new ArrayList();
- groups.add("groupA=DENY");
- groups.add("groupB=PUB|SUB");
- groups.add("groupC=PUB");
- plainAccess.setGroupPerms(groups);
- plainAccessResource = plainPermissionLoader.buildPlainAccessResource(plainAccess);
- Map resourcePermMap = plainAccessResource.getResourcePermMap();
- Assert.assertEquals(resourcePermMap.size(), 3);
-
- Assert.assertEquals(resourcePermMap.get(PlainAccessResource.getRetryTopic("groupA")).byteValue(), Permission.DENY);
- Assert.assertEquals(resourcePermMap.get(PlainAccessResource.getRetryTopic("groupB")).byteValue(), Permission.PUB|Permission.SUB);
- Assert.assertEquals(resourcePermMap.get(PlainAccessResource.getRetryTopic("groupC")).byteValue(), Permission.PUB);
-
- List topics = new ArrayList();
- topics.add("topicA=DENY");
- topics.add("topicB=PUB|SUB");
- topics.add("topicC=PUB");
- plainAccess.setTopicPerms(topics);
- plainAccessResource = plainPermissionLoader.buildPlainAccessResource(plainAccess);
- resourcePermMap = plainAccessResource.getResourcePermMap();
- Assert.assertEquals(resourcePermMap.size(), 6);
-
- Assert.assertEquals(resourcePermMap.get("topicA").byteValue(), Permission.DENY);
- Assert.assertEquals(resourcePermMap.get("topicB").byteValue(), Permission.PUB|Permission.SUB);
- Assert.assertEquals(resourcePermMap.get("topicC").byteValue(), Permission.PUB);
- }
-
- @Test(expected = AclException.class)
- public void checkPermAdmin() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setRequestCode(17);
- plainPermissionLoader.checkPerm(plainAccessResource, PUBPlainAccessResource);
- }
-
- @Test
- public void checkPerm() {
-
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.addResourceAndPerm("topicA", Permission.PUB);
- plainPermissionLoader.checkPerm(plainAccessResource, PUBPlainAccessResource);
- plainAccessResource.addResourceAndPerm("topicB", Permission.SUB);
- plainPermissionLoader.checkPerm(plainAccessResource, ANYPlainAccessResource);
-
- plainAccessResource = new PlainAccessResource();
- plainAccessResource.addResourceAndPerm("topicB", Permission.SUB);
- plainPermissionLoader.checkPerm(plainAccessResource, SUBPlainAccessResource);
- plainAccessResource.addResourceAndPerm("topicA", Permission.PUB);
- plainPermissionLoader.checkPerm(plainAccessResource, ANYPlainAccessResource);
-
- }
- @Test(expected = AclException.class)
- public void checkErrorPermDefaultValueNotMatch() {
-
- plainAccessResource = new PlainAccessResource();
- plainAccessResource.addResourceAndPerm("topicF", Permission.PUB);
- plainPermissionLoader.checkPerm(plainAccessResource, SUBPlainAccessResource);
- }
- @Test(expected = AclException.class)
- public void accountNullTest() {
- plainAccessConfig.setAccessKey(null);
- plainPermissionLoader.buildPlainAccessResource(plainAccessConfig);
- }
-
- @Test(expected = AclException.class)
- public void accountThanTest() {
- plainAccessConfig.setAccessKey("123");
- plainPermissionLoader.buildPlainAccessResource(plainAccessConfig);
- }
-
- @Test(expected = AclException.class)
- public void passWordtNullTest() {
- plainAccessConfig.setAccessKey(null);
- plainPermissionLoader.buildPlainAccessResource(plainAccessConfig);
- }
-
- @Test(expected = AclException.class)
- public void passWordThanTest() {
- plainAccessConfig.setAccessKey("123");
- plainPermissionLoader.buildPlainAccessResource(plainAccessConfig);
- }
-
- @Test(expected = AclException.class)
- public void testPlainAclPlugEngineInit() {
- System.setProperty("rocketmq.home.dir", "");
- new PlainPermissionLoader().load();
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void cleanAuthenticationInfoTest() throws IllegalAccessException {
- //plainPermissionLoader.addPlainAccessResource(plainAccessResource);
- Map> plainAccessResourceMap = (Map>) FieldUtils.readDeclaredField(plainPermissionLoader, "plainAccessResourceMap", true);
- Assert.assertFalse(plainAccessResourceMap.isEmpty());
-
- plainPermissionLoader.clearPermissionInfo();
- plainAccessResourceMap = (Map>) FieldUtils.readDeclaredField(plainPermissionLoader, "plainAccessResourceMap", true);
- Assert.assertTrue(plainAccessResourceMap.isEmpty());
- }
-
- @Test
- public void isWatchStartTest() {
-
- PlainPermissionLoader plainPermissionLoader = new PlainPermissionLoader();
- Assert.assertTrue(plainPermissionLoader.isWatchStart());
- }
-
-
- @Test
- public void testWatch() throws IOException, IllegalAccessException ,InterruptedException{
- System.setProperty("rocketmq.home.dir", "src/test/resources");
- System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl-test.yml");
- String fileName =System.getProperty("rocketmq.home.dir", "src/test/resources")+System.getProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml");
- File transport = new File(fileName);
- transport.delete();
- transport.createNewFile();
- FileWriter writer = new FileWriter(transport);
- writer.write("accounts:\r\n");
- writer.write("- accessKey: watchrocketmq\r\n");
- writer.write(" secretKey: 12345678\r\n");
- writer.write(" whiteRemoteAddress: 127.0.0.1\r\n");
- writer.write(" admin: true\r\n");
- writer.flush();
- writer.close();
-
- PlainPermissionLoader plainPermissionLoader = new PlainPermissionLoader();
- Assert.assertTrue(plainPermissionLoader.isWatchStart());
-
- {
- Map plainAccessResourceMap = (Map) FieldUtils.readDeclaredField(plainPermissionLoader, "plainAccessResourceMap", true);
- PlainAccessResource accessResource = plainAccessResourceMap.get("watchrocketmq");
- Assert.assertNotNull(accessResource);
- Assert.assertEquals(accessResource.getSecretKey(), "12345678");
- Assert.assertTrue(accessResource.isAdmin());
-
- }
-
- writer = new FileWriter(new File(fileName), true);
- writer.write("- accessKey: watchrocketmq1\r\n");
- writer.write(" secretKey: 88888888\r\n");
- writer.write(" whiteRemoteAddress: 127.0.0.1\r\n");
- writer.write(" admin: false\r\n");
- writer.flush();
- writer.close();
-
- Thread.sleep(1000);
- {
- Map plainAccessResourceMap = (Map) FieldUtils.readDeclaredField(plainPermissionLoader, "plainAccessResourceMap", true);
- PlainAccessResource accessResource = plainAccessResourceMap.get("watchrocketmq1");
- Assert.assertNotNull(accessResource);
- Assert.assertEquals(accessResource.getSecretKey(), "88888888");
- Assert.assertFalse(accessResource.isAdmin());
-
- }
- transport.delete();
- System.setProperty("rocketmq.home.dir", "src/test/resources");
- System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl.yml");
- }
-
- @Test(expected = AclException.class)
- public void initializeTest() {
- System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl_null.yml");
- new PlainPermissionLoader();
-
- }
-
-}
diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java
deleted file mode 100644
index 53391f41186..00000000000
--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyTest.java
+++ /dev/null
@@ -1,218 +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.rocketmq.acl.plain;
-
-import org.apache.rocketmq.acl.common.AclException;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class RemoteAddressStrategyTest {
-
- RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory();
-
- @Test
- public void netaddressStrategyFactoryExceptionTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource).getClass(),
- RemoteAddressStrategyFactory.BlankRemoteAddressStrategy.class);
- }
-
- @Test
- public void netaddressStrategyFactoryTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
-
- plainAccessResource.setWhiteRemoteAddress("*");
- RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy, RemoteAddressStrategyFactory.NULL_NET_ADDRESS_STRATEGY);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.OneRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1,127.0.0.2,127.0.0.3");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.MultipleRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.{1,2,3}");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.MultipleRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1-200");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.RangeRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.*");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.RangeRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.1-20.*");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.RangeRemoteAddressStrategy.class);
-
- plainAccessResource.setWhiteRemoteAddress("");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- Assert.assertEquals(remoteAddressStrategy.getClass(), RemoteAddressStrategyFactory.BlankRemoteAddressStrategy.class);
- }
-
- @Test(expected = AclException.class)
- public void verifyTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1");
- remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- plainAccessResource.setWhiteRemoteAddress("256.0.0.1");
- remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- }
-
- @Test
- public void nullNetaddressStrategyTest() {
- boolean isMatch = RemoteAddressStrategyFactory.NULL_NET_ADDRESS_STRATEGY.match(new PlainAccessResource());
- Assert.assertTrue(isMatch);
- }
-
- @Test
- public void blankNetaddressStrategyTest() {
- boolean isMatch = RemoteAddressStrategyFactory.BLANK_NET_ADDRESS_STRATEGY.match(new PlainAccessResource());
- Assert.assertFalse(isMatch);
- }
-
- public void oneNetaddressStrategyTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1");
- RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- plainAccessResource.setWhiteRemoteAddress("");
- boolean match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertFalse(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.2");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertFalse(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertTrue(match);
- }
-
- @Test
- public void multipleNetaddressStrategyTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1,127.0.0.2,127.0.0.3");
- RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- multipleNetaddressStrategyTest(remoteAddressStrategy);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.{1,2,3}");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- multipleNetaddressStrategyTest(remoteAddressStrategy);
-
- }
-
- @Test(expected = AclException.class)
- public void multipleNetaddressStrategyExceptionTest() {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1,2,3}");
- remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- }
-
- private void multipleNetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy) {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1");
- boolean match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertTrue(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.2");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertTrue(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.3");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertTrue(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.4");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertFalse(match);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.0.0");
- match = remoteAddressStrategy.match(plainAccessResource);
- Assert.assertFalse(match);
-
- }
-
- @Test
- public void rangeNetaddressStrategyTest() {
- String head = "127.0.0.";
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress("127.0.0.1-200");
- RemoteAddressStrategy remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- rangeNetaddressStrategyTest(remoteAddressStrategy, head, 1, 200, true);
- plainAccessResource.setWhiteRemoteAddress("127.0.0.*");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- rangeNetaddressStrategyTest(remoteAddressStrategy, head, 0, 255, true);
-
- plainAccessResource.setWhiteRemoteAddress("127.0.1-200.*");
- remoteAddressStrategy = remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- rangeNetaddressStrategyThirdlyTest(remoteAddressStrategy, head, 1, 200);
- }
-
- private void rangeNetaddressStrategyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start,
- int end,
- boolean isFalse) {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- for (int i = -10; i < 300; i++) {
- plainAccessResource.setWhiteRemoteAddress(head + i);
- boolean match = remoteAddressStrategy.match(plainAccessResource);
- if (isFalse && i >= start && i <= end) {
- Assert.assertTrue(match);
- continue;
- }
- Assert.assertFalse(match);
-
- }
- }
-
- private void rangeNetaddressStrategyThirdlyTest(RemoteAddressStrategy remoteAddressStrategy, String head, int start,
- int end) {
- String newHead;
- for (int i = -10; i < 300; i++) {
- newHead = head + i;
- if (i >= start && i <= end) {
- rangeNetaddressStrategyTest(remoteAddressStrategy, newHead, 0, 255, false);
- }
- }
- }
-
- @Test(expected = AclException.class)
- public void rangeNetaddressStrategyExceptionStartGreaterEndTest() {
- rangeNetaddressStrategyExceptionTest("127.0.0.2-1");
- }
-
- @Test(expected = AclException.class)
- public void rangeNetaddressStrategyExceptionScopeTest() {
- rangeNetaddressStrategyExceptionTest("127.0.0.-1-200");
- }
-
- @Test(expected = AclException.class)
- public void rangeNetaddressStrategyExceptionScopeTwoTest() {
- rangeNetaddressStrategyExceptionTest("127.0.0.0-256");
- }
-
- private void rangeNetaddressStrategyExceptionTest(String netaddress) {
- PlainAccessResource plainAccessResource = new PlainAccessResource();
- plainAccessResource.setWhiteRemoteAddress(netaddress);
- remoteAddressStrategyFactory.getRemoteAddressStrategy(plainAccessResource);
- }
-
-}
diff --git a/acl/src/test/resources/conf/plain_acl.yml b/acl/src/test/resources/conf/plain_acl.yml
deleted file mode 100644
index 2c24795ff64..00000000000
--- a/acl/src/test/resources/conf/plain_acl.yml
+++ /dev/null
@@ -1,44 +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.
-
-## suggested format
-
-globalWhiteRemoteAddresses:
-- 10.10.103.*
-- 192.168.0.*
-
-accounts:
-- accessKey: RocketMQ
- secretKey: 12345678
- whiteRemoteAddress: 192.168.0.*
- admin: false
- defaultTopicPerm: DENY
- defaultGroupPerm: SUB
- topicPerms:
- - topicA=DENY
- - topicB=PUB|SUB
- - topicC=SUB
- groupPerms:
- # the group should convert to retry topic
- - groupA=DENY
- - groupB=SUB
- - groupC=SUB
-
-- accessKey: rocketmq2
- secretKey: 12345678
- whiteRemoteAddress: 192.168.1.*
- # if it is admin, it could access all resources
- admin: true
-
diff --git a/acl/src/test/resources/conf/plain_acl_null.yml b/acl/src/test/resources/conf/plain_acl_null.yml
deleted file mode 100644
index bc30380c888..00000000000
--- a/acl/src/test/resources/conf/plain_acl_null.yml
+++ /dev/null
@@ -1,18 +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.
-
-## suggested format
-
-
diff --git a/acl/src/test/resources/conf/watch/plain_acl_watch.yml b/acl/src/test/resources/conf/watch/plain_acl_watch.yml
deleted file mode 100644
index 9d2c3954941..00000000000
--- a/acl/src/test/resources/conf/watch/plain_acl_watch.yml
+++ /dev/null
@@ -1,25 +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.
-
-## suggested format
-accounts:
-- accessKey: watchrocketmq
- secretKey: 12345678
- whiteRemoteAddress: 127.0.0.1
- admin: true
-- accessKey: watchrocketmq1
- secretKey: 88888888
- whiteRemoteAddress: 127.0.0.1
- admin: false
diff --git a/acl/src/test/resources/logback-test.xml b/acl/src/test/resources/logback-test.xml
deleted file mode 100644
index e556c649e52..00000000000
--- a/acl/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
- %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
- UTF-8
-
-
-
-
-
-
-
-
-
-
-
diff --git a/auth/BUILD.bazel b/auth/BUILD.bazel
new file mode 100644
index 00000000000..bc15ca3c9e0
--- /dev/null
+++ b/auth/BUILD.bazel
@@ -0,0 +1,77 @@
+#
+# 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.
+#
+load("//bazel:GenTestRules.bzl", "GenTestRules")
+
+java_library(
+ name = "auth",
+ srcs = glob(["src/main/java/**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//common",
+ "//remoting",
+ "//srvutil",
+ "//client",
+ "@maven//:commons_codec_commons_codec",
+ "@maven//:org_apache_commons_commons_lang3",
+ "@maven//:commons_collections_commons_collections",
+ "@maven//:com_alibaba_fastjson2_fastjson2",
+ "@maven//:org_apache_rocketmq_rocketmq_proto",
+ "@maven//:org_slf4j_slf4j_api",
+ "@maven//:com_github_ben_manes_caffeine_caffeine",
+ "@maven//:io_grpc_grpc_api",
+ "@maven//:com_google_protobuf_protobuf_java",
+ "@maven//:com_google_protobuf_protobuf_java_util",
+ "@maven//:io_netty_netty_all",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_apache_rocketmq_rocketmq_rocksdb",
+ ],
+)
+
+java_library(
+ name = "tests",
+ srcs = glob(["src/test/java/**/*.java"]),
+ resources = glob(["src/test/resources/**/*.yml"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ ":auth",
+ "//:test_deps",
+ "//common",
+ "//remoting",
+ "//client",
+ "@maven//:commons_codec_commons_codec",
+ "@maven//:org_apache_commons_commons_lang3",
+ "@maven//:commons_collections_commons_collections",
+ "@maven//:com_alibaba_fastjson2_fastjson2",
+ "@maven//:org_apache_rocketmq_rocketmq_proto",
+ "@maven//:org_slf4j_slf4j_api",
+ "@maven//:com_github_ben_manes_caffeine_caffeine",
+ "@maven//:io_grpc_grpc_api",
+ "@maven//:com_google_protobuf_protobuf_java",
+ "@maven//:com_google_protobuf_protobuf_java_util",
+ "@maven//:io_netty_netty_all",
+ "@maven//:com_google_guava_guava",
+ "@maven//:org_apache_rocketmq_rocketmq_rocksdb",
+ ],
+)
+
+GenTestRules(
+ name = "GeneratedTestRules",
+ test_files = glob(["src/test/java/**/*Test.java"]),
+ deps = [
+ ":tests",
+ ],
+)
diff --git a/auth/pom.xml b/auth/pom.xml
new file mode 100644
index 00000000000..73b4d87dc6f
--- /dev/null
+++ b/auth/pom.xml
@@ -0,0 +1,78 @@
+
+
+ 4.0.0
+
+ org.apache.rocketmq
+ rocketmq-all
+ 5.3.3
+
+ rocketmq-auth
+ rocketmq-auth ${project.version}
+
+
+ ${basedir}/..
+
+
+
+
+ ${project.groupId}
+ rocketmq-proto
+
+
+ ${project.groupId}
+ rocketmq-client
+
+
+ commons-codec
+ commons-codec
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ com.google.protobuf
+ protobuf-java-util
+
+
+ org.slf4j
+ slf4j-api
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ org.checkerframework
+ checker-qual
+
+
+
+
+ junit
+ junit
+
+
+
+
+
+
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ 1
+ false
+
+
+
+
+
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/AuthenticationEvaluator.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/AuthenticationEvaluator.java
new file mode 100644
index 00000000000..3b7d45d18e8
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/AuthenticationEvaluator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.rocketmq.auth.authentication;
+
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public class AuthenticationEvaluator {
+
+ private final AuthenticationStrategy authenticationStrategy;
+
+ public AuthenticationEvaluator(AuthConfig authConfig) {
+ this(authConfig, null);
+ }
+
+ public AuthenticationEvaluator(AuthConfig authConfig, Supplier> metadataService) {
+ this.authenticationStrategy = AuthenticationFactory.getStrategy(authConfig, metadataService);
+ }
+
+ public void evaluate(AuthenticationContext context) {
+ if (context == null) {
+ return;
+ }
+ this.authenticationStrategy.evaluate(context);
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/AuthenticationContextBuilder.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/AuthenticationContextBuilder.java
new file mode 100644
index 00000000000..0176f01a34c
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/AuthenticationContextBuilder.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.auth.authentication.builder;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public interface AuthenticationContextBuilder {
+
+ AuthenticationContext build(Metadata metadata, GeneratedMessageV3 request);
+
+ AuthenticationContext build(ChannelHandlerContext context, RemotingCommand request);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java
new file mode 100644
index 00000000000..c1e970fa6eb
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java
@@ -0,0 +1,131 @@
+/*
+ * 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.rocketmq.auth.authentication.builder;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.acl.common.AclUtils;
+import org.apache.rocketmq.acl.common.SessionCredentials;
+import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.common.MQVersion;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.constant.CommonConstants;
+import org.apache.rocketmq.common.constant.GrpcConstants;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class DefaultAuthenticationContextBuilder implements AuthenticationContextBuilder {
+
+ private static final String CREDENTIAL = "Credential";
+ private static final String SIGNATURE = "Signature";
+
+ @Override
+ public DefaultAuthenticationContext build(Metadata metadata, GeneratedMessageV3 request) {
+ try {
+ DefaultAuthenticationContext context = new DefaultAuthenticationContext();
+ context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID));
+ context.setRpcCode(request.getDescriptorForType().getFullName());
+ String authorization = metadata.get(GrpcConstants.AUTHORIZATION);
+ if (StringUtils.isEmpty(authorization)) {
+ return context;
+ }
+ String datetime = metadata.get(GrpcConstants.DATE_TIME);
+ if (StringUtils.isEmpty(datetime)) {
+ throw new AuthenticationException("datetime is null.");
+ }
+
+ String[] result = authorization.split(CommonConstants.SPACE, 2);
+ if (result.length != 2) {
+ throw new AuthenticationException("authentication header is incorrect.");
+ }
+ String[] keyValues = result[1].split(CommonConstants.COMMA);
+ for (String keyValue : keyValues) {
+ String[] kv = keyValue.trim().split(CommonConstants.EQUAL, 2);
+ int kvLength = kv.length;
+ if (kv.length != 2) {
+ throw new AuthenticationException("authentication keyValues length is incorrect, actual length={}.", kvLength);
+ }
+ String authItem = kv[0];
+ if (CREDENTIAL.equals(authItem)) {
+ String[] credential = kv[1].split(CommonConstants.SLASH);
+ int credentialActualLength = credential.length;
+ if (credentialActualLength == 0) {
+ throw new AuthenticationException("authentication credential length is incorrect, actual length={}.", credentialActualLength);
+ }
+ context.setUsername(credential[0]);
+ continue;
+ }
+ if (SIGNATURE.equals(authItem)) {
+ context.setSignature(this.hexToBase64(kv[1]));
+ }
+ }
+
+ context.setContent(datetime.getBytes(StandardCharsets.UTF_8));
+
+ return context;
+ } catch (AuthenticationException e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new AuthenticationException("create authentication context error.", e);
+ }
+ }
+
+ @Override
+ public DefaultAuthenticationContext build(ChannelHandlerContext context, RemotingCommand request) {
+ HashMap fields = request.getExtFields();
+ DefaultAuthenticationContext result = new DefaultAuthenticationContext();
+ result.setChannelId(context.channel().id().asLongText());
+ result.setRpcCode(String.valueOf(request.getCode()));
+ if (MapUtils.isEmpty(fields)) {
+ return result;
+ }
+ if (!fields.containsKey(SessionCredentials.ACCESS_KEY)) {
+ return result;
+ }
+ result.setUsername(fields.get(SessionCredentials.ACCESS_KEY));
+ result.setSignature(fields.get(SessionCredentials.SIGNATURE));
+ // Content
+ SortedMap map = new TreeMap<>();
+ for (Map.Entry entry : fields.entrySet()) {
+ if (request.getVersion() <= MQVersion.Version.V4_9_3.ordinal() &&
+ MixAll.UNIQUE_MSG_QUERY_FLAG.equals(entry.getKey())) {
+ continue;
+ }
+ if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+ result.setContent(AclUtils.combineRequestContent(request, map));
+ return result;
+ }
+
+ public String hexToBase64(String input) throws DecoderException {
+ byte[] bytes = Hex.decodeHex(input);
+ return Base64.encodeBase64String(bytes);
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/chain/DefaultAuthenticationHandler.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/chain/DefaultAuthenticationHandler.java
new file mode 100644
index 00000000000..4b50de756ab
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/chain/DefaultAuthenticationHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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.rocketmq.auth.authentication.chain;
+
+import java.security.MessageDigest;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.acl.common.AclSigner;
+import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import org.apache.rocketmq.auth.authentication.enums.UserStatus;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.chain.Handler;
+import org.apache.rocketmq.common.chain.HandlerChain;
+
+public class DefaultAuthenticationHandler implements Handler> {
+
+ private final AuthenticationMetadataProvider authenticationMetadataProvider;
+
+ public DefaultAuthenticationHandler(AuthConfig config, Supplier> metadataService) {
+ this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
+ }
+
+ @Override
+ public CompletableFuture handle(DefaultAuthenticationContext context,
+ HandlerChain> chain) {
+ return getUser(context).thenAccept(user -> doAuthenticate(context, user));
+ }
+
+ protected CompletableFuture getUser(DefaultAuthenticationContext context) {
+ if (this.authenticationMetadataProvider == null) {
+ throw new AuthenticationException("The authenticationMetadataProvider is not configured");
+ }
+ if (StringUtils.isEmpty(context.getUsername())) {
+ throw new AuthenticationException("username cannot be null.");
+ }
+ return this.authenticationMetadataProvider.getUser(context.getUsername());
+ }
+
+ protected void doAuthenticate(DefaultAuthenticationContext context, User user) {
+ if (user == null) {
+ throw new AuthenticationException("User:{} is not found.", context.getUsername());
+ }
+ if (user.getUserStatus() == UserStatus.DISABLE) {
+ throw new AuthenticationException("User:{} is disabled.", context.getUsername());
+ }
+ String signature = AclSigner.calSignature(context.getContent(), user.getPassword());
+ if (context.getSignature() == null
+ || !MessageDigest.isEqual(signature.getBytes(AclSigner.DEFAULT_CHARSET), context.getSignature().getBytes(AclSigner.DEFAULT_CHARSET))) {
+ throw new AuthenticationException("check signature failed.");
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/AuthenticationContext.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/AuthenticationContext.java
new file mode 100644
index 00000000000..7c10f044c76
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/AuthenticationContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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.rocketmq.auth.authentication.context;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
+public abstract class AuthenticationContext {
+
+ private String channelId;
+
+ private String rpcCode;
+
+ private Map extInfo;
+
+ public String getChannelId() {
+ return channelId;
+ }
+
+ public void setChannelId(String channelId) {
+ this.channelId = channelId;
+ }
+
+ public String getRpcCode() {
+ return rpcCode;
+ }
+
+ public void setRpcCode(String rpcCode) {
+ this.rpcCode = rpcCode;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getExtInfo(String key) {
+ if (StringUtils.isBlank(key)) {
+ return null;
+ }
+ if (this.extInfo == null) {
+ return null;
+ }
+ Object value = this.extInfo.get(key);
+ if (value == null) {
+ return null;
+ }
+ return (T) value;
+ }
+
+ public void setExtInfo(String key, Object value) {
+ if (StringUtils.isBlank(key) || value == null) {
+ return;
+ }
+ if (this.extInfo == null) {
+ this.extInfo = new HashMap<>();
+ }
+ this.extInfo.put(key, value);
+ }
+
+ public boolean hasExtInfo(String key) {
+ Object value = getExtInfo(key);
+ return value != null;
+ }
+
+ public Map getExtInfo() {
+ return extInfo;
+ }
+
+ public void setExtInfo(Map extInfo) {
+ this.extInfo = extInfo;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/DefaultAuthenticationContext.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/DefaultAuthenticationContext.java
new file mode 100644
index 00000000000..a6fff86602c
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/context/DefaultAuthenticationContext.java
@@ -0,0 +1,50 @@
+/*
+ * 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.rocketmq.auth.authentication.context;
+
+public class DefaultAuthenticationContext extends AuthenticationContext {
+
+ private String username;
+
+ private byte[] content;
+
+ private String signature;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public byte[] getContent() {
+ return content;
+ }
+
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public void setSignature(String signature) {
+ this.signature = signature;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/SubjectType.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/SubjectType.java
new file mode 100644
index 00000000000..64a40f96ac2
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/SubjectType.java
@@ -0,0 +1,51 @@
+/*
+ * 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.rocketmq.auth.authentication.enums;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+
+public enum SubjectType {
+
+ USER((byte) 1, "User");
+
+ @JSONField(value = true)
+ private final byte code;
+ private final String name;
+
+ SubjectType(byte code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public static SubjectType getByName(String name) {
+ for (SubjectType subjectType : SubjectType.values()) {
+ if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
+ return subjectType;
+ }
+ }
+ return null;
+ }
+
+ public byte getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserStatus.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserStatus.java
new file mode 100644
index 00000000000..9bb25a2d559
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserStatus.java
@@ -0,0 +1,54 @@
+/*
+ * 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.rocketmq.auth.authentication.enums;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+
+public enum UserStatus {
+
+ ENABLE((byte) 1, "enable"),
+
+ DISABLE((byte) 2, "disable");
+
+ @JSONField(value = true)
+ private final byte code;
+
+ private final String name;
+
+ UserStatus(byte code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public static UserStatus getByName(String name) {
+ for (UserStatus subjectType : UserStatus.values()) {
+ if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
+ return subjectType;
+ }
+ }
+ return null;
+ }
+
+ public byte getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserType.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserType.java
new file mode 100644
index 00000000000..6dc786f0f60
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/enums/UserType.java
@@ -0,0 +1,54 @@
+/*
+ * 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.rocketmq.auth.authentication.enums;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+
+public enum UserType {
+
+ SUPER((byte) 1, "Super"),
+
+ NORMAL((byte) 2, "Normal");
+
+ @JSONField(value = true)
+ private final byte code;
+
+ private final String name;
+
+ UserType(byte code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public static UserType getByName(String name) {
+ for (UserType subjectType : UserType.values()) {
+ if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
+ return subjectType;
+ }
+ }
+ return null;
+ }
+
+ public byte getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/exception/AuthenticationException.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/exception/AuthenticationException.java
new file mode 100644
index 00000000000..9b66c81bb17
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/exception/AuthenticationException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.rocketmq.auth.authentication.exception;
+
+import org.slf4j.helpers.MessageFormatter;
+
+public class AuthenticationException extends RuntimeException {
+
+ public AuthenticationException(String message) {
+ super(message);
+ }
+
+ public AuthenticationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AuthenticationException(String messagePattern, Object... argArray) {
+ super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage());
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/factory/AuthenticationFactory.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/factory/AuthenticationFactory.java
new file mode 100644
index 00000000000..3ba82add5ab
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/factory/AuthenticationFactory.java
@@ -0,0 +1,153 @@
+/*
+ * 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.rocketmq.auth.authentication.factory;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
+import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManagerImpl;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider;
+import org.apache.rocketmq.auth.authentication.provider.DefaultAuthenticationProvider;
+import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy;
+import org.apache.rocketmq.auth.authentication.strategy.StatelessAuthenticationStrategy;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class AuthenticationFactory {
+
+ private static final Map INSTANCE_MAP = new HashMap<>();
+ private static final String PROVIDER_PREFIX = "PROVIDER_";
+ private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_";
+ private static final String EVALUATOR_PREFIX = "EVALUATOR_";
+
+ @SuppressWarnings("unchecked")
+ public static AuthenticationProvider getProvider(AuthConfig config) {
+ if (config == null) {
+ return null;
+ }
+ return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> {
+ try {
+ Class extends AuthenticationProvider extends AuthenticationContext>> clazz =
+ DefaultAuthenticationProvider.class;
+ if (StringUtils.isNotBlank(config.getAuthenticationProvider())) {
+ clazz = (Class extends AuthenticationProvider extends AuthenticationContext>>) Class.forName(config.getAuthenticationProvider());
+ }
+ return (AuthenticationProvider) clazz.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load the authentication provider.", e);
+ }
+ });
+ }
+
+ public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config) {
+ return getMetadataProvider(config, null);
+ }
+
+ public static AuthenticationMetadataManager getMetadataManager(AuthConfig config) {
+ return new AuthenticationMetadataManagerImpl(config);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config, Supplier> metadataService) {
+ if (config == null) {
+ return null;
+ }
+ return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> {
+ try {
+ if (StringUtils.isBlank(config.getAuthenticationMetadataProvider())) {
+ return null;
+ }
+ Class extends AuthenticationMetadataProvider> clazz = (Class extends AuthenticationMetadataProvider>)
+ Class.forName(config.getAuthenticationMetadataProvider());
+ AuthenticationMetadataProvider result = clazz.getDeclaredConstructor().newInstance();
+ result.initialize(config, metadataService);
+ return result;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load the authentication metadata provider", e);
+ }
+ });
+ }
+
+ public static AuthenticationEvaluator getEvaluator(AuthConfig config) {
+ return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config));
+ }
+
+ public static AuthenticationEvaluator getEvaluator(AuthConfig config, Supplier> metadataService) {
+ return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config, metadataService));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static AuthenticationStrategy getStrategy(AuthConfig config, Supplier> metadataService) {
+ try {
+ Class extends AuthenticationStrategy> clazz = StatelessAuthenticationStrategy.class;
+ if (StringUtils.isNotBlank(config.getAuthenticationStrategy())) {
+ clazz = (Class extends AuthenticationStrategy>) Class.forName(config.getAuthenticationStrategy());
+ }
+ return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static AuthenticationContext newContext(AuthConfig config, Metadata metadata, GeneratedMessageV3 request) {
+ AuthenticationProvider authenticationProvider = getProvider(config);
+ if (authenticationProvider == null) {
+ return null;
+ }
+ return authenticationProvider.newContext(metadata, request);
+ }
+
+ public static AuthenticationContext newContext(AuthConfig config, ChannelHandlerContext context,
+ RemotingCommand command) {
+ AuthenticationProvider authenticationProvider = getProvider(config);
+ if (authenticationProvider == null) {
+ return null;
+ }
+ return authenticationProvider.newContext(context, command);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static V computeIfAbsent(String key, Function function) {
+ Object result = null;
+ if (INSTANCE_MAP.containsKey(key)) {
+ result = INSTANCE_MAP.get(key);
+ }
+ if (result == null) {
+ synchronized (INSTANCE_MAP) {
+ if (INSTANCE_MAP.containsKey(key)) {
+ result = INSTANCE_MAP.get(key);
+ }
+ if (result == null) {
+ result = function.apply(key);
+ if (result != null) {
+ INSTANCE_MAP.put(key, result);
+ }
+ }
+ }
+ }
+ return result != null ? (V) result : null;
+ }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManager.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManager.java
new file mode 100644
index 00000000000..b3906437dc7
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManager.java
@@ -0,0 +1,41 @@
+/*
+ * 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.rocketmq.auth.authentication.manager;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public interface AuthenticationMetadataManager {
+
+ void shutdown();
+
+ void initUser(AuthConfig authConfig);
+
+ CompletableFuture createUser(User user);
+
+ CompletableFuture updateUser(User user);
+
+ CompletableFuture deleteUser(String username);
+
+ CompletableFuture getUser(String username);
+
+ CompletableFuture> listUser(String filter);
+
+ CompletableFuture isSuperUser(String username);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManagerImpl.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManagerImpl.java
new file mode 100644
index 00000000000..39620ca8d25
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/manager/AuthenticationMetadataManagerImpl.java
@@ -0,0 +1,222 @@
+/*
+ * 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.rocketmq.auth.authentication.manager;
+
+import com.alibaba.fastjson2.JSON;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.acl.common.SessionCredentials;
+import org.apache.rocketmq.auth.authentication.enums.UserStatus;
+import org.apache.rocketmq.auth.authentication.enums.UserType;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.utils.ExceptionUtils;
+
+public class AuthenticationMetadataManagerImpl implements AuthenticationMetadataManager {
+
+ private final AuthenticationMetadataProvider authenticationMetadataProvider;
+
+ private final AuthorizationMetadataProvider authorizationMetadataProvider;
+
+ public AuthenticationMetadataManagerImpl(AuthConfig authConfig) {
+ this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig);
+ this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig);
+ this.initUser(authConfig);
+ }
+
+ @Override
+ public void shutdown() {
+ if (this.authenticationMetadataProvider != null) {
+ this.authenticationMetadataProvider.shutdown();
+ }
+ if (this.authorizationMetadataProvider != null) {
+ this.authorizationMetadataProvider.shutdown();
+ }
+ }
+
+ @Override
+ public void initUser(AuthConfig authConfig) {
+ if (authConfig == null) {
+ return;
+ }
+ if (StringUtils.isNotBlank(authConfig.getInitAuthenticationUser())) {
+ try {
+ User initUser = JSON.parseObject(authConfig.getInitAuthenticationUser(), User.class);
+ initUser.setUserType(UserType.SUPER);
+ this.getUser(initUser.getUsername()).thenCompose(user -> {
+ if (user != null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return this.createUser(initUser);
+ }).join();
+ } catch (Exception e) {
+ throw new AuthenticationException("Init authentication user error.", e);
+ }
+ }
+ if (StringUtils.isNotBlank(authConfig.getInnerClientAuthenticationCredentials())) {
+ try {
+ SessionCredentials credentials = JSON.parseObject(authConfig.getInnerClientAuthenticationCredentials(), SessionCredentials.class);
+ User innerUser = User.of(credentials.getAccessKey(), credentials.getSecretKey(), UserType.SUPER);
+ this.getUser(innerUser.getUsername()).thenCompose(user -> {
+ if (user != null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return this.createUser(innerUser);
+ }).join();
+ } catch (Exception e) {
+ throw new AuthenticationException("Init inner client authentication credentials error", e);
+ }
+ }
+ }
+
+ @Override
+ public CompletableFuture createUser(User user) {
+ CompletableFuture result = new CompletableFuture<>();
+ try {
+ this.validate(user, true);
+ if (user.getUserType() == null) {
+ user.setUserType(UserType.NORMAL);
+ }
+ if (user.getUserStatus() == null) {
+ user.setUserStatus(UserStatus.ENABLE);
+ }
+ result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> {
+ if (old != null) {
+ throw new AuthenticationException("The user is existed");
+ }
+ return this.getAuthenticationMetadataProvider().createUser(user);
+ });
+ } catch (Exception e) {
+ this.handleException(e, result);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture updateUser(User user) {
+ CompletableFuture result = new CompletableFuture<>();
+ try {
+ this.validate(user, false);
+ result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> {
+ if (old == null) {
+ throw new AuthenticationException("The user is not exist");
+ }
+ if (StringUtils.isNotBlank(user.getPassword())) {
+ old.setPassword(user.getPassword());
+ }
+ if (user.getUserType() != null) {
+ old.setUserType(user.getUserType());
+ }
+ if (user.getUserStatus() != null) {
+ old.setUserStatus(user.getUserStatus());
+ }
+ return this.getAuthenticationMetadataProvider().updateUser(old);
+ });
+ } catch (Exception e) {
+ this.handleException(e, result);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture deleteUser(String username) {
+ CompletableFuture result = new CompletableFuture<>();
+ try {
+ if (StringUtils.isBlank(username)) {
+ throw new AuthenticationException("username can not be blank");
+ }
+ CompletableFuture deleteUser = this.getAuthenticationMetadataProvider().deleteUser(username);
+ CompletableFuture deleteAcl = this.getAuthorizationMetadataProvider().deleteAcl(User.of(username));
+ return CompletableFuture.allOf(deleteUser, deleteAcl);
+ } catch (Exception e) {
+ this.handleException(e, result);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture getUser(String username) {
+ CompletableFuture result = new CompletableFuture<>();
+ try {
+ if (StringUtils.isBlank(username)) {
+ throw new AuthenticationException("username can not be blank");
+ }
+ result = this.getAuthenticationMetadataProvider().getUser(username);
+ } catch (Exception e) {
+ this.handleException(e, result);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture> listUser(String filter) {
+ CompletableFuture> result = new CompletableFuture<>();
+ try {
+ result = this.getAuthenticationMetadataProvider().listUser(filter);
+ } catch (Exception e) {
+ this.handleException(e, result);
+ }
+ return result;
+ }
+
+ @Override
+ public CompletableFuture isSuperUser(String username) {
+ return this.getUser(username).thenApply(user -> {
+ if (user == null) {
+ throw new AuthenticationException("User:{} is not found", username);
+ }
+ return user.getUserType() == UserType.SUPER;
+ });
+ }
+
+ private void validate(User user, boolean isCreate) {
+ if (user == null) {
+ throw new AuthenticationException("user can not be null");
+ }
+ if (StringUtils.isBlank(user.getUsername())) {
+ throw new AuthenticationException("username can not be blank");
+ }
+ if (isCreate && StringUtils.isBlank(user.getPassword())) {
+ throw new AuthenticationException("password can not be blank");
+ }
+ }
+
+ private void handleException(Exception e, CompletableFuture> result) {
+ Throwable throwable = ExceptionUtils.getRealException(e);
+ result.completeExceptionally(throwable);
+ }
+
+ private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
+ if (authenticationMetadataProvider == null) {
+ throw new IllegalStateException("The authenticationMetadataProvider is not configured");
+ }
+ return authenticationMetadataProvider;
+ }
+
+ private AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
+ if (authorizationMetadataProvider == null) {
+ throw new IllegalStateException("The authorizationMetadataProvider is not configured");
+ }
+ return authorizationMetadataProvider;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/Subject.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/Subject.java
new file mode 100644
index 00000000000..25b5dcb3162
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/Subject.java
@@ -0,0 +1,47 @@
+/*
+ * 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.rocketmq.auth.authentication.model;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.enums.SubjectType;
+import org.apache.rocketmq.common.constant.CommonConstants;
+
+public interface Subject {
+
+ @JSONField(serialize = false)
+ String getSubjectKey();
+
+ SubjectType getSubjectType();
+
+ default boolean isSubject(SubjectType subjectType) {
+ return subjectType == this.getSubjectType();
+ }
+
+ @SuppressWarnings("unchecked")
+ static T of(String subjectKey) {
+ String type = StringUtils.substringBefore(subjectKey, CommonConstants.COLON);
+ SubjectType subjectType = SubjectType.getByName(type);
+ if (subjectType == null) {
+ return null;
+ }
+ if (subjectType == SubjectType.USER) {
+ return (T) User.of(StringUtils.substringAfter(subjectKey, CommonConstants.COLON));
+ }
+ return null;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/User.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/User.java
new file mode 100644
index 00000000000..4b009ca6163
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/model/User.java
@@ -0,0 +1,96 @@
+/*
+ * 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.rocketmq.auth.authentication.model;
+
+import org.apache.rocketmq.auth.authentication.enums.SubjectType;
+import org.apache.rocketmq.auth.authentication.enums.UserStatus;
+import org.apache.rocketmq.auth.authentication.enums.UserType;
+import org.apache.rocketmq.common.constant.CommonConstants;
+
+public class User implements Subject {
+
+ private String username;
+
+ private String password;
+
+ private UserType userType;
+
+ private UserStatus userStatus;
+
+ public static User of(String username) {
+ User user = new User();
+ user.setUsername(username);
+ return user;
+ }
+
+ public static User of(String username, String password) {
+ User user = new User();
+ user.setUsername(username);
+ user.setPassword(password);
+ return user;
+ }
+
+ public static User of(String username, String password, UserType userType) {
+ User user = new User();
+ user.setUsername(username);
+ user.setPassword(password);
+ user.setUserType(userType);
+ return user;
+ }
+
+ @Override
+ public String getSubjectKey() {
+ return this.getSubjectType().getName() + CommonConstants.COLON + this.username;
+ }
+
+ @Override
+ public SubjectType getSubjectType() {
+ return SubjectType.USER;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public UserType getUserType() {
+ return userType;
+ }
+
+ public void setUserType(UserType userType) {
+ this.userType = userType;
+ }
+
+ public UserStatus getUserStatus() {
+ return userStatus;
+ }
+
+ public void setUserStatus(UserStatus userStatus) {
+ this.userStatus = userStatus;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationMetadataProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationMetadataProvider.java
new file mode 100644
index 00000000000..59c3ec16022
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationMetadataProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.rocketmq.auth.authentication.provider;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public interface AuthenticationMetadataProvider {
+
+ void initialize(AuthConfig authConfig, Supplier> metadataService);
+
+ void shutdown();
+
+ CompletableFuture createUser(User user);
+
+ CompletableFuture deleteUser(String username);
+
+ CompletableFuture updateUser(User user);
+
+ CompletableFuture getUser(String username);
+
+ CompletableFuture> listUser(String filter);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationProvider.java
new file mode 100644
index 00000000000..61660b5a18e
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/AuthenticationProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.rocketmq.auth.authentication.provider;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public interface AuthenticationProvider {
+
+ void initialize(AuthConfig config, Supplier> metadataService);
+
+ CompletableFuture authenticate(AuthenticationContext context);
+
+ AuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request);
+
+ AuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/DefaultAuthenticationProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/DefaultAuthenticationProvider.java
new file mode 100644
index 00000000000..98e7ede7ee3
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/DefaultAuthenticationProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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.rocketmq.auth.authentication.provider;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.builder.AuthenticationContextBuilder;
+import org.apache.rocketmq.auth.authentication.builder.DefaultAuthenticationContextBuilder;
+import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import org.apache.rocketmq.auth.authentication.chain.DefaultAuthenticationHandler;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.chain.HandlerChain;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultAuthenticationProvider implements AuthenticationProvider {
+
+ protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME);
+ protected AuthConfig authConfig;
+ protected Supplier> metadataService;
+ protected AuthenticationContextBuilder authenticationContextBuilder;
+
+ @Override
+ public void initialize(AuthConfig config, Supplier> metadataService) {
+ this.authConfig = config;
+ this.metadataService = metadataService;
+ this.authenticationContextBuilder = new DefaultAuthenticationContextBuilder();
+ }
+
+ @Override
+ public CompletableFuture authenticate(DefaultAuthenticationContext context) {
+ return this.newHandlerChain().handle(context)
+ .whenComplete((nil, ex) -> doAuditLog(context, ex));
+ }
+
+ @Override
+ public DefaultAuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request) {
+ return this.authenticationContextBuilder.build(metadata, request);
+ }
+
+ @Override
+ public DefaultAuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command) {
+ return this.authenticationContextBuilder.build(context, command);
+ }
+
+ protected HandlerChain> newHandlerChain() {
+ return HandlerChain.>create()
+ .addNext(new DefaultAuthenticationHandler(this.authConfig, metadataService));
+ }
+
+ protected void doAuditLog(DefaultAuthenticationContext context, Throwable ex) {
+ if (StringUtils.isBlank(context.getUsername())) {
+ return;
+ }
+ if (ex != null) {
+ log.info("[AUTHENTICATION] User:{} is authenticated failed with Signature = {}.", context.getUsername(), context.getSignature());
+ } else {
+ log.debug("[AUTHENTICATION] User:{} is authenticated success with Signature = {}.", context.getUsername(), context.getSignature());
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/LocalAuthenticationMetadataProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/LocalAuthenticationMetadataProvider.java
new file mode 100644
index 00000000000..dcf90618229
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/provider/LocalAuthenticationMetadataProvider.java
@@ -0,0 +1,167 @@
+/*
+ * 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.rocketmq.auth.authentication.provider;
+
+import com.alibaba.fastjson2.JSON;
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
+import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+import org.rocksdb.RocksIterator;
+
+public class LocalAuthenticationMetadataProvider implements AuthenticationMetadataProvider {
+
+ private ConfigRocksDBStorage storage;
+
+ private LoadingCache userCache;
+
+ @Override
+ public void initialize(AuthConfig authConfig, Supplier> metadataService) {
+ this.storage = new ConfigRocksDBStorage(authConfig.getAuthConfigPath() + File.separator + "users");
+ if (!this.storage.start()) {
+ throw new RuntimeException("Failed to load rocksdb for auth_user, please check whether it is occupied");
+ }
+
+ ThreadPoolExecutor cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor(
+ 1,
+ 1,
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ "UserCacheRefresh",
+ 100000
+ );
+
+ this.userCache = Caffeine.newBuilder()
+ .maximumSize(authConfig.getUserCacheMaxNum())
+ .expireAfterAccess(authConfig.getUserCacheExpiredSecond(), TimeUnit.SECONDS)
+ .refreshAfterWrite(authConfig.getUserCacheRefreshSecond(), TimeUnit.SECONDS)
+ .executor(cacheRefreshExecutor)
+ .build(new UserCacheLoader(this.storage));
+ }
+
+ @Override
+ public CompletableFuture createUser(User user) {
+ try {
+ byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8);
+ byte[] valueBytes = JSON.toJSONBytes(user);
+ this.storage.put(keyBytes, keyBytes.length, valueBytes);
+ this.storage.flushWAL();
+ this.userCache.invalidate(user.getUsername());
+ } catch (Exception e) {
+ throw new AuthenticationException("create user to RocksDB failed", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture deleteUser(String username) {
+ try {
+ this.storage.delete(username.getBytes(StandardCharsets.UTF_8));
+ this.storage.flushWAL();
+ this.userCache.invalidate(username);
+ } catch (Exception e) {
+ throw new AuthenticationException("delete user from RocksDB failed", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture updateUser(User user) {
+ try {
+ byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8);
+ byte[] valueBytes = JSON.toJSONBytes(user);
+ this.storage.put(keyBytes, keyBytes.length, valueBytes);
+ this.storage.flushWAL();
+ this.userCache.invalidate(user.getUsername());
+ } catch (Exception e) {
+ throw new AuthenticationException("update user to RocksDB failed", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture getUser(String username) {
+ User user = this.userCache.get(username);
+ if (user == UserCacheLoader.EMPTY_USER) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.completedFuture(user);
+ }
+
+ @Override
+ public CompletableFuture> listUser(String filter) {
+ List result = new ArrayList<>();
+ try (RocksIterator iterator = this.storage.iterator()) {
+ iterator.seekToFirst();
+ while (iterator.isValid()) {
+ String username = new String(iterator.key(), StandardCharsets.UTF_8);
+ if (StringUtils.isNotBlank(filter) && !username.contains(filter)) {
+ iterator.next();
+ continue;
+ }
+ User user = JSON.parseObject(new String(iterator.value(), StandardCharsets.UTF_8), User.class);
+ result.add(user);
+ iterator.next();
+ }
+ }
+ return CompletableFuture.completedFuture(result);
+ }
+
+ @Override
+ public void shutdown() {
+ if (this.storage != null) {
+ this.storage.shutdown();
+ }
+ }
+
+ private static class UserCacheLoader implements CacheLoader {
+ private final ConfigRocksDBStorage storage;
+ public static final User EMPTY_USER = new User();
+
+ public UserCacheLoader(ConfigRocksDBStorage storage) {
+ this.storage = storage;
+ }
+
+ @Override
+ public User load(String username) {
+ try {
+ byte[] keyBytes = username.getBytes(StandardCharsets.UTF_8);
+ byte[] valueBytes = storage.get(keyBytes);
+ if (ArrayUtils.isEmpty(valueBytes)) {
+ return EMPTY_USER;
+ }
+ return JSON.parseObject(new String(valueBytes, StandardCharsets.UTF_8), User.class);
+ } catch (Exception e) {
+ throw new AuthenticationException("Get user from RocksDB failed.", e);
+ }
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
new file mode 100644
index 00000000000..b27b6e33ec4
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AbstractAuthenticationStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * 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.rocketmq.auth.authentication.strategy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.utils.ExceptionUtils;
+
+public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy {
+
+ protected final AuthConfig authConfig;
+ protected final List authenticationWhitelist = new ArrayList<>();
+ protected final AuthenticationProvider authenticationProvider;
+
+ public AbstractAuthenticationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ this.authConfig = authConfig;
+ this.authenticationProvider = AuthenticationFactory.getProvider(authConfig);
+ if (this.authenticationProvider != null) {
+ this.authenticationProvider.initialize(authConfig, metadataService);
+ }
+ if (StringUtils.isNotBlank(authConfig.getAuthenticationWhitelist())) {
+ String[] whitelist = StringUtils.split(authConfig.getAuthenticationWhitelist(), ",");
+ for (String rpcCode : whitelist) {
+ this.authenticationWhitelist.add(StringUtils.trim(rpcCode));
+ }
+ }
+ }
+
+ protected void doEvaluate(AuthenticationContext context) {
+ if (context == null) {
+ return;
+ }
+ if (!authConfig.isAuthenticationEnabled()) {
+ return;
+ }
+ if (this.authenticationProvider == null) {
+ return;
+ }
+ if (this.authenticationWhitelist.contains(context.getRpcCode())) {
+ return;
+ }
+ try {
+ this.authenticationProvider.authenticate(context).join();
+ } catch (AuthenticationException ex) {
+ throw ex;
+ } catch (Throwable ex) {
+ Throwable exception = ExceptionUtils.getRealException(ex);
+ if (exception instanceof AuthenticationException) {
+ throw (AuthenticationException) exception;
+ }
+ throw new AuthenticationException("Authentication failed. Please verify the credentials and try again.", exception);
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AuthenticationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AuthenticationStrategy.java
new file mode 100644
index 00000000000..22eee6f41be
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/AuthenticationStrategy.java
@@ -0,0 +1,24 @@
+/*
+ * 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.rocketmq.auth.authentication.strategy;
+
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+
+public interface AuthenticationStrategy {
+
+ void evaluate(AuthenticationContext context);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
new file mode 100644
index 00000000000..914d99ac2e8
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatefulAuthenticationStrategy.java
@@ -0,0 +1,72 @@
+/*
+ * 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.rocketmq.auth.authentication.strategy;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.constant.CommonConstants;
+
+public class StatefulAuthenticationStrategy extends AbstractAuthenticationStrategy {
+
+ protected Cache> authCache;
+
+ public StatefulAuthenticationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ super(authConfig, metadataService);
+ this.authCache = Caffeine.newBuilder()
+ .expireAfterWrite(authConfig.getStatefulAuthenticationCacheExpiredSecond(), TimeUnit.SECONDS)
+ .maximumSize(authConfig.getStatefulAuthenticationCacheMaxNum())
+ .build();
+ }
+
+ @Override
+ public void evaluate(AuthenticationContext context) {
+ if (StringUtils.isBlank(context.getChannelId())) {
+ this.doEvaluate(context);
+ return;
+ }
+ Pair result = this.authCache.get(buildKey(context), key -> {
+ try {
+ this.doEvaluate(context);
+ return Pair.of(true, null);
+ } catch (AuthenticationException ex) {
+ return Pair.of(false, ex);
+ }
+ });
+ if (result != null && result.getObject1() == Boolean.FALSE) {
+ throw result.getObject2();
+ }
+ }
+
+ private String buildKey(AuthenticationContext context) {
+ if (context instanceof DefaultAuthenticationContext) {
+ DefaultAuthenticationContext ctx = (DefaultAuthenticationContext) context;
+ if (StringUtils.isBlank(ctx.getUsername())) {
+ return ctx.getChannelId();
+ }
+ return ctx.getChannelId() + CommonConstants.POUND + ctx.getUsername();
+ }
+ throw new AuthenticationException("The request of {} is not support.", context.getClass().getSimpleName());
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatelessAuthenticationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatelessAuthenticationStrategy.java
new file mode 100644
index 00000000000..05649824175
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authentication/strategy/StatelessAuthenticationStrategy.java
@@ -0,0 +1,33 @@
+/*
+ * 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.rocketmq.auth.authentication.strategy;
+
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public class StatelessAuthenticationStrategy extends AbstractAuthenticationStrategy {
+
+ public StatelessAuthenticationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ super(authConfig, metadataService);
+ }
+
+ @Override
+ public void evaluate(AuthenticationContext context) {
+ super.doEvaluate(context);
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluator.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluator.java
new file mode 100644
index 00000000000..f043810cc98
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/AuthorizationEvaluator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.rocketmq.auth.authorization;
+
+import java.util.List;
+import java.util.function.Supplier;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public class AuthorizationEvaluator {
+
+ private final AuthorizationStrategy authorizationStrategy;
+
+ public AuthorizationEvaluator(AuthConfig authConfig) {
+ this(authConfig, null);
+ }
+
+ public AuthorizationEvaluator(AuthConfig authConfig, Supplier> metadataService) {
+ this.authorizationStrategy = AuthorizationFactory.getStrategy(authConfig, metadataService);
+ }
+
+ public void evaluate(List contexts) {
+ if (CollectionUtils.isEmpty(contexts)) {
+ return;
+ }
+ contexts.forEach(this.authorizationStrategy::evaluate);
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/AuthorizationContextBuilder.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/AuthorizationContextBuilder.java
new file mode 100644
index 00000000000..e1e8dccfb81
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/AuthorizationContextBuilder.java
@@ -0,0 +1,31 @@
+/*
+ * 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.rocketmq.auth.authorization.builder;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.List;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public interface AuthorizationContextBuilder {
+
+ List build(Metadata metadata, GeneratedMessageV3 message);
+
+ List build(ChannelHandlerContext context, RemotingCommand command);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
new file mode 100644
index 00000000000..fababc0ee71
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/builder/DefaultAuthorizationContextBuilder.java
@@ -0,0 +1,504 @@
+/*
+ * 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.rocketmq.auth.authorization.builder;
+
+import apache.rocketmq.v2.AckMessageRequest;
+import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
+import apache.rocketmq.v2.ClientType;
+import apache.rocketmq.v2.EndTransactionRequest;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.QueryAssignmentRequest;
+import apache.rocketmq.v2.QueryRouteRequest;
+import apache.rocketmq.v2.RecallMessageRequest;
+import apache.rocketmq.v2.ReceiveMessageRequest;
+import apache.rocketmq.v2.SendMessageRequest;
+import apache.rocketmq.v2.Subscription;
+import apache.rocketmq.v2.SubscriptionEntry;
+import apache.rocketmq.v2.TelemetryCommand;
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.acl.common.AclException;
+import org.apache.rocketmq.acl.common.SessionCredentials;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.common.action.RocketMQAction;
+import org.apache.rocketmq.common.constant.CommonConstants;
+import org.apache.rocketmq.common.constant.GrpcConstants;
+import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.resource.ResourcePattern;
+import org.apache.rocketmq.common.resource.ResourceType;
+import org.apache.rocketmq.common.resource.RocketMQResource;
+import org.apache.rocketmq.remoting.CommandCustomHeader;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.apache.rocketmq.remoting.protocol.NamespaceUtil;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.remoting.protocol.RequestCode;
+import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry;
+import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
+import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader;
+import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader;
+import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader;
+import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader;
+import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData;
+import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
+
+public class DefaultAuthorizationContextBuilder implements AuthorizationContextBuilder {
+
+ private static final String TOPIC = "topic";
+ private static final String GROUP = "group";
+ private static final String A = "a";
+ private static final String B = "b";
+ private static final String CONSUMER_GROUP = "consumerGroup";
+ private final AuthConfig authConfig;
+
+ private final RequestHeaderRegistry requestHeaderRegistry;
+
+ public DefaultAuthorizationContextBuilder(AuthConfig authConfig) {
+ this.authConfig = authConfig;
+ this.requestHeaderRegistry = RequestHeaderRegistry.getInstance();
+ }
+
+ @Override
+ public List build(Metadata metadata, GeneratedMessageV3 message) {
+ List result = null;
+ if (message instanceof SendMessageRequest) {
+ SendMessageRequest request = (SendMessageRequest) message;
+ if (request.getMessagesCount() <= 0) {
+ throw new AuthorizationException("message is null.");
+ }
+ result = newPubContext(metadata, request.getMessages(0).getTopic());
+ }
+ if (message instanceof RecallMessageRequest) {
+ RecallMessageRequest request = (RecallMessageRequest) message;
+ result = newPubContext(metadata, request.getTopic());
+ }
+ if (message instanceof EndTransactionRequest) {
+ EndTransactionRequest request = (EndTransactionRequest) message;
+ result = newPubContext(metadata, request.getTopic());
+ }
+ if (message instanceof HeartbeatRequest) {
+ HeartbeatRequest request = (HeartbeatRequest) message;
+ if (!isConsumerClientType(request.getClientType())) {
+ return null;
+ }
+ result = newGroupSubContexts(metadata, request.getGroup());
+ }
+ if (message instanceof ReceiveMessageRequest) {
+ ReceiveMessageRequest request = (ReceiveMessageRequest) message;
+ if (!request.hasMessageQueue()) {
+ throw new AuthorizationException("messageQueue is null.");
+ }
+ result = newSubContexts(metadata, request.getGroup(), request.getMessageQueue().getTopic());
+ }
+ if (message instanceof AckMessageRequest) {
+ AckMessageRequest request = (AckMessageRequest) message;
+ result = newSubContexts(metadata, request.getGroup(), request.getTopic());
+ }
+ if (message instanceof ForwardMessageToDeadLetterQueueRequest) {
+ ForwardMessageToDeadLetterQueueRequest request = (ForwardMessageToDeadLetterQueueRequest) message;
+ result = newSubContexts(metadata, request.getGroup(), request.getTopic());
+ }
+ if (message instanceof NotifyClientTerminationRequest) {
+ NotifyClientTerminationRequest request = (NotifyClientTerminationRequest) message;
+ if (StringUtils.isNotBlank(request.getGroup().getName())) {
+ result = newGroupSubContexts(metadata, request.getGroup());
+ }
+ }
+ if (message instanceof ChangeInvisibleDurationRequest) {
+ ChangeInvisibleDurationRequest request = (ChangeInvisibleDurationRequest) message;
+ result = newGroupSubContexts(metadata, request.getGroup());
+ }
+ if (message instanceof QueryRouteRequest) {
+ QueryRouteRequest request = (QueryRouteRequest) message;
+ result = newContext(metadata, request);
+ }
+ if (message instanceof QueryAssignmentRequest) {
+ QueryAssignmentRequest request = (QueryAssignmentRequest) message;
+ result = newSubContexts(metadata, request.getGroup(), request.getTopic());
+ }
+ if (message instanceof TelemetryCommand) {
+ TelemetryCommand request = (TelemetryCommand) message;
+ result = newContext(metadata, request);
+ }
+ if (CollectionUtils.isNotEmpty(result)) {
+ result.forEach(context -> {
+ context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID));
+ context.setRpcCode(message.getDescriptorForType().getFullName());
+ });
+ }
+ return result;
+ }
+
+ @Override
+ public List build(ChannelHandlerContext context, RemotingCommand command) {
+ List result = new ArrayList<>();
+ try {
+ HashMap fields = command.getExtFields();
+ if (MapUtils.isEmpty(fields)) {
+ return result;
+ }
+ Subject subject = null;
+ if (fields.containsKey(SessionCredentials.ACCESS_KEY)) {
+ subject = User.of(fields.get(SessionCredentials.ACCESS_KEY));
+ }
+ String remoteAddr = RemotingHelper.parseChannelRemoteAddr(context.channel());
+ String sourceIp = StringUtils.substringBeforeLast(remoteAddr, CommonConstants.COLON);
+
+ Resource topic;
+ Resource group;
+ switch (command.getCode()) {
+ case RequestCode.GET_ROUTEINFO_BY_TOPIC:
+ if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
+ group = Resource.ofGroup(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
+ } else {
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.PUB, Action.SUB, Action.GET), sourceIp));
+ }
+ break;
+ case RequestCode.SEND_MESSAGE:
+ if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
+ if (StringUtils.isNotBlank(fields.get(GROUP))) {
+ group = Resource.ofGroup(fields.get(GROUP));
+ } else {
+ group = Resource.ofGroup(fields.get(TOPIC));
+ }
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ } else {
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
+ }
+ break;
+ case RequestCode.SEND_MESSAGE_V2:
+ case RequestCode.SEND_BATCH_MESSAGE:
+ if (NamespaceUtil.isRetryTopic(fields.get(B))) {
+ if (StringUtils.isNotBlank(fields.get(A))) {
+ group = Resource.ofGroup(fields.get(A));
+ } else {
+ group = Resource.ofGroup(fields.get(B));
+ }
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ } else {
+ topic = Resource.ofTopic(fields.get(B));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
+ }
+ break;
+ case RequestCode.RECALL_MESSAGE:
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
+ break;
+ case RequestCode.END_TRANSACTION:
+ if (StringUtils.isNotBlank(fields.get(TOPIC))) {
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
+ }
+ break;
+ case RequestCode.CONSUMER_SEND_MSG_BACK:
+ group = Resource.ofGroup(fields.get(GROUP));
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ break;
+ case RequestCode.PULL_MESSAGE:
+ if (!NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
+ }
+ group = Resource.ofGroup(fields.get(CONSUMER_GROUP));
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ break;
+ case RequestCode.QUERY_MESSAGE:
+ topic = Resource.ofTopic(fields.get(TOPIC));
+ result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp));
+ break;
+ case RequestCode.HEART_BEAT:
+ HeartbeatData heartbeatData = HeartbeatData.decode(command.getBody(), HeartbeatData.class);
+ for (ConsumerData data : heartbeatData.getConsumerDataSet()) {
+ group = Resource.ofGroup(data.getGroupName());
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) {
+ if (NamespaceUtil.isRetryTopic(subscriptionData.getTopic())) {
+ continue;
+ }
+ topic = Resource.ofTopic(subscriptionData.getTopic());
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
+ }
+ }
+ break;
+ case RequestCode.UNREGISTER_CLIENT:
+ final UnregisterClientRequestHeader unregisterClientRequestHeader =
+ command.decodeCommandCustomHeader(UnregisterClientRequestHeader.class);
+ if (StringUtils.isNotBlank(unregisterClientRequestHeader.getConsumerGroup())) {
+ group = Resource.ofGroup(unregisterClientRequestHeader.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ }
+ break;
+ case RequestCode.GET_CONSUMER_LIST_BY_GROUP:
+ final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader =
+ command.decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class);
+ group = Resource.ofGroup(getConsumerListByGroupRequestHeader.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
+ break;
+ case RequestCode.QUERY_CONSUMER_OFFSET:
+ final QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader =
+ command.decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class);
+ if (!NamespaceUtil.isRetryTopic(queryConsumerOffsetRequestHeader.getTopic())) {
+ topic = Resource.ofTopic(queryConsumerOffsetRequestHeader.getTopic());
+ result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp));
+ }
+ group = Resource.ofGroup(queryConsumerOffsetRequestHeader.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
+ break;
+ case RequestCode.UPDATE_CONSUMER_OFFSET:
+ final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader =
+ command.decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class);
+ if (!NamespaceUtil.isRetryTopic(updateConsumerOffsetRequestHeader.getTopic())) {
+ topic = Resource.ofTopic(updateConsumerOffsetRequestHeader.getTopic());
+ result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp));
+ }
+ group = Resource.ofGroup(updateConsumerOffsetRequestHeader.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp));
+ break;
+ case RequestCode.LOCK_BATCH_MQ:
+ LockBatchRequestBody lockBatchRequestBody = LockBatchRequestBody.decode(command.getBody(), LockBatchRequestBody.class);
+ group = Resource.ofGroup(lockBatchRequestBody.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ if (CollectionUtils.isNotEmpty(lockBatchRequestBody.getMqSet())) {
+ for (MessageQueue messageQueue : lockBatchRequestBody.getMqSet()) {
+ if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) {
+ continue;
+ }
+ topic = Resource.ofTopic(messageQueue.getTopic());
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
+ }
+ }
+ break;
+ case RequestCode.UNLOCK_BATCH_MQ:
+ UnlockBatchRequestBody unlockBatchRequestBody = LockBatchRequestBody.decode(command.getBody(), UnlockBatchRequestBody.class);
+ group = Resource.ofGroup(unlockBatchRequestBody.getConsumerGroup());
+ result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
+ if (CollectionUtils.isNotEmpty(unlockBatchRequestBody.getMqSet())) {
+ for (MessageQueue messageQueue : unlockBatchRequestBody.getMqSet()) {
+ if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) {
+ continue;
+ }
+ topic = Resource.ofTopic(messageQueue.getTopic());
+ result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
+ }
+ }
+ break;
+ default:
+ result = buildContextByAnnotation(subject, command, sourceIp);
+ break;
+ }
+ if (CollectionUtils.isNotEmpty(result)) {
+ result.forEach(r -> {
+ r.setChannelId(context.channel().id().asLongText());
+ r.setRpcCode(String.valueOf(command.getCode()));
+ });
+ }
+ } catch (AuthorizationException ex) {
+ throw ex;
+ } catch (Throwable t) {
+ throw new AuthorizationException("parse authorization context error.", t);
+ }
+ return result;
+ }
+
+ private List buildContextByAnnotation(Subject subject, RemotingCommand request,
+ String sourceIp) throws Exception {
+ List result = new ArrayList<>();
+
+ Class extends CommandCustomHeader> clazz = this.requestHeaderRegistry.getRequestHeader(request.getCode());
+ if (clazz == null) {
+ return result;
+ }
+ CommandCustomHeader header = request.decodeCommandCustomHeader(clazz);
+
+ RocketMQAction rocketMQAction = clazz.getAnnotation(RocketMQAction.class);
+ ResourceType resourceType = rocketMQAction.resource();
+ Action[] actions = rocketMQAction.action();
+ Resource resource = null;
+ if (resourceType == ResourceType.CLUSTER) {
+ resource = Resource.ofCluster(authConfig.getClusterName());
+ }
+
+ Field[] fields = clazz.getDeclaredFields();
+ if (ArrayUtils.isNotEmpty(fields)) {
+ for (Field field : fields) {
+ RocketMQResource rocketMQResource = field.getAnnotation(RocketMQResource.class);
+ if (rocketMQResource == null) {
+ continue;
+ }
+ field.setAccessible(true);
+ try {
+ resourceType = rocketMQResource.value();
+ String splitter = rocketMQResource.splitter();
+ Object value = field.get(header);
+ if (value == null) {
+ continue;
+ }
+ String[] resourceValues;
+ if (StringUtils.isNotBlank(splitter)) {
+ resourceValues = StringUtils.split(value.toString(), splitter);
+ } else {
+ resourceValues = new String[] {value.toString()};
+ }
+ for (String resourceValue : resourceValues) {
+ if (resourceType == ResourceType.TOPIC && NamespaceUtil.isRetryTopic(resourceValue)) {
+ resource = Resource.ofGroup(resourceValue);
+ result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
+ } else {
+ resource = Resource.of(resourceType, resourceValue, ResourcePattern.LITERAL);
+ result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
+ }
+ }
+ } finally {
+ field.setAccessible(false);
+ }
+ }
+ }
+
+ if (CollectionUtils.isEmpty(result) && resource != null) {
+ result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
+ }
+
+ return result;
+ }
+
+ private List newContext(Metadata metadata, QueryRouteRequest request) {
+ apache.rocketmq.v2.Resource topic = request.getTopic();
+ if (StringUtils.isBlank(topic.getName())) {
+ throw new AuthorizationException("topic is null.");
+ }
+ Subject subject = null;
+ if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
+ subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
+ }
+ Resource resource = Resource.ofTopic(topic.getName());
+ String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
+ DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Arrays.asList(Action.PUB, Action.SUB), sourceIp);
+ return Collections.singletonList(context);
+ }
+
+ private static List newContext(Metadata metadata, TelemetryCommand request) {
+ if (request.getCommandCase() != TelemetryCommand.CommandCase.SETTINGS) {
+ return null;
+ }
+ if (!request.getSettings().hasPublishing() && !request.getSettings().hasSubscription()) {
+ throw new AclException("settings command doesn't have publishing or subscription.");
+ }
+ List result = new ArrayList<>();
+ if (request.getSettings().hasPublishing()) {
+ List topicList = request.getSettings().getPublishing().getTopicsList();
+ for (apache.rocketmq.v2.Resource topic : topicList) {
+ result.addAll(newPubContext(metadata, topic));
+ }
+ }
+ if (request.getSettings().hasSubscription()) {
+ Subscription subscription = request.getSettings().getSubscription();
+ result.addAll(newSubContexts(metadata, ResourceType.GROUP, subscription.getGroup()));
+ for (SubscriptionEntry entry : subscription.getSubscriptionsList()) {
+ result.addAll(newSubContexts(metadata, ResourceType.TOPIC, entry.getTopic()));
+ }
+ }
+ return result;
+ }
+
+ private boolean isConsumerClientType(ClientType clientType) {
+ return Arrays.asList(ClientType.PUSH_CONSUMER, ClientType.SIMPLE_CONSUMER, ClientType.PULL_CONSUMER)
+ .contains(clientType);
+ }
+
+ private static List newPubContext(Metadata metadata, apache.rocketmq.v2.Resource topic) {
+ if (topic == null || StringUtils.isBlank(topic.getName())) {
+ throw new AuthorizationException("topic is null.");
+ }
+ Subject subject = null;
+ if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
+ subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
+ }
+ Resource resource = Resource.ofTopic(topic.getName());
+ String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
+ DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Action.PUB, sourceIp);
+ return Collections.singletonList(context);
+ }
+
+ private List newSubContexts(Metadata metadata, apache.rocketmq.v2.Resource group,
+ apache.rocketmq.v2.Resource topic) {
+ List result = new ArrayList<>();
+ result.addAll(newGroupSubContexts(metadata, group));
+ result.addAll(newTopicSubContexts(metadata, topic));
+ return result;
+ }
+
+ private static List newTopicSubContexts(Metadata metadata,
+ apache.rocketmq.v2.Resource resource) {
+ return newSubContexts(metadata, ResourceType.TOPIC, resource);
+ }
+
+ private static List newGroupSubContexts(Metadata metadata,
+ apache.rocketmq.v2.Resource resource) {
+ return newSubContexts(metadata, ResourceType.GROUP, resource);
+ }
+
+ private static List newSubContexts(Metadata metadata, ResourceType resourceType,
+ apache.rocketmq.v2.Resource resource) {
+ if (resourceType == ResourceType.GROUP) {
+ if (resource == null || StringUtils.isBlank(resource.getName())) {
+ throw new AuthorizationException("group is null.");
+ }
+ return newSubContexts(metadata, Resource.ofGroup(resource.getName()));
+ }
+ if (resourceType == ResourceType.TOPIC) {
+ if (resource == null || StringUtils.isBlank(resource.getName())) {
+ throw new AuthorizationException("topic is null.");
+ }
+ return newSubContexts(metadata, Resource.ofTopic(resource.getName()));
+ }
+ throw new AuthorizationException("unknown resource type.");
+ }
+
+ private static List newSubContexts(Metadata metadata, Resource resource) {
+ List result = new ArrayList<>();
+ Subject subject = null;
+ if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
+ subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
+ }
+ String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
+ result.add(DefaultAuthorizationContext.of(subject, resource, Action.SUB, sourceIp));
+ return result;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/AclAuthorizationHandler.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/AclAuthorizationHandler.java
new file mode 100644
index 00000000000..06a130b2e0a
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/AclAuthorizationHandler.java
@@ -0,0 +1,167 @@
+/*
+ * 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.rocketmq.auth.authorization.chain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.authorization.model.Environment;
+import org.apache.rocketmq.auth.authorization.model.Policy;
+import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.chain.Handler;
+import org.apache.rocketmq.common.chain.HandlerChain;
+import org.apache.rocketmq.common.resource.ResourcePattern;
+import org.apache.rocketmq.common.resource.ResourceType;
+
+public class AclAuthorizationHandler implements Handler> {
+
+ private final AuthorizationMetadataProvider authorizationMetadataProvider;
+
+ public AclAuthorizationHandler(AuthConfig config) {
+ this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config);
+ }
+
+ public AclAuthorizationHandler(AuthConfig config, Supplier> metadataService) {
+ this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config, metadataService);
+ }
+
+ @Override
+ public CompletableFuture handle(DefaultAuthorizationContext context,
+ HandlerChain> chain) {
+ if (this.authorizationMetadataProvider == null) {
+ throw new AuthorizationException("The authorizationMetadataProvider is not configured");
+ }
+ return this.authorizationMetadataProvider.getAcl(context.getSubject()).thenAccept(acl -> {
+ if (acl == null) {
+ throwException(context, "no matched policies.");
+ }
+
+ // 1. get the defined acl entries which match the request.
+ PolicyEntry matchedEntry = matchPolicyEntries(context, acl);
+
+ // 2. if no matched acl entries, return deny
+ if (matchedEntry == null) {
+ throwException(context, "no matched policies.");
+ }
+
+ // 3. judge is the entries has denied decision.
+ if (matchedEntry.getDecision() == Decision.DENY) {
+ throwException(context, "the decision is deny.");
+ }
+ });
+ }
+
+ private PolicyEntry matchPolicyEntries(DefaultAuthorizationContext context, Acl acl) {
+ List policyEntries = new ArrayList<>();
+
+ Policy policy = acl.getPolicy(PolicyType.CUSTOM);
+ if (policy != null) {
+ List entries = matchPolicyEntries(context, policy.getEntries());
+ if (CollectionUtils.isNotEmpty(entries)) {
+ policyEntries.addAll(entries);
+ }
+ }
+
+ if (CollectionUtils.isEmpty(policyEntries)) {
+ policy = acl.getPolicy(PolicyType.DEFAULT);
+ if (policy != null) {
+ List entries = matchPolicyEntries(context, policy.getEntries());
+ if (CollectionUtils.isNotEmpty(entries)) {
+ policyEntries.addAll(entries);
+ }
+ }
+ }
+
+ if (CollectionUtils.isEmpty(policyEntries)) {
+ return null;
+ }
+
+ policyEntries.sort(this::comparePolicyEntries);
+
+ return policyEntries.get(0);
+ }
+
+ private List matchPolicyEntries(DefaultAuthorizationContext context, List entries) {
+ if (CollectionUtils.isEmpty(entries)) {
+ return null;
+ }
+ return entries.stream()
+ .filter(entry -> entry.isMatchResource(context.getResource()))
+ .filter(entry -> entry.isMatchAction(context.getActions()))
+ .filter(entry -> entry.isMatchEnvironment(Environment.of(context.getSourceIp())))
+ .collect(Collectors.toList());
+ }
+
+ private int comparePolicyEntries(PolicyEntry o1, PolicyEntry o2) {
+ int compare = 0;
+ Resource r1 = o1.getResource();
+ Resource r2 = o2.getResource();
+ if (r1.getResourceType() != r2.getResourceType()) {
+ if (r1.getResourceType() == ResourceType.ANY) {
+ compare = 1;
+ }
+ if (r2.getResourceType() == ResourceType.ANY) {
+ compare = -1;
+ }
+ } else if (r1.getResourcePattern() == r2.getResourcePattern()) {
+ if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
+ String n1 = r1.getResourceName();
+ String n2 = r2.getResourceName();
+ compare = Integer.compare(n1.length(), n2.length());
+ }
+ } else {
+ if (r1.getResourcePattern() == ResourcePattern.LITERAL) {
+ compare = 1;
+ }
+ if (r1.getResourcePattern() == ResourcePattern.LITERAL) {
+ compare = -1;
+ }
+ if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
+ compare = 1;
+ }
+ if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
+ compare = -1;
+ }
+ }
+
+ if (compare != 0) {
+ return compare;
+ }
+
+ // the decision deny has higher priority
+ Decision d1 = o1.getDecision();
+ Decision d2 = o2.getDecision();
+ return d1 == Decision.DENY ? 1 : d2 == Decision.DENY ? -1 : 0;
+ }
+
+ private static void throwException(DefaultAuthorizationContext context, String detail) {
+ throw new AuthorizationException("{} has no permission to access {} from {}, " + detail,
+ context.getSubject().getSubjectKey(), context.getResource().getResourceKey(), context.getSourceIp());
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/UserAuthorizationHandler.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/UserAuthorizationHandler.java
new file mode 100644
index 00000000000..1c391df54f5
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/chain/UserAuthorizationHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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.rocketmq.auth.authorization.chain;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authentication.enums.SubjectType;
+import org.apache.rocketmq.auth.authentication.enums.UserStatus;
+import org.apache.rocketmq.auth.authentication.enums.UserType;
+import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.chain.Handler;
+import org.apache.rocketmq.common.chain.HandlerChain;
+
+public class UserAuthorizationHandler implements Handler> {
+
+ private final AuthenticationMetadataProvider authenticationMetadataProvider;
+
+ public UserAuthorizationHandler(AuthConfig config, Supplier> metadataService) {
+ this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
+ }
+
+ @Override
+ public CompletableFuture handle(DefaultAuthorizationContext context, HandlerChain> chain) {
+ if (!context.getSubject().isSubject(SubjectType.USER)) {
+ return chain.handle(context);
+ }
+ return this.getUser(context.getSubject()).thenCompose(user -> {
+ if (user.getUserType() == UserType.SUPER) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return chain.handle(context);
+ });
+ }
+
+ private CompletableFuture getUser(Subject subject) {
+ if (this.authenticationMetadataProvider == null) {
+ throw new AuthorizationException("The authenticationMetadataProvider is not configured");
+ }
+ User user = (User) subject;
+ return authenticationMetadataProvider.getUser(user.getUsername()).thenApply(result -> {
+ if (result == null) {
+ throw new AuthorizationException("User:{} not found.", user.getUsername());
+ }
+ if (user.getUserStatus() == UserStatus.DISABLE) {
+ throw new AuthenticationException("User:{} is disabled.", user.getUsername());
+ }
+ return result;
+ });
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/AuthorizationContext.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/AuthorizationContext.java
new file mode 100644
index 00000000000..d2286d787e4
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/AuthorizationContext.java
@@ -0,0 +1,84 @@
+/*
+ * 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.rocketmq.auth.authorization.context;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+
+public abstract class AuthorizationContext {
+
+ private String channelId;
+
+ private String rpcCode;
+
+ private Map extInfo;
+
+ @SuppressWarnings("unchecked")
+ public T getExtInfo(String key) {
+ if (StringUtils.isBlank(key)) {
+ return null;
+ }
+ if (this.extInfo == null) {
+ return null;
+ }
+ Object value = this.extInfo.get(key);
+ if (value == null) {
+ return null;
+ }
+ return (T) value;
+ }
+
+ public void setExtInfo(String key, Object value) {
+ if (StringUtils.isBlank(key) || value == null) {
+ return;
+ }
+ if (this.extInfo == null) {
+ this.extInfo = new HashMap<>();
+ }
+ this.extInfo.put(key, value);
+ }
+
+ public boolean hasExtInfo(String key) {
+ Object value = getExtInfo(key);
+ return value != null;
+ }
+
+ public String getChannelId() {
+ return channelId;
+ }
+
+ public void setChannelId(String channelId) {
+ this.channelId = channelId;
+ }
+
+ public String getRpcCode() {
+ return rpcCode;
+ }
+
+ public void setRpcCode(String rpcCode) {
+ this.rpcCode = rpcCode;
+ }
+
+ public Map getExtInfo() {
+ return extInfo;
+ }
+
+ public void setExtInfo(Map extInfo) {
+ this.extInfo = extInfo;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/DefaultAuthorizationContext.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/DefaultAuthorizationContext.java
new file mode 100644
index 00000000000..8e38ed20fae
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/context/DefaultAuthorizationContext.java
@@ -0,0 +1,92 @@
+/*
+ * 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.rocketmq.auth.authorization.context;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.common.action.Action;
+
+public class DefaultAuthorizationContext extends AuthorizationContext {
+
+ private Subject subject;
+
+ private Resource resource;
+
+ private List actions;
+
+ private String sourceIp;
+
+ public static DefaultAuthorizationContext of(Subject subject, Resource resource, Action action, String sourceIp) {
+ DefaultAuthorizationContext context = new DefaultAuthorizationContext();
+ context.setSubject(subject);
+ context.setResource(resource);
+ context.setActions(Collections.singletonList(action));
+ context.setSourceIp(sourceIp);
+ return context;
+ }
+
+ public static DefaultAuthorizationContext of(Subject subject, Resource resource, List actions, String sourceIp) {
+ DefaultAuthorizationContext context = new DefaultAuthorizationContext();
+ context.setSubject(subject);
+ context.setResource(resource);
+ context.setActions(actions);
+ context.setSourceIp(sourceIp);
+ return context;
+ }
+
+ public String getSubjectKey() {
+ return this.subject != null ? this.subject.getSubjectKey() : null;
+ }
+
+ public String getResourceKey() {
+ return this.resource != null ? this.resource.getResourceKey() : null;
+ }
+
+ public Subject getSubject() {
+ return subject;
+ }
+
+ public void setSubject(Subject subject) {
+ this.subject = subject;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ public List getActions() {
+ return actions;
+ }
+
+ public void setActions(List actions) {
+ this.actions = actions;
+ }
+
+ public String getSourceIp() {
+ return sourceIp;
+ }
+
+ public void setSourceIp(String sourceIp) {
+ this.sourceIp = sourceIp;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/Decision.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/Decision.java
new file mode 100644
index 00000000000..b7e69b745b0
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/Decision.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.auth.authorization.enums;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+
+public enum Decision {
+
+ ALLOW((byte) 1, "Allow"),
+
+ DENY((byte) 2, "Deny");
+
+ @JSONField(value = true)
+ private final byte code;
+ private final String name;
+
+ Decision(byte code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public static Decision getByName(String name) {
+ for (Decision decision : Decision.values()) {
+ if (StringUtils.equalsIgnoreCase(decision.getName(), name)) {
+ return decision;
+ }
+ }
+ return null;
+ }
+
+ public byte getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/PolicyType.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/PolicyType.java
new file mode 100644
index 00000000000..fff71d60ead
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/enums/PolicyType.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.auth.authorization.enums;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import org.apache.commons.lang3.StringUtils;
+
+public enum PolicyType {
+
+ CUSTOM((byte) 1, "Custom"),
+
+ DEFAULT((byte) 2, "Default");
+
+ @JSONField(value = true)
+ private final byte code;
+ private final String name;
+
+ PolicyType(byte code, String name) {
+ this.code = code;
+ this.name = name;
+ }
+
+ public static PolicyType getByName(String name) {
+ for (PolicyType policyType : PolicyType.values()) {
+ if (StringUtils.equalsIgnoreCase(policyType.getName(), name)) {
+ return policyType;
+ }
+ }
+ return null;
+ }
+
+ public byte getCode() {
+ return code;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/exception/AuthorizationException.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/exception/AuthorizationException.java
new file mode 100644
index 00000000000..e2aadcbe6f1
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/exception/AuthorizationException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.rocketmq.auth.authorization.exception;
+
+import org.slf4j.helpers.MessageFormatter;
+
+public class AuthorizationException extends RuntimeException {
+
+ public AuthorizationException(String message) {
+ super(message);
+ }
+
+ public AuthorizationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AuthorizationException(String messagePattern, Object... argArray) {
+ super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage());
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/factory/AuthorizationFactory.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/factory/AuthorizationFactory.java
new file mode 100644
index 00000000000..29748a9ed44
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/factory/AuthorizationFactory.java
@@ -0,0 +1,156 @@
+/*
+ * 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.rocketmq.auth.authorization.factory;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator;
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
+import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManagerImpl;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider;
+import org.apache.rocketmq.auth.authorization.provider.DefaultAuthorizationProvider;
+import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy;
+import org.apache.rocketmq.auth.authorization.strategy.StatelessAuthorizationStrategy;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class AuthorizationFactory {
+
+ private static final Map INSTANCE_MAP = new HashMap<>();
+ private static final String PROVIDER_PREFIX = "PROVIDER_";
+ private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_";
+ private static final String EVALUATOR_PREFIX = "EVALUATOR_";
+
+ @SuppressWarnings("unchecked")
+ public static AuthorizationProvider getProvider(AuthConfig config) {
+ if (config == null) {
+ return null;
+ }
+ return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> {
+ try {
+ Class extends AuthorizationProvider extends AuthorizationContext>> clazz =
+ DefaultAuthorizationProvider.class;
+ if (StringUtils.isNotBlank(config.getAuthorizationProvider())) {
+ clazz = (Class extends AuthorizationProvider extends AuthorizationContext>>) Class.forName(config.getAuthorizationProvider());
+ }
+ return (AuthorizationProvider) clazz
+ .getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load the authorization provider.", e);
+ }
+ });
+ }
+
+ public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config) {
+ return getMetadataProvider(config, null);
+ }
+
+ public static AuthorizationMetadataManager getMetadataManager(AuthConfig config) {
+ return new AuthorizationMetadataManagerImpl(config);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config, Supplier> metadataService) {
+ if (config == null) {
+ return null;
+ }
+ return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> {
+ try {
+ if (StringUtils.isBlank(config.getAuthorizationMetadataProvider())) {
+ return null;
+ }
+ Class extends AuthorizationMetadataProvider> clazz = (Class extends AuthorizationMetadataProvider>)
+ Class.forName(config.getAuthorizationMetadataProvider());
+ AuthorizationMetadataProvider result = clazz.getDeclaredConstructor().newInstance();
+ result.initialize(config, metadataService);
+ return result;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load the authorization metadata provider.", e);
+ }
+ });
+ }
+
+ public static AuthorizationEvaluator getEvaluator(AuthConfig config) {
+ return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config));
+ }
+
+ public static AuthorizationEvaluator getEvaluator(AuthConfig config, Supplier> metadataService) {
+ return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config, metadataService));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static AuthorizationStrategy getStrategy(AuthConfig config, Supplier> metadataService) {
+ try {
+ Class extends AuthorizationStrategy> clazz = StatelessAuthorizationStrategy.class;
+ if (StringUtils.isNotBlank(config.getAuthorizationStrategy())) {
+ clazz = (Class extends AuthorizationStrategy>) Class.forName(config.getAuthorizationStrategy());
+ }
+ return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List newContexts(AuthConfig config, Metadata metadata,
+ GeneratedMessageV3 message) {
+ AuthorizationProvider authorizationProvider = getProvider(config);
+ if (authorizationProvider == null) {
+ return null;
+ }
+ return authorizationProvider.newContexts(metadata, message);
+ }
+
+ public static List newContexts(AuthConfig config, ChannelHandlerContext context,
+ RemotingCommand command) {
+ AuthorizationProvider authorizationProvider = getProvider(config);
+ if (authorizationProvider == null) {
+ return null;
+ }
+ return authorizationProvider.newContexts(context, command);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static V computeIfAbsent(String key, Function function) {
+ Object result = null;
+ if (INSTANCE_MAP.containsKey(key)) {
+ result = INSTANCE_MAP.get(key);
+ }
+ if (result == null) {
+ synchronized (INSTANCE_MAP) {
+ if (INSTANCE_MAP.containsKey(key)) {
+ result = INSTANCE_MAP.get(key);
+ }
+ if (result == null) {
+ result = function.apply(key);
+ if (result != null) {
+ INSTANCE_MAP.put(key, result);
+ }
+ }
+ }
+ }
+ return result != null ? (V) result : null;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManager.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManager.java
new file mode 100644
index 00000000000..ce96230606b
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManager.java
@@ -0,0 +1,41 @@
+/*
+ * 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.rocketmq.auth.authorization.manager;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+
+public interface AuthorizationMetadataManager {
+
+ void shutdown();
+
+ CompletableFuture createAcl(Acl acl);
+
+ CompletableFuture updateAcl(Acl acl);
+
+ CompletableFuture deleteAcl(Subject subject);
+
+ CompletableFuture deleteAcl(Subject subject, PolicyType policyType, Resource resource);
+
+ CompletableFuture getAcl(Subject subject);
+
+ CompletableFuture> listAcl(String subjectFilter, String resourceFilter);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManagerImpl.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManagerImpl.java
new file mode 100644
index 00000000000..52b62f72b3c
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/manager/AuthorizationMetadataManagerImpl.java
@@ -0,0 +1,284 @@
+/*
+ * 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.rocketmq.auth.authorization.manager;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.enums.SubjectType;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.authorization.model.Environment;
+import org.apache.rocketmq.auth.authorization.model.Policy;
+import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.common.utils.ExceptionUtils;
+import org.apache.rocketmq.common.utils.IPAddressUtils;
+
+public class AuthorizationMetadataManagerImpl implements AuthorizationMetadataManager {
+
+ private final AuthorizationMetadataProvider authorizationMetadataProvider;
+
+ private final AuthenticationMetadataProvider authenticationMetadataProvider;
+
+ public AuthorizationMetadataManagerImpl(AuthConfig authConfig) {
+ this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig);
+ this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig);
+ }
+
+ @Override
+ public void shutdown() {
+ if (this.authenticationMetadataProvider != null) {
+ this.authenticationMetadataProvider.shutdown();
+ }
+ if (this.authorizationMetadataProvider != null) {
+ this.authorizationMetadataProvider.shutdown();
+ }
+ }
+
+ @Override
+ public CompletableFuture createAcl(Acl acl) {
+ try {
+ validate(acl);
+
+ initAcl(acl);
+
+ CompletableFuture extends Subject> subjectFuture;
+ if (acl.getSubject().isSubject(SubjectType.USER)) {
+ User user = (User) acl.getSubject();
+ subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
+ } else {
+ subjectFuture = CompletableFuture.completedFuture(acl.getSubject());
+ }
+
+ return subjectFuture.thenCompose(subject -> {
+ if (subject == null) {
+ throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey());
+ }
+ return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject());
+ }).thenCompose(oldAcl -> {
+ if (oldAcl == null) {
+ return this.getAuthorizationMetadataProvider().createAcl(acl);
+ }
+ oldAcl.updatePolicy(acl.getPolicies());
+ return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
+ });
+
+ } catch (Exception e) {
+ return this.handleException(e);
+ }
+ }
+
+ @Override
+ public CompletableFuture updateAcl(Acl acl) {
+ try {
+ validate(acl);
+
+ initAcl(acl);
+
+ CompletableFuture extends Subject> subjectFuture;
+ if (acl.getSubject().isSubject(SubjectType.USER)) {
+ User user = (User) acl.getSubject();
+ subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
+ } else {
+ subjectFuture = CompletableFuture.completedFuture(acl.getSubject());
+ }
+
+ return subjectFuture.thenCompose(subject -> {
+ if (subject == null) {
+ throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey());
+ }
+ return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject());
+ }).thenCompose(oldAcl -> {
+ if (oldAcl == null) {
+ return this.getAuthorizationMetadataProvider().createAcl(acl);
+ }
+ oldAcl.updatePolicy(acl.getPolicies());
+ return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
+ });
+
+ } catch (Exception e) {
+ return this.handleException(e);
+ }
+ }
+
+ @Override
+ public CompletableFuture deleteAcl(Subject subject) {
+ return this.deleteAcl(subject, null, null);
+ }
+
+ @Override
+ public CompletableFuture deleteAcl(Subject subject, PolicyType policyType, Resource resource) {
+ try {
+ if (subject == null) {
+ throw new AuthorizationException("The subject is null.");
+ }
+ if (policyType == null) {
+ policyType = PolicyType.CUSTOM;
+ }
+
+ CompletableFuture extends Subject> subjectFuture;
+ if (subject.isSubject(SubjectType.USER)) {
+ User user = (User) subject;
+ subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
+ } else {
+ subjectFuture = CompletableFuture.completedFuture(subject);
+ }
+ CompletableFuture aclFuture = this.getAuthorizationMetadataProvider().getAcl(subject);
+
+ PolicyType finalPolicyType = policyType;
+ return subjectFuture.thenCombine(aclFuture, (sub, oldAcl) -> {
+ if (sub == null) {
+ throw new AuthorizationException("The subject is not exist.");
+ }
+ if (oldAcl == null) {
+ throw new AuthorizationException("The acl is not exist.");
+ }
+ return oldAcl;
+ }).thenCompose(oldAcl -> {
+ if (resource != null) {
+ oldAcl.deletePolicy(finalPolicyType, resource);
+ }
+ if (resource == null || CollectionUtils.isEmpty(oldAcl.getPolicies())) {
+ return this.getAuthorizationMetadataProvider().deleteAcl(subject);
+ }
+ return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
+ });
+
+ } catch (Exception e) {
+ return this.handleException(e);
+ }
+ }
+
+ @Override
+ public CompletableFuture getAcl(Subject subject) {
+ CompletableFuture extends Subject> subjectFuture;
+ if (subject.isSubject(SubjectType.USER)) {
+ User user = (User) subject;
+ subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
+ } else {
+ subjectFuture = CompletableFuture.completedFuture(subject);
+ }
+ return subjectFuture.thenCompose(sub -> {
+ if (sub == null) {
+ throw new AuthorizationException("The subject is not exist.");
+ }
+ return this.getAuthorizationMetadataProvider().getAcl(subject);
+ });
+ }
+
+ @Override
+ public CompletableFuture> listAcl(String subjectFilter, String resourceFilter) {
+ return this.getAuthorizationMetadataProvider().listAcl(subjectFilter, resourceFilter);
+ }
+
+ private static void initAcl(Acl acl) {
+ acl.getPolicies().forEach(policy -> {
+ if (policy.getPolicyType() == null) {
+ policy.setPolicyType(PolicyType.CUSTOM);
+ }
+ });
+ }
+
+ private void validate(Acl acl) {
+ Subject subject = acl.getSubject();
+ if (subject.getSubjectType() == null) {
+ throw new AuthorizationException("The subject type is null.");
+ }
+ List policies = acl.getPolicies();
+ if (CollectionUtils.isEmpty(policies)) {
+ throw new AuthorizationException("The policies is empty.");
+ }
+ for (Policy policy : policies) {
+ this.validate(policy);
+ }
+ }
+
+ private void validate(Policy policy) {
+ List policyEntries = policy.getEntries();
+ if (CollectionUtils.isEmpty(policyEntries)) {
+ throw new AuthorizationException("The policy entries is empty.");
+ }
+ for (PolicyEntry policyEntry : policyEntries) {
+ this.validate(policyEntry);
+ }
+ }
+
+ private void validate(PolicyEntry entry) {
+ Resource resource = entry.getResource();
+ if (resource == null) {
+ throw new AuthorizationException("The resource is null.");
+ }
+ if (resource.getResourceType() == null) {
+ throw new AuthorizationException("The resource type is null.");
+ }
+ if (resource.getResourcePattern() == null) {
+ throw new AuthorizationException("The resource pattern is null.");
+ }
+ if (CollectionUtils.isEmpty(entry.getActions())) {
+ throw new AuthorizationException("The actions is empty.");
+ }
+ if (entry.getActions().contains(Action.ANY)) {
+ throw new AuthorizationException("The actions can not be Any.");
+ }
+ Environment environment = entry.getEnvironment();
+ if (environment != null && CollectionUtils.isNotEmpty(environment.getSourceIps())) {
+ for (String sourceIp : environment.getSourceIps()) {
+ if (StringUtils.isBlank(sourceIp)) {
+ throw new AuthorizationException("The source ip is empty.");
+ }
+ if (!IPAddressUtils.isValidIPOrCidr(sourceIp)) {
+ throw new AuthorizationException("The source ip is invalid.");
+ }
+ }
+ }
+ if (entry.getDecision() == null) {
+ throw new AuthorizationException("The decision is null or illegal.");
+ }
+ }
+
+ private CompletableFuture handleException(Exception e) {
+ CompletableFuture result = new CompletableFuture<>();
+ Throwable throwable = ExceptionUtils.getRealException(e);
+ result.completeExceptionally(throwable);
+ return result;
+ }
+
+ private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
+ if (authorizationMetadataProvider == null) {
+ throw new IllegalStateException("The authenticationMetadataProvider is not configured.");
+ }
+ return authenticationMetadataProvider;
+ }
+
+ private AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
+ if (authenticationMetadataProvider == null) {
+ throw new IllegalStateException("The authenticationMetadataProvider is not configured.");
+ }
+ return authorizationMetadataProvider;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Acl.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Acl.java
new file mode 100644
index 00000000000..75d34b08145
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Acl.java
@@ -0,0 +1,110 @@
+/*
+ * 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.rocketmq.auth.authorization.model;
+
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+import org.apache.rocketmq.common.action.Action;
+
+public class Acl {
+
+ private Subject subject;
+
+ private List policies;
+
+ public static Acl of(Subject subject, Policy policy) {
+ return of(subject, Lists.newArrayList(policy));
+ }
+
+ public static Acl of(Subject subject, List policies) {
+ Acl acl = new Acl();
+ acl.setSubject(subject);
+ acl.setPolicies(policies);
+ return acl;
+ }
+
+ public static Acl of(Subject subject, List resources, List actions, Environment environment,
+ Decision decision) {
+ Acl acl = new Acl();
+ acl.setSubject(subject);
+ Policy policy = Policy.of(resources, actions, environment, decision);
+ acl.setPolicies(Lists.newArrayList(policy));
+ return acl;
+ }
+
+ public void updatePolicy(Policy policy) {
+ this.updatePolicy(Lists.newArrayList(policy));
+ }
+
+ public void updatePolicy(List policies) {
+ if (this.policies == null) {
+ this.policies = new ArrayList<>();
+ }
+ policies.forEach(newPolicy -> {
+ Policy oldPolicy = this.getPolicy(newPolicy.getPolicyType());
+ if (oldPolicy == null) {
+ this.policies.add(newPolicy);
+ } else {
+ oldPolicy.updateEntry(newPolicy.getEntries());
+ }
+ });
+ }
+
+ public void deletePolicy(PolicyType policyType, Resource resource) {
+ Policy policy = getPolicy(policyType);
+ if (policy == null) {
+ return;
+ }
+ policy.deleteEntry(resource);
+ if (CollectionUtils.isEmpty(policy.getEntries())) {
+ this.policies.remove(policy);
+ }
+ }
+
+ public Policy getPolicy(PolicyType policyType) {
+ if (CollectionUtils.isEmpty(this.policies)) {
+ return null;
+ }
+ for (Policy policy : this.policies) {
+ if (policy.getPolicyType() == policyType) {
+ return policy;
+ }
+ }
+ return null;
+ }
+
+ public Subject getSubject() {
+ return subject;
+ }
+
+ public void setSubject(Subject subject) {
+ this.subject = subject;
+ }
+
+ public List getPolicies() {
+ return policies;
+ }
+
+ public void setPolicies(List policies) {
+ this.policies = policies;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Environment.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Environment.java
new file mode 100644
index 00000000000..f514e20750f
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Environment.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.auth.authorization.model;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.utils.IPAddressUtils;
+
+public class Environment {
+
+ private List sourceIps;
+
+ public static Environment of(String sourceIp) {
+ if (StringUtils.isEmpty(sourceIp)) {
+ return null;
+ }
+ return of(Collections.singletonList(sourceIp));
+ }
+
+ public static Environment of(List sourceIps) {
+ if (CollectionUtils.isEmpty(sourceIps)) {
+ return null;
+ }
+ Environment environment = new Environment();
+ environment.setSourceIps(sourceIps);
+ return environment;
+ }
+
+ public boolean isMatch(Environment environment) {
+ if (CollectionUtils.isEmpty(this.sourceIps)) {
+ return true;
+ }
+ if (CollectionUtils.isEmpty(environment.getSourceIps())) {
+ return false;
+ }
+ String targetIp = environment.getSourceIps().get(0);
+ for (String sourceIp : this.sourceIps) {
+ if (IPAddressUtils.isIPInRange(targetIp, sourceIp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List getSourceIps() {
+ return sourceIps;
+ }
+
+ public void setSourceIps(List sourceIps) {
+ this.sourceIps = sourceIps;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Policy.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Policy.java
new file mode 100644
index 00000000000..fb4979d46ef
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Policy.java
@@ -0,0 +1,106 @@
+/*
+ * 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.rocketmq.auth.authorization.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+
+public class Policy {
+
+ private PolicyType policyType;
+
+ private List entries;
+
+ public static Policy of(List resources, List actions, Environment environment,
+ Decision decision) {
+ return of(PolicyType.CUSTOM, resources, actions, environment, decision);
+ }
+
+ public static Policy of(PolicyType policyType, List resources, List actions,
+ Environment environment,
+ Decision decision) {
+ Policy policy = new Policy();
+ policy.setPolicyType(policyType);
+ List entries = resources.stream()
+ .map(resource -> PolicyEntry.of(resource, actions, environment, decision))
+ .collect(Collectors.toList());
+ policy.setEntries(entries);
+ return policy;
+ }
+
+ public static Policy of(PolicyType type, List entries) {
+ Policy policy = new Policy();
+ policy.setPolicyType(type);
+ policy.setEntries(entries);
+ return policy;
+ }
+
+ public void updateEntry(List newEntries) {
+ if (this.entries == null) {
+ this.entries = new ArrayList<>();
+ }
+ newEntries.forEach(newEntry -> {
+ PolicyEntry entry = getEntry(newEntry.getResource());
+ if (entry == null) {
+ this.entries.add(newEntry);
+ } else {
+ entry.updateEntry(newEntry.getActions(), newEntry.getEnvironment(), newEntry.getDecision());
+ }
+ });
+ }
+
+ public void deleteEntry(Resource resources) {
+ PolicyEntry entry = getEntry(resources);
+ if (entry != null) {
+ this.entries.remove(entry);
+ }
+ }
+
+ private PolicyEntry getEntry(Resource resource) {
+ if (CollectionUtils.isEmpty(this.entries)) {
+ return null;
+ }
+ for (PolicyEntry entry : this.entries) {
+ if (Objects.equals(entry.getResource(), resource)) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ public PolicyType getPolicyType() {
+ return policyType;
+ }
+
+ public void setPolicyType(PolicyType policyType) {
+ this.policyType = policyType;
+ }
+
+ public List getEntries() {
+ return entries;
+ }
+
+ public void setEntries(List entries) {
+ this.entries = entries;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/PolicyEntry.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/PolicyEntry.java
new file mode 100644
index 00000000000..f1199842c68
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/PolicyEntry.java
@@ -0,0 +1,120 @@
+/*
+ * 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.rocketmq.auth.authorization.model;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+
+public class PolicyEntry {
+
+ private Resource resource;
+
+ private List actions;
+
+ private Environment environment;
+
+ private Decision decision;
+
+ public static PolicyEntry of(Resource resource, List actions, Environment environment, Decision decision) {
+ PolicyEntry policyEntry = new PolicyEntry();
+ policyEntry.setResource(resource);
+ policyEntry.setActions(actions);
+ policyEntry.setEnvironment(environment);
+ policyEntry.setDecision(decision);
+ return policyEntry;
+ }
+
+ public void updateEntry(List actions, Environment environment,
+ Decision decision) {
+ this.setActions(actions);
+ this.setEnvironment(environment);
+ this.setDecision(decision);
+ }
+
+ public boolean isMatchResource(Resource resource) {
+ return this.resource.isMatch(resource);
+ }
+
+ public boolean isMatchAction(List actions) {
+ if (CollectionUtils.isEmpty(this.actions)) {
+ return false;
+ }
+ if (actions.contains(Action.ANY)) {
+ return true;
+ }
+ return actions.stream()
+ .anyMatch(action -> this.actions.contains(action)
+ || this.actions.contains(Action.ALL));
+ }
+
+ public boolean isMatchEnvironment(Environment environment) {
+ if (this.environment == null) {
+ return true;
+ }
+ return this.environment.isMatch(environment);
+ }
+
+ public String toResourceStr() {
+ if (resource == null) {
+ return null;
+ }
+ return resource.getResourceKey();
+ }
+
+ public List toActionsStr() {
+ if (CollectionUtils.isEmpty(actions)) {
+ return null;
+ }
+ return actions.stream().map(Action::getName)
+ .collect(Collectors.toList());
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ public List getActions() {
+ return actions;
+ }
+
+ public void setActions(List actions) {
+ this.actions = actions;
+ }
+
+ public Environment getEnvironment() {
+ return environment;
+ }
+
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ public Decision getDecision() {
+ return decision;
+ }
+
+ public void setDecision(Decision decision) {
+ this.decision = decision;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/RequestContext.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/RequestContext.java
new file mode 100644
index 00000000000..88b814de5f1
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/RequestContext.java
@@ -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.
+ */
+package org.apache.rocketmq.auth.authorization.model;
+
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.common.action.Action;
+
+public class RequestContext {
+
+ private Subject subject;
+
+ private Resource resource;
+
+ private Action action;
+
+ private String sourceIp;
+
+ public Subject getSubject() {
+ return subject;
+ }
+
+ public void setSubject(Subject subject) {
+ this.subject = subject;
+ }
+
+ public Resource getResource() {
+ return resource;
+ }
+
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ public void setAction(Action action) {
+ this.action = action;
+ }
+
+ public String getSourceIp() {
+ return sourceIp;
+ }
+
+ public void setSourceIp(String sourceIp) {
+ this.sourceIp = sourceIp;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Resource.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Resource.java
new file mode 100644
index 00000000000..67a37578dec
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/model/Resource.java
@@ -0,0 +1,168 @@
+/*
+ * 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.rocketmq.auth.authorization.model;
+
+import com.alibaba.fastjson2.annotation.JSONField;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.resource.ResourceType;
+import org.apache.rocketmq.common.resource.ResourcePattern;
+import org.apache.rocketmq.common.constant.CommonConstants;
+import org.apache.rocketmq.remoting.protocol.NamespaceUtil;
+
+public class Resource {
+
+ private ResourceType resourceType;
+
+ private String resourceName;
+
+ private ResourcePattern resourcePattern;
+
+ public static Resource ofCluster(String clusterName) {
+ return of(ResourceType.CLUSTER, clusterName, ResourcePattern.LITERAL);
+ }
+
+ public static Resource ofTopic(String topicName) {
+ return of(ResourceType.TOPIC, topicName, ResourcePattern.LITERAL);
+ }
+
+ public static Resource ofGroup(String groupName) {
+ if (NamespaceUtil.isRetryTopic(groupName)) {
+ groupName = NamespaceUtil.withOutRetryAndDLQ(groupName);
+ }
+ return of(ResourceType.GROUP, groupName, ResourcePattern.LITERAL);
+ }
+
+ public static Resource of(ResourceType resourceType, String resourceName, ResourcePattern resourcePattern) {
+ Resource resource = new Resource();
+ resource.resourceType = resourceType;
+ resource.resourceName = resourceName;
+ resource.resourcePattern = resourcePattern;
+ return resource;
+ }
+
+ public static List of(List resourceKeys) {
+ if (CollectionUtils.isEmpty(resourceKeys)) {
+ return null;
+ }
+ return resourceKeys.stream().map(Resource::of).collect(Collectors.toList());
+ }
+
+ public static Resource of(String resourceKey) {
+ if (StringUtils.isBlank(resourceKey)) {
+ return null;
+ }
+ if (StringUtils.equals(resourceKey, CommonConstants.ASTERISK)) {
+ return of(ResourceType.ANY, null, ResourcePattern.ANY);
+ }
+ String type = StringUtils.substringBefore(resourceKey, CommonConstants.COLON);
+ ResourceType resourceType = ResourceType.getByName(type);
+ if (resourceType == null) {
+ return null;
+ }
+ String resourceName = StringUtils.substringAfter(resourceKey, CommonConstants.COLON);
+ ResourcePattern resourcePattern = ResourcePattern.LITERAL;
+ if (StringUtils.equals(resourceName, CommonConstants.ASTERISK)) {
+ resourceName = null;
+ resourcePattern = ResourcePattern.ANY;
+ } else if (StringUtils.endsWith(resourceName, CommonConstants.ASTERISK)) {
+ resourceName = StringUtils.substringBefore(resourceName, CommonConstants.ASTERISK);
+ resourcePattern = ResourcePattern.PREFIXED;
+ }
+ return of(resourceType, resourceName, resourcePattern);
+ }
+
+ @JSONField(serialize = false)
+ public String getResourceKey() {
+ if (resourceType == ResourceType.ANY) {
+ return CommonConstants.ASTERISK;
+ }
+ switch (resourcePattern) {
+ case ANY:
+ return resourceType.getName() + CommonConstants.COLON + CommonConstants.ASTERISK;
+ case LITERAL:
+ return resourceType.getName() + CommonConstants.COLON + resourceName;
+ case PREFIXED:
+ return resourceType.getName() + CommonConstants.COLON + resourceName + CommonConstants.ASTERISK;
+ default:
+ return null;
+ }
+ }
+
+ public boolean isMatch(Resource resource) {
+ if (this.resourceType == ResourceType.ANY) {
+ return true;
+ }
+ if (this.resourceType != resource.resourceType) {
+ return false;
+ }
+ switch (resourcePattern) {
+ case ANY:
+ return true;
+ case LITERAL:
+ return StringUtils.equals(resource.resourceName, this.resourceName);
+ case PREFIXED:
+ return StringUtils.startsWith(resource.resourceName, this.resourceName);
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Resource resource = (Resource) o;
+ return resourceType == resource.resourceType
+ && Objects.equals(resourceName, resource.resourceName)
+ && resourcePattern == resource.resourcePattern;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(resourceType, resourceName, resourcePattern);
+ }
+
+ public ResourceType getResourceType() {
+ return resourceType;
+ }
+
+ public void setResourceType(ResourceType resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ public String getResourceName() {
+ return resourceName;
+ }
+
+ public void setResourceName(String resourceName) {
+ this.resourceName = resourceName;
+ }
+
+ public ResourcePattern getResourcePattern() {
+ return resourcePattern;
+ }
+
+ public void setResourcePattern(ResourcePattern resourcePattern) {
+ this.resourcePattern = resourcePattern;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationMetadataProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationMetadataProvider.java
new file mode 100644
index 00000000000..02bae743d54
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationMetadataProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.rocketmq.auth.authorization.provider;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public interface AuthorizationMetadataProvider {
+
+ void initialize(AuthConfig authConfig, Supplier> metadataService);
+
+ void shutdown();
+
+ CompletableFuture createAcl(Acl acl);
+
+ CompletableFuture deleteAcl(Subject subject);
+
+ CompletableFuture updateAcl(Acl acl);
+
+ CompletableFuture getAcl(Subject subject);
+
+ CompletableFuture> listAcl(String subjectFilter, String resourceFilter);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationProvider.java
new file mode 100644
index 00000000000..98bd39c27b0
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/AuthorizationProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.auth.authorization.provider;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public interface AuthorizationProvider {
+
+ void initialize(AuthConfig config);
+
+ void initialize(AuthConfig config, Supplier> metadataService);
+
+ CompletableFuture authorize(AuthorizationContext context);
+
+ List newContexts(Metadata metadata, GeneratedMessageV3 message);
+
+ List newContexts(ChannelHandlerContext context, RemotingCommand command);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/DefaultAuthorizationProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/DefaultAuthorizationProvider.java
new file mode 100644
index 00000000000..75111030328
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/DefaultAuthorizationProvider.java
@@ -0,0 +1,102 @@
+/*
+ * 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.rocketmq.auth.authorization.provider;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Metadata;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import org.apache.rocketmq.auth.authorization.builder.AuthorizationContextBuilder;
+import org.apache.rocketmq.auth.authorization.builder.DefaultAuthorizationContextBuilder;
+import org.apache.rocketmq.auth.authorization.chain.AclAuthorizationHandler;
+import org.apache.rocketmq.auth.authorization.chain.UserAuthorizationHandler;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.common.chain.HandlerChain;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultAuthorizationProvider implements AuthorizationProvider {
+
+ protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME);
+ protected AuthConfig authConfig;
+ protected Supplier> metadataService;
+ protected AuthorizationContextBuilder authorizationContextBuilder;
+
+ @Override
+ public void initialize(AuthConfig config) {
+ this.initialize(config, null);
+ }
+
+ @Override
+ public void initialize(AuthConfig config, Supplier> metadataService) {
+ this.authConfig = config;
+ this.metadataService = metadataService;
+ this.authorizationContextBuilder = new DefaultAuthorizationContextBuilder(config);
+ }
+
+ @Override
+ public CompletableFuture authorize(DefaultAuthorizationContext context) {
+ return this.newHandlerChain().handle(context)
+ .whenComplete((nil, ex) -> doAuditLog(context, ex));
+ }
+
+ @Override
+ public List newContexts(Metadata metadata, GeneratedMessageV3 message) {
+ return this.authorizationContextBuilder.build(metadata, message);
+ }
+
+ @Override
+ public List newContexts(ChannelHandlerContext context, RemotingCommand command) {
+ return this.authorizationContextBuilder.build(context, command);
+ }
+
+ protected HandlerChain> newHandlerChain() {
+ return HandlerChain.>create()
+ .addNext(new UserAuthorizationHandler(authConfig, metadataService))
+ .addNext(new AclAuthorizationHandler(authConfig, metadataService));
+ }
+
+ protected void doAuditLog(DefaultAuthorizationContext context, Throwable ex) {
+ if (context.getSubject() == null) {
+ return;
+ }
+ Decision decision = Decision.ALLOW;
+ if (ex != null) {
+ decision = Decision.DENY;
+ }
+ String subject = context.getSubject().getSubjectKey();
+ String actions = context.getActions().stream().map(Action::getName)
+ .collect(Collectors.joining(","));
+ String sourceIp = context.getSourceIp();
+ String resource = context.getResource().getResourceKey();
+ String request = context.getRpcCode();
+ String format = "[AUTHORIZATION] Subject = {} is {} Action = {} from sourceIp = {} on resource = {} for request = {}.";
+ if (decision == Decision.ALLOW) {
+ log.debug(format, subject, decision.getName(), actions, sourceIp, resource, request);
+ } else {
+ log.info(format, subject, decision.getName(), actions, sourceIp, resource, request);
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/LocalAuthorizationMetadataProvider.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/LocalAuthorizationMetadataProvider.java
new file mode 100644
index 00000000000..bc631781084
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/provider/LocalAuthorizationMetadataProvider.java
@@ -0,0 +1,199 @@
+/*
+ * 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.rocketmq.auth.authorization.provider;
+
+import com.alibaba.fastjson2.JSON;
+import com.github.benmanes.caffeine.cache.CacheLoader;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.authorization.model.Policy;
+import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
+import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+import org.rocksdb.RocksIterator;
+
+public class LocalAuthorizationMetadataProvider implements AuthorizationMetadataProvider {
+
+ private ConfigRocksDBStorage storage;
+
+ private LoadingCache aclCache;
+
+ @Override
+ public void initialize(AuthConfig authConfig, Supplier> metadataService) {
+ this.storage = new ConfigRocksDBStorage(authConfig.getAuthConfigPath() + File.separator + "acls");
+ if (!this.storage.start()) {
+ throw new RuntimeException("Failed to load rocksdb for auth_acl, please check whether it is occupied.");
+ }
+ ThreadPoolExecutor cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor(
+ 1,
+ 1,
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ "AclCacheRefresh",
+ 100000
+ );
+
+ this.aclCache = Caffeine.newBuilder()
+ .maximumSize(authConfig.getAclCacheMaxNum())
+ .expireAfterAccess(authConfig.getAclCacheExpiredSecond(), TimeUnit.SECONDS)
+ .refreshAfterWrite(authConfig.getAclCacheRefreshSecond(), TimeUnit.SECONDS)
+ .executor(cacheRefreshExecutor)
+ .build(new AclCacheLoader(this.storage));
+ }
+
+ @Override
+ public CompletableFuture createAcl(Acl acl) {
+ try {
+ Subject subject = acl.getSubject();
+ byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
+ byte[] valueBytes = JSON.toJSONBytes(acl);
+ this.storage.put(keyBytes, keyBytes.length, valueBytes);
+ this.storage.flushWAL();
+ this.aclCache.invalidate(subject.getSubjectKey());
+ } catch (Exception e) {
+ throw new AuthorizationException("create Acl to RocksDB failed.", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture deleteAcl(Subject subject) {
+ try {
+ byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
+ this.storage.delete(keyBytes);
+ this.storage.flushWAL();
+ this.aclCache.invalidate(subject.getSubjectKey());
+ } catch (Exception e) {
+ throw new AuthorizationException("delete Acl from RocksDB failed.", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture updateAcl(Acl acl) {
+ try {
+ Subject subject = acl.getSubject();
+ byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
+ byte[] valueBytes = JSON.toJSONBytes(acl);
+ this.storage.put(keyBytes, keyBytes.length, valueBytes);
+ this.storage.flushWAL();
+ this.aclCache.invalidate(subject.getSubjectKey());
+ } catch (Exception e) {
+ throw new AuthorizationException("update Acl to RocksDB failed.", e);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ public CompletableFuture getAcl(Subject subject) {
+ Acl acl = aclCache.get(subject.getSubjectKey());
+ if (acl == AclCacheLoader.EMPTY_ACL) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.completedFuture(acl);
+ }
+
+ @Override
+ public CompletableFuture> listAcl(String subjectFilter, String resourceFilter) {
+ List result = new ArrayList<>();
+ try (RocksIterator iterator = this.storage.iterator()) {
+ iterator.seekToFirst();
+ while (iterator.isValid()) {
+ String subjectKey = new String(iterator.key(), StandardCharsets.UTF_8);
+ if (StringUtils.isNotBlank(subjectFilter) && !subjectKey.contains(subjectFilter)) {
+ iterator.next();
+ continue;
+ }
+ Subject subject = Subject.of(subjectKey);
+ Acl acl = JSON.parseObject(new String(iterator.value(), StandardCharsets.UTF_8), Acl.class);
+ List policies = acl.getPolicies();
+ if (!CollectionUtils.isNotEmpty(policies)) {
+ iterator.next();
+ continue;
+ }
+ Iterator policyIterator = policies.iterator();
+ while (policyIterator.hasNext()) {
+ Policy policy = policyIterator.next();
+ List entries = policy.getEntries();
+ if (CollectionUtils.isEmpty(entries)) {
+ continue;
+ }
+ if (StringUtils.isNotBlank(resourceFilter) && !subjectKey.contains(resourceFilter)) {
+ entries.removeIf(entry -> !entry.toResourceStr().contains(resourceFilter));
+ }
+ if (CollectionUtils.isEmpty(entries)) {
+ policyIterator.remove();
+ }
+ }
+ if (CollectionUtils.isNotEmpty(policies)) {
+ result.add(Acl.of(subject, policies));
+ }
+ iterator.next();
+ }
+ }
+ return CompletableFuture.completedFuture(result);
+ }
+
+ @Override
+ public void shutdown() {
+ if (this.storage != null) {
+ this.storage.shutdown();
+ }
+ }
+
+ private static class AclCacheLoader implements CacheLoader {
+ private final ConfigRocksDBStorage storage;
+ public static final Acl EMPTY_ACL = new Acl();
+
+ public AclCacheLoader(ConfigRocksDBStorage storage) {
+ this.storage = storage;
+ }
+
+ @Override
+ public Acl load(String subjectKey) {
+ try {
+ byte[] keyBytes = subjectKey.getBytes(StandardCharsets.UTF_8);
+ Subject subject = Subject.of(subjectKey);
+
+ byte[] valueBytes = this.storage.get(keyBytes);
+ if (ArrayUtils.isEmpty(valueBytes)) {
+ return EMPTY_ACL;
+ }
+ Acl acl = JSON.parseObject(valueBytes, Acl.class);
+ return Acl.of(subject, acl.getPolicies());
+ } catch (Exception e) {
+ throw new AuthorizationException("get Acl from RocksDB failed.", e);
+ }
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AbstractAuthorizationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AbstractAuthorizationStrategy.java
new file mode 100644
index 00000000000..849c3082d31
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AbstractAuthorizationStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * 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.rocketmq.auth.authorization.strategy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.utils.ExceptionUtils;
+
+public abstract class AbstractAuthorizationStrategy implements AuthorizationStrategy {
+
+ protected final AuthConfig authConfig;
+ protected final List authorizationWhitelist = new ArrayList<>();
+ protected final AuthorizationProvider authorizationProvider;
+
+ public AbstractAuthorizationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ this.authConfig = authConfig;
+ this.authorizationProvider = AuthorizationFactory.getProvider(authConfig);
+ if (this.authorizationProvider != null) {
+ this.authorizationProvider.initialize(authConfig, metadataService);
+ }
+ if (StringUtils.isNotBlank(authConfig.getAuthorizationWhitelist())) {
+ String[] whitelist = StringUtils.split(authConfig.getAuthorizationWhitelist(), ",");
+ for (String rpcCode : whitelist) {
+ this.authorizationWhitelist.add(StringUtils.trim(rpcCode));
+ }
+ }
+ }
+
+ public void doEvaluate(AuthorizationContext context) {
+ if (context == null) {
+ return;
+ }
+ if (!this.authConfig.isAuthorizationEnabled()) {
+ return;
+ }
+ if (this.authorizationProvider == null) {
+ return;
+ }
+ if (this.authorizationWhitelist.contains(context.getRpcCode())) {
+ return;
+ }
+ try {
+ this.authorizationProvider.authorize(context).join();
+ } catch (AuthorizationException ex) {
+ throw ex;
+ } catch (Throwable ex) {
+ Throwable exception = ExceptionUtils.getRealException(ex);
+ if (exception instanceof AuthorizationException) {
+ throw (AuthorizationException) exception;
+ }
+ throw new AuthorizationException("Authorization failed. Please verify your access rights and try again.", exception);
+ }
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AuthorizationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AuthorizationStrategy.java
new file mode 100644
index 00000000000..d65ca82e10a
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/AuthorizationStrategy.java
@@ -0,0 +1,24 @@
+/*
+ * 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.rocketmq.auth.authorization.strategy;
+
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+
+public interface AuthorizationStrategy {
+
+ void evaluate(AuthorizationContext context);
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatefulAuthorizationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatefulAuthorizationStrategy.java
new file mode 100644
index 00000000000..d5b252ee721
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatefulAuthorizationStrategy.java
@@ -0,0 +1,73 @@
+/*
+ * 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.rocketmq.auth.authorization.strategy;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
+import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.constant.CommonConstants;
+
+public class StatefulAuthorizationStrategy extends AbstractAuthorizationStrategy {
+
+ protected Cache> authCache;
+
+ public StatefulAuthorizationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ super(authConfig, metadataService);
+ this.authCache = Caffeine.newBuilder()
+ .expireAfterWrite(authConfig.getStatefulAuthorizationCacheExpiredSecond(), TimeUnit.SECONDS)
+ .maximumSize(authConfig.getStatefulAuthorizationCacheMaxNum())
+ .build();
+ }
+
+ @Override
+ public void evaluate(AuthorizationContext context) {
+ if (StringUtils.isBlank(context.getChannelId())) {
+ this.doEvaluate(context);
+ return;
+ }
+ Pair result = this.authCache.get(buildKey(context), key -> {
+ try {
+ this.doEvaluate(context);
+ return Pair.of(true, null);
+ } catch (AuthorizationException ex) {
+ return Pair.of(false, ex);
+ }
+ });
+ if (result != null && result.getObject1() == Boolean.FALSE) {
+ throw result.getObject2();
+ }
+ }
+
+ private String buildKey(AuthorizationContext context) {
+ if (context instanceof DefaultAuthorizationContext) {
+ DefaultAuthorizationContext ctx = (DefaultAuthorizationContext) context;
+ return ctx.getChannelId()
+ + (ctx.getSubject() != null ? CommonConstants.POUND + ctx.getSubjectKey() : "")
+ + CommonConstants.POUND + ctx.getResourceKey()
+ + CommonConstants.POUND + StringUtils.join(ctx.getActions(), CommonConstants.COMMA)
+ + CommonConstants.POUND + ctx.getSourceIp();
+ }
+ throw new AuthorizationException("The request of {} is not support.", context.getClass().getSimpleName());
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatelessAuthorizationStrategy.java b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatelessAuthorizationStrategy.java
new file mode 100644
index 00000000000..e5d5e53f3ee
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/authorization/strategy/StatelessAuthorizationStrategy.java
@@ -0,0 +1,33 @@
+/*
+ * 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.rocketmq.auth.authorization.strategy;
+
+import java.util.function.Supplier;
+import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
+import org.apache.rocketmq.auth.config.AuthConfig;
+
+public class StatelessAuthorizationStrategy extends AbstractAuthorizationStrategy {
+
+ public StatelessAuthorizationStrategy(AuthConfig authConfig, Supplier> metadataService) {
+ super(authConfig, metadataService);
+ }
+
+ @Override
+ public void evaluate(AuthorizationContext context) {
+ super.doEvaluate(context);
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java b/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java
new file mode 100644
index 00000000000..ed294c8ecbf
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/config/AuthConfig.java
@@ -0,0 +1,289 @@
+/*
+ * 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.rocketmq.auth.config;
+
+public class AuthConfig implements Cloneable {
+
+ private String configName;
+
+ private String clusterName;
+
+ private String authConfigPath;
+
+ private boolean authenticationEnabled = false;
+
+ private String authenticationProvider;
+
+ private String authenticationMetadataProvider;
+
+ private String authenticationStrategy;
+
+ private String authenticationWhitelist;
+
+ private String initAuthenticationUser;
+
+ private String innerClientAuthenticationCredentials;
+
+ private boolean authorizationEnabled = false;
+
+ private String authorizationProvider;
+
+ private String authorizationMetadataProvider;
+
+ private String authorizationStrategy;
+
+ private String authorizationWhitelist;
+
+ private boolean migrateAuthFromV1Enabled = false;
+
+ private int userCacheMaxNum = 1000;
+
+ private int userCacheExpiredSecond = 600;
+
+ private int userCacheRefreshSecond = 60;
+
+ private int aclCacheMaxNum = 1000;
+
+ private int aclCacheExpiredSecond = 600;
+
+ private int aclCacheRefreshSecond = 60;
+
+ private int statefulAuthenticationCacheMaxNum = 10000;
+
+ private int statefulAuthenticationCacheExpiredSecond = 60;
+
+ private int statefulAuthorizationCacheMaxNum = 10000;
+
+ private int statefulAuthorizationCacheExpiredSecond = 60;
+
+ @Override
+ public AuthConfig clone() {
+ try {
+ return (AuthConfig) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public String getConfigName() {
+ return configName;
+ }
+
+ public void setConfigName(String configName) {
+ this.configName = configName;
+ }
+
+ public String getClusterName() {
+ return clusterName;
+ }
+
+ public void setClusterName(String clusterName) {
+ this.clusterName = clusterName;
+ }
+
+ public String getAuthConfigPath() {
+ return authConfigPath;
+ }
+
+ public void setAuthConfigPath(String authConfigPath) {
+ this.authConfigPath = authConfigPath;
+ }
+
+ public boolean isAuthenticationEnabled() {
+ return authenticationEnabled;
+ }
+
+ public void setAuthenticationEnabled(boolean authenticationEnabled) {
+ this.authenticationEnabled = authenticationEnabled;
+ }
+
+ public String getAuthenticationProvider() {
+ return authenticationProvider;
+ }
+
+ public void setAuthenticationProvider(String authenticationProvider) {
+ this.authenticationProvider = authenticationProvider;
+ }
+
+ public String getAuthenticationMetadataProvider() {
+ return authenticationMetadataProvider;
+ }
+
+ public void setAuthenticationMetadataProvider(String authenticationMetadataProvider) {
+ this.authenticationMetadataProvider = authenticationMetadataProvider;
+ }
+
+ public String getAuthenticationStrategy() {
+ return authenticationStrategy;
+ }
+
+ public void setAuthenticationStrategy(String authenticationStrategy) {
+ this.authenticationStrategy = authenticationStrategy;
+ }
+
+ public String getAuthenticationWhitelist() {
+ return authenticationWhitelist;
+ }
+
+ public void setAuthenticationWhitelist(String authenticationWhitelist) {
+ this.authenticationWhitelist = authenticationWhitelist;
+ }
+
+ public String getInitAuthenticationUser() {
+ return initAuthenticationUser;
+ }
+
+ public void setInitAuthenticationUser(String initAuthenticationUser) {
+ this.initAuthenticationUser = initAuthenticationUser;
+ }
+
+ public String getInnerClientAuthenticationCredentials() {
+ return innerClientAuthenticationCredentials;
+ }
+
+ public void setInnerClientAuthenticationCredentials(String innerClientAuthenticationCredentials) {
+ this.innerClientAuthenticationCredentials = innerClientAuthenticationCredentials;
+ }
+
+ public boolean isAuthorizationEnabled() {
+ return authorizationEnabled;
+ }
+
+ public void setAuthorizationEnabled(boolean authorizationEnabled) {
+ this.authorizationEnabled = authorizationEnabled;
+ }
+
+ public String getAuthorizationProvider() {
+ return authorizationProvider;
+ }
+
+ public void setAuthorizationProvider(String authorizationProvider) {
+ this.authorizationProvider = authorizationProvider;
+ }
+
+ public String getAuthorizationMetadataProvider() {
+ return authorizationMetadataProvider;
+ }
+
+ public void setAuthorizationMetadataProvider(String authorizationMetadataProvider) {
+ this.authorizationMetadataProvider = authorizationMetadataProvider;
+ }
+
+ public String getAuthorizationStrategy() {
+ return authorizationStrategy;
+ }
+
+ public void setAuthorizationStrategy(String authorizationStrategy) {
+ this.authorizationStrategy = authorizationStrategy;
+ }
+
+ public String getAuthorizationWhitelist() {
+ return authorizationWhitelist;
+ }
+
+ public void setAuthorizationWhitelist(String authorizationWhitelist) {
+ this.authorizationWhitelist = authorizationWhitelist;
+ }
+
+ public boolean isMigrateAuthFromV1Enabled() {
+ return migrateAuthFromV1Enabled;
+ }
+
+ public void setMigrateAuthFromV1Enabled(boolean migrateAuthFromV1Enabled) {
+ this.migrateAuthFromV1Enabled = migrateAuthFromV1Enabled;
+ }
+
+ public int getUserCacheMaxNum() {
+ return userCacheMaxNum;
+ }
+
+ public void setUserCacheMaxNum(int userCacheMaxNum) {
+ this.userCacheMaxNum = userCacheMaxNum;
+ }
+
+ public int getUserCacheExpiredSecond() {
+ return userCacheExpiredSecond;
+ }
+
+ public void setUserCacheExpiredSecond(int userCacheExpiredSecond) {
+ this.userCacheExpiredSecond = userCacheExpiredSecond;
+ }
+
+ public int getUserCacheRefreshSecond() {
+ return userCacheRefreshSecond;
+ }
+
+ public void setUserCacheRefreshSecond(int userCacheRefreshSecond) {
+ this.userCacheRefreshSecond = userCacheRefreshSecond;
+ }
+
+ public int getAclCacheMaxNum() {
+ return aclCacheMaxNum;
+ }
+
+ public void setAclCacheMaxNum(int aclCacheMaxNum) {
+ this.aclCacheMaxNum = aclCacheMaxNum;
+ }
+
+ public int getAclCacheExpiredSecond() {
+ return aclCacheExpiredSecond;
+ }
+
+ public void setAclCacheExpiredSecond(int aclCacheExpiredSecond) {
+ this.aclCacheExpiredSecond = aclCacheExpiredSecond;
+ }
+
+ public int getAclCacheRefreshSecond() {
+ return aclCacheRefreshSecond;
+ }
+
+ public void setAclCacheRefreshSecond(int aclCacheRefreshSecond) {
+ this.aclCacheRefreshSecond = aclCacheRefreshSecond;
+ }
+
+ public int getStatefulAuthenticationCacheMaxNum() {
+ return statefulAuthenticationCacheMaxNum;
+ }
+
+ public void setStatefulAuthenticationCacheMaxNum(int statefulAuthenticationCacheMaxNum) {
+ this.statefulAuthenticationCacheMaxNum = statefulAuthenticationCacheMaxNum;
+ }
+
+ public int getStatefulAuthenticationCacheExpiredSecond() {
+ return statefulAuthenticationCacheExpiredSecond;
+ }
+
+ public void setStatefulAuthenticationCacheExpiredSecond(int statefulAuthenticationCacheExpiredSecond) {
+ this.statefulAuthenticationCacheExpiredSecond = statefulAuthenticationCacheExpiredSecond;
+ }
+
+ public int getStatefulAuthorizationCacheMaxNum() {
+ return statefulAuthorizationCacheMaxNum;
+ }
+
+ public void setStatefulAuthorizationCacheMaxNum(int statefulAuthorizationCacheMaxNum) {
+ this.statefulAuthorizationCacheMaxNum = statefulAuthorizationCacheMaxNum;
+ }
+
+ public int getStatefulAuthorizationCacheExpiredSecond() {
+ return statefulAuthorizationCacheExpiredSecond;
+ }
+
+ public void setStatefulAuthorizationCacheExpiredSecond(int statefulAuthorizationCacheExpiredSecond) {
+ this.statefulAuthorizationCacheExpiredSecond = statefulAuthorizationCacheExpiredSecond;
+ }
+}
diff --git a/auth/src/main/java/org/apache/rocketmq/auth/migration/AuthMigrator.java b/auth/src/main/java/org/apache/rocketmq/auth/migration/AuthMigrator.java
new file mode 100644
index 00000000000..d2ab4dda88f
--- /dev/null
+++ b/auth/src/main/java/org/apache/rocketmq/auth/migration/AuthMigrator.java
@@ -0,0 +1,230 @@
+/*
+ * 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.rocketmq.auth.migration;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.acl.common.AclConstants;
+import org.apache.rocketmq.auth.authentication.enums.UserType;
+import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
+import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
+import org.apache.rocketmq.auth.authentication.model.Subject;
+import org.apache.rocketmq.auth.authentication.model.User;
+import org.apache.rocketmq.auth.authorization.enums.Decision;
+import org.apache.rocketmq.auth.authorization.enums.PolicyType;
+import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
+import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
+import org.apache.rocketmq.auth.authorization.model.Acl;
+import org.apache.rocketmq.auth.authorization.model.Policy;
+import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
+import org.apache.rocketmq.auth.authorization.model.Resource;
+import org.apache.rocketmq.auth.config.AuthConfig;
+import org.apache.rocketmq.auth.migration.v1.PlainPermissionManager;
+import org.apache.rocketmq.auth.migration.v1.AclConfig;
+import org.apache.rocketmq.auth.migration.v1.PlainAccessConfig;
+import org.apache.rocketmq.common.action.Action;
+import org.apache.rocketmq.common.constant.CommonConstants;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.resource.ResourcePattern;
+import org.apache.rocketmq.common.resource.ResourceType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+public class AuthMigrator {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+
+ private final AuthConfig authConfig;
+
+ private final PlainPermissionManager plainPermissionManager;
+
+ private final AuthenticationMetadataManager authenticationMetadataManager;
+
+ private final AuthorizationMetadataManager authorizationMetadataManager;
+
+ public AuthMigrator(AuthConfig authConfig) {
+ this.authConfig = authConfig;
+ this.plainPermissionManager = new PlainPermissionManager();
+ this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
+ this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig);
+ }
+
+ public void migrate() {
+ if (!authConfig.isMigrateAuthFromV1Enabled()) {
+ return;
+ }
+
+ AclConfig aclConfig = this.plainPermissionManager.getAllAclConfig();
+ List accessConfigs = aclConfig.getPlainAccessConfigs();
+ if (CollectionUtils.isEmpty(accessConfigs)) {
+ return;
+ }
+
+ for (PlainAccessConfig accessConfig : accessConfigs) {
+ doMigrate(accessConfig);
+ }
+ }
+
+ private void doMigrate(PlainAccessConfig accessConfig) {
+ this.isUserExisted(accessConfig.getAccessKey()).thenCompose(existed -> {
+ if (existed) {
+ return CompletableFuture.completedFuture(null);
+ }
+ return createUserAndAcl(accessConfig);
+ }).exceptionally(ex -> {
+ LOG.error("[ACL MIGRATE] An error occurred while migrating ACL configurations for AccessKey:{}.", accessConfig.getAccessKey(), ex);
+ return null;
+ }).join();
+ }
+
+ private CompletableFuture createUserAndAcl(PlainAccessConfig accessConfig) {
+ return createUser(accessConfig).thenCompose(nil -> createAcl(accessConfig));
+ }
+
+ private CompletableFuture createUser(PlainAccessConfig accessConfig) {
+ User user = new User();
+ user.setUsername(accessConfig.getAccessKey());
+ user.setPassword(accessConfig.getSecretKey());
+ if (accessConfig.isAdmin()) {
+ user.setUserType(UserType.SUPER);
+ } else {
+ user.setUserType(UserType.NORMAL);
+ }
+ return this.authenticationMetadataManager.createUser(user);
+ }
+
+ private CompletableFuture createAcl(PlainAccessConfig config) {
+ Subject subject = User.of(config.getAccessKey());
+ List