upload source code
Some checks are pending
Build and Run Tests by Bazel / bazel-compile (ubuntu-latest) (push) Waiting to run
CodeQL Analysis / CodeQL-Build (push) Waiting to run
Coverage / calculate-coverage (push) Waiting to run
Run Integration Tests / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (macos-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (windows-latest, JDK-8) (push) Waiting to run
Misspell Check / misspell-check (push) Waiting to run
PUSH-CI / Build dist tar (push) Waiting to run
PUSH-CI / Docker images (ubuntu, 8) (push) Blocked by required conditions
PUSH-CI / List version (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For E2E (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For Benchmarking (push) Blocked by required conditions
PUSH-CI / Test E2E grpc java (push) Blocked by required conditions
PUSH-CI / Test E2E golang (push) Blocked by required conditions
PUSH-CI / Test E2E remoting java (push) Blocked by required conditions
PUSH-CI / Performance benchmark test (push) Blocked by required conditions
PUSH-CI / Clean E2E (push) Blocked by required conditions
PUSH-CI / Clean Benchmarking (push) Blocked by required conditions
Some checks are pending
Build and Run Tests by Bazel / bazel-compile (ubuntu-latest) (push) Waiting to run
CodeQL Analysis / CodeQL-Build (push) Waiting to run
Coverage / calculate-coverage (push) Waiting to run
Run Integration Tests / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (macos-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (windows-latest, JDK-8) (push) Waiting to run
Misspell Check / misspell-check (push) Waiting to run
PUSH-CI / Build dist tar (push) Waiting to run
PUSH-CI / Docker images (ubuntu, 8) (push) Blocked by required conditions
PUSH-CI / List version (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For E2E (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For Benchmarking (push) Blocked by required conditions
PUSH-CI / Test E2E grpc java (push) Blocked by required conditions
PUSH-CI / Test E2E golang (push) Blocked by required conditions
PUSH-CI / Test E2E remoting java (push) Blocked by required conditions
PUSH-CI / Performance benchmark test (push) Blocked by required conditions
PUSH-CI / Clean E2E (push) Blocked by required conditions
PUSH-CI / Clean Benchmarking (push) Blocked by required conditions
This commit is contained in:
53
.asf.yaml
Normal file
53
.asf.yaml
Normal file
@@ -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
|
||||
69
.bazelrc
Normal file
69
.bazelrc
Normal file
@@ -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
|
||||
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.2.0
|
||||
112
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
112
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -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
|
||||
23
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
23
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -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
|
||||
55
.github/ISSUE_TEMPLATE/doc.yml
vendored
Normal file
55
.github/ISSUE_TEMPLATE/doc.yml
vendored
Normal file
@@ -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!"
|
||||
75
.github/ISSUE_TEMPLATE/enhancement_request.yml
vendored
Normal file
75
.github/ISSUE_TEMPLATE/enhancement_request.yml
vendored
Normal file
@@ -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
|
||||
58
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -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
|
||||
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<!-- Please make sure the target branch is right. In most case, the target branch should be `develop`. -->
|
||||
|
||||
### Which Issue(s) This PR Fixes
|
||||
|
||||
<!-- Please ensure that the related issue has already been created, and [link this pull request to that issue using keywords](<https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword>) to ensure automatic closure. -->
|
||||
|
||||
Fixes #issue_id
|
||||
|
||||
### Brief Description
|
||||
|
||||
<!-- Write a brief description for your pull request to help the maintainer understand the reasons behind your changes. -->
|
||||
|
||||
### How Did You Test This Change?
|
||||
|
||||
<!-- In order to ensure the code quality of Apache RocketMQ, we expect every pull request to have undergone thorough testing. -->
|
||||
38
.github/asf-deploy-settings.xml
vendored
Normal file
38
.github/asf-deploy-settings.xml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://maven.apache.org/SETTINGS/1.0.0
|
||||
http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>apache.snapshots.https</id>
|
||||
<username>${env.NEXUS_DEPLOY_USERNAME}</username>
|
||||
<password>${env.NEXUS_DEPLOY_PASSWORD}</password>
|
||||
<configuration>
|
||||
<snapshotRepository>
|
||||
<maxUniqueSnapshots>60</maxUniqueSnapshots>
|
||||
</snapshotRepository>
|
||||
</configuration>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
23
.github/workflows/bazel.yml
vendored
Normal file
23
.github/workflows/bazel.yml
vendored
Normal file
@@ -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 //...
|
||||
32
.github/workflows/codeql_analysis.yml
vendored
Normal file
32
.github/workflows/codeql_analysis.yml
vendored
Normal file
@@ -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
|
||||
25
.github/workflows/coverage.yml
vendored
Normal file
25
.github/workflows/coverage.yml
vendored
Normal file
@@ -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
|
||||
45
.github/workflows/integration-test.yml
vendored
Normal file
45
.github/workflows/integration-test.yml
vendored
Normal file
@@ -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
|
||||
34
.github/workflows/license-checker.yaml
vendored
Normal file
34
.github/workflows/license-checker.yaml
vendored
Normal file
@@ -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
|
||||
46
.github/workflows/maven.yaml
vendored
Normal file
46
.github/workflows/maven.yaml
vendored
Normal file
@@ -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
|
||||
17
.github/workflows/misspell_check.yml
vendored
Normal file
17
.github/workflows/misspell_check.yml
vendored
Normal file
@@ -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
|
||||
36
.github/workflows/pr-ci.yml
vendored
Normal file
36
.github/workflows/pr-ci.yml
vendored
Normal file
@@ -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/
|
||||
263
.github/workflows/pr-e2e-test.yml
vendored
Normal file
263
.github/workflows/pr-e2e-test.yml
vendored
Normal file
@@ -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 }}
|
||||
348
.github/workflows/push-ci.yml
vendored
Normal file
348
.github/workflows/push-ci.yml
vendored
Normal file
@@ -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 }}"
|
||||
22
.github/workflows/rerun-workflow.yml
vendored
Normal file
22
.github/workflows/rerun-workflow.yml
vendored
Normal file
@@ -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
|
||||
261
.github/workflows/snapshot-automation.yml
vendored
Normal file
261
.github/workflows/snapshot-automation.yml
vendored
Normal file
@@ -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
|
||||
32
.github/workflows/stale.yml
vendored
Normal file
32
.github/workflows/stale.yml
vendored
Normal file
@@ -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
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
*dependency-reduced-pom.xml
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
target/
|
||||
devenv
|
||||
*.log.*
|
||||
*.iml
|
||||
.idea/
|
||||
*.versionsBackup
|
||||
!NOTICE-BIN
|
||||
!LICENSE-BIN
|
||||
.DS_Store
|
||||
localbin
|
||||
nohup.out
|
||||
bazel-out
|
||||
bazel-bin
|
||||
bazel-rocketmq
|
||||
bazel-testlogs
|
||||
.vscode
|
||||
MODULE.bazel.lock
|
||||
50
.licenserc.yaml
Normal file
50
.licenserc.yaml
Normal file
@@ -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
|
||||
49
BUILD.bazel
Normal file
49
BUILD.bazel
Normal file
@@ -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",
|
||||
],
|
||||
)
|
||||
37
BUILDING
Normal file
37
BUILDING
Normal file
@@ -0,0 +1,37 @@
|
||||
Build Instructions for Apache RocketMQ
|
||||
|
||||
====================================================
|
||||
|
||||
(1) Prerequisites
|
||||
|
||||
JDK 1.7+ is required in order to compile and run RocketMQ.
|
||||
|
||||
RocketMQ utilizes Maven as a distribution management and packaging tool. Version 3.0.3 or later is required.
|
||||
Maven installation and configuration instructions can be found here:
|
||||
|
||||
http://maven.apache.org/run-maven/index.html
|
||||
|
||||
|
||||
(2) Run test cases
|
||||
|
||||
Execute the following command in order to compile and run test cases of each components:
|
||||
|
||||
$ mvn test
|
||||
|
||||
|
||||
(3) Import projects to Eclipse IDE
|
||||
|
||||
First, generate eclipse project files:
|
||||
|
||||
$ mvn -U eclipse:eclipse
|
||||
|
||||
Then, import to eclipse by specifying the root directory of the project via:
|
||||
|
||||
[File] > [Import] > [Existing Projects into Workspace].
|
||||
|
||||
|
||||
(4) Build distribution packages
|
||||
|
||||
Execute the following command in order to build the tar.gz packages and install JAR into local repository:
|
||||
|
||||
$ mvn -Prelease-all -DskipTests clean install -U
|
||||
48
CONTRIBUTING.md
Normal file
48
CONTRIBUTING.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## How To Contribute
|
||||
|
||||
We are always very happy to have contributions, whether for trivial cleanups or big new features.
|
||||
We want to have high quality, well documented codes for each programming language, as well as the surrounding [ecosystem](https://github.com/apache/rocketmq-externals) of integration tools that people use with RocketMQ.
|
||||
|
||||
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 [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 SDK(C++\.Net\Php\Python\Go\Node.js)
|
||||
#### 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
|
||||
###### 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/)
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"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 (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.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
||||
22
MODULE.bazel
Normal file
22
MODULE.bazel
Normal file
@@ -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
|
||||
###############################################################################
|
||||
5
NOTICE
Normal file
5
NOTICE
Normal file
@@ -0,0 +1,5 @@
|
||||
Apache RocketMQ
|
||||
Copyright 2016-2025 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
||||
250
README.md
Normal file
250
README.md
Normal file
@@ -0,0 +1,250 @@
|
||||
## 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:
|
||||
|
||||
* 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
|
||||
* Reliable FIFO and strict ordered messaging in the same queue
|
||||
* Efficient pull and push consumption model
|
||||
* Million-level message accumulation capacity in a single queue
|
||||
* 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
|
||||
* 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.3/rocketmq-all-5.3.3-bin-release.zip) to download the 5.3.3 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.3/rocketmq-all-5.3.3-bin-release.zip
|
||||
|
||||
# Unpack the release
|
||||
$ unzip rocketmq-all-5.3.3-bin-release.zip
|
||||
```
|
||||
|
||||
Prepare a terminal and change to the extracted `bin` directory:
|
||||
```shell
|
||||
$ cd rocketmq-all-5.3.3-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: <https://rocketmq.apache.org/about/contact/>
|
||||
* Home: <https://rocketmq.apache.org>
|
||||
* Docs: <https://rocketmq.apache.org/docs/quick-start/>
|
||||
* Issues: <https://github.com/apache/rocketmq/issues>
|
||||
* Rips: <https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal>
|
||||
* Ask: <https://stackoverflow.com/questions/tagged/rocketmq>
|
||||
* Slack: <https://rocketmq-invite-automation.herokuapp.com/>
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
<http://www.wassenaar.org/> 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
|
||||
144
WORKSPACE
Normal file
144
WORKSPACE
Normal file
@@ -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",
|
||||
],
|
||||
)
|
||||
77
auth/BUILD.bazel
Normal file
77
auth/BUILD.bazel
Normal file
@@ -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",
|
||||
],
|
||||
)
|
||||
78
auth/pom.xml
Normal file
78
auth/pom.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<!-- 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. -->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-all</artifactId>
|
||||
<version>5.3.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>rocketmq-auth</artifactId>
|
||||
<name>rocketmq-auth ${project.version}</name>
|
||||
|
||||
<properties>
|
||||
<project.root>${basedir}/..</project.root>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>rocketmq-proto</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>rocketmq-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-qual</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
<configuration>
|
||||
<forkCount>1</forkCount>
|
||||
<reuseForks>false</reuseForks>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
AuthenticationContext build(Metadata metadata, GeneratedMessageV3 request);
|
||||
|
||||
AuthenticationContext build(ChannelHandlerContext context, RemotingCommand request);
|
||||
}
|
||||
@@ -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<DefaultAuthenticationContext> {
|
||||
|
||||
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<String, String> 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<String, String> map = new TreeMap<>();
|
||||
for (Map.Entry<String, String> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultAuthenticationContext, CompletableFuture<Void>> {
|
||||
|
||||
private final AuthenticationMetadataProvider authenticationMetadataProvider;
|
||||
|
||||
public DefaultAuthenticationHandler(AuthConfig config, Supplier<?> metadataService) {
|
||||
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> handle(DefaultAuthenticationContext context,
|
||||
HandlerChain<DefaultAuthenticationContext, CompletableFuture<Void>> chain) {
|
||||
return getUser(context).thenAccept(user -> doAuthenticate(context, user));
|
||||
}
|
||||
|
||||
protected CompletableFuture<User> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> 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> 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<String, Object> getExtInfo() {
|
||||
return extInfo;
|
||||
}
|
||||
|
||||
public void setExtInfo(Map<String, Object> extInfo) {
|
||||
this.extInfo = extInfo;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> 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<AuthenticationContext> 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<AuthenticationContext>) 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<AuthenticationContext> authenticationProvider = getProvider(config);
|
||||
if (authenticationProvider == null) {
|
||||
return null;
|
||||
}
|
||||
return authenticationProvider.newContext(metadata, request);
|
||||
}
|
||||
|
||||
public static AuthenticationContext newContext(AuthConfig config, ChannelHandlerContext context,
|
||||
RemotingCommand command) {
|
||||
AuthenticationProvider<AuthenticationContext> authenticationProvider = getProvider(config);
|
||||
if (authenticationProvider == null) {
|
||||
return null;
|
||||
}
|
||||
return authenticationProvider.newContext(context, command);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <V> V computeIfAbsent(String key, Function<String, ? extends V> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> createUser(User user);
|
||||
|
||||
CompletableFuture<Void> updateUser(User user);
|
||||
|
||||
CompletableFuture<Void> deleteUser(String username);
|
||||
|
||||
CompletableFuture<User> getUser(String username);
|
||||
|
||||
CompletableFuture<List<User>> listUser(String filter);
|
||||
|
||||
CompletableFuture<Boolean> isSuperUser(String username);
|
||||
}
|
||||
@@ -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<Void> createUser(User user) {
|
||||
CompletableFuture<Void> 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<Void> updateUser(User user) {
|
||||
CompletableFuture<Void> 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<Void> deleteUser(String username) {
|
||||
CompletableFuture<Void> result = new CompletableFuture<>();
|
||||
try {
|
||||
if (StringUtils.isBlank(username)) {
|
||||
throw new AuthenticationException("username can not be blank");
|
||||
}
|
||||
CompletableFuture<Void> deleteUser = this.getAuthenticationMetadataProvider().deleteUser(username);
|
||||
CompletableFuture<Void> deleteAcl = this.getAuthorizationMetadataProvider().deleteAcl(User.of(username));
|
||||
return CompletableFuture.allOf(deleteUser, deleteAcl);
|
||||
} catch (Exception e) {
|
||||
this.handleException(e, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<User> getUser(String username) {
|
||||
CompletableFuture<User> 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<List<User>> listUser(String filter) {
|
||||
CompletableFuture<List<User>> result = new CompletableFuture<>();
|
||||
try {
|
||||
result = this.getAuthenticationMetadataProvider().listUser(filter);
|
||||
} catch (Exception e) {
|
||||
this.handleException(e, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> 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;
|
||||
}
|
||||
}
|
||||
@@ -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 extends Subject> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> createUser(User user);
|
||||
|
||||
CompletableFuture<Void> deleteUser(String username);
|
||||
|
||||
CompletableFuture<Void> updateUser(User user);
|
||||
|
||||
CompletableFuture<User> getUser(String username);
|
||||
|
||||
CompletableFuture<List<User>> listUser(String filter);
|
||||
}
|
||||
@@ -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<AuthenticationContext> {
|
||||
|
||||
void initialize(AuthConfig config, Supplier<?> metadataService);
|
||||
|
||||
CompletableFuture<Void> authenticate(AuthenticationContext context);
|
||||
|
||||
AuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request);
|
||||
|
||||
AuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command);
|
||||
}
|
||||
@@ -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<DefaultAuthenticationContext> {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME);
|
||||
protected AuthConfig authConfig;
|
||||
protected Supplier<?> metadataService;
|
||||
protected AuthenticationContextBuilder<DefaultAuthenticationContext> authenticationContextBuilder;
|
||||
|
||||
@Override
|
||||
public void initialize(AuthConfig config, Supplier<?> metadataService) {
|
||||
this.authConfig = config;
|
||||
this.metadataService = metadataService;
|
||||
this.authenticationContextBuilder = new DefaultAuthenticationContextBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> 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<DefaultAuthenticationContext, CompletableFuture<Void>> newHandlerChain() {
|
||||
return HandlerChain.<DefaultAuthenticationContext, CompletableFuture<Void>>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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, User> 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<Void> 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<Void> 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<Void> 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<User> 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<List<User>> listUser(String filter) {
|
||||
List<User> 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<String, User> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> authenticationWhitelist = new ArrayList<>();
|
||||
protected final AuthenticationProvider<AuthenticationContext> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String, Pair<Boolean, AuthenticationException>> 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<Boolean, AuthenticationException> 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<AuthorizationContext> contexts) {
|
||||
if (CollectionUtils.isEmpty(contexts)) {
|
||||
return;
|
||||
}
|
||||
contexts.forEach(this.authorizationStrategy::evaluate);
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultAuthorizationContext> build(Metadata metadata, GeneratedMessageV3 message);
|
||||
|
||||
List<DefaultAuthorizationContext> build(ChannelHandlerContext context, RemotingCommand command);
|
||||
}
|
||||
@@ -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<DefaultAuthorizationContext> build(Metadata metadata, GeneratedMessageV3 message) {
|
||||
List<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> build(ChannelHandlerContext context, RemotingCommand command) {
|
||||
List<DefaultAuthorizationContext> result = new ArrayList<>();
|
||||
try {
|
||||
HashMap<String, String> 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<DefaultAuthorizationContext> buildContextByAnnotation(Subject subject, RemotingCommand request,
|
||||
String sourceIp) throws Exception {
|
||||
List<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> result = new ArrayList<>();
|
||||
if (request.getSettings().hasPublishing()) {
|
||||
List<apache.rocketmq.v2.Resource> 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<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> newSubContexts(Metadata metadata, apache.rocketmq.v2.Resource group,
|
||||
apache.rocketmq.v2.Resource topic) {
|
||||
List<DefaultAuthorizationContext> result = new ArrayList<>();
|
||||
result.addAll(newGroupSubContexts(metadata, group));
|
||||
result.addAll(newTopicSubContexts(metadata, topic));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<DefaultAuthorizationContext> newTopicSubContexts(Metadata metadata,
|
||||
apache.rocketmq.v2.Resource resource) {
|
||||
return newSubContexts(metadata, ResourceType.TOPIC, resource);
|
||||
}
|
||||
|
||||
private static List<DefaultAuthorizationContext> newGroupSubContexts(Metadata metadata,
|
||||
apache.rocketmq.v2.Resource resource) {
|
||||
return newSubContexts(metadata, ResourceType.GROUP, resource);
|
||||
}
|
||||
|
||||
private static List<DefaultAuthorizationContext> 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<DefaultAuthorizationContext> newSubContexts(Metadata metadata, Resource resource) {
|
||||
List<DefaultAuthorizationContext> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultAuthorizationContext, CompletableFuture<Void>> {
|
||||
|
||||
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<Void> handle(DefaultAuthorizationContext context,
|
||||
HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> 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<PolicyEntry> policyEntries = new ArrayList<>();
|
||||
|
||||
Policy policy = acl.getPolicy(PolicyType.CUSTOM);
|
||||
if (policy != null) {
|
||||
List<PolicyEntry> 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<PolicyEntry> 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<PolicyEntry> matchPolicyEntries(DefaultAuthorizationContext context, List<PolicyEntry> 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());
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultAuthorizationContext, CompletableFuture<Void>> {
|
||||
|
||||
private final AuthenticationMetadataProvider authenticationMetadataProvider;
|
||||
|
||||
public UserAuthorizationHandler(AuthConfig config, Supplier<?> metadataService) {
|
||||
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> handle(DefaultAuthorizationContext context, HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> 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<User> 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> extInfo;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> 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<String, Object> getExtInfo() {
|
||||
return extInfo;
|
||||
}
|
||||
|
||||
public void setExtInfo(Map<String, Object> extInfo) {
|
||||
this.extInfo = extInfo;
|
||||
}
|
||||
}
|
||||
@@ -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<Action> 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<Action> 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<Action> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void setActions(List<Action> actions) {
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public String getSourceIp() {
|
||||
return sourceIp;
|
||||
}
|
||||
|
||||
public void setSourceIp(String sourceIp) {
|
||||
this.sourceIp = sourceIp;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> 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<AuthorizationContext> 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<AuthorizationContext>) 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<AuthorizationContext> newContexts(AuthConfig config, Metadata metadata,
|
||||
GeneratedMessageV3 message) {
|
||||
AuthorizationProvider<AuthorizationContext> authorizationProvider = getProvider(config);
|
||||
if (authorizationProvider == null) {
|
||||
return null;
|
||||
}
|
||||
return authorizationProvider.newContexts(metadata, message);
|
||||
}
|
||||
|
||||
public static List<AuthorizationContext> newContexts(AuthConfig config, ChannelHandlerContext context,
|
||||
RemotingCommand command) {
|
||||
AuthorizationProvider<AuthorizationContext> authorizationProvider = getProvider(config);
|
||||
if (authorizationProvider == null) {
|
||||
return null;
|
||||
}
|
||||
return authorizationProvider.newContexts(context, command);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <V> V computeIfAbsent(String key, Function<String, ? extends V> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> createAcl(Acl acl);
|
||||
|
||||
CompletableFuture<Void> updateAcl(Acl acl);
|
||||
|
||||
CompletableFuture<Void> deleteAcl(Subject subject);
|
||||
|
||||
CompletableFuture<Void> deleteAcl(Subject subject, PolicyType policyType, Resource resource);
|
||||
|
||||
CompletableFuture<Acl> getAcl(Subject subject);
|
||||
|
||||
CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter);
|
||||
}
|
||||
@@ -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<Void> 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<Void> 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<Void> deleteAcl(Subject subject) {
|
||||
return this.deleteAcl(subject, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> 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<Acl> 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<Acl> 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<List<Acl>> 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<Policy> 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<PolicyEntry> 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 <T> CompletableFuture<T> handleException(Exception e) {
|
||||
CompletableFuture<T> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Policy> policies;
|
||||
|
||||
public static Acl of(Subject subject, Policy policy) {
|
||||
return of(subject, Lists.newArrayList(policy));
|
||||
}
|
||||
|
||||
public static Acl of(Subject subject, List<Policy> policies) {
|
||||
Acl acl = new Acl();
|
||||
acl.setSubject(subject);
|
||||
acl.setPolicies(policies);
|
||||
return acl;
|
||||
}
|
||||
|
||||
public static Acl of(Subject subject, List<Resource> resources, List<Action> 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<Policy> 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<Policy> getPolicies() {
|
||||
return policies;
|
||||
}
|
||||
|
||||
public void setPolicies(List<Policy> policies) {
|
||||
this.policies = policies;
|
||||
}
|
||||
}
|
||||
@@ -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<String> sourceIps;
|
||||
|
||||
public static Environment of(String sourceIp) {
|
||||
if (StringUtils.isEmpty(sourceIp)) {
|
||||
return null;
|
||||
}
|
||||
return of(Collections.singletonList(sourceIp));
|
||||
}
|
||||
|
||||
public static Environment of(List<String> 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<String> getSourceIps() {
|
||||
return sourceIps;
|
||||
}
|
||||
|
||||
public void setSourceIps(List<String> sourceIps) {
|
||||
this.sourceIps = sourceIps;
|
||||
}
|
||||
}
|
||||
@@ -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<PolicyEntry> entries;
|
||||
|
||||
public static Policy of(List<Resource> resources, List<Action> actions, Environment environment,
|
||||
Decision decision) {
|
||||
return of(PolicyType.CUSTOM, resources, actions, environment, decision);
|
||||
}
|
||||
|
||||
public static Policy of(PolicyType policyType, List<Resource> resources, List<Action> actions,
|
||||
Environment environment,
|
||||
Decision decision) {
|
||||
Policy policy = new Policy();
|
||||
policy.setPolicyType(policyType);
|
||||
List<PolicyEntry> 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<PolicyEntry> entries) {
|
||||
Policy policy = new Policy();
|
||||
policy.setPolicyType(type);
|
||||
policy.setEntries(entries);
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void updateEntry(List<PolicyEntry> 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<PolicyEntry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void setEntries(List<PolicyEntry> entries) {
|
||||
this.entries = entries;
|
||||
}
|
||||
}
|
||||
@@ -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<Action> actions;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
private Decision decision;
|
||||
|
||||
public static PolicyEntry of(Resource resource, List<Action> 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<Action> 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<Action> 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<String> 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<Action> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public void setActions(List<Action> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Resource> of(List<String> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> createAcl(Acl acl);
|
||||
|
||||
CompletableFuture<Void> deleteAcl(Subject subject);
|
||||
|
||||
CompletableFuture<Void> updateAcl(Acl acl);
|
||||
|
||||
CompletableFuture<Acl> getAcl(Subject subject);
|
||||
|
||||
CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter);
|
||||
}
|
||||
@@ -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<AuthorizationContext> {
|
||||
|
||||
void initialize(AuthConfig config);
|
||||
|
||||
void initialize(AuthConfig config, Supplier<?> metadataService);
|
||||
|
||||
CompletableFuture<Void> authorize(AuthorizationContext context);
|
||||
|
||||
List<AuthorizationContext> newContexts(Metadata metadata, GeneratedMessageV3 message);
|
||||
|
||||
List<AuthorizationContext> newContexts(ChannelHandlerContext context, RemotingCommand command);
|
||||
}
|
||||
@@ -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<DefaultAuthorizationContext> {
|
||||
|
||||
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<Void> authorize(DefaultAuthorizationContext context) {
|
||||
return this.newHandlerChain().handle(context)
|
||||
.whenComplete((nil, ex) -> doAuditLog(context, ex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DefaultAuthorizationContext> newContexts(Metadata metadata, GeneratedMessageV3 message) {
|
||||
return this.authorizationContextBuilder.build(metadata, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DefaultAuthorizationContext> newContexts(ChannelHandlerContext context, RemotingCommand command) {
|
||||
return this.authorizationContextBuilder.build(context, command);
|
||||
}
|
||||
|
||||
protected HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> newHandlerChain() {
|
||||
return HandlerChain.<DefaultAuthorizationContext, CompletableFuture<Void>>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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, Acl> 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<Void> 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<Void> 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<Void> 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<Acl> 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<List<Acl>> listAcl(String subjectFilter, String resourceFilter) {
|
||||
List<Acl> 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<Policy> policies = acl.getPolicies();
|
||||
if (!CollectionUtils.isNotEmpty(policies)) {
|
||||
iterator.next();
|
||||
continue;
|
||||
}
|
||||
Iterator<Policy> policyIterator = policies.iterator();
|
||||
while (policyIterator.hasNext()) {
|
||||
Policy policy = policyIterator.next();
|
||||
List<PolicyEntry> 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<String, Acl> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> authorizationWhitelist = new ArrayList<>();
|
||||
protected final AuthorizationProvider<AuthorizationContext> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String, Pair<Boolean, AuthorizationException>> 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<Boolean, AuthorizationException> 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<PlainAccessConfig> 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<Void> createUserAndAcl(PlainAccessConfig accessConfig) {
|
||||
return createUser(accessConfig).thenCompose(nil -> createAcl(accessConfig));
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> 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<Void> createAcl(PlainAccessConfig config) {
|
||||
Subject subject = User.of(config.getAccessKey());
|
||||
List<Policy> policies = new ArrayList<>();
|
||||
|
||||
Policy customPolicy = null;
|
||||
if (CollectionUtils.isNotEmpty(config.getTopicPerms())) {
|
||||
for (String topicPerm : config.getTopicPerms()) {
|
||||
String[] temp = StringUtils.split(topicPerm, CommonConstants.EQUAL);
|
||||
if (temp.length != 2) {
|
||||
continue;
|
||||
}
|
||||
String topicName = StringUtils.trim(temp[0]);
|
||||
String perm = StringUtils.trim(temp[1]);
|
||||
Resource resource = Resource.ofTopic(topicName);
|
||||
List<Action> actions = parseActions(perm);
|
||||
Decision decision = parseDecision(perm);
|
||||
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
|
||||
if (customPolicy == null) {
|
||||
customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>());
|
||||
}
|
||||
customPolicy.getEntries().add(policyEntry);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(config.getGroupPerms())) {
|
||||
for (String groupPerm : config.getGroupPerms()) {
|
||||
String[] temp = StringUtils.split(groupPerm, CommonConstants.EQUAL);
|
||||
if (temp.length != 2) {
|
||||
continue;
|
||||
}
|
||||
String groupName = StringUtils.trim(temp[0]);
|
||||
String perm = StringUtils.trim(temp[1]);
|
||||
Resource resource = Resource.ofGroup(groupName);
|
||||
List<Action> actions = parseActions(perm);
|
||||
Decision decision = parseDecision(perm);
|
||||
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
|
||||
if (customPolicy == null) {
|
||||
customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>());
|
||||
}
|
||||
customPolicy.getEntries().add(policyEntry);
|
||||
}
|
||||
}
|
||||
if (customPolicy != null) {
|
||||
policies.add(customPolicy);
|
||||
}
|
||||
|
||||
Policy defaultPolicy = null;
|
||||
if (StringUtils.isNotBlank(config.getDefaultTopicPerm())) {
|
||||
String topicPerm = StringUtils.trim(config.getDefaultTopicPerm());
|
||||
Resource resource = Resource.of(ResourceType.TOPIC, null, ResourcePattern.ANY);
|
||||
List<Action> actions = parseActions(topicPerm);
|
||||
Decision decision = parseDecision(topicPerm);
|
||||
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
|
||||
defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>());
|
||||
defaultPolicy.getEntries().add(policyEntry);
|
||||
}
|
||||
if (StringUtils.isNotBlank(config.getDefaultGroupPerm())) {
|
||||
String groupPerm = StringUtils.trim(config.getDefaultGroupPerm());
|
||||
Resource resource = Resource.of(ResourceType.GROUP, null, ResourcePattern.ANY);
|
||||
List<Action> actions = parseActions(groupPerm);
|
||||
Decision decision = parseDecision(groupPerm);
|
||||
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
|
||||
if (defaultPolicy == null) {
|
||||
defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>());
|
||||
}
|
||||
defaultPolicy.getEntries().add(policyEntry);
|
||||
}
|
||||
if (defaultPolicy != null) {
|
||||
policies.add(defaultPolicy);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(policies)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
Acl acl = Acl.of(subject, policies);
|
||||
return this.authorizationMetadataManager.createAcl(acl);
|
||||
}
|
||||
|
||||
private Decision parseDecision(String str) {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return Decision.DENY;
|
||||
}
|
||||
return StringUtils.equals(str, AclConstants.DENY) ? Decision.DENY : Decision.ALLOW;
|
||||
}
|
||||
|
||||
private List<Action> parseActions(String str) {
|
||||
List<Action> result = new ArrayList<>();
|
||||
if (StringUtils.isBlank(str)) {
|
||||
result.add(Action.ALL);
|
||||
}
|
||||
switch (StringUtils.trim(str)) {
|
||||
case AclConstants.PUB:
|
||||
result.add(Action.PUB);
|
||||
break;
|
||||
case AclConstants.SUB:
|
||||
result.add(Action.SUB);
|
||||
break;
|
||||
case AclConstants.PUB_SUB:
|
||||
case AclConstants.SUB_PUB:
|
||||
result.add(Action.PUB);
|
||||
result.add(Action.SUB);
|
||||
break;
|
||||
case AclConstants.DENY:
|
||||
result.add(Action.ALL);
|
||||
break;
|
||||
default:
|
||||
result.add(Action.ALL);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> isUserExisted(String username) {
|
||||
return this.authenticationMetadataManager.getUser(username).thenApply(Objects::nonNull);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.v1;
|
||||
|
||||
public interface AccessResource {
|
||||
}
|
||||
@@ -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.migration.v1;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AclConfig {
|
||||
|
||||
private List<String> globalWhiteAddrs;
|
||||
|
||||
private List<PlainAccessConfig> plainAccessConfigs;
|
||||
|
||||
|
||||
public List<String> getGlobalWhiteAddrs() {
|
||||
return globalWhiteAddrs;
|
||||
}
|
||||
|
||||
public void setGlobalWhiteAddrs(List<String> globalWhiteAddrs) {
|
||||
this.globalWhiteAddrs = globalWhiteAddrs;
|
||||
}
|
||||
|
||||
public List<PlainAccessConfig> getPlainAccessConfigs() {
|
||||
return plainAccessConfigs;
|
||||
}
|
||||
|
||||
public void setPlainAccessConfigs(List<PlainAccessConfig> plainAccessConfigs) {
|
||||
this.plainAccessConfigs = plainAccessConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AclConfig{" +
|
||||
"globalWhiteAddrs=" + globalWhiteAddrs +
|
||||
", plainAccessConfigs=" + plainAccessConfigs +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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.migration.v1;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PlainAccessConfig implements Serializable {
|
||||
private static final long serialVersionUID = -4517357000307227637L;
|
||||
|
||||
private String accessKey;
|
||||
|
||||
private String secretKey;
|
||||
|
||||
private String whiteRemoteAddress;
|
||||
|
||||
private boolean admin;
|
||||
|
||||
private String defaultTopicPerm;
|
||||
|
||||
private String defaultGroupPerm;
|
||||
|
||||
private List<String> topicPerms;
|
||||
|
||||
private List<String> 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<String> getTopicPerms() {
|
||||
return topicPerms;
|
||||
}
|
||||
|
||||
public void setTopicPerms(List<String> topicPerms) {
|
||||
this.topicPerms = topicPerms;
|
||||
}
|
||||
|
||||
public List<String> getGroupPerms() {
|
||||
return groupPerms;
|
||||
}
|
||||
|
||||
public void setGroupPerms(List<String> groupPerms) {
|
||||
this.groupPerms = groupPerms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlainAccessConfig{" +
|
||||
"accessKey='" + accessKey + '\'' +
|
||||
", whiteRemoteAddress='" + whiteRemoteAddress + '\'' +
|
||||
", admin=" + admin +
|
||||
", defaultTopicPerm='" + defaultTopicPerm + '\'' +
|
||||
", defaultGroupPerm='" + defaultGroupPerm + '\'' +
|
||||
", topicPerms=" + topicPerms +
|
||||
", groupPerms=" + groupPerms +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
PlainAccessConfig config = (PlainAccessConfig) o;
|
||||
return admin == config.admin && Objects.equals(accessKey, config.accessKey) && Objects.equals(secretKey, config.secretKey) && Objects.equals(whiteRemoteAddress, config.whiteRemoteAddress) && Objects.equals(defaultTopicPerm, config.defaultTopicPerm) && Objects.equals(defaultGroupPerm, config.defaultGroupPerm) && Objects.equals(topicPerms, config.topicPerms) && Objects.equals(groupPerms, config.groupPerms);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return Objects.hash(accessKey, secretKey, whiteRemoteAddress, admin, defaultTopicPerm, defaultGroupPerm, topicPerms, groupPerms);
|
||||
}
|
||||
}
|
||||
@@ -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.migration.v1;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PlainAccessData implements Serializable {
|
||||
private static final long serialVersionUID = -7971775135605117152L;
|
||||
|
||||
private List<String> globalWhiteRemoteAddresses = new ArrayList<>();
|
||||
private List<PlainAccessConfig> accounts = new ArrayList<>();
|
||||
private List<DataVersion> dataVersion = new ArrayList<>();
|
||||
|
||||
public List<String> getGlobalWhiteRemoteAddresses() {
|
||||
return globalWhiteRemoteAddresses;
|
||||
}
|
||||
|
||||
public void setGlobalWhiteRemoteAddresses(List<String> globalWhiteRemoteAddresses) {
|
||||
this.globalWhiteRemoteAddresses = globalWhiteRemoteAddresses;
|
||||
}
|
||||
|
||||
public List<PlainAccessConfig> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void setAccounts(List<PlainAccessConfig> accounts) {
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
public List<DataVersion> getDataVersion() {
|
||||
return dataVersion;
|
||||
}
|
||||
|
||||
public void setDataVersion(List<DataVersion> dataVersion) {
|
||||
this.dataVersion = dataVersion;
|
||||
}
|
||||
|
||||
public static class DataVersion implements Serializable {
|
||||
private static final long serialVersionUID = 6437361970079056954L;
|
||||
private long timestamp;
|
||||
private long counter;
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public long getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
public void setCounter(long counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DataVersion that = (DataVersion) o;
|
||||
return timestamp == that.timestamp && counter == that.counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(timestamp, counter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PlainAccessData that = (PlainAccessData) o;
|
||||
return Objects.equals(globalWhiteRemoteAddresses, that.globalWhiteRemoteAddresses) && Objects.equals(accounts, that.accounts) && Objects.equals(dataVersion, that.dataVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(globalWhiteRemoteAddresses, accounts, dataVersion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.v1;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.rocketmq.common.KeyBuilder;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlainAccessResource implements AccessResource {
|
||||
|
||||
// Identify the user
|
||||
private String accessKey;
|
||||
|
||||
private String secretKey;
|
||||
|
||||
private String whiteRemoteAddress;
|
||||
|
||||
private boolean admin;
|
||||
|
||||
private byte defaultTopicPerm = 1;
|
||||
|
||||
private byte defaultGroupPerm = 1;
|
||||
|
||||
private Map<String, Byte> resourcePermMap;
|
||||
|
||||
private int requestCode;
|
||||
|
||||
// The content to calculate the content
|
||||
private byte[] content;
|
||||
|
||||
private String signature;
|
||||
|
||||
private String secretToken;
|
||||
|
||||
private String recognition;
|
||||
|
||||
public PlainAccessResource() {
|
||||
}
|
||||
|
||||
public static String getGroupFromRetryTopic(String retryTopic) {
|
||||
if (retryTopic == null) {
|
||||
return null;
|
||||
}
|
||||
return KeyBuilder.parseGroup(retryTopic);
|
||||
}
|
||||
|
||||
public static String getRetryTopic(String group) {
|
||||
if (group == null) {
|
||||
return null;
|
||||
}
|
||||
return MixAll.getRetryTopic(group);
|
||||
}
|
||||
|
||||
public void addResourceAndPerm(String resource, byte perm) {
|
||||
if (resource == null) {
|
||||
return;
|
||||
}
|
||||
if (resourcePermMap == null) {
|
||||
resourcePermMap = new HashMap<>();
|
||||
}
|
||||
resourcePermMap.put(resource, perm);
|
||||
}
|
||||
|
||||
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 byte getDefaultTopicPerm() {
|
||||
return defaultTopicPerm;
|
||||
}
|
||||
|
||||
public void setDefaultTopicPerm(byte defaultTopicPerm) {
|
||||
this.defaultTopicPerm = defaultTopicPerm;
|
||||
}
|
||||
|
||||
public byte getDefaultGroupPerm() {
|
||||
return defaultGroupPerm;
|
||||
}
|
||||
|
||||
public void setDefaultGroupPerm(byte defaultGroupPerm) {
|
||||
this.defaultGroupPerm = defaultGroupPerm;
|
||||
}
|
||||
|
||||
public Map<String, Byte> getResourcePermMap() {
|
||||
return resourcePermMap;
|
||||
}
|
||||
|
||||
public String getRecognition() {
|
||||
return recognition;
|
||||
}
|
||||
|
||||
public void setRecognition(String recognition) {
|
||||
this.recognition = recognition;
|
||||
}
|
||||
|
||||
public int getRequestCode() {
|
||||
return requestCode;
|
||||
}
|
||||
|
||||
public void setRequestCode(int requestCode) {
|
||||
this.requestCode = requestCode;
|
||||
}
|
||||
|
||||
public String getSecretToken() {
|
||||
return secretToken;
|
||||
}
|
||||
|
||||
public void setSecretToken(String secretToken) {
|
||||
this.secretToken = secretToken;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this);
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(byte[] content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.v1;
|
||||
|
||||
import org.apache.rocketmq.acl.common.AclException;
|
||||
import org.apache.rocketmq.acl.common.AclUtils;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.apache.rocketmq.common.constant.LoggerName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class PlainPermissionManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME);
|
||||
|
||||
private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
|
||||
System.getenv(MixAll.ROCKETMQ_HOME_ENV));
|
||||
|
||||
private String defaultAclDir;
|
||||
|
||||
private String defaultAclFile;
|
||||
|
||||
private List<String> fileList = new ArrayList<>();
|
||||
|
||||
|
||||
public PlainPermissionManager() {
|
||||
this.defaultAclDir = MixAll.dealFilePath(fileHome + File.separator + "conf" + File.separator + "acl");
|
||||
this.defaultAclFile = MixAll.dealFilePath(fileHome + File.separator + System.getProperty("rocketmq.acl.plain.file", "conf" + File.separator + "plain_acl.yml"));
|
||||
load();
|
||||
}
|
||||
|
||||
public List<String> getAllAclFiles(String path) {
|
||||
if (!new File(path).exists()) {
|
||||
log.info("The default acl dir {} is not exist", path);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> allAclFileFullPath = new ArrayList<>();
|
||||
File file = new File(path);
|
||||
File[] files = file.listFiles();
|
||||
for (int i = 0; files != null && i < files.length; i++) {
|
||||
String fileName = files[i].getAbsolutePath();
|
||||
File f = new File(fileName);
|
||||
if (fileName.equals(fileHome + MixAll.ACL_CONF_TOOLS_FILE)) {
|
||||
continue;
|
||||
} else if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) {
|
||||
allAclFileFullPath.add(fileName);
|
||||
} else if (f.isDirectory()) {
|
||||
allAclFileFullPath.addAll(getAllAclFiles(fileName));
|
||||
}
|
||||
}
|
||||
return allAclFileFullPath;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
if (fileHome == null || fileHome.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
assureAclConfigFilesExist();
|
||||
|
||||
fileList = getAllAclFiles(defaultAclDir);
|
||||
if (new File(defaultAclFile).exists() && !fileList.contains(defaultAclFile)) {
|
||||
fileList.add(defaultAclFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently GlobalWhiteAddress is defined in {@link #defaultAclFile}, so make sure it exists.
|
||||
*/
|
||||
private void assureAclConfigFilesExist() {
|
||||
final Path defaultAclFilePath = Paths.get(this.defaultAclFile);
|
||||
if (!Files.exists(defaultAclFilePath)) {
|
||||
try {
|
||||
Files.createFile(defaultAclFilePath);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
// Maybe created by other threads
|
||||
} catch (IOException e) {
|
||||
log.error("Error in creating " + this.defaultAclFile, e);
|
||||
throw new AclException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AclConfig getAllAclConfig() {
|
||||
AclConfig aclConfig = new AclConfig();
|
||||
List<PlainAccessConfig> configs = new ArrayList<>();
|
||||
List<String> whiteAddrs = new ArrayList<>();
|
||||
Set<String> accessKeySets = new HashSet<>();
|
||||
|
||||
for (String path : fileList) {
|
||||
PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(path, PlainAccessData.class);
|
||||
if (plainAclConfData == null) {
|
||||
continue;
|
||||
}
|
||||
List<String> globalWhiteAddrs = plainAclConfData.getGlobalWhiteRemoteAddresses();
|
||||
if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) {
|
||||
whiteAddrs.addAll(globalWhiteAddrs);
|
||||
}
|
||||
|
||||
List<PlainAccessConfig> plainAccessConfigs = plainAclConfData.getAccounts();
|
||||
if (plainAccessConfigs != null && !plainAccessConfigs.isEmpty()) {
|
||||
for (PlainAccessConfig accessConfig : plainAccessConfigs) {
|
||||
if (!accessKeySets.contains(accessConfig.getAccessKey())) {
|
||||
accessKeySets.add(accessConfig.getAccessKey());
|
||||
PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
|
||||
plainAccessConfig.setGroupPerms(accessConfig.getGroupPerms());
|
||||
plainAccessConfig.setDefaultTopicPerm(accessConfig.getDefaultTopicPerm());
|
||||
plainAccessConfig.setDefaultGroupPerm(accessConfig.getDefaultGroupPerm());
|
||||
plainAccessConfig.setAccessKey(accessConfig.getAccessKey());
|
||||
plainAccessConfig.setSecretKey(accessConfig.getSecretKey());
|
||||
plainAccessConfig.setAdmin(accessConfig.isAdmin());
|
||||
plainAccessConfig.setTopicPerms(accessConfig.getTopicPerms());
|
||||
plainAccessConfig.setWhiteRemoteAddress(accessConfig.getWhiteRemoteAddress());
|
||||
configs.add(plainAccessConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
aclConfig.setPlainAccessConfigs(configs);
|
||||
aclConfig.setGlobalWhiteAddrs(whiteAddrs);
|
||||
return aclConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
|
||||
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
|
||||
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
|
||||
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
|
||||
import org.apache.rocketmq.auth.authentication.model.User;
|
||||
import org.apache.rocketmq.auth.config.AuthConfig;
|
||||
import org.apache.rocketmq.auth.helper.AuthTestHelper;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AuthenticationEvaluatorTest {
|
||||
|
||||
private AuthConfig authConfig;
|
||||
private AuthenticationEvaluator evaluator;
|
||||
private AuthenticationMetadataManager authenticationMetadataManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig = AuthTestHelper.createDefaultConfig();
|
||||
this.evaluator = new AuthenticationEvaluator(authConfig);
|
||||
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
|
||||
this.clearAllUsers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.clearAllUsers();
|
||||
this.authenticationMetadataManager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate1() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user);
|
||||
|
||||
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
|
||||
context.setRpcCode("11");
|
||||
context.setUsername("test");
|
||||
context.setContent("test".getBytes(StandardCharsets.UTF_8));
|
||||
context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y=");
|
||||
this.evaluator.evaluate(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate2() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
|
||||
context.setRpcCode("11");
|
||||
context.setUsername("test");
|
||||
context.setContent("test".getBytes(StandardCharsets.UTF_8));
|
||||
context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y=");
|
||||
Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate3() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user);
|
||||
|
||||
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
|
||||
context.setRpcCode("11");
|
||||
context.setUsername("test");
|
||||
context.setContent("test".getBytes(StandardCharsets.UTF_8));
|
||||
context.setSignature("test");
|
||||
Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate4() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig.setAuthenticationWhitelist("11");
|
||||
this.evaluator = new AuthenticationEvaluator(authConfig);
|
||||
|
||||
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
|
||||
context.setRpcCode("11");
|
||||
context.setUsername("test");
|
||||
context.setContent("test".getBytes(StandardCharsets.UTF_8));
|
||||
context.setSignature("test");
|
||||
this.evaluator.evaluate(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate5() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig.setAuthenticationEnabled(false);
|
||||
this.evaluator = new AuthenticationEvaluator(authConfig);
|
||||
|
||||
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
|
||||
context.setRpcCode("11");
|
||||
context.setUsername("test");
|
||||
context.setContent("test".getBytes(StandardCharsets.UTF_8));
|
||||
context.setSignature("test");
|
||||
this.evaluator.evaluate(context);
|
||||
}
|
||||
|
||||
private void clearAllUsers() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
List<User> users = this.authenticationMetadataManager.listUser(null).join();
|
||||
if (CollectionUtils.isEmpty(users)) {
|
||||
return;
|
||||
}
|
||||
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 apache.rocketmq.v2.Message;
|
||||
import apache.rocketmq.v2.Resource;
|
||||
import apache.rocketmq.v2.SendMessageRequest;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.grpc.Metadata;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelId;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
|
||||
import org.apache.rocketmq.common.constant.GrpcConstants;
|
||||
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
|
||||
import org.apache.rocketmq.remoting.protocol.RequestCode;
|
||||
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class DefaultAuthenticationContextBuilderTest {
|
||||
|
||||
private DefaultAuthenticationContextBuilder builder;
|
||||
|
||||
@Mock
|
||||
private ChannelHandlerContext channelHandlerContext;
|
||||
|
||||
@Mock
|
||||
private Channel channel;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
builder = new DefaultAuthenticationContextBuilder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void build1() {
|
||||
Resource topic = Resource.newBuilder().setName("topic-test").build();
|
||||
{
|
||||
SendMessageRequest request = SendMessageRequest.newBuilder()
|
||||
.addMessages(Message.newBuilder().setTopic(topic)
|
||||
.setBody(ByteString.copyFromUtf8("message-body"))
|
||||
.build())
|
||||
.build();
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put(GrpcConstants.AUTHORIZATION, "MQv2-HMAC-SHA1 Credential=abc, SignedHeaders=x-mq-date-time, Signature=D18A9CBCDDBA9041D6693268FEF15A989E64430B");
|
||||
metadata.put(GrpcConstants.DATE_TIME, "20231227T194619Z");
|
||||
DefaultAuthenticationContext context = builder.build(metadata, request);
|
||||
Assert.assertNotNull(context);
|
||||
Assert.assertEquals("abc", context.getUsername());
|
||||
Assert.assertEquals("0YqcvN26kEHWaTJo/vFamJ5kQws=", context.getSignature());
|
||||
Assert.assertEquals("20231227T194619Z", new String(context.getContent(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void build2() {
|
||||
when(channel.id()).thenReturn(mockChannelId("channel-id"));
|
||||
when(channelHandlerContext.channel()).thenReturn(channel);
|
||||
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
|
||||
requestHeader.setTopic("topic-test");
|
||||
requestHeader.setQueueId(0);
|
||||
requestHeader.setBornTimestamp(117036786441330L);
|
||||
requestHeader.setBname("brokerName-1");
|
||||
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "abc");
|
||||
request.addExtField("Signature", "ZG26exJ5u9q1fwZlO4DCmz2Rs88=");
|
||||
request.makeCustomHeaderToNet();
|
||||
DefaultAuthenticationContext context = builder.build(channelHandlerContext, request);
|
||||
Assert.assertNotNull(context);
|
||||
Assert.assertEquals("abc", context.getUsername());
|
||||
Assert.assertEquals("ZG26exJ5u9q1fwZlO4DCmz2Rs88=", context.getSignature());
|
||||
Assert.assertEquals("abcbrokerName-11170367864413300topic-test", new String(context.getContent(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private ChannelId mockChannelId(String channelId) {
|
||||
return new ChannelId() {
|
||||
@Override
|
||||
public String asShortText() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asLongText() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ChannelId o) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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 org.apache.commons.collections.CollectionUtils;
|
||||
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.config.AuthConfig;
|
||||
import org.apache.rocketmq.auth.helper.AuthTestHelper;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AuthenticationMetadataManagerTest {
|
||||
|
||||
private AuthConfig authConfig;
|
||||
private AuthenticationMetadataManager authenticationMetadataManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig = AuthTestHelper.createDefaultConfig();
|
||||
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
|
||||
this.clearAllUsers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.clearAllUsers();
|
||||
this.authenticationMetadataManager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createUser() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "test");
|
||||
Assert.assertEquals(user.getPassword(), "test");
|
||||
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
|
||||
|
||||
user = User.of("super", "super", UserType.SUPER);
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("super").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "super");
|
||||
Assert.assertEquals(user.getPassword(), "super");
|
||||
Assert.assertEquals(user.getUserType(), UserType.SUPER);
|
||||
|
||||
Assert.assertThrows(AuthenticationException.class, () -> {
|
||||
try {
|
||||
User user2 = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user2).join();
|
||||
} catch (Exception e) {
|
||||
AuthTestHelper.handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateUser() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "test");
|
||||
Assert.assertEquals(user.getPassword(), "test");
|
||||
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
|
||||
|
||||
user.setPassword("123");
|
||||
this.authenticationMetadataManager.updateUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "test");
|
||||
Assert.assertEquals(user.getPassword(), "123");
|
||||
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
|
||||
|
||||
user.setUserType(UserType.SUPER);
|
||||
this.authenticationMetadataManager.updateUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "test");
|
||||
Assert.assertEquals(user.getPassword(), "123");
|
||||
Assert.assertEquals(user.getUserType(), UserType.SUPER);
|
||||
|
||||
Assert.assertThrows(AuthenticationException.class, () -> {
|
||||
try {
|
||||
User user2 = User.of("no_user", "no_user");
|
||||
this.authenticationMetadataManager.updateUser(user2).join();
|
||||
} catch (Exception e) {
|
||||
AuthTestHelper.handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteUser() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
this.authenticationMetadataManager.deleteUser("test").join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNull(user);
|
||||
|
||||
this.authenticationMetadataManager.deleteUser("no_user").join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUser() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
user = this.authenticationMetadataManager.getUser("test").join();
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals(user.getUsername(), "test");
|
||||
Assert.assertEquals(user.getPassword(), "test");
|
||||
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
|
||||
|
||||
user = this.authenticationMetadataManager.getUser("no_user").join();
|
||||
Assert.assertNull(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listUser() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
List<User> users = this.authenticationMetadataManager.listUser(null).join();
|
||||
Assert.assertTrue(CollectionUtils.isEmpty(users));
|
||||
|
||||
User user = User.of("test-1", "test-1");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
users = this.authenticationMetadataManager.listUser(null).join();
|
||||
Assert.assertEquals(users.size(), 1);
|
||||
|
||||
user = User.of("test-2", "test-2");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
users = this.authenticationMetadataManager.listUser("test").join();
|
||||
Assert.assertEquals(users.size(), 2);
|
||||
}
|
||||
|
||||
private void clearAllUsers() {
|
||||
List<User> users = this.authenticationMetadataManager.listUser(null).join();
|
||||
if (CollectionUtils.isEmpty(users)) {
|
||||
return;
|
||||
}
|
||||
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
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.context.AuthorizationContext;
|
||||
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.manager.AuthorizationMetadataManager;
|
||||
import org.apache.rocketmq.auth.authorization.model.Acl;
|
||||
import org.apache.rocketmq.auth.authorization.model.Resource;
|
||||
import org.apache.rocketmq.auth.config.AuthConfig;
|
||||
import org.apache.rocketmq.auth.helper.AuthTestHelper;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.apache.rocketmq.common.action.Action;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AuthorizationEvaluatorTest {
|
||||
|
||||
private AuthConfig authConfig;
|
||||
private AuthorizationEvaluator evaluator;
|
||||
private AuthenticationMetadataManager authenticationMetadataManager;
|
||||
private AuthorizationMetadataManager authorizationMetadataManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig = AuthTestHelper.createDefaultConfig();
|
||||
this.evaluator = new AuthorizationEvaluator(authConfig);
|
||||
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
|
||||
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig);
|
||||
this.clearAllAcls();
|
||||
this.clearAllUsers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.clearAllAcls();
|
||||
this.clearAllUsers();
|
||||
this.authenticationMetadataManager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate1() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl).join();
|
||||
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
|
||||
// acl sourceIp is null
|
||||
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", null, Decision.ALLOW);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
|
||||
subject = Subject.of("User:test");
|
||||
resource = Resource.ofTopic("test");
|
||||
action = Action.PUB;
|
||||
sourceIp = "192.168.0.1";
|
||||
context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate2() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*,Group:test*", "Sub", "192.168.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl).join();
|
||||
|
||||
List<AuthorizationContext> contexts = new ArrayList<>();
|
||||
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.SUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context1 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context1.setRpcCode("11");
|
||||
contexts.add(context1);
|
||||
|
||||
subject = Subject.of("User:test");
|
||||
resource = Resource.ofGroup("test");
|
||||
action = Action.SUB;
|
||||
sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context2 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context2.setRpcCode("11");
|
||||
contexts.add(context2);
|
||||
|
||||
this.evaluator.evaluate(contexts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate4() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl).join();
|
||||
|
||||
// user not exist
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:abc");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
// resource not match
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("abc");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
// action not match
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.SUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
// sourceIp not match
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "10.10.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
// decision is deny
|
||||
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate5() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl = AuthTestHelper.buildAcl("User:test", "*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl).join();
|
||||
|
||||
acl = AuthTestHelper.buildAcl("User:test", "Topic:*", "Pub,Sub", "192.168.0.0/24", Decision.DENY);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
|
||||
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
|
||||
acl = AuthTestHelper.buildAcl("User:test", "Topic:test-1", "Pub,Sub", "192.168.0.0/24", Decision.DENY);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test-1");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
{
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test-2");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("abc");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
{
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofGroup("test-2");
|
||||
Action action = Action.SUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate6() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig.setAuthorizationWhitelist("10");
|
||||
this.evaluator = new AuthorizationEvaluator(this.authConfig);
|
||||
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate7() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig.setAuthorizationEnabled(false);
|
||||
this.evaluator = new AuthorizationEvaluator(this.authConfig);
|
||||
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void evaluate8() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY);
|
||||
this.authorizationMetadataManager.createAcl(acl).join();
|
||||
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("test");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("abc");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
});
|
||||
|
||||
acl = AuthTestHelper.buildAcl("User:test", PolicyType.DEFAULT, "Topic:*", "Pub", null, Decision.ALLOW);
|
||||
this.authorizationMetadataManager.updateAcl(acl).join();
|
||||
{
|
||||
Subject subject = Subject.of("User:test");
|
||||
Resource resource = Resource.ofTopic("abc");
|
||||
Action action = Action.PUB;
|
||||
String sourceIp = "192.168.0.1";
|
||||
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
|
||||
context.setRpcCode("10");
|
||||
this.evaluator.evaluate(Collections.singletonList(context));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearAllUsers() {
|
||||
List<User> users = this.authenticationMetadataManager.listUser(null).join();
|
||||
if (CollectionUtils.isEmpty(users)) {
|
||||
return;
|
||||
}
|
||||
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
|
||||
}
|
||||
|
||||
private void clearAllAcls() {
|
||||
List<Acl> acls = this.authorizationMetadataManager.listAcl(null, null).join();
|
||||
if (CollectionUtils.isEmpty(acls)) {
|
||||
return;
|
||||
}
|
||||
acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* 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.Message;
|
||||
import apache.rocketmq.v2.MessageQueue;
|
||||
import apache.rocketmq.v2.NotifyClientTerminationRequest;
|
||||
import apache.rocketmq.v2.Publishing;
|
||||
import apache.rocketmq.v2.QueryAssignmentRequest;
|
||||
import apache.rocketmq.v2.QueryRouteRequest;
|
||||
import apache.rocketmq.v2.RecallMessageRequest;
|
||||
import apache.rocketmq.v2.ReceiveMessageRequest;
|
||||
import apache.rocketmq.v2.Resource;
|
||||
import apache.rocketmq.v2.SendMessageRequest;
|
||||
import apache.rocketmq.v2.Settings;
|
||||
import apache.rocketmq.v2.Subscription;
|
||||
import apache.rocketmq.v2.SubscriptionEntry;
|
||||
import apache.rocketmq.v2.TelemetryCommand;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.protobuf.GeneratedMessageV3;
|
||||
import io.grpc.Metadata;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
|
||||
import org.apache.rocketmq.auth.config.AuthConfig;
|
||||
import org.apache.rocketmq.common.TopicFilterType;
|
||||
import org.apache.rocketmq.common.action.Action;
|
||||
import org.apache.rocketmq.common.constant.GrpcConstants;
|
||||
import org.apache.rocketmq.common.resource.ResourceType;
|
||||
import org.apache.rocketmq.remoting.netty.AttributeKeys;
|
||||
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.header.ConsumerSendMsgBackRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
|
||||
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
|
||||
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;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class DefaultAuthorizationContextBuilderTest {
|
||||
|
||||
private AuthorizationContextBuilder builder;
|
||||
|
||||
@Mock
|
||||
private ChannelHandlerContext channelHandlerContext;
|
||||
|
||||
@Mock
|
||||
private Channel channel;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
AuthConfig authConfig = new AuthConfig();
|
||||
authConfig.setClusterName("DefaultCluster");
|
||||
builder = new DefaultAuthorizationContextBuilder(authConfig);
|
||||
RequestHeaderRegistry.getInstance().initialize();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildGrpc() {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.put(GrpcConstants.AUTHORIZATION_AK, "rocketmq");
|
||||
metadata.put(GrpcConstants.REMOTE_ADDRESS, "192.168.0.1");
|
||||
metadata.put(GrpcConstants.CHANNEL_ID, "channel-id");
|
||||
|
||||
GeneratedMessageV3 request = SendMessageRequest.newBuilder()
|
||||
.addMessages(Message.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.build())
|
||||
.build();
|
||||
List<DefaultAuthorizationContext> result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1");
|
||||
Assert.assertEquals(result.get(0).getChannelId(), "channel-id");
|
||||
Assert.assertEquals(result.get(0).getRpcCode(), SendMessageRequest.getDescriptor().getFullName());
|
||||
|
||||
request = RecallMessageRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.setRecallHandle("handle")
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1");
|
||||
Assert.assertEquals(result.get(0).getChannelId(), "channel-id");
|
||||
Assert.assertEquals(result.get(0).getRpcCode(), RecallMessageRequest.getDescriptor().getFullName());
|
||||
|
||||
request = EndTransactionRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
|
||||
request = HeartbeatRequest.newBuilder()
|
||||
.setClientType(ClientType.PUSH_CONSUMER)
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = ReceiveMessageRequest.newBuilder()
|
||||
.setMessageQueue(MessageQueue.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.build())
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = AckMessageRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = ForwardMessageToDeadLetterQueueRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = NotifyClientTerminationRequest.newBuilder()
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = ChangeInvisibleDurationRequest.newBuilder()
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = QueryRouteRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB, Action.SUB)));
|
||||
|
||||
request = QueryAssignmentRequest.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
request = TelemetryCommand.newBuilder()
|
||||
.setSettings(Settings.newBuilder()
|
||||
.setPublishing(Publishing.newBuilder()
|
||||
.addTopics(Resource.newBuilder().setName("topic").build())
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
|
||||
request = TelemetryCommand.newBuilder()
|
||||
.setSettings(Settings.newBuilder()
|
||||
.setSubscription(Subscription.newBuilder()
|
||||
.setGroup(Resource.newBuilder().setName("group").build())
|
||||
.addSubscriptions(SubscriptionEntry.newBuilder()
|
||||
.setTopic(Resource.newBuilder().setName("topic").build())
|
||||
.build())
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
result = builder.build(metadata, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
|
||||
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildRemoting() {
|
||||
when(channel.id()).thenReturn(mockChannelId("channel-id"));
|
||||
when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(true);
|
||||
when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(mockAttribute("192.168.0.1"));
|
||||
when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(true);
|
||||
when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(mockAttribute("1234"));
|
||||
when(channelHandlerContext.channel()).thenReturn(channel);
|
||||
|
||||
SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader();
|
||||
sendMessageRequestHeader.setTopic("topic");
|
||||
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
List<DefaultAuthorizationContext> result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp());
|
||||
Assert.assertEquals("channel-id", result.get(0).getChannelId());
|
||||
Assert.assertEquals(RequestCode.SEND_MESSAGE + "", result.get(0).getRpcCode());
|
||||
|
||||
sendMessageRequestHeader = new SendMessageRequestHeader();
|
||||
sendMessageRequestHeader.setTopic("%RETRY%group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
SendMessageRequestHeaderV2 sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2();
|
||||
sendMessageRequestHeaderV2.setTopic("topic");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
|
||||
sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2();
|
||||
sendMessageRequestHeaderV2.setTopic("%RETRY%group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
RecallMessageRequestHeader recallMessageRequestHeader = new RecallMessageRequestHeader();
|
||||
recallMessageRequestHeader.setTopic("topic");
|
||||
recallMessageRequestHeader.setRecallHandle("handle");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, recallMessageRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp());
|
||||
Assert.assertEquals("channel-id", result.get(0).getChannelId());
|
||||
Assert.assertEquals(RequestCode.RECALL_MESSAGE + "", result.get(0).getRpcCode());
|
||||
|
||||
EndTransactionRequestHeader endTransactionRequestHeader = new EndTransactionRequestHeader();
|
||||
endTransactionRequestHeader.setTopic("topic");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
|
||||
|
||||
endTransactionRequestHeader = new EndTransactionRequestHeader();
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(0, result.size());
|
||||
|
||||
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader();
|
||||
consumerSendMsgBackRequestHeader.setGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, consumerSendMsgBackRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader();
|
||||
pullMessageRequestHeader.setTopic("topic");
|
||||
pullMessageRequestHeader.setConsumerGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
QueryMessageRequestHeader queryMessageRequestHeader = new QueryMessageRequestHeader();
|
||||
queryMessageRequestHeader.setTopic("topic");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, queryMessageRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET)));
|
||||
|
||||
HeartbeatRequestHeader heartbeatRequestHeader = new HeartbeatRequestHeader();
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, heartbeatRequestHeader);
|
||||
HeartbeatData heartbeatData = new HeartbeatData();
|
||||
ConsumerData consumerData = new ConsumerData();
|
||||
consumerData.setGroupName("group");
|
||||
SubscriptionData subscriptionData = new SubscriptionData();
|
||||
subscriptionData.setTopic("topic");
|
||||
consumerData.setSubscriptionDataSet(Sets.newHashSet(subscriptionData));
|
||||
heartbeatData.setConsumerDataSet(Sets.newHashSet(consumerData));
|
||||
request.setBody(JSON.toJSONBytes(heartbeatData));
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
UnregisterClientRequestHeader unregisterClientRequestHeader = new UnregisterClientRequestHeader();
|
||||
unregisterClientRequestHeader.setConsumerGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, unregisterClientRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = new GetConsumerListByGroupRequestHeader();
|
||||
getConsumerListByGroupRequestHeader.setConsumerGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, getConsumerListByGroupRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET)));
|
||||
|
||||
QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader = new QueryConsumerOffsetRequestHeader();
|
||||
queryConsumerOffsetRequestHeader.setTopic("topic");
|
||||
queryConsumerOffsetRequestHeader.setConsumerGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, queryConsumerOffsetRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
|
||||
|
||||
UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = new UpdateConsumerOffsetRequestHeader();
|
||||
updateConsumerOffsetRequestHeader.setTopic("topic");
|
||||
updateConsumerOffsetRequestHeader.setConsumerGroup("group");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, updateConsumerOffsetRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(2, result.size());
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE)));
|
||||
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
|
||||
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE)));
|
||||
|
||||
CreateTopicRequestHeader createTopicRequestHeader = new CreateTopicRequestHeader();
|
||||
createTopicRequestHeader.setTopic("topic");
|
||||
createTopicRequestHeader.setTopicFilterType(TopicFilterType.SINGLE_TAG.name());
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, createTopicRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.CREATE)));
|
||||
|
||||
CreateUserRequestHeader createUserRequestHeader = new CreateUserRequestHeader();
|
||||
createUserRequestHeader.setUsername("abc");
|
||||
request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader);
|
||||
request.setVersion(441);
|
||||
request.addExtField("AccessKey", "rocketmq");
|
||||
request.makeCustomHeaderToNet();
|
||||
result = builder.build(channelHandlerContext, request);
|
||||
Assert.assertEquals(1, result.size());
|
||||
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
|
||||
Assert.assertEquals("Cluster:DefaultCluster", result.get(0).getResource().getResourceKey());
|
||||
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.UPDATE)));
|
||||
}
|
||||
|
||||
private DefaultAuthorizationContext getContext(List<DefaultAuthorizationContext> contexts,
|
||||
ResourceType resourceType) {
|
||||
return contexts.stream().filter(context -> context.getResource().getResourceType() == resourceType)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private ChannelId mockChannelId(String channelId) {
|
||||
return new ChannelId() {
|
||||
@Override
|
||||
public String asShortText() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asLongText() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ChannelId o) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Attribute<String> mockAttribute(String value) {
|
||||
return new Attribute<String>() {
|
||||
@Override
|
||||
public AttributeKey<String> key() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAndSet(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setIfAbsent(String value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAndRemove() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean compareAndSet(String oldValue, String newValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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 org.apache.commons.collections.CollectionUtils;
|
||||
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.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.Policy;
|
||||
import org.apache.rocketmq.auth.authorization.model.Resource;
|
||||
import org.apache.rocketmq.auth.config.AuthConfig;
|
||||
import org.apache.rocketmq.auth.helper.AuthTestHelper;
|
||||
import org.apache.rocketmq.common.MixAll;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AuthorizationMetadataManagerTest {
|
||||
|
||||
private AuthConfig authConfig;
|
||||
|
||||
private AuthenticationMetadataManager authenticationMetadataManager;
|
||||
|
||||
private AuthorizationMetadataManager authorizationMetadataManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.authConfig = AuthTestHelper.createDefaultConfig();
|
||||
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
|
||||
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig);
|
||||
this.clearAllAcls();
|
||||
this.clearAllUsers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
this.clearAllAcls();
|
||||
this.clearAllUsers();
|
||||
this.authenticationMetadataManager.shutdown();
|
||||
this.authorizationMetadataManager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAcl() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
|
||||
|
||||
user = User.of("abc", "abc");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
acl1 = AuthTestHelper.buildAcl("User:abc", PolicyType.DEFAULT, "Topic:*,Group:*", "PUB,SUB",
|
||||
null, Decision.DENY);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
|
||||
|
||||
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl3).join();
|
||||
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
try {
|
||||
Acl acl5 = AuthTestHelper.buildAcl("User:ddd", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl5).join();
|
||||
} catch (Exception e) {
|
||||
AuthTestHelper.handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateAcl() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
|
||||
Acl acl2 = AuthTestHelper.buildAcl("User:test", "Topic:abc,Group:abc", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.updateAcl(acl2).join();
|
||||
|
||||
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test,Topic:abc,Group:abc", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
|
||||
|
||||
Policy policy = AuthTestHelper.buildPolicy("Topic:test,Group:test", "PUB,SUB,Create", "192.168.0.0/24", Decision.DENY);
|
||||
acl4.updatePolicy(policy);
|
||||
this.authorizationMetadataManager.updateAcl(acl4);
|
||||
Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl4, acl5));
|
||||
|
||||
User user2 = User.of("abc", "abc");
|
||||
this.authenticationMetadataManager.createUser(user2).join();
|
||||
Acl acl6 = AuthTestHelper.buildAcl("User:abc", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.updateAcl(acl6).join();
|
||||
Acl acl7 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl6, acl7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteAcl() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
|
||||
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("abc")).join();
|
||||
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
|
||||
|
||||
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("test")).join();
|
||||
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
|
||||
|
||||
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"));
|
||||
Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertNull(acl5);
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
try {
|
||||
this.authorizationMetadataManager.deleteAcl(Subject.of("User:abc")).join();
|
||||
} catch (Exception e) {
|
||||
AuthTestHelper.handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAcl() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user = User.of("test", "test");
|
||||
this.authenticationMetadataManager.createUser(user).join();
|
||||
|
||||
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
|
||||
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
|
||||
|
||||
Assert.assertThrows(AuthorizationException.class, () -> {
|
||||
try {
|
||||
this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
|
||||
} catch (Exception e) {
|
||||
AuthTestHelper.handleException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listAcl() {
|
||||
if (MixAll.isMac()) {
|
||||
return;
|
||||
}
|
||||
User user1 = User.of("test-1", "test-1");
|
||||
this.authenticationMetadataManager.createUser(user1).join();
|
||||
User user2 = User.of("test-2", "test-2");
|
||||
this.authenticationMetadataManager.createUser(user2).join();
|
||||
|
||||
Acl acl1 = AuthTestHelper.buildAcl("User:test-1", "Topic:test-1,Group:test-1", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl1).join();
|
||||
|
||||
Acl acl2 = AuthTestHelper.buildAcl("User:test-2", "Topic:test-2,Group:test-2", "PUB,SUB",
|
||||
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
|
||||
this.authorizationMetadataManager.createAcl(acl2).join();
|
||||
|
||||
List<Acl> acls1 = this.authorizationMetadataManager.listAcl(null, null).join();
|
||||
Assert.assertEquals(acls1.size(), 2);
|
||||
|
||||
List<Acl> acls2 = this.authorizationMetadataManager.listAcl("User:test-1", null).join();
|
||||
Assert.assertEquals(acls2.size(), 1);
|
||||
|
||||
List<Acl> acls3 = this.authorizationMetadataManager.listAcl("test", null).join();
|
||||
Assert.assertEquals(acls3.size(), 2);
|
||||
|
||||
List<Acl> acls4 = this.authorizationMetadataManager.listAcl(null, "Topic:test-1").join();
|
||||
Assert.assertEquals(acls4.size(), 1);
|
||||
Assert.assertEquals(acls4.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 1);
|
||||
|
||||
List<Acl> acls5 = this.authorizationMetadataManager.listAcl(null, "test-1").join();
|
||||
Assert.assertEquals(acls5.size(), 1);
|
||||
Assert.assertEquals(acls4.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 1);
|
||||
|
||||
List<Acl> acls6 = this.authorizationMetadataManager.listAcl("User:abc", null).join();
|
||||
Assert.assertTrue(CollectionUtils.isEmpty(acls6));
|
||||
|
||||
List<Acl> acls7 = this.authorizationMetadataManager.listAcl(null, "Topic:abc").join();
|
||||
Assert.assertTrue(CollectionUtils.isEmpty(acls7));
|
||||
}
|
||||
|
||||
private void clearAllUsers() {
|
||||
List<User> users = this.authenticationMetadataManager.listUser(null).join();
|
||||
if (CollectionUtils.isEmpty(users)) {
|
||||
return;
|
||||
}
|
||||
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
|
||||
}
|
||||
|
||||
private void clearAllAcls() {
|
||||
List<Acl> acls = this.authorizationMetadataManager.listAcl(null, null).join();
|
||||
if (CollectionUtils.isEmpty(acls)) {
|
||||
return;
|
||||
}
|
||||
acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join());
|
||||
}
|
||||
}
|
||||
@@ -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.model;
|
||||
|
||||
import org.apache.rocketmq.common.resource.ResourcePattern;
|
||||
import org.apache.rocketmq.common.resource.ResourceType;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ResourceTest {
|
||||
|
||||
@Test
|
||||
public void parseResource() {
|
||||
Resource resource = Resource.of("*");
|
||||
Assert.assertEquals(resource.getResourceType(), ResourceType.ANY);
|
||||
Assert.assertNull(resource.getResourceName());
|
||||
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY);
|
||||
|
||||
resource = Resource.of("Topic:*");
|
||||
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
|
||||
Assert.assertNull(resource.getResourceName());
|
||||
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY);
|
||||
|
||||
resource = Resource.of("Topic:test-*");
|
||||
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
|
||||
Assert.assertEquals(resource.getResourceName(), "test-");
|
||||
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.PREFIXED);
|
||||
|
||||
resource = Resource.of("Topic:test-1");
|
||||
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
|
||||
Assert.assertEquals(resource.getResourceName(), "test-1");
|
||||
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.LITERAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMatch() {
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user