mirror of
https://github.com/apache/rocketmq.git
synced 2026-08-28 20:09:14 +08:00
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
+131
@@ -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);
|
||||
}
|
||||
}
|
||||
+66
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -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;
|
||||
}
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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());
|
||||
}
|
||||
}
|
||||
+151
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
+222
@@ -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;
|
||||
}
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
+36
@@ -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);
|
||||
}
|
||||
+81
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
+72
@@ -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());
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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);
|
||||
}
|
||||
+484
@@ -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;
|
||||
}
|
||||
}
|
||||
+164
@@ -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());
|
||||
}
|
||||
}
|
||||
+68
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
+84
@@ -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;
|
||||
}
|
||||
}
|
||||
+92
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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());
|
||||
}
|
||||
}
|
||||
+154
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
+284
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
+39
@@ -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);
|
||||
}
|
||||
+102
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+200
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
+73
@@ -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());
|
||||
}
|
||||
}
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+123
@@ -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());
|
||||
}
|
||||
}
|
||||
+118
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+164
@@ -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());
|
||||
}
|
||||
}
|
||||
+335
@@ -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());
|
||||
}
|
||||
}
|
||||
+550
@@ -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() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+240
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+63
@@ -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);
|
||||
}
|
||||
}
|
||||
+65
@@ -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();
|
||||
}
|
||||
|
||||
+350
-6
@@ -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);
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+263
-3
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+2
-1
@@ -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
|
||||
*
|
||||
|
||||
+1
-11
@@ -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
|
||||
}
|
||||
|
||||
+1
-11
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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() {
|
||||
|
||||
+6
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = "#";
|
||||
}
|
||||
+5
-2
@@ -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
Reference in New Issue
Block a user