[ISSUE #7560] [RIP-68] Support RocketMQ ACL 2.0 (#7725)

[ISSUE #7560] [RIP-68] Support RocketMQ ACL 2.0 (#7725)
This commit is contained in:
dingshuangxi888
2024-03-18 14:07:51 +08:00
committed by GitHub
parent e656f91da5
commit e1339ac8a0
293 changed files with 13027 additions and 401 deletions
+1
View File
@@ -41,6 +41,7 @@ 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",
+78
View 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.2.1-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-remoting</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rocketmq-common</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>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</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>
</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();
if (MapUtils.isEmpty(fields)) {
throw new AuthenticationException("authentication field is null.");
}
DefaultAuthenticationContext result = new DefaultAuthenticationContext();
result.setChannelId(context.channel().id().asLongText());
result.setRpcCode(String.valueOf(request.getCode()));
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,66 @@
/*
* 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.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 (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 (!StringUtils.equals(signature, context.getSignature())) {
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,151 @@
/*
* 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.provider.LocalAuthenticationMetadataProvider;
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 {
Class<? extends AuthenticationMetadataProvider> clazz = LocalAuthenticationMetadataProvider.class;
if (StringUtils.isNotBlank(config.getAuthenticationMetadataProvider())) {
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);
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.fastjson.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 AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
if (authenticationMetadataProvider == null) {
throw new IllegalStateException("The authenticationMetadataProvider is not configured");
}
return authorizationMetadataProvider;
}
private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
if (authorizationMetadataProvider == null) {
throw new IllegalStateException("The authorizationMetadataProvider is not configured");
}
return authenticationMetadataProvider;
}
}
@@ -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));
}
private 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,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.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.checkerframework.checker.nullness.qual.NonNull;
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(@NonNull 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,484 @@
/*
* 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.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.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 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;
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();
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.substringBefore(remoteAddr, CommonConstants.COLON);
Resource topic;
Resource group;
switch (command.getCode()) {
case RequestCode.GET_ROUTEINFO_BY_TOPIC:
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.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.substringBefore(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.substringBefore(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.substringBefore(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
result.add(DefaultAuthorizationContext.of(subject, resource, Action.SUB, sourceIp));
return result;
}
}
@@ -0,0 +1,164 @@
/*
* 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) {
return 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,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.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) {
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,154 @@
/*
* 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.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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.provider.LocalAuthorizationMetadataProvider;
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 ConcurrentMap<String, Object> INSTANCE_MAP = new ConcurrentHashMap<>();
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 {
Class<? extends AuthorizationMetadataProvider> clazz = LocalAuthorizationMetadataProvider.class;
if (StringUtils.isNotBlank(config.getAuthorizationMetadataProvider())) {
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.getAuthenticationStrategy())) {
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);
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 AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
if (authenticationMetadataProvider == null) {
throw new IllegalStateException("The authenticationMetadataProvider is not configured.");
}
return authorizationMetadataProvider;
}
private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
if (authorizationMetadataProvider == null) {
throw new IllegalStateException("The authorizationMetadataProvider is not configured.");
}
return authenticationMetadataProvider;
}
}
@@ -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));
}
private 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,200 @@
/*
* 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.checkerframework.checker.nullness.qual.NonNull;
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(@NonNull 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,229 @@
/*
* 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 java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclConstants;
import org.apache.rocketmq.acl.plain.PlainPermissionManager;
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.common.AclConfig;
import org.apache.rocketmq.common.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.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
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,123 @@
/*
* 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.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 {
this.authConfig = AuthTestHelper.createDefaultConfig();
this.evaluator = new AuthenticationEvaluator(authConfig);
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void evaluate1() {
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() {
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() {
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() {
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() {
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() {
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,118 @@
/*
* 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.jetbrains.annotations.NotNull;
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(@NotNull ChannelId o) {
return 0;
}
};
}
}
@@ -0,0 +1,164 @@
/*
* 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.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 {
this.authConfig = AuthTestHelper.createDefaultConfig();
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void createUser() {
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() {
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() {
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() {
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() {
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,335 @@
/*
* 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.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 {
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 {
this.clearAllAcls();
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void evaluate1() {
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() {
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() {
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() {
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() {
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() {
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() {
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,550 @@
/*
* 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.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.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.jetbrains.annotations.NotNull;
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 = 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)));
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(@NotNull 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,240 @@
/*
* 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.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 {
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 {
this.clearAllAcls();
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
this.authorizationMetadataManager.shutdown();
}
@Test
public void createAcl() {
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() {
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() {
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() {
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() {
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() {
}
}
@@ -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.
*/
package org.apache.rocketmq.auth.helper;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.provider.DefaultAuthenticationProvider;
import org.apache.rocketmq.auth.authentication.provider.LocalAuthenticationMetadataProvider;
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.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.DefaultAuthorizationProvider;
import org.apache.rocketmq.auth.authorization.provider.LocalAuthorizationMetadataProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.utils.ExceptionUtils;
public class AuthTestHelper {
public static AuthConfig createDefaultConfig() {
AuthConfig authConfig = new AuthConfig();
authConfig.setConfigName("test-" + System.nanoTime());
authConfig.setAuthConfigPath("~/config");
authConfig.setAuthenticationEnabled(true);
authConfig.setAuthenticationProvider(DefaultAuthenticationProvider.class.getName());
authConfig.setAuthenticationMetadataProvider(LocalAuthenticationMetadataProvider.class.getName());
authConfig.setAuthorizationEnabled(true);
authConfig.setAuthorizationProvider(DefaultAuthorizationProvider.class.getName());
authConfig.setAuthorizationMetadataProvider(LocalAuthorizationMetadataProvider.class.getName());
return authConfig;
}
public static Acl buildAcl(String subjectKey, String resources, String actions, String sourceIps,
Decision decision) {
return buildAcl(subjectKey, null, resources, actions, sourceIps, decision);
}
public static Acl buildAcl(String subjectKey, PolicyType policyType, String resources, String actions,
String sourceIps, Decision decision) {
Subject subject = Subject.of(subjectKey);
Policy policy = buildPolicy(policyType, resources, actions, sourceIps, decision);
return Acl.of(subject, policy);
}
public static Policy buildPolicy(String resources, String actions, String sourceIps,
Decision decision) {
return buildPolicy(null, resources, actions, sourceIps, decision);
}
public static Policy buildPolicy(PolicyType policyType, String resources, String actions, String sourceIps,
Decision decision) {
List<Resource> resourceList = Arrays.stream(StringUtils.split(resources, ","))
.map(Resource::of).collect(Collectors.toList());
List<Action> actionList = Arrays.stream(StringUtils.split(actions, ","))
.map(Action::getByName).collect(Collectors.toList());
Environment environment = null;
if (StringUtils.isNotBlank(sourceIps)) {
environment = Environment.of(Arrays.stream(StringUtils.split(sourceIps, ","))
.collect(Collectors.toList()));
}
return Policy.of(policyType, resourceList, actionList, environment, decision);
}
public static boolean isEquals(Acl acl1, Acl acl2) {
if (acl1 == null && acl2 == null) {
return true;
}
if (acl1 == null || acl2 == null) {
return false;
}
Subject subject1 = acl1.getSubject();
Subject subject2 = acl2.getSubject();
if (!isEquals(subject1, subject2)) {
return false;
}
Map<PolicyType, Policy> policyMap1 = new HashMap<>();
Map<PolicyType, Policy> policyMap2 = new HashMap<>();
if (CollectionUtils.isNotEmpty(acl1.getPolicies())) {
acl1.getPolicies().forEach(policy -> {
if (policy.getPolicyType() == null) {
policy.setPolicyType(PolicyType.CUSTOM);
}
policyMap1.put(policy.getPolicyType(), policy);
});
}
if (CollectionUtils.isNotEmpty(acl2.getPolicies())) {
acl2.getPolicies().forEach(policy -> {
if (policy.getPolicyType() == null) {
policy.setPolicyType(PolicyType.CUSTOM);
}
policyMap2.put(policy.getPolicyType(), policy);
});
}
if (policyMap1.size() != policyMap2.size()) {
return false;
}
Policy customPolicy1 = policyMap1.get(PolicyType.CUSTOM);
Policy customPolicy2 = policyMap2.get(PolicyType.CUSTOM);
if (!isEquals(customPolicy1, customPolicy2)) {
return false;
}
Policy defaultPolicy1 = policyMap1.get(PolicyType.DEFAULT);
Policy defaultPolicy2 = policyMap2.get(PolicyType.DEFAULT);
if (!isEquals(defaultPolicy1, defaultPolicy2)) {
return false;
}
return true;
}
private static boolean isEquals(Policy policy1, Policy policy2) {
if (policy1 == null && policy2 == null) {
return true;
}
if (policy1 == null || policy2 == null) {
return false;
}
if (policy1.getPolicyType() != policy2.getPolicyType()) {
return false;
}
Map<String, PolicyEntry> policyEntryMap1 = new HashMap<>();
Map<String, PolicyEntry> policyEntryMap2 = new HashMap<>();
if (CollectionUtils.isNotEmpty(policy1.getEntries())) {
policy1.getEntries().forEach(policyEntry -> {
policyEntryMap1.put(policyEntry.getResource().getResourceKey(), policyEntry);
});
}
if (CollectionUtils.isNotEmpty(policy2.getEntries())) {
policy2.getEntries().forEach(policyEntry -> {
policyEntryMap2.put(policyEntry.getResource().getResourceKey(), policyEntry);
});
}
if (policyEntryMap1.size() != policyEntryMap2.size()) {
return false;
}
for (String resourceKey : policyEntryMap1.keySet()) {
if (!isEquals(policyEntryMap1.get(resourceKey), policyEntryMap2.get(resourceKey))) {
return false;
}
}
for (String resourceKey : policyEntryMap2.keySet()) {
if (!isEquals(policyEntryMap1.get(resourceKey), policyEntryMap2.get(resourceKey))) {
return false;
}
}
return true;
}
private static boolean isEquals(PolicyEntry entry1, PolicyEntry entry2) {
if (entry1 == null && entry2 == null) {
return true;
}
if (entry1 == null || entry2 == null) {
return false;
}
Resource resource1 = entry1.getResource();
Resource resource2 = entry2.getResource();
if (!isEquals(resource1, resource2)) {
return false;
}
List<Action> actions1 = entry1.getActions();
List<Action> actions2 = entry2.getActions();
if (CollectionUtils.isEmpty(actions1) && CollectionUtils.isNotEmpty(actions2)) {
return false;
}
if (CollectionUtils.isNotEmpty(actions1) && CollectionUtils.isEmpty(actions2)) {
return false;
}
if (CollectionUtils.isNotEmpty(actions1) && CollectionUtils.isNotEmpty(actions2)
&& !CollectionUtils.isEqualCollection(actions1, actions2)) {
return false;
}
Environment environment1 = entry1.getEnvironment();
Environment environment2 = entry2.getEnvironment();
if (!isEquals(environment1, environment2)) {
return false;
}
return entry1.getDecision() == entry2.getDecision();
}
private static boolean isEquals(Resource resource1, Resource resource2) {
if (resource1 == null && resource2 == null) {
return true;
}
if (resource1 == null || resource2 == null) {
return false;
}
return Objects.equals(resource1, resource2);
}
private static boolean isEquals(Environment environment1, Environment environment2) {
if (environment1 == null && environment2 == null) {
return true;
}
if (environment1 == null || environment2 == null) {
return false;
}
List<String> sourceIp1 = environment1.getSourceIps();
List<String> sourceIp2 = environment2.getSourceIps();
if (CollectionUtils.isEmpty(sourceIp1) && CollectionUtils.isEmpty(sourceIp2)) {
return true;
}
if (CollectionUtils.isEmpty(sourceIp1) || CollectionUtils.isEmpty(sourceIp2)) {
return false;
}
return CollectionUtils.isEqualCollection(sourceIp1, sourceIp2);
}
private static boolean isEquals(Subject subject1, Subject subject2) {
if (subject1 == null && subject2 == null) {
return true;
}
if (subject1 == null || subject2 == null) {
return false;
}
return subject1.getSubjectType() == subject2.getSubjectType()
&& StringUtils.equals(subject1.getSubjectKey(), subject2.getSubjectKey());
}
public static void handleException(Throwable e) {
Throwable throwable = ExceptionUtils.getRealException(e);
if (throwable instanceof AuthenticationException) {
throw (AuthenticationException) throwable;
}
if (throwable instanceof AuthorizationException) {
throw (AuthorizationException) throwable;
}
throw new RuntimeException(e);
}
}
+4
View File
@@ -62,6 +62,10 @@
<groupId>${project.groupId}</groupId>
<artifactId>rocketmq-acl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-auth</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
@@ -16,6 +16,8 @@
*/
package org.apache.rocketmq.broker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.AbstractMap;
@@ -40,11 +42,16 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import org.apache.rocketmq.acl.AccessValidator;
import org.apache.rocketmq.acl.plain.PlainAccessValidator;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.migration.AuthMigrator;
import org.apache.rocketmq.broker.auth.pipeline.AuthenticationPipeline;
import org.apache.rocketmq.broker.auth.pipeline.AuthorizationPipeline;
import org.apache.rocketmq.broker.client.ClientHousekeepingService;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ConsumerManager;
@@ -137,11 +144,13 @@ import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
import org.apache.rocketmq.remoting.netty.NettyServerConfig;
import org.apache.rocketmq.remoting.netty.RequestTask;
import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
import org.apache.rocketmq.remoting.pipeline.RequestPipeline;
import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo;
import org.apache.rocketmq.remoting.protocol.DataVersion;
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.BrokerMemberGroup;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
@@ -178,6 +187,7 @@ public class BrokerController {
private final NettyServerConfig nettyServerConfig;
private final NettyClientConfig nettyClientConfig;
protected final MessageStoreConfig messageStoreConfig;
private final AuthConfig authConfig;
protected final ConsumerOffsetManager consumerOffsetManager;
protected final BroadcastOffsetManager broadcastOffsetManager;
protected final ConsumerManager consumerManager;
@@ -279,15 +289,18 @@ public class BrokerController {
private ColdDataPullRequestHoldService coldDataPullRequestHoldService;
private ColdDataCgCtrService coldDataCgCtrService;
private TransactionMetricsFlushService transactionMetricsFlushService;
private AuthenticationMetadataManager authenticationMetadataManager;
private AuthorizationMetadataManager authorizationMetadataManager;
public BrokerController(
final BrokerConfig brokerConfig,
final NettyServerConfig nettyServerConfig,
final NettyClientConfig nettyClientConfig,
final MessageStoreConfig messageStoreConfig,
final AuthConfig authConfig,
final ShutdownHook shutdownHook
) {
this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig);
this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, authConfig);
this.shutdownHook = shutdownHook;
}
@@ -295,7 +308,7 @@ public class BrokerController {
final BrokerConfig brokerConfig,
final MessageStoreConfig messageStoreConfig
) {
this(brokerConfig, null, null, messageStoreConfig);
this(brokerConfig, null, null, messageStoreConfig, null);
}
public BrokerController(
@@ -303,11 +316,22 @@ public class BrokerController {
final NettyServerConfig nettyServerConfig,
final NettyClientConfig nettyClientConfig,
final MessageStoreConfig messageStoreConfig
) {
this(brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, null);
}
public BrokerController(
final BrokerConfig brokerConfig,
final NettyServerConfig nettyServerConfig,
final NettyClientConfig nettyClientConfig,
final MessageStoreConfig messageStoreConfig,
final AuthConfig authConfig
) {
this.brokerConfig = brokerConfig;
this.nettyServerConfig = nettyServerConfig;
this.nettyClientConfig = nettyClientConfig;
this.messageStoreConfig = messageStoreConfig;
this.authConfig = authConfig;
this.setStoreHost(new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), getListenPort()));
this.brokerStatsManager = messageStoreConfig.isEnableLmq() ? new LmqBrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()) : new BrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat());
this.broadcastOffsetManager = new BroadcastOffsetManager(this);
@@ -321,6 +345,8 @@ public class BrokerController {
this.consumerOffsetManager = messageStoreConfig.isEnableLmq() ? new LmqConsumerOffsetManager(this) : new ConsumerOffsetManager(this);
}
this.topicQueueMappingManager = new TopicQueueMappingManager(this);
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig);
this.pullMessageProcessor = new PullMessageProcessor(this);
this.peekMessageProcessor = new PeekMessageProcessor(this);
this.pullRequestHoldService = messageStoreConfig.isEnableLmq() ? new LmqPullRequestHoldService(this) : new PullRequestHoldService(this);
@@ -345,7 +371,7 @@ public class BrokerController {
this.coldDataCgCtrService = new ColdDataCgCtrService(this);
if (nettyClientConfig != null) {
this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig);
this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig, authConfig);
}
this.queryAssignmentProcessor = new QueryAssignmentProcessor(this);
@@ -414,6 +440,14 @@ public class BrokerController {
if (this.brokerConfig.isEnableSlaveActingMaster() && !this.brokerConfig.isSkipPreOnline()) {
this.brokerPreOnlineService = new BrokerPreOnlineService(this);
}
if (this.authConfig != null && this.authConfig.isMigrateAuthFromV1Enabled()) {
new AuthMigrator(this.authConfig).migrate();
}
}
public AuthConfig getAuthConfig() {
return authConfig;
}
public BrokerConfig getBrokerConfig() {
@@ -845,6 +879,8 @@ public class BrokerController {
initialRpcHooks();
initialRequestPipeline();
if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
// Register a listener to reload SslContext
try {
@@ -1012,6 +1048,23 @@ public class BrokerController {
}
}
private void initialRequestPipeline() {
if (this.authConfig == null) {
return;
}
RequestPipeline pipeline = (ctx, request) -> {
};
// add pipeline
// the last pipe add will execute at the first
try {
pipeline = pipeline.pipe(new AuthorizationPipeline(authConfig))
.pipe(new AuthenticationPipeline(authConfig));
this.setRequestPipeline(pipeline);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void registerProcessor() {
/*
* SendMessageProcessor
@@ -1129,6 +1182,11 @@ public class BrokerController {
AdminBrokerProcessor adminProcessor = new AdminBrokerProcessor(this);
this.remotingServer.registerDefaultProcessor(adminProcessor, this.adminBrokerExecutor);
this.fastRemotingServer.registerDefaultProcessor(adminProcessor, this.adminBrokerExecutor);
/*
* Initialize the mapping of request codes to request headers.
*/
RequestHeaderRegistry.getInstance().initialize();
}
public BrokerStats getBrokerStats() {
@@ -1487,6 +1545,14 @@ public class BrokerController {
this.consumerOffsetManager.stop();
}
if (this.authenticationMetadataManager != null) {
this.authenticationMetadataManager.shutdown();
}
if (this.authorizationMetadataManager != null) {
this.authorizationMetadataManager.shutdown();
}
for (BrokerAttachedPlugin brokerAttachedPlugin : brokerAttachedPlugins) {
if (brokerAttachedPlugin != null) {
brokerAttachedPlugin.shutdown();
@@ -2154,6 +2220,26 @@ public class BrokerController {
return topicQueueMappingManager;
}
public AuthenticationMetadataManager getAuthenticationMetadataManager() {
return authenticationMetadataManager;
}
@VisibleForTesting
public void setAuthenticationMetadataManager(
AuthenticationMetadataManager authenticationMetadataManager) {
this.authenticationMetadataManager = authenticationMetadataManager;
}
public AuthorizationMetadataManager getAuthorizationMetadataManager() {
return authorizationMetadataManager;
}
@VisibleForTesting
public void setAuthorizationMetadataManager(
AuthorizationMetadataManager authorizationMetadataManager) {
this.authorizationMetadataManager = authorizationMetadataManager;
}
public String getHAServerAddr() {
return this.brokerConfig.getBrokerIP2() + ":" + this.messageStoreConfig.getHaListenPort();
}
@@ -2217,6 +2303,11 @@ public class BrokerController {
this.fastRemotingServer.registerRPCHook(rpcHook);
}
public void setRequestPipeline(RequestPipeline pipeline) {
this.getRemotingServer().setRequestPipeline(pipeline);
this.fastRemotingServer.setRequestPipeline(pipeline);
}
public RemotingServer getRemotingServer() {
return remotingServer;
}
@@ -17,6 +17,7 @@
package org.apache.rocketmq.broker;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -27,6 +28,7 @@ import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.BrokerConfig;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
@@ -86,6 +88,7 @@ public class BrokerStartup {
final NettyServerConfig nettyServerConfig = new NettyServerConfig();
final NettyClientConfig nettyClientConfig = new NettyClientConfig();
final MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
final AuthConfig authConfig = new AuthConfig();
nettyServerConfig.setListenPort(10911);
messageStoreConfig.setHaListenPort(0);
@@ -112,6 +115,7 @@ public class BrokerStartup {
MixAll.properties2Object(properties, nettyServerConfig);
MixAll.properties2Object(properties, nettyClientConfig);
MixAll.properties2Object(properties, messageStoreConfig);
MixAll.properties2Object(properties, authConfig);
}
MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), brokerConfig);
@@ -204,8 +208,12 @@ public class BrokerStartup {
MixAll.printObjectProperties(log, nettyClientConfig);
MixAll.printObjectProperties(log, messageStoreConfig);
authConfig.setConfigName(brokerConfig.getBrokerName());
authConfig.setClusterName(brokerConfig.getBrokerClusterName());
authConfig.setAuthConfigPath(messageStoreConfig.getStorePathRootDir() + File.separator + "config");
final BrokerController controller = new BrokerController(
brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig);
brokerConfig, nettyServerConfig, nettyClientConfig, messageStoreConfig, authConfig);
// Remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
@@ -0,0 +1,126 @@
/*
* 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.broker.auth.converter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.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.common.action.Action;
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
public class AclConverter {
public static Acl convertAcl(AclInfo aclInfo) {
if (aclInfo == null) {
return null;
}
Subject subject = Subject.of(aclInfo.getSubject());
List<Policy> policies = new ArrayList<>();
for (AclInfo.PolicyInfo policy : aclInfo.getPolicies()) {
PolicyType policyType = PolicyType.getByName(policy.getPolicyType());
List<AclInfo.PolicyEntryInfo> entryInfos = policy.getEntries();
if (CollectionUtils.isEmpty(entryInfos)) {
continue;
}
List<PolicyEntry> entries = new ArrayList<>();
for (AclInfo.PolicyEntryInfo entryInfo : entryInfos) {
Resource resource = Resource.of(entryInfo.getResource());
List<Action> actions = new ArrayList<>();
for (String a : entryInfo.getActions()) {
Action action = Action.getByName(a);
if (action == null) {
continue;
}
actions.add(action);
}
Environment environment = new Environment();
if (CollectionUtils.isNotEmpty(entryInfo.getSourceIps())) {
environment.setSourceIps(entryInfo.getSourceIps());
}
Decision decision = Decision.getByName(entryInfo.getDecision());
entries.add(PolicyEntry.of(resource, actions, environment, decision));
}
policies.add(Policy.of(policyType, entries));
}
return Acl.of(subject, policies);
}
public static List<AclInfo> convertAcls(List<Acl> acls) {
if (CollectionUtils.isEmpty(acls)) {
return null;
}
return acls.stream().map(AclConverter::convertAcl)
.collect(Collectors.toList());
}
public static AclInfo convertAcl(Acl acl) {
if (acl == null) {
return null;
}
AclInfo aclInfo = new AclInfo();
aclInfo.setSubject(acl.getSubject().getSubjectKey());
if (CollectionUtils.isEmpty(acl.getPolicies())) {
return aclInfo;
}
List<AclInfo.PolicyInfo> policyInfos = acl.getPolicies().stream()
.map(AclConverter::convertPolicy)
.collect(Collectors.toList());
aclInfo.setPolicies(policyInfos);
return aclInfo;
}
private static AclInfo.PolicyInfo convertPolicy(Policy policy) {
AclInfo.PolicyInfo policyInfo = new AclInfo.PolicyInfo();
if (policy.getPolicyType() != null) {
policyInfo.setPolicyType(policy.getPolicyType().getName());
}
if (CollectionUtils.isEmpty(policy.getEntries())) {
return policyInfo;
}
List<AclInfo.PolicyEntryInfo> entryInfos = policy.getEntries().stream()
.map(AclConverter::convertPolicyEntry).collect(Collectors.toList());
policyInfo.setEntries(entryInfos);
return policyInfo;
}
private static AclInfo.PolicyEntryInfo convertPolicyEntry(PolicyEntry entry) {
AclInfo.PolicyEntryInfo entryInfo = new AclInfo.PolicyEntryInfo();
entryInfo.setResource(entry.toResourceStr());
entryInfo.setActions(entry.toActionsStr());
if (entry.getEnvironment() != null) {
entryInfo.setSourceIps(entry.getEnvironment().getSourceIps());
}
entryInfo.setDecision(entry.getDecision().getName());
return entryInfo;
}
}
@@ -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.broker.auth.converter;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
public class UserConverter {
public static List<UserInfo> convertUsers(List<User> users) {
return users.stream().map(UserConverter::convertUser)
.collect(Collectors.toList());
}
public static UserInfo convertUser(User user) {
UserInfo result = new UserInfo();
result.setUsername(user.getUsername());
result.setPassword(user.getPassword());
if (user.getUserType() != null) {
result.setUserType(user.getUserType().getName());
}
if (user.getUserStatus() != null) {
result.setUserStatus(user.getUserStatus().getName());
}
return result;
}
public static User convertUser(UserInfo userInfo) {
User result = new User();
result.setUsername(userInfo.getUsername());
result.setPassword(userInfo.getPassword());
result.setUserType(UserType.getByName(userInfo.getUserType()));
result.setUserStatus(UserStatus.getByName(userInfo.getUserStatus()));
return result;
}
}
@@ -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.broker.auth.pipeline;
import io.netty.channel.ChannelHandlerContext;
import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator;
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.config.AuthConfig;
import org.apache.rocketmq.common.AbortProcessException;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.remoting.pipeline.RequestPipeline;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
public class AuthenticationPipeline implements RequestPipeline {
protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
private final AuthConfig authConfig;
private final AuthenticationEvaluator evaluator;
public AuthenticationPipeline(AuthConfig authConfig) {
this.authConfig = authConfig;
this.evaluator = AuthenticationFactory.getEvaluator(authConfig);
}
@Override
public void execute(ChannelHandlerContext ctx, RemotingCommand request) throws Exception {
if (!authConfig.isAuthenticationEnabled()) {
return;
}
try {
AuthenticationContext authenticationContext = newContext(ctx, request);
evaluator.evaluate(authenticationContext);
} catch (AuthenticationException ex) {
throw new AbortProcessException(ResponseCode.NO_PERMISSION, ex.getMessage());
} catch (Throwable ex) {
LOGGER.error("authenticate failed, request:{}", request, ex);
throw ex;
}
}
protected AuthenticationContext newContext(ChannelHandlerContext ctx, RemotingCommand request) {
return AuthenticationFactory.newContext(authConfig, ctx, request);
}
}
@@ -0,0 +1,65 @@
/*
* 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.broker.auth.pipeline;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator;
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.config.AuthConfig;
import org.apache.rocketmq.common.AbortProcessException;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.remoting.pipeline.RequestPipeline;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
public class AuthorizationPipeline implements RequestPipeline {
protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
private final AuthConfig authConfig;
private final AuthorizationEvaluator evaluator;
public AuthorizationPipeline(AuthConfig authConfig) {
this.authConfig = authConfig;
this.evaluator = AuthorizationFactory.getEvaluator(authConfig);
}
@Override
public void execute(ChannelHandlerContext ctx, RemotingCommand request) throws Exception {
if (!authConfig.isAuthorizationEnabled()) {
return;
}
try {
List<AuthorizationContext> contexts = newContexts(ctx, request);
evaluator.evaluate(contexts);
} catch (AuthorizationException | AuthenticationException ex) {
throw new AbortProcessException(ResponseCode.NO_PERMISSION, ex.getMessage());
} catch (Throwable ex) {
LOGGER.error("authorization failed, request:{}", request, ex);
throw ex;
}
}
protected List<AuthorizationContext> newContexts(ChannelHandlerContext ctx, RemotingCommand request) {
return AuthorizationFactory.newContexts(authConfig, ctx, request);
}
}
@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.broker.out;
import com.alibaba.fastjson2.JSON;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
@@ -30,6 +31,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
import org.apache.rocketmq.client.exception.MQBrokerException;
@@ -154,17 +158,30 @@ public class BrokerOuterAPI {
private final RpcClient rpcClient;
private String nameSrvAddr = null;
public BrokerOuterAPI(final NettyClientConfig nettyClientConfig) {
this(nettyClientConfig, new DynamicalExtFieldRPCHook(), new ClientMetadata());
public BrokerOuterAPI(final NettyClientConfig nettyClientConfig, AuthConfig authConfig) {
this(nettyClientConfig, authConfig, new DynamicalExtFieldRPCHook(), new ClientMetadata());
}
private BrokerOuterAPI(final NettyClientConfig nettyClientConfig, RPCHook rpcHook, ClientMetadata clientMetadata) {
private BrokerOuterAPI(final NettyClientConfig nettyClientConfig, AuthConfig authConfig, RPCHook rpcHook, ClientMetadata clientMetadata) {
this.remotingClient = new NettyRemotingClient(nettyClientConfig);
this.clientMetadata = clientMetadata;
this.remotingClient.registerRPCHook(rpcHook);
this.remotingClient.registerRPCHook(newAclRPCHook(authConfig));
this.rpcClient = new RpcClientImpl(this.clientMetadata, this.remotingClient);
}
private RPCHook newAclRPCHook(AuthConfig config) {
if (config == null || StringUtils.isBlank(config.getInnerClientAuthenticationCredentials())) {
return null;
}
SessionCredentials sessionCredentials =
JSON.parseObject(config.getInnerClientAuthenticationCredentials(), SessionCredentials.class);
if (StringUtils.isBlank(sessionCredentials.getAccessKey()) || StringUtils.isBlank(sessionCredentials.getSecretKey())) {
return null;
}
return new AclClientRPCHook(sessionCredentials);
}
public void start() {
this.remotingClient.start();
}
@@ -38,10 +38,21 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.AccessValidator;
import org.apache.rocketmq.acl.plain.PlainAccessValidator;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.auth.converter.AclConverter;
import org.apache.rocketmq.broker.auth.converter.UserConverter;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerGroupInfo;
import org.apache.rocketmq.broker.controller.ReplicasManager;
@@ -75,6 +86,7 @@ import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.stats.StatsItem;
import org.apache.rocketmq.common.stats.StatsSnapshot;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.common.utils.ExceptionUtils;
import org.apache.rocketmq.filter.util.BitsArray;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
@@ -93,6 +105,7 @@ import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.OffsetWrapper;
import org.apache.rocketmq.remoting.protocol.admin.TopicOffset;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup;
import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData;
import org.apache.rocketmq.remoting.protocol.body.BrokerStatsItem;
@@ -118,15 +131,21 @@ import org.apache.rocketmq.remoting.protocol.body.SyncStateSet;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.apache.rocketmq.remoting.protocol.header.CloneGroupOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateAccessConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteAccessConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ExchangeHAInfoResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.GetBrokerAclConfigResponseHeader;
@@ -146,6 +165,9 @@ import org.apache.rocketmq.remoting.protocol.header.GetProducerConnectionListReq
import org.apache.rocketmq.remoting.protocol.header.GetSubscriptionGroupConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.NotifyBrokerRoleChangedRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.NotifyMinBrokerIdChangeRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.QueryConsumeQueueRequestHeader;
@@ -159,8 +181,10 @@ import org.apache.rocketmq.remoting.protocol.header.ResetOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.SearchOffsetResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader;
import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.remoting.protocol.statictopic.LogicQueueMappingItem;
@@ -335,6 +359,26 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
return this.getBrokerEpochCache(ctx, request);
case RequestCode.NOTIFY_BROKER_ROLE_CHANGED:
return this.notifyBrokerRoleChanged(ctx, request);
case RequestCode.AUTH_CREATE_USER:
return this.createUser(ctx, request);
case RequestCode.AUTH_UPDATE_USER:
return this.updateUser(ctx, request);
case RequestCode.AUTH_DELETE_USER:
return this.deleteUser(ctx, request);
case RequestCode.AUTH_GET_USER:
return this.getUser(ctx, request);
case RequestCode.AUTH_LIST_USER:
return this.listUser(ctx, request);
case RequestCode.AUTH_CREATE_ACL:
return this.createAcl(ctx, request);
case RequestCode.AUTH_UPDATE_ACL:
return this.updateAcl(ctx, request);
case RequestCode.AUTH_DELETE_ACL:
return this.deleteAcl(ctx, request);
case RequestCode.AUTH_GET_ACL:
return this.getAcl(ctx, request);
case RequestCode.AUTH_LIST_ACL:
return this.listAcl(ctx, request);
default:
return getUnknownCmdResponse(ctx, request);
}
@@ -1851,13 +1895,13 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
/**
* Reset consumer offset.
*
* @param topic Required, not null.
* @param group Required, not null.
* @param queueId if target queue ID is negative, all message queues will be reset; otherwise, only the target queue
* would get reset.
* @param topic Required, not null.
* @param group Required, not null.
* @param queueId if target queue ID is negative, all message queues will be reset; otherwise, only the target queue
* would get reset.
* @param timestamp if timestamp is negative, offset would be reset to broker offset at the time being; otherwise,
* binary search is performed to locate target offset.
* @param offset Target offset to reset to if target queue ID is properly provided.
* binary search is performed to locate target offset.
* @param offset Target offset to reset to if target queue ID is properly provided.
* @return Affected queues and their new offset
*/
private RemotingCommand resetOffsetInner(String topic, String group, int queueId, long timestamp, Long offset) {
@@ -2797,6 +2841,306 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
return response;
}
private RemotingCommand createUser(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
CreateUserRequestHeader requestHeader = request.decodeCommandCustomHeader(CreateUserRequestHeader.class);
if (StringUtils.isEmpty(requestHeader.getUsername())) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("The username is blank");
return response;
}
UserInfo userInfo = RemotingSerializable.decode(request.getBody(), UserInfo.class);
userInfo.setUsername(requestHeader.getUsername());
User user = UserConverter.convertUser(userInfo);
if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("The super user can only be create by super user");
return response;
}
this.brokerController.getAuthenticationMetadataManager().createUser(user)
.thenAccept(nil -> response.setCode(ResponseCode.SUCCESS))
.exceptionally(ex -> {
LOGGER.error("create user {} error", user.getUsername(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand updateUser(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
UpdateUserRequestHeader requestHeader = request.decodeCommandCustomHeader(UpdateUserRequestHeader.class);
if (StringUtils.isEmpty(requestHeader.getUsername())) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("The username is blank");
return response;
}
UserInfo userInfo = RemotingSerializable.decode(request.getBody(), UserInfo.class);
userInfo.setUsername(requestHeader.getUsername());
User user = UserConverter.convertUser(userInfo);
if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("The super user can only be update by super user");
return response;
}
this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername())
.thenCompose(old -> {
if (old == null) {
throw new AuthenticationException("The user is not exist");
}
if (old.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) {
throw new AuthenticationException("The super user can only be update by super user");
}
return this.brokerController.getAuthenticationMetadataManager().updateUser(old);
}).thenAccept(nil -> response.setCode(ResponseCode.SUCCESS))
.exceptionally(ex -> {
LOGGER.error("delete user {} error", requestHeader.getUsername(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand deleteUser(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
DeleteUserRequestHeader requestHeader = request.decodeCommandCustomHeader(DeleteUserRequestHeader.class);
this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername())
.thenCompose(user -> {
if (user == null) {
return CompletableFuture.completedFuture(null);
}
if (user.getUserType() == UserType.SUPER && isNotSuperUserLogin(request)) {
throw new AuthenticationException("The super user can only be update by super user");
}
return this.brokerController.getAuthenticationMetadataManager().deleteUser(requestHeader.getUsername());
}).thenAccept(nil -> response.setCode(ResponseCode.SUCCESS))
.exceptionally(ex -> {
LOGGER.error("delete user {} error", requestHeader.getUsername(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand getUser(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
GetUserRequestHeader requestHeader = request.decodeCommandCustomHeader(GetUserRequestHeader.class);
if (StringUtils.isBlank(requestHeader.getUsername())) {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("The username is blank");
return response;
}
this.brokerController.getAuthenticationMetadataManager().getUser(requestHeader.getUsername())
.thenAccept(user -> {
response.setCode(ResponseCode.SUCCESS);
if (user != null) {
UserInfo userInfo = UserConverter.convertUser(user);
response.setBody(JSON.toJSONString(userInfo).getBytes(StandardCharsets.UTF_8));
}
})
.exceptionally(ex -> {
LOGGER.error("get user {} error", requestHeader.getUsername(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand listUser(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
ListUsersRequestHeader requestHeader = request.decodeCommandCustomHeader(ListUsersRequestHeader.class);
this.brokerController.getAuthenticationMetadataManager().listUser(requestHeader.getFilter())
.thenAccept(users -> {
response.setCode(ResponseCode.SUCCESS);
if (CollectionUtils.isNotEmpty(users)) {
List<UserInfo> userInfos = UserConverter.convertUsers(users);
response.setBody(JSON.toJSONString(userInfos).getBytes(StandardCharsets.UTF_8));
}
})
.exceptionally(ex -> {
LOGGER.error("list user by {} error", requestHeader.getFilter(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand createAcl(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
CreateAclRequestHeader requestHeader = request.decodeCommandCustomHeader(CreateAclRequestHeader.class);
Subject subject = Subject.of(requestHeader.getSubject());
AclInfo aclInfo = RemotingSerializable.decode(request.getBody(), AclInfo.class);
if (aclInfo == null || CollectionUtils.isEmpty(aclInfo.getPolicies())) {
throw new AuthorizationException("The body of acl is null");
}
Acl acl = AclConverter.convertAcl(aclInfo);
if (acl != null && acl.getSubject() == null) {
acl.setSubject(subject);
}
this.brokerController.getAuthorizationMetadataManager().createAcl(acl)
.thenAccept(nil -> response.setCode(ResponseCode.SUCCESS))
.exceptionally(ex -> {
LOGGER.error("create acl for {} error", requestHeader.getSubject(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand updateAcl(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
RemotingCommand response = RemotingCommand.createResponseCommand(null);
UpdateAclRequestHeader requestHeader = request.decodeCommandCustomHeader(UpdateAclRequestHeader.class);
Subject subject = Subject.of(requestHeader.getSubject());
AclInfo aclInfo = RemotingSerializable.decode(request.getBody(), AclInfo.class);
if (aclInfo == null || CollectionUtils.isEmpty(aclInfo.getPolicies())) {
throw new AuthorizationException("The body of acl is null");
}
Acl acl = AclConverter.convertAcl(aclInfo);
if (acl != null && acl.getSubject() == null) {
acl.setSubject(subject);
}
this.brokerController.getAuthorizationMetadataManager().updateAcl(acl)
.thenAccept(nil -> response.setCode(ResponseCode.SUCCESS))
.exceptionally(ex -> {
LOGGER.error("update acl for {} error", requestHeader.getSubject(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand deleteAcl(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
DeleteAclRequestHeader requestHeader = request.decodeCommandCustomHeader(DeleteAclRequestHeader.class);
Subject subject = Subject.of(requestHeader.getSubject());
PolicyType policyType = PolicyType.getByName(requestHeader.getPolicyType());
Resource resource = Resource.of(requestHeader.getResource());
this.brokerController.getAuthorizationMetadataManager().deleteAcl(subject, policyType, resource)
.thenAccept(nil -> {
response.setCode(ResponseCode.SUCCESS);
})
.exceptionally(ex -> {
LOGGER.error("delete acl for {} error", requestHeader.getSubject(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand getAcl(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
GetAclRequestHeader requestHeader = request.decodeCommandCustomHeader(GetAclRequestHeader.class);
Subject subject = Subject.of(requestHeader.getSubject());
this.brokerController.getAuthorizationMetadataManager().getAcl(subject)
.thenAccept(acl -> {
response.setCode(ResponseCode.SUCCESS);
if (acl != null) {
AclInfo aclInfo = AclConverter.convertAcl(acl);
String body = JSON.toJSONString(aclInfo);
response.setBody(body.getBytes(StandardCharsets.UTF_8));
}
})
.exceptionally(ex -> {
LOGGER.error("get acl for {} error", requestHeader.getSubject(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private RemotingCommand listAcl(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
ListAclsRequestHeader requestHeader = request.decodeCommandCustomHeader(ListAclsRequestHeader.class);
this.brokerController.getAuthorizationMetadataManager()
.listAcl(requestHeader.getSubjectFilter(), requestHeader.getResourceFilter())
.thenAccept(acls -> {
response.setCode(ResponseCode.SUCCESS);
if (CollectionUtils.isNotEmpty(acls)) {
List<AclInfo> aclInfos = AclConverter.convertAcls(acls);
String body = JSON.toJSONString(aclInfos);
response.setBody(body.getBytes(StandardCharsets.UTF_8));
}
})
.exceptionally(ex -> {
LOGGER.error("list acl error, subjectFilter:{}, resourceFilter:{}", requestHeader.getSubjectFilter(), requestHeader.getResourceFilter(), ex);
return handleAuthException(response, ex);
})
.join();
return response;
}
private boolean isNotSuperUserLogin(RemotingCommand request) {
String accessKey = request.getExtFields().get("AccessKey");
// if accessKey is null, it may be authentication is not enabled.
if (StringUtils.isEmpty(accessKey)) {
return false;
}
return !this.brokerController.getAuthenticationMetadataManager()
.isSuperUser(accessKey).join();
}
private Void handleAuthException(RemotingCommand response, Throwable ex) {
Throwable throwable = ExceptionUtils.getRealException(ex);
if (throwable instanceof AuthenticationException || throwable instanceof AuthorizationException) {
response.setCode(ResponseCode.NO_PERMISSION);
response.setRemark(throwable.getMessage());
} else {
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("An system error occurred, please try again later.");
LOGGER.error("An system error occurred when processing auth admin request.", ex);
}
return null;
}
private boolean validateSlave(RemotingCommand response) {
if (this.brokerController.getMessageStoreConfig().getBrokerRole().equals(BrokerRole.SLAVE)) {
response.setCode(ResponseCode.SYSTEM_ERROR);
@@ -50,6 +50,7 @@ public abstract class AbstractTransactionalMessageCheckListener {
public void sendCheckMessage(MessageExt msgExt) throws Exception {
CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();
checkTransactionStateRequestHeader.setTopic(msgExt.getTopic());
checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
@@ -592,6 +592,39 @@
<appender-ref ref="RocketmqBrokerMetricsSiftingAppender_inner"/>
</appender>
<appender name="RocketmqAuthAuditSiftingAppender_inner" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>brokerContainerLogDir</key>
<defaultValue>${file.separator}</defaultValue>
</discriminator>
<sift>
<appender name="RocketmqAuthAuditAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>
${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}auth_audit.log
</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>
${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}auth_audit.%i.log.gz
</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>512MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
<charset class="java.nio.charset.Charset">UTF-8</charset>
</encoder>
</appender>
</sift>
</appender>
<appender name="RocketmqAuthAuditSiftingAppender" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="RocketmqAuthAuditSiftingAppender_inner"/>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
@@ -681,6 +714,10 @@
<appender-ref ref="RocketmqBrokerMetricsSiftingAppender"/>
</logger>
<logger name="RocketmqAuthAudit" additivity="false" level="INFO">
<appender-ref ref="RocketmqAuthAuditSiftingAppender"/>
</logger>
<root level="INFO">
<appender-ref ref="DefaultSiftingAppender"/>
</root>
@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.broker.out.BrokerOuterAPI;
import org.apache.rocketmq.common.BrokerConfig;
import org.apache.rocketmq.common.BrokerIdentity;
@@ -93,7 +94,7 @@ public class BrokerOuterAPITest {
private BrokerOuterAPI brokerOuterAPI;
public void init() throws Exception {
brokerOuterAPI = new BrokerOuterAPI(new NettyClientConfig());
brokerOuterAPI = new BrokerOuterAPI(new NettyClientConfig(), new AuthConfig());
Field field = BrokerOuterAPI.class.getDeclaredField("remotingClient");
field.setAccessible(true);
field.set(brokerOuterAPI, nettyRemotingClient);
@@ -25,13 +25,25 @@ import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.LongAdder;
import org.apache.rocketmq.auth.authentication.enums.UserType;
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.manager.AuthorizationMetadataManager;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Environment;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.client.ConsumerGroupInfo;
import org.apache.rocketmq.broker.client.ConsumerManager;
@@ -47,6 +59,7 @@ import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.TopicFilterType;
import org.apache.rocketmq.common.TopicQueueId;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.constant.PermName;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageAccessor;
@@ -59,17 +72,29 @@ import org.apache.rocketmq.remoting.netty.NettyServerConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAllTopicConfigResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.GetEarliestMsgStoretimeRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetMinOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ResumeCheckHalfMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.SearchOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
@@ -93,6 +118,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -110,9 +136,8 @@ public class AdminBrokerProcessorTest {
private Channel channel;
@Spy
private BrokerController
brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(),
new MessageStoreConfig());
private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(),
new MessageStoreConfig(), null);
@Mock
private MessageStore messageStore;
@@ -140,10 +165,16 @@ public class AdminBrokerProcessorTest {
private DefaultMessageStore defaultMessageStore;
@Mock
private ScheduleMessageService scheduleMessageService;
@Mock
private AuthenticationMetadataManager authenticationMetadataManager;
@Mock
private AuthorizationMetadataManager authorizationMetadataManager;
@Before
public void init() throws Exception {
brokerController.setMessageStore(messageStore);
brokerController.setAuthenticationMetadataManager(authenticationMetadataManager);
brokerController.setAuthorizationMetadataManager(authorizationMetadataManager);
//doReturn(sendMessageProcessor).when(brokerController).getSendMessageProcessor();
@@ -634,6 +665,234 @@ public class AdminBrokerProcessorTest {
}
}
@Test
public void testCreateUser() throws RemotingCommandException {
when(authenticationMetadataManager.createUser(any(User.class)))
.thenReturn(CompletableFuture.completedFuture(null));
CreateUserRequestHeader createUserRequestHeader = new CreateUserRequestHeader();
createUserRequestHeader.setUsername("abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
UserInfo userInfo = UserInfo.of("abc", "123", UserType.NORMAL.getName());
request.setBody(JSON.toJSONBytes(userInfo));
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true));
createUserRequestHeader = new CreateUserRequestHeader();
createUserRequestHeader.setUsername("super");
request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
userInfo = UserInfo.of("super", "123", UserType.SUPER.getName());
request.setBody(JSON.toJSONBytes(userInfo));
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false));
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
}
@Test
public void testUpdateUser() throws RemotingCommandException {
when(authenticationMetadataManager.updateUser(any(User.class)))
.thenReturn(CompletableFuture.completedFuture(null));
when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL)));
when(authenticationMetadataManager.getUser(eq("super"))).thenReturn(CompletableFuture.completedFuture(User.of("super", "123", UserType.SUPER)));
UpdateUserRequestHeader updateUserRequestHeader = new UpdateUserRequestHeader();
updateUserRequestHeader.setUsername("abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, updateUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
UserInfo userInfo = UserInfo.of("abc", "123", UserType.NORMAL.getName());
request.setBody(JSON.toJSONBytes(userInfo));
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true));
updateUserRequestHeader = new UpdateUserRequestHeader();
updateUserRequestHeader.setUsername("super");
request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, updateUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
userInfo = UserInfo.of("super", "123", UserType.SUPER.getName());
request.setBody(JSON.toJSONBytes(userInfo));
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false));
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
}
@Test
public void testDeleteUser() throws RemotingCommandException {
when(authenticationMetadataManager.deleteUser(any(String.class)))
.thenReturn(CompletableFuture.completedFuture(null));
when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL)));
when(authenticationMetadataManager.getUser(eq("super"))).thenReturn(CompletableFuture.completedFuture(User.of("super", "123", UserType.SUPER)));
DeleteUserRequestHeader deleteUserRequestHeader = new DeleteUserRequestHeader();
deleteUserRequestHeader.setUsername("abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, deleteUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(true));
deleteUserRequestHeader = new DeleteUserRequestHeader();
deleteUserRequestHeader.setUsername("super");
request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, deleteUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
when(authenticationMetadataManager.isSuperUser(eq("rocketmq"))).thenReturn(CompletableFuture.completedFuture(false));
response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
}
@Test
public void testGetUser() throws RemotingCommandException {
when(authenticationMetadataManager.getUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(User.of("abc", "123", UserType.NORMAL)));
GetUserRequestHeader getUserRequestHeader = new GetUserRequestHeader();
getUserRequestHeader.setUsername("abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, getUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
UserInfo userInfo = JSON.parseObject(new String(response.getBody()), UserInfo.class);
assertThat(userInfo.getUsername()).isEqualTo("abc");
assertThat(userInfo.getPassword()).isEqualTo("123");
assertThat(userInfo.getUserType()).isEqualTo("Normal");
}
@Test
public void testListUser() throws RemotingCommandException {
when(authenticationMetadataManager.listUser(eq("abc"))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(User.of("abc", "123", UserType.NORMAL))));
ListUsersRequestHeader listUserRequestHeader = new ListUsersRequestHeader();
listUserRequestHeader.setFilter("abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_USER, listUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
List<UserInfo> userInfo = JSON.parseArray(new String(response.getBody()), UserInfo.class);
assertThat(userInfo.get(0).getUsername()).isEqualTo("abc");
assertThat(userInfo.get(0).getPassword()).isEqualTo("123");
assertThat(userInfo.get(0).getUserType()).isEqualTo("Normal");
}
@Test
public void testCreateAcl() throws RemotingCommandException {
when(authorizationMetadataManager.createAcl(any(Acl.class)))
.thenReturn(CompletableFuture.completedFuture(null));
CreateAclRequestHeader createAclRequestHeader = new CreateAclRequestHeader();
createAclRequestHeader.setSubject("User:abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_ACL, createAclRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
AclInfo aclInfo = AclInfo.of("User:abc", Arrays.asList("Topic:*"), Arrays.asList("PUB"), Arrays.asList("192.168.0.1"), "Grant");
request.setBody(JSON.toJSONBytes(aclInfo));
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
}
@Test
public void testUpdateAcl() throws RemotingCommandException {
when(authorizationMetadataManager.updateAcl(any(Acl.class)))
.thenReturn(CompletableFuture.completedFuture(null));
UpdateAclRequestHeader updateAclRequestHeader = new UpdateAclRequestHeader();
updateAclRequestHeader.setSubject("User:abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_ACL, updateAclRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
AclInfo aclInfo = AclInfo.of("User:abc", Arrays.asList("Topic:*"), Arrays.asList("PUB"), Arrays.asList("192.168.0.1"), "Grant");
request.setBody(JSON.toJSONBytes(aclInfo));
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
}
@Test
public void testDeleteAcl() throws RemotingCommandException {
when(authorizationMetadataManager.deleteAcl(any(), any(), any()))
.thenReturn(CompletableFuture.completedFuture(null));
DeleteAclRequestHeader deleteAclRequestHeader = new DeleteAclRequestHeader();
deleteAclRequestHeader.setSubject("User:abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_ACL, deleteAclRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
}
@Test
public void testGetAcl() throws RemotingCommandException {
Acl aclInfo = Acl.of(User.of("abc"), Arrays.asList(Resource.of("Topic:*")), Arrays.asList(Action.PUB), Environment.of("192.168.0.1"), Decision.ALLOW);
when(authorizationMetadataManager.getAcl(any(Subject.class))).thenReturn(CompletableFuture.completedFuture(aclInfo));
GetAclRequestHeader getAclRequestHeader = new GetAclRequestHeader();
getAclRequestHeader.setSubject("User:abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_ACL, getAclRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
AclInfo aclInfoData = JSON.parseObject(new String(response.getBody()), AclInfo.class);
assertThat(aclInfoData.getSubject()).isEqualTo("User:abc");
assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getResource()).isEqualTo("Topic:*");
assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getActions()).containsAll(Arrays.asList(Action.PUB.getName()));
assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getSourceIps()).containsAll(Arrays.asList("192.168.0.1"));
assertThat(aclInfoData.getPolicies().get(0).getEntries().get(0).getDecision()).isEqualTo("Allow");
}
@Test
public void testListAcl() throws RemotingCommandException {
Acl aclInfo = Acl.of(User.of("abc"), Arrays.asList(Resource.of("Topic:*")), Arrays.asList(Action.PUB), Environment.of("192.168.0.1"), Decision.ALLOW);
when(authorizationMetadataManager.listAcl(any(), any())).thenReturn(CompletableFuture.completedFuture(Arrays.asList(aclInfo)));
ListAclsRequestHeader listAclRequestHeader = new ListAclsRequestHeader();
listAclRequestHeader.setSubjectFilter("User:abc");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_ACL, listAclRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
List<AclInfo> aclInfoData = JSON.parseArray(new String(response.getBody()), AclInfo.class);
assertThat(aclInfoData.get(0).getSubject()).isEqualTo("User:abc");
assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getResource()).isEqualTo("Topic:*");
assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getActions()).containsAll(Arrays.asList(Action.PUB.getName()));
assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getSourceIps()).containsAll(Arrays.asList("192.168.0.1"));
assertThat(aclInfoData.get(0).getPolicies().get(0).getEntries().get(0).getDecision()).isEqualTo("Allow");
}
private RemotingCommand buildCreateTopicRequest(String topic) {
CreateTopicRequestHeader requestHeader = new CreateTopicRequestHeader();
requestHeader.setTopic(topic);
@@ -675,6 +934,7 @@ public class AdminBrokerProcessorTest {
private ResumeCheckHalfMessageRequestHeader createResumeCheckHalfMessageRequestHeader() {
ResumeCheckHalfMessageRequestHeader header = new ResumeCheckHalfMessageRequestHeader();
header.setTopic("topic");
header.setMsgId("C0A803CA00002A9F0000000000031367");
return header;
}
@@ -62,7 +62,7 @@ import static org.mockito.Mockito.when;
public class ChangeInvisibleTimeProcessorTest {
private ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor;
@Spy
private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig(), null);
@Mock
private ChannelHandlerContext handlerContext;
@Mock
@@ -67,7 +67,7 @@ public class EndTransactionProcessorTest {
@Spy
private BrokerController
brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(),
new MessageStoreConfig());
new MessageStoreConfig(), null);
@Mock
private MessageStore messageStore;
@@ -166,6 +166,7 @@ public class EndTransactionProcessorTest {
private EndTransactionRequestHeader createEndTransactionRequestHeader(int status, boolean isCheckMsg) {
EndTransactionRequestHeader header = new EndTransactionRequestHeader();
header.setTopic("topic");
header.setCommitLogOffset(123456789L);
header.setFromTransactionCheck(isCheckMsg);
header.setCommitOrRollback(status);
@@ -72,7 +72,7 @@ public class TransactionalMessageServiceImplTest {
@Spy
private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(),
new NettyClientConfig(), new MessageStoreConfig());
new NettyClientConfig(), new MessageStoreConfig(), null);
@Mock
private AbstractTransactionalMessageCheckListener listener;
@@ -237,6 +237,7 @@ public class TransactionalMessageServiceImplTest {
private EndTransactionRequestHeader createEndTransactionRequestHeader(int status) {
EndTransactionRequestHeader header = new EndTransactionRequestHeader();
header.setTopic("topic");
header.setCommitLogOffset(123456789L);
header.setCommitOrRollback(status);
header.setMsgId("12345678");
@@ -83,15 +83,6 @@ public interface MQAdmin {
*/
long earliestMsgStoreTime(final MessageQueue mq) throws MQClientException;
/**
* Query message according to message id
*
* @param offsetMsgId message id
* @return message
*/
MessageExt viewMessage(final String offsetMsgId) throws RemotingException, MQBrokerException,
InterruptedException, MQClientException;
/**
* Query messages
*
@@ -175,16 +175,6 @@ public class DefaultMQPullConsumer extends ClientConfig implements MQPullConsume
return this.defaultMQPullConsumerImpl.earliestMsgStoreTime(queueWithNamespace(mq));
}
/**
* This method will be removed in a certain version after April 5, 2020, so please do not use this method.
*/
@Deprecated
@Override
public MessageExt viewMessage(String offsetMsgId) throws RemotingException, MQBrokerException,
InterruptedException, MQClientException {
return this.defaultMQPullConsumerImpl.viewMessage(offsetMsgId);
}
/**
* This method will be removed in a certain version after April 5, 2020, so please do not use this method.
*/
@@ -405,7 +395,7 @@ public class DefaultMQPullConsumer extends ClientConfig implements MQPullConsume
String uniqKey) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
try {
MessageDecoder.decodeMessageId(uniqKey);
return this.viewMessage(uniqKey);
return this.defaultMQPullConsumerImpl.viewMessage(topic, uniqKey);
} catch (Exception e) {
// Ignore
}
@@ -514,16 +514,6 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
return this.defaultMQPushConsumerImpl.earliestMsgStoreTime(queueWithNamespace(mq));
}
/**
* This method will be removed in a certain version after April 5, 2020, so please do not use this method.
*/
@Deprecated
@Override
public MessageExt viewMessage(
String offsetMsgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return this.defaultMQPushConsumerImpl.viewMessage(offsetMsgId);
}
/**
* This method will be removed in a certain version after April 5, 2020, so please do not use this method.
*/
@@ -543,7 +533,7 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
try {
MessageDecoder.decodeMessageId(msgId);
return this.viewMessage(msgId);
return this.defaultMQPushConsumerImpl.viewMessage(withNamespace(topic), msgId);
} catch (Exception e) {
// Ignore
}
@@ -262,16 +262,16 @@ public class MQAdminImpl {
throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}
public MessageExt viewMessage(String msgId)
public MessageExt viewMessage(String topic, String msgId)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
MessageId messageId = null;
try {
messageId = MessageDecoder.decodeMessageId(msgId);
} catch (Exception e) {
throw new MQClientException(ResponseCode.NO_MESSAGE, "query message by id finished, but no message.");
return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId);
} catch (Exception ignored) {
}
return this.mQClientFactory.getMQClientAPIImpl().viewMessage(NetworkUtil.socketAddress2String(messageId.getAddress()),
messageId.getOffset(), timeoutMillis);
topic, messageId.getOffset(), timeoutMillis);
}
public QueryResult queryMessage(String topic, String key, int maxNum, long begin,
@@ -104,6 +104,7 @@ import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.ResponseCode;
import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
import org.apache.rocketmq.remoting.protocol.body.AclInfo;
import org.apache.rocketmq.remoting.protocol.body.BatchAck;
import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody;
import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup;
@@ -138,6 +139,7 @@ import org.apache.rocketmq.remoting.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.remoting.protocol.body.TopicList;
import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.body.UserInfo;
import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.AddBrokerRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ChangeInvisibleTimeRequestHeader;
@@ -146,12 +148,17 @@ import org.apache.rocketmq.remoting.protocol.header.CloneGroupOffsetRequestHeade
import org.apache.rocketmq.remoting.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateAccessConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteAccessConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteSubscriptionGroupRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.DeleteUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ExtraInfoUtil;
import org.apache.rocketmq.remoting.protocol.header.GetAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetAllProducerInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetBrokerAclConfigResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.GetConsumeStatsInBrokerHeader;
@@ -172,6 +179,9 @@ import org.apache.rocketmq.remoting.protocol.header.GetSubscriptionGroupConfigRe
import org.apache.rocketmq.remoting.protocol.header.GetTopicConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetTopicStatsInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetTopicsByClusterRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListAclsRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ListUsersRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.LockBatchMqRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader;
@@ -198,9 +208,11 @@ import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader;
import org.apache.rocketmq.remoting.protocol.header.UnlockBatchMqRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateAclRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateGlobalWhiteAddrsConfigRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
@@ -1227,9 +1239,10 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
return sortMap;
}
public MessageExt viewMessage(final String addr, final long phyoffset, final long timeoutMillis)
public MessageExt viewMessage(final String addr, final String topic, final long phyoffset, final long timeoutMillis)
throws RemotingException, MQBrokerException, InterruptedException {
ViewMessageRequestHeader requestHeader = new ViewMessageRequestHeader();
requestHeader.setTopic(topic);
requestHeader.setOffset(phyoffset);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, requestHeader);
@@ -2983,9 +2996,10 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
}
public boolean resumeCheckHalfMessage(final String addr, String msgId,
public boolean resumeCheckHalfMessage(final String addr, String topic, String msgId,
final long timeoutMillis) throws RemotingException, InterruptedException {
ResumeCheckHalfMessageRequestHeader requestHeader = new ResumeCheckHalfMessageRequestHeader();
requestHeader.setTopic(topic);
requestHeader.setMsgId(msgId);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.RESUME_CHECK_HALF_MESSAGE, requestHeader);
@@ -3297,4 +3311,158 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void createUser(String addr, UserInfo userInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
CreateUserRequestHeader requestHeader = new CreateUserRequestHeader(userInfo.getUsername());
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, requestHeader);
request.setBody(RemotingSerializable.encode(userInfo));
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void updateUser(String addr, UserInfo userInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
UpdateUserRequestHeader requestHeader = new UpdateUserRequestHeader(userInfo.getUsername());
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_USER, requestHeader);
request.setBody(RemotingSerializable.encode(userInfo));
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void deleteUser(String addr, String username, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
DeleteUserRequestHeader requestHeader = new DeleteUserRequestHeader(username);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_USER, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public UserInfo getUser(String addr, String username, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
GetUserRequestHeader requestHeader = new GetUserRequestHeader(username);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_USER, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return RemotingSerializable.decode(response.getBody(), UserInfo.class);
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public List<UserInfo> listUser(String addr, String filter, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
ListUsersRequestHeader requestHeader = new ListUsersRequestHeader(filter);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_USER, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return RemotingSerializable.decodeList(response.getBody(), UserInfo.class);
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void createAcl(String addr, AclInfo aclInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
CreateAclRequestHeader requestHeader = new CreateAclRequestHeader(aclInfo.getSubject());
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_ACL, requestHeader);
request.setBody(RemotingSerializable.encode(aclInfo));
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void updateAcl(String addr, AclInfo aclInfo, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
UpdateAclRequestHeader requestHeader = new UpdateAclRequestHeader(aclInfo.getSubject());
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_UPDATE_ACL, requestHeader);
request.setBody(RemotingSerializable.encode(aclInfo));
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public void deleteAcl(String addr, String subject, String resource, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
DeleteAclRequestHeader requestHeader = new DeleteAclRequestHeader(subject, resource);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_DELETE_ACL, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return;
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public AclInfo getAcl(String addr, String subject, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
GetAclRequestHeader requestHeader = new GetAclRequestHeader(subject);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_GET_ACL, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return RemotingSerializable.decode(response.getBody(), AclInfo.class);
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
public List<AclInfo> listAcl(String addr, String subjectFilter, String resourceFilter, long millis) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, MQBrokerException {
ListAclsRequestHeader requestHeader = new ListAclsRequestHeader(subjectFilter, resourceFilter);
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.AUTH_LIST_ACL, requestHeader);
RemotingCommand response = this.remotingClient.invokeSync(addr, request, millis);
assert response != null;
switch (response.getCode()) {
case ResponseCode.SUCCESS: {
return RemotingSerializable.decodeList(response.getBody(), AclInfo.class);
}
default:
break;
}
throw new MQBrokerException(response.getCode(), response.getRemark());
}
}
@@ -804,10 +804,10 @@ public class DefaultMQPullConsumerImpl implements MQConsumerInner {
this.offsetStore.updateOffset(mq, offset, false);
}
public MessageExt viewMessage(String msgId)
public MessageExt viewMessage(String topic, String msgId)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
this.isRunning();
return this.mQClientFactory.getMQAdminImpl().viewMessage(msgId);
return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId);
}
public void registerFilterMessageHook(final FilterMessageHook hook) {
@@ -1309,9 +1309,9 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
this.consumeMessageService.updateCorePoolSize(corePoolSize);
}
public MessageExt viewMessage(String msgId)
public MessageExt viewMessage(String topic, String msgId)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return this.mQClientFactory.getMQAdminImpl().viewMessage(msgId);
return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId);
}
public RebalanceImpl getRebalanceImpl() {
@@ -386,6 +386,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
}
this.processTransactionState(
checkRequestHeader.getTopic(),
localTransactionState,
group,
exception);
@@ -395,10 +396,12 @@ public class DefaultMQProducerImpl implements MQProducerInner {
}
private void processTransactionState(
final String topic,
final LocalTransactionState localTransactionState,
final String producerGroup,
final Throwable exception) {
final EndTransactionRequestHeader thisHeader = new EndTransactionRequestHeader();
thisHeader.setTopic(topic);
thisHeader.setCommitLogOffset(checkRequestHeader.getCommitLogOffset());
thisHeader.setProducerGroup(producerGroup);
thisHeader.setTranStateTableOffset(checkRequestHeader.getTranStateTableOffset());
@@ -506,11 +509,11 @@ public class DefaultMQProducerImpl implements MQProducerInner {
return this.mQClientFactory.getMQAdminImpl().earliestMsgStoreTime(mq);
}
public MessageExt viewMessage(
public MessageExt viewMessage(String topic,
String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
this.makeSureStateOK();
return this.mQClientFactory.getMQAdminImpl().viewMessage(msgId);
return this.mQClientFactory.getMQAdminImpl().viewMessage(topic, msgId);
}
public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end)
@@ -1484,6 +1487,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
final String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(defaultMQProducer.queueWithNamespace(sendResult.getMessageQueue()));
final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(destBrokerName);
EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
requestHeader.setTopic(msg.getTopic());
requestHeader.setTransactionId(transactionId);
requestHeader.setCommitLogOffset(id.getOffset());
requestHeader.setBrokerName(destBrokerName);
@@ -1002,25 +1002,6 @@ public class DefaultMQProducer extends ClientConfig implements MQProducer {
return this.defaultMQProducerImpl.earliestMsgStoreTime(queueWithNamespace(mq));
}
/**
* Query message of the given offset message ID.
* <p>
* This method will be removed in a certain version after April 5, 2020, so please do not use this method.
*
* @param offsetMsgId message id
* @return Message specified.
* @throws MQBrokerException if there is any broker error.
* @throws MQClientException if there is any client error.
* @throws RemotingException if there is any network-tier error.
* @throws InterruptedException if the sending thread is interrupted.
*/
@Deprecated
@Override
public MessageExt viewMessage(
String offsetMsgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
return this.defaultMQProducerImpl.viewMessage(offsetMsgId);
}
/**
* Query message by key.
* <p>
@@ -1060,7 +1041,7 @@ public class DefaultMQProducer extends ClientConfig implements MQProducer {
public MessageExt viewMessage(String topic,
String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
try {
return this.viewMessage(msgId);
return this.defaultMQProducerImpl.viewMessage(topic, msgId);
} catch (Exception ignored) {
}
return this.defaultMQProducerImpl.queryMessageByUniqKey(withNamespace(topic), msgId);
@@ -355,7 +355,7 @@ public class MQClientAPIImplTest {
}
}).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong());
boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "test", 3000);
boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "topic,", "test", 3000);
assertThat(result).isEqualTo(false);
}
@@ -369,7 +369,7 @@ public class MQClientAPIImplTest {
}
}).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong());
boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "test", 3000);
boolean result = mqClientAPI.resumeCheckHalfMessage(brokerAddr, "topic", "test", 3000);
assertThat(result).isEqualTo(true);
}
@@ -726,7 +726,7 @@ public class MQClientAPIImplTest {
}
}).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong());
MessageExt messageExt = mqClientAPI.viewMessage(brokerAddr, 100L, 10000);
MessageExt messageExt = mqClientAPI.viewMessage(brokerAddr, "topic", 100L, 10000);
assertThat(messageExt.getTopic()).isEqualTo(topic);
}
+4
View File
@@ -36,6 +36,10 @@
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
@@ -27,6 +27,10 @@ public class Pair<T1, T2> implements Serializable {
this.object2 = object2;
}
public static <T1, T2> Pair<T1, T2> of(T1 object1, T2 object2) {
return new Pair<>(object1, object2);
}
public T1 getObject1() {
return object1;
}
@@ -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.
*/
package org.apache.rocketmq.common.action;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum Action {
UNKNOWN((byte) 0, "Unknown"),
ALL((byte) 1, "All"),
ANY((byte) 2, "Any"),
PUB((byte) 3, "Pub"),
SUB((byte) 4, "Sub"),
CREATE((byte) 5, "Create"),
UPDATE((byte) 6, "Update"),
DELETE((byte) 7, "Delete"),
GET((byte) 8, "Get"),
LIST((byte) 9, "List");
@JSONField(value = true)
private final byte code;
private final String name;
Action(byte code, String name) {
this.code = code;
this.name = name;
}
public static Action getByName(String name) {
for (Action action : Action.values()) {
if (StringUtils.equalsIgnoreCase(action.getName(), name)) {
return action;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}
@@ -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.common.action;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.apache.rocketmq.common.resource.ResourceType;
@Retention(RetentionPolicy.RUNTIME)
public @interface RocketMQAction {
int value();
ResourceType resource() default ResourceType.UNKNOWN;
Action[] action();
}
@@ -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.
*/
package org.apache.rocketmq.common.chain;
public interface Handler<T, R> {
R handle(T t, HandlerChain<T, R> chain);
}
@@ -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.common.chain;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class HandlerChain<T, R> {
private List<Handler<T, R>> handlers;
private Iterator<Handler<T, R>> iterator;
public static <T, R> HandlerChain<T, R> create() {
return new HandlerChain<>();
}
public HandlerChain<T, R> addNext(Handler<T, R> handler) {
if (this.handlers == null) {
this.handlers = new ArrayList<>();
}
this.handlers.add(handler);
return this;
}
public R handle(T t) {
if (iterator == null) {
iterator = handlers.iterator();
}
if (iterator.hasNext()) {
Handler<T, R> handler = iterator.next();
return handler.handle(t, this);
}
return null;
}
}
@@ -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.common.constant;
public class CommonConstants {
public static final String COLON = ":";
public static final String ASTERISK = "*";
public static final String COMMA = ",";
public static final String EQUAL = "=";
public static final String SLASH = "/";
public static final String SPACE = " ";
public static final String HYPHEN = "-";
public static final String POUND = "#";
}
@@ -15,12 +15,12 @@
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.interceptor;
package org.apache.rocketmq.common.constant;
import io.grpc.Context;
import io.grpc.Metadata;
public class InterceptorConstants {
public class GrpcConstants {
public static final Context.Key<Metadata> METADATA = Context.key("rpc-metadata");
/**
@@ -70,4 +70,7 @@ public class InterceptorConstants {
public static final Metadata.Key<String> AUTHORIZATION_AK
= Metadata.Key.of("x-mq-authorization-ak", Metadata.ASCII_STRING_MARSHALLER);
public static final Metadata.Key<String> CHANNEL_ID
= Metadata.Key.of("x-mq-channel-id", Metadata.ASCII_STRING_MARSHALLER);
}
@@ -19,6 +19,7 @@ package org.apache.rocketmq.common.constant;
public class HAProxyConstants {
public static final String CHANNEL_ID = "channel_id";
public static final String PROXY_PROTOCOL_PREFIX = "proxy_protocol_";
public static final String PROXY_PROTOCOL_ADDR = PROXY_PROTOCOL_PREFIX + "addr";
public static final String PROXY_PROTOCOL_PORT = PROXY_PROTOCOL_PREFIX + "port";
@@ -53,4 +53,6 @@ public class LoggerName {
public static final String PROXY_WATER_MARK_LOGGER_NAME = "RocketmqProxyWatermark";
public static final String ROCKETMQ_COLDCTR_LOGGER_NAME = "RocketmqColdCtr";
public static final String ROCKSDB_LOGGER_NAME = "RocketmqRocksDB";
public static final String ROCKETMQ_AUTH_AUDIT_LOGGER_NAME = "RocketmqAuthAudit";
}
@@ -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.common.resource;
import com.alibaba.fastjson2.annotation.JSONField;
public enum ResourcePattern {
ANY((byte) 1, "ANY"),
LITERAL((byte) 2, "LITERAL"),
PREFIXED((byte) 3, "PREFIXED");
@JSONField(value = true)
private final byte code;
private final String name;
ResourcePattern(byte code, String name) {
this.code = code;
this.name = name;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}
@@ -0,0 +1,61 @@
/*
* 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.common.resource;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum ResourceType {
UNKNOWN((byte) 0, "Unknown"),
ANY((byte) 1, "Any"),
CLUSTER((byte) 2, "Cluster"),
NAMESPACE((byte) 3, "Namespace"),
TOPIC((byte) 4, "Topic"),
GROUP((byte) 5, "Group");
@JSONField(value = true)
private final byte code;
private final String name;
ResourceType(byte code, String name) {
this.code = code;
this.name = name;
}
public static ResourceType getByName(String name) {
for (ResourceType resourceType : ResourceType.values()) {
if (StringUtils.equalsIgnoreCase(resourceType.getName(), name)) {
return resourceType;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

Some files were not shown because too many files have changed in this diff Show More