mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2026-02-01 14:56:20 +08:00
* [ISSUE #5]Add permission control when loginRequired is true. * optimize the code * Wildcard characters are supported * add Apache License headers Co-authored-by: zhangjidi2016 <zhangjidi@cmss.chinamobile.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.ClusterService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/cluster")
|
||||
@Permission
|
||||
public class ClusterController {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.apache.rocketmq.dashboard.model.ConnectionInfo;
|
||||
import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo;
|
||||
import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
|
||||
import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.ConsumerService;
|
||||
import org.apache.rocketmq.dashboard.util.JsonUtil;
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/consumer")
|
||||
@Permission
|
||||
public class ConsumerController {
|
||||
private Logger logger = LoggerFactory.getLogger(ConsumerController.class);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.apache.rocketmq.dashboard.controller;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.DashboardService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/dashboard")
|
||||
@Permission
|
||||
public class DashboardController {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
|
||||
import org.apache.rocketmq.dashboard.model.MessagePage;
|
||||
import org.apache.rocketmq.dashboard.model.MessageView;
|
||||
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.MessageService;
|
||||
import org.apache.rocketmq.dashboard.util.JsonUtil;
|
||||
import org.apache.rocketmq.tools.admin.api.MessageTrack;
|
||||
@@ -41,6 +42,7 @@ import java.util.Map;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/message")
|
||||
@Permission
|
||||
public class MessageController {
|
||||
private Logger logger = LoggerFactory.getLogger(MessageController.class);
|
||||
@Resource
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.common.Pair;
|
||||
import org.apache.rocketmq.dashboard.model.MessageView;
|
||||
import org.apache.rocketmq.dashboard.model.trace.MessageTraceGraph;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.MessageService;
|
||||
import org.apache.rocketmq.dashboard.service.MessageTraceService;
|
||||
import org.apache.rocketmq.tools.admin.api.MessageTrack;
|
||||
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/messageTrace")
|
||||
@Permission
|
||||
public class MessageTraceController {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.dashboard.model.ConsumerMonitorConfig;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.MonitorService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/monitor")
|
||||
@Permission
|
||||
public class MonitorController {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MonitorController.class);
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.dashboard.aspect.admin.annotation.OriginalControllerReturnValue;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.OpsService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/rocketmq")
|
||||
@Permission
|
||||
public class NamesvrController {
|
||||
@Resource
|
||||
private OpsService opsService;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.OpsService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/ops")
|
||||
@Permission
|
||||
public class OpsController {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.apache.rocketmq.dashboard.controller;
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
|
||||
import org.apache.rocketmq.dashboard.model.ConnectionInfo;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.dashboard.service.ProducerService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/producer")
|
||||
@Permission
|
||||
public class ProducerController {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.apache.rocketmq.dashboard.controller;
|
||||
|
||||
import org.apache.rocketmq.client.exception.MQClientException;
|
||||
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||
import org.apache.rocketmq.remoting.exception.RemotingException;
|
||||
import org.apache.rocketmq.dashboard.model.request.SendTopicMessageRequest;
|
||||
import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/topic")
|
||||
@Permission
|
||||
public class TopicController {
|
||||
private Logger logger = LoggerFactory.getLogger(TopicController.class);
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.permisssion;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Permission {
|
||||
}
|
||||
@@ -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.dashboard.permisssion;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
||||
import org.apache.rocketmq.dashboard.exception.ServiceException;
|
||||
import org.apache.rocketmq.dashboard.model.UserInfo;
|
||||
import org.apache.rocketmq.dashboard.service.PermissionService;
|
||||
import org.apache.rocketmq.dashboard.util.WebUtil;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class PermissionAspect {
|
||||
|
||||
@Resource
|
||||
private RMQConfigure configure;
|
||||
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
|
||||
/**
|
||||
* @Permission can be applied to the Controller class to implement Permission verification on all methods in the class
|
||||
* can also be applied to methods in a class for fine control
|
||||
*/
|
||||
@Pointcut("@annotation(org.apache.rocketmq.dashboard.permisssion.Permission) || @within(org.apache.rocketmq.dashboard.permisssion.Permission)")
|
||||
private void permission() {
|
||||
|
||||
}
|
||||
|
||||
@Around("permission()")
|
||||
public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
if (configure.isLoginRequired()) {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String url = request.getRequestURI();
|
||||
UserInfo userInfo = (UserInfo) request.getSession().getAttribute(WebUtil.USER_INFO);
|
||||
if (userInfo == null || userInfo.getUser() == null) {
|
||||
throw new ServiceException(-1, "user not login");
|
||||
}
|
||||
boolean checkResult = permissionService.checkUrlAvailable(userInfo, url);
|
||||
if (!checkResult) {
|
||||
throw new ServiceException(-1, "no permission");
|
||||
}
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.permisssion;
|
||||
|
||||
public enum UserRoleEnum {
|
||||
ADMIN(1, "admin"),
|
||||
ORDINARY(0, "ordinary");
|
||||
|
||||
private int roleType;
|
||||
private String roleName;
|
||||
|
||||
UserRoleEnum(int roleType, String roleName) {
|
||||
this.roleType = roleType;
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
public int getRoleType() {
|
||||
return roleType;
|
||||
}
|
||||
|
||||
public String getRoleName() {
|
||||
return roleName;
|
||||
}
|
||||
}
|
||||
@@ -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.dashboard.service;
|
||||
|
||||
import org.apache.rocketmq.dashboard.model.UserInfo;
|
||||
|
||||
public interface PermissionService {
|
||||
|
||||
boolean checkUrlAvailable(UserInfo userInfo, String url);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.service.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
||||
import org.apache.rocketmq.srvutil.FileWatchService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AbstractFileStore {
|
||||
public final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
public String filePath;
|
||||
|
||||
public AbstractFileStore(RMQConfigure configure, String fileName) {
|
||||
filePath = configure.getRocketMqDashboardDataPath() + File.separator + fileName;
|
||||
if (!new File(filePath).exists()) {
|
||||
// Use the default path
|
||||
InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
|
||||
if (inputStream == null) {
|
||||
log.error(String.format("Can not found the file %s in Spring Boot jar", fileName));
|
||||
System.exit(1);
|
||||
} else {
|
||||
try {
|
||||
load(inputStream);
|
||||
} catch (Exception e) {
|
||||
log.error("fail to load file {}", filePath, e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
log.error("inputStream close exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info(String.format("configure file is %s", filePath));
|
||||
load();
|
||||
watch();
|
||||
}
|
||||
}
|
||||
|
||||
abstract void load(InputStream inputStream);
|
||||
|
||||
private void load() {
|
||||
load(null);
|
||||
}
|
||||
|
||||
private boolean watch() {
|
||||
try {
|
||||
FileWatchService fileWatchService = new FileWatchService(new String[] {filePath}, new FileWatchService.Listener() {
|
||||
@Override
|
||||
public void onChanged(String path) {
|
||||
log.info("The file changed, reload the context");
|
||||
load();
|
||||
}
|
||||
});
|
||||
fileWatchService.start();
|
||||
log.info("Succeed to start FileWatcherService");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to start FileWatcherService", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.Resource;
|
||||
import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
||||
import org.apache.rocketmq.dashboard.exception.ServiceException;
|
||||
import org.apache.rocketmq.dashboard.model.UserInfo;
|
||||
import org.apache.rocketmq.dashboard.service.PermissionService;
|
||||
import org.apache.rocketmq.dashboard.util.MatcherUtil;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ADMIN;
|
||||
import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ORDINARY;
|
||||
|
||||
@Service
|
||||
public class PermissionServiceImpl implements PermissionService, InitializingBean {
|
||||
|
||||
@Resource
|
||||
private RMQConfigure configure;
|
||||
|
||||
private PermissionFileStore permissionFileStore;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (configure.isLoginRequired()) {
|
||||
permissionFileStore = new PermissionFileStore(configure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkUrlAvailable(UserInfo userInfo, String url) {
|
||||
int type = userInfo.getUser().getType();
|
||||
// if it is admin, it could access all resources
|
||||
if (type == ADMIN.getRoleType()) {
|
||||
return true;
|
||||
}
|
||||
String loginUserRole = ORDINARY.getRoleName();
|
||||
Map<String, List<String>> rolePerms = PermissionFileStore.rolePerms;
|
||||
List<String> perms = rolePerms.get(loginUserRole);
|
||||
for (String perm : perms) {
|
||||
if (MatcherUtil.match(perm, url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class PermissionFileStore extends AbstractFileStore {
|
||||
private static final String FILE_NAME = "role-permission.yml";
|
||||
|
||||
private static Map<String/**role**/, List<String>/**accessUrls**/> rolePerms = new ConcurrentHashMap<>();
|
||||
|
||||
public PermissionFileStore(RMQConfigure configure) {
|
||||
super(configure, FILE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(InputStream inputStream) {
|
||||
Yaml yaml = new Yaml();
|
||||
JSONObject rolePermsData = null;
|
||||
try {
|
||||
if (inputStream == null) {
|
||||
rolePermsData = yaml.loadAs(new FileReader(filePath), JSONObject.class);
|
||||
} else {
|
||||
rolePermsData = yaml.loadAs(inputStream, JSONObject.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("load user-permission.yml failed", e);
|
||||
throw new ServiceException(0, String.format("Failed to load rolePermission property file: %s", filePath));
|
||||
}
|
||||
rolePerms.clear();
|
||||
rolePerms.putAll(rolePermsData.getObject("rolePerms", Map.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,11 @@ import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
||||
import org.apache.rocketmq.dashboard.exception.ServiceException;
|
||||
import org.apache.rocketmq.dashboard.model.User;
|
||||
import org.apache.rocketmq.dashboard.service.UserService;
|
||||
import org.apache.rocketmq.srvutil.FileWatchService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
@@ -40,9 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService, InitializingBean {
|
||||
@Resource
|
||||
RMQConfigure configure;
|
||||
private RMQConfigure configure;
|
||||
|
||||
FileBasedUserInfoStore fileBasedUserInfoStore;
|
||||
private FileBasedUserInfoStore fileBasedUserInfoStore;
|
||||
|
||||
@Override
|
||||
public User queryByName(String name) {
|
||||
@@ -61,40 +57,17 @@ public class UserServiceImpl implements UserService, InitializingBean {
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileBasedUserInfoStore {
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
public static class FileBasedUserInfoStore extends AbstractFileStore {
|
||||
private static final String FILE_NAME = "users.properties";
|
||||
|
||||
private String filePath;
|
||||
private final Map<String, User> userMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static Map<String, User> userMap = new ConcurrentHashMap<>();
|
||||
|
||||
public FileBasedUserInfoStore(RMQConfigure configure) {
|
||||
filePath = configure.getRocketMqDashboardDataPath() + File.separator + FILE_NAME;
|
||||
if (!new File(filePath).exists()) {
|
||||
//Use the default path
|
||||
InputStream inputStream = getClass().getResourceAsStream("/" + FILE_NAME);
|
||||
if (inputStream == null) {
|
||||
log.error(String.format("Can not found the file %s in Spring Boot jar", FILE_NAME));
|
||||
System.out.printf(String.format("Can not found file %s in Spring Boot jar or %s, stop the dashboard starting",
|
||||
FILE_NAME, configure.getRocketMqDashboardDataPath()));
|
||||
System.exit(1);
|
||||
} else {
|
||||
load(inputStream);
|
||||
}
|
||||
} else {
|
||||
log.info(String.format("Login Users configure file is %s", filePath));
|
||||
load();
|
||||
watch();
|
||||
}
|
||||
super(configure, FILE_NAME);
|
||||
}
|
||||
|
||||
private void load() {
|
||||
load(null);
|
||||
}
|
||||
|
||||
private void load(InputStream inputStream) {
|
||||
|
||||
@Override
|
||||
public void load(InputStream inputStream) {
|
||||
Properties prop = new Properties();
|
||||
try {
|
||||
if (inputStream == null) {
|
||||
@@ -112,7 +85,8 @@ public class UserServiceImpl implements UserService, InitializingBean {
|
||||
int role;
|
||||
for (String key : prop.stringPropertyNames()) {
|
||||
String v = prop.getProperty(key);
|
||||
if (v == null) continue;
|
||||
if (v == null)
|
||||
continue;
|
||||
arrs = v.split(",", 2);
|
||||
if (arrs.length == 0) {
|
||||
continue;
|
||||
@@ -125,30 +99,10 @@ public class UserServiceImpl implements UserService, InitializingBean {
|
||||
loadUserMap.put(key, new User(key, arrs[0].trim(), role));
|
||||
}
|
||||
|
||||
|
||||
userMap.clear();
|
||||
userMap.putAll(loadUserMap);
|
||||
}
|
||||
|
||||
private boolean watch() {
|
||||
try {
|
||||
FileWatchService fileWatchService = new FileWatchService(new String[]{filePath}, new FileWatchService.Listener() {
|
||||
@Override
|
||||
public void onChanged(String path) {
|
||||
log.info("The loginUserInfo property file changed, reload the context");
|
||||
load();
|
||||
}
|
||||
});
|
||||
fileWatchService.start();
|
||||
log.info("Succeed to start LoginUserWatcherService");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to start LoginUserWatcherService", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public User queryByName(String name) {
|
||||
return userMap.get(name);
|
||||
}
|
||||
@@ -158,7 +112,6 @@ public class UserServiceImpl implements UserService, InitializingBean {
|
||||
if (user != null && password.equals(user.getPassword())) {
|
||||
return user.cloneOne();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,12 @@ public class GlobalExceptionHandler {
|
||||
public JsonResult<Object> jsonErrorHandler(HttpServletRequest req, Exception ex) throws Exception {
|
||||
JsonResult<Object> value = null;
|
||||
if (ex != null) {
|
||||
logger.error("op=global_exception_handler_print_error", ex);
|
||||
if (ex instanceof ServiceException) {
|
||||
logger.error("Occur service exception: {}", ex.getMessage());
|
||||
value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
|
||||
}
|
||||
else {
|
||||
logger.error("op=global_exception_handler_print_error", ex);
|
||||
value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MatcherUtil {
|
||||
|
||||
public static boolean match(String accessUrl, String reqPath) {
|
||||
String regPath = getRegPath(accessUrl);
|
||||
return Pattern.compile(regPath).matcher(reqPath).matches();
|
||||
}
|
||||
|
||||
private static String getRegPath(String path) {
|
||||
char[] chars = path.toCharArray();
|
||||
int len = chars.length;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean preX = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (chars[i] == '*') {
|
||||
if (preX) {
|
||||
sb.append(".*");
|
||||
preX = false;
|
||||
} else if (i + 1 == len) {
|
||||
sb.append("[^/]*");
|
||||
} else {
|
||||
preX = true;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (preX) {
|
||||
sb.append("[^/]*");
|
||||
preX = false;
|
||||
}
|
||||
if (chars[i] == '?') {
|
||||
sb.append('.');
|
||||
} else {
|
||||
sb.append(chars[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
35
src/main/resources/role-permission.yml
Normal file
35
src/main/resources/role-permission.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
# contributor license agreements. See the NOTICE file distributed with
|
||||
# this work for additional information regarding copyright ownership.
|
||||
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# This file supports hot change, any change will be auto-reloaded without Console restarting.
|
||||
# the interface paths can be configured with wildcard characters.
|
||||
# ?: Matches 1 characters.
|
||||
# *: Matches 0 or more characters that are not /.
|
||||
# **: Matches 0 or more characters.
|
||||
|
||||
rolePerms:
|
||||
ordinary:
|
||||
- /rocketmq/nsaddr
|
||||
- /ops/*
|
||||
- /dashboard/**
|
||||
- /topic/*.query
|
||||
- /topic/sendTopicMessage.do
|
||||
- /producer/*.query
|
||||
- /message/*
|
||||
- /messageTrace/*
|
||||
- /monitor/*
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.permission;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.rocketmq.dashboard.BaseTest;
|
||||
import org.apache.rocketmq.dashboard.config.RMQConfigure;
|
||||
import org.apache.rocketmq.dashboard.model.User;
|
||||
import org.apache.rocketmq.dashboard.model.UserInfo;
|
||||
import org.apache.rocketmq.dashboard.permisssion.PermissionAspect;
|
||||
import org.apache.rocketmq.dashboard.service.impl.PermissionServiceImpl;
|
||||
import org.apache.rocketmq.dashboard.util.JsonUtil;
|
||||
import org.apache.rocketmq.dashboard.util.WebUtil;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class PermissionAspectTest extends BaseTest {
|
||||
|
||||
@InjectMocks
|
||||
private PermissionAspect permissionAspect;
|
||||
|
||||
@Mock
|
||||
private RMQConfigure configure;
|
||||
|
||||
@Spy
|
||||
private PermissionServiceImpl permissionService;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
autoInjection();
|
||||
when(configure.isLoginRequired()).thenReturn(true);
|
||||
when(configure.getDashboardCollectData()).thenReturn("/tmp/rocketmq-console/test/data");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckPermission() throws Throwable {
|
||||
ReflectionTestUtils.setField(permissionAspect, "configure", configure);
|
||||
ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
|
||||
permissionService.afterPropertiesSet();
|
||||
ReflectionTestUtils.setField(permissionAspect, "permissionService", permissionService);
|
||||
|
||||
// user not login
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
try {
|
||||
permissionAspect.checkPermission(joinPoint);
|
||||
} catch (Throwable throwable) {
|
||||
Assert.assertEquals(throwable.getMessage(), "user not login");
|
||||
}
|
||||
// userRole is admin
|
||||
UserInfo info = new UserInfo();
|
||||
User adminUser = new User("admin", "admin", 1);
|
||||
info.setUser(adminUser);
|
||||
request.getSession().setAttribute(WebUtil.USER_INFO, info);
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
permissionAspect.checkPermission(joinPoint);
|
||||
|
||||
// userRole is ordinary
|
||||
User ordinaryUser = new User("user1", "user1", 0);
|
||||
info.setUser(ordinaryUser);
|
||||
request = new MockHttpServletRequest("", "/topic/deleteTopic.do");
|
||||
request.getSession().setAttribute(WebUtil.USER_INFO, info);
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
try {
|
||||
permissionAspect.checkPermission(joinPoint);
|
||||
} catch (Throwable throwable) {
|
||||
Assert.assertEquals(throwable.getMessage(), "no permission");
|
||||
}
|
||||
|
||||
// no permission
|
||||
request = new MockHttpServletRequest("", "/topic/route.query");
|
||||
request.getSession().setAttribute(WebUtil.USER_INFO, info);
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
permissionAspect.checkPermission(joinPoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileBasedPermissionStoreWatch() throws Exception {
|
||||
when(configure.getRocketMqDashboardDataPath()).thenReturn("/tmp/rocketmq-console/test/data");
|
||||
Map<String, Map<String, List<String>>> rolePermsMap = new HashMap<>();
|
||||
Map<String, List<String>> rolePerms = new HashMap<>();
|
||||
List<String> accessUrls = Lists.asList("/topic/route.query", new String[] {"/topic/stats.query"});
|
||||
rolePerms.put("admin", accessUrls);
|
||||
rolePermsMap.put("rolePerms", rolePerms);
|
||||
File file = createTestFile(rolePermsMap);
|
||||
new PermissionServiceImpl.PermissionFileStore(configure);
|
||||
rolePerms.put("ordinary", accessUrls);
|
||||
// Update file and flush to yaml file
|
||||
Files.write(JsonUtil.obj2String(rolePerms).getBytes(), file);
|
||||
Thread.sleep(1000);
|
||||
if (file != null && file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private File createTestFile(Map map) throws Exception {
|
||||
String fileName = "/tmp/rocketmq-console/test/data/role-permission.yml";
|
||||
File file = new File(fileName);
|
||||
file.delete();
|
||||
file.createNewFile();
|
||||
Files.write(JsonUtil.obj2String(map).getBytes(), file);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.rocketmq.dashboard.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MatcherUtilTest {
|
||||
|
||||
@Test
|
||||
public void testMatch() {
|
||||
boolean b = MatcherUtil.match("/topic/*.query", "/topic/route.query");
|
||||
boolean b1 = MatcherUtil.match("/**/**.do", "/consumer/route.do");
|
||||
boolean b2 = MatcherUtil.match("/*", "/topic/qqq/route.do");
|
||||
Assert.assertTrue(b);
|
||||
Assert.assertTrue(b1);
|
||||
Assert.assertFalse(b2);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user