nacos 1.2.1

This commit is contained in:
冷冷 committed 2020-04-10 16:41:52 +08:00
1 parent 6231887d25
commit ce58c8d7c7
33 files changed
+2735

No files matched your search

@@ -0,0 +1,63 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.config;
import com.alibaba.nacos.core.code.ControllerMethodsCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.annotation.PostConstruct;
/**
* @author yshen
* @author nkorange
* @since 1.2.0
*/
@Component
@EnableScheduling
@PropertySource("/application.properties")
public class ConsoleConfig {
@Autowired
private ControllerMethodsCache methodsCache;
@PostConstruct
public void init() {
methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");
methodsCache.initClassMethod("com.alibaba.nacos.console.controller");
methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setMaxAge(18000L);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.controller;
import com.alibaba.nacos.config.server.model.RestResult;
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* Permission operation controller
*
* @author nkorange
* @since 1.2.0
*/
@RestController
@RequestMapping("/v1/auth/permissions")
public class PermissionController {
@Autowired
private NacosRoleServiceImpl nacosRoleService;
/**
* Query permissions of a role
*
* @param role the role
* @param pageNo page index
* @param pageSize page size
* @return permission of a role
*/
@GetMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ)
public Object getPermissions(@RequestParam int pageNo, @RequestParam int pageSize,
@RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role) {
return nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize);
}
/**
* Add a permission to a role
*
* @param role the role
* @param resource the related resource
* @param action the related action
* @return ok if succeed
*/
@PostMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE)
public Object addPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) {
nacosRoleService.addPermission(role, resource, action);
return new RestResult<>(200, "add permission ok!");
}
/**
* Delete a permission from a role
*
* @param role the role
* @param resource the related resource
* @param action the related action
* @return ok if succeed
*/
@DeleteMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE)
public Object deletePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) {
nacosRoleService.deletePermission(role, resource, action);
return new RestResult<>(200, "delete permission ok!");
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.controller;
import com.alibaba.nacos.config.server.model.RestResult;
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.core.auth.ActionTypes;
import com.alibaba.nacos.core.auth.Secured;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* Role operation controller
*
* @author nkorange
* @since 1.2.0
*/
@RestController
@RequestMapping("/v1/auth/roles")
public class RoleController {
@Autowired
private NacosRoleServiceImpl roleService;
/**
* Get roles list
*
* @param pageNo number index of page
* @param pageSize page size
* @param username optional, username of user
* @return role list
*/
@GetMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ)
public Object getRoles(@RequestParam int pageNo, @RequestParam int pageSize,
@RequestParam(name = "username", defaultValue = "") String username) {
return roleService.getRolesFromDatabase(username, pageNo, pageSize);
}
/**
* Add a role to a user
* <p>
* This method is used for 2 functions:
* 1. create a role and bind it to GLOBAL_ADMIN.
* 2. bind a role to an user.
*
* @param role
* @param username
* @return
*/
@PostMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE)
public Object addRole(@RequestParam String role, @RequestParam String username) {
roleService.addRole(role, username);
return new RestResult<>(200, "add role ok!");
}
/**
* Delete a role. If no username is specified, all users under this role are deleted
*
* @param role role
* @param username username
* @return ok if succeed
*/
@DeleteMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE)
public Object deleteRole(@RequestParam String role,
@RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) {
if (StringUtils.isBlank(username)) {
roleService.deleteRole(role);
} else {
roleService.deleteRole(role, username);
}
return new RestResult<>(200, "delete role of user " + username + " ok!");
}
}
@@ -0,0 +1,232 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.controller;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.config.server.auth.RoleInfo;
import com.alibaba.nacos.config.server.model.RestResult;
import com.alibaba.nacos.config.server.model.User;
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
import com.alibaba.nacos.console.security.nacos.NacosAuthManager;
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.console.security.nacos.users.NacosUser;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.console.utils.JwtTokenUtils;
import com.alibaba.nacos.console.utils.PasswordEncoderUtil;
import com.alibaba.nacos.core.auth.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* User related methods entry
*
* @author wfnuser
* @author nkorange
*/
@RestController("user")
@RequestMapping({"/v1/auth", "/v1/auth/users"})
public class UserController {
@Autowired
private JwtTokenUtils jwtTokenUtils;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Autowired
private NacosRoleServiceImpl roleService;
@Autowired
private AuthConfigs authConfigs;
@Autowired
private NacosAuthManager authManager;
/**
* Create a new user
*
* @param username username
* @param password password
* @return ok if create succeed
* @throws IllegalArgumentException if user already exist
* @since 1.2.0
*/
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
@PostMapping
public Object createUser(@RequestParam String username, @RequestParam String password) {
User user = userDetailsService.getUserFromDatabase(username);
if (user != null) {
throw new IllegalArgumentException("user '" + username + "' already exist!");
}
userDetailsService.createUser(username, PasswordEncoderUtil.encode(password));
return new RestResult<>(200, "create user ok!");
}
/**
* Delete an existed user
*
* @param username username of user
* @return ok if deleted succeed, keep silent if user not exist
* @since 1.2.0
*/
@DeleteMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
public Object deleteUser(@RequestParam String username) {
List<RoleInfo> roleInfoList = roleService.getRoles(username);
if (roleInfoList != null) {
for (RoleInfo roleInfo : roleInfoList) {
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
throw new IllegalArgumentException("cannot delete admin: " + username);
}
}
}
userDetailsService.deleteUser(username);
return new RestResult<>(200, "delete user ok!");
}
/**
* Update an user
*
* @param username username of user
* @param newPassword new password of user
* @return ok if update succeed
* @throws IllegalArgumentException if user not exist or oldPassword is incorrect
* @since 1.2.0
*/
@PutMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
public Object updateUser(@RequestParam String username, @RequestParam String newPassword) {
User user = userDetailsService.getUserFromDatabase(username);
if (user == null) {
throw new IllegalArgumentException("user " + username + " not exist!");
}
userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword));
return new RestResult<>(200, "update user ok!");
}
/**
* Get paged users
*
* @param pageNo number index of page
* @param pageSize size of page
* @return A collection of users, empty set if no user is found
* @since 1.2.0
*/
@GetMapping
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ)
public Object getUsers(@RequestParam int pageNo, @RequestParam int pageSize) {
return userDetailsService.getUsersFromDatabase(pageNo, pageSize);
}
/**
* Login to Nacos
* <p>
* This methods uses username and password to require a new token.
*
* @param username username of user
* @param password password
* @param response http response
* @param request http request
* @return new token of the user
* @throws AccessException if user info is incorrect
*/
@PostMapping("/login")
public Object login(@RequestParam String username, @RequestParam String password,
HttpServletResponse response, HttpServletRequest request) throws AccessException {
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
NacosUser user = (NacosUser) authManager.login(request);
response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER,
NacosAuthConfig.TOKEN_PREFIX + user.getToken());
JSONObject result = new JSONObject();
result.put(Constants.ACCESS_TOKEN, user.getToken());
result.put(Constants.TOKEN_TTL, authConfigs.getTokenValidityInSeconds());
result.put(Constants.GLOBAL_ADMIN, user.isGlobalAdmin());
return result;
}
// 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
RestResult<String> rr = new RestResult<String>();
try {
//通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象
Authentication authentication = authenticationManager.authenticate(authenticationToken);
//将 Authentication 绑定到 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
//生成Token
String token = jwtTokenUtils.createToken(authentication);
//将Token写入到Http头部
response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, "Bearer " + token);
rr.setCode(200);
rr.setData("Bearer " + token);
return rr;
} catch (BadCredentialsException authentication) {
rr.setCode(401);
rr.setMessage("Login failed");
return rr;
}
}
@PutMapping("/password")
@Deprecated
public RestResult<String> updatePassword(@RequestParam(value = "oldPassword") String oldPassword,
@RequestParam(value = "newPassword") String newPassword) {
RestResult<String> rr = new RestResult<String>();
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = ((UserDetails) principal).getUsername();
User user = userDetailsService.getUserFromDatabase(username);
String password = user.getPassword();
// TODO: throw out more fine grained exceptions
try {
if (PasswordEncoderUtil.matches(oldPassword, password)) {
userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword));
rr.setCode(200);
rr.setMessage("Update password success");
} else {
rr.setCode(401);
rr.setMessage("Old password is invalid");
}
} catch (Exception e) {
rr.setCode(500);
rr.setMessage("Update userpassword failed");
}
return rr;
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.exception;
import com.alibaba.nacos.core.auth.AccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* Exception handler for console module
*
* @author nkorange
* @since 1.2.0
*/
@ControllerAdvice
public class ConsoleExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ConsoleExceptionHandler.class);
@ExceptionHandler(AccessException.class)
private ResponseEntity<String> handleAccessException(AccessException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
}
@ExceptionHandler(IllegalArgumentException.class)
private ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.toString());
}
@ExceptionHandler(Exception.class)
private ResponseEntity<String> handleException(Exception e) {
logger.error("CONSOLE", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.toString());
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
/**
* auth provider
*
* @author wfnuser
*/
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!password.equals(userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(username, null, null);
}
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* jwt auth fail point
*
* @author wfnuser
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.core.auth.AuthConfigs;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* JWT token manager
*
* @author wfnuser
* @author nkorange
*/
@Component
public class JwtTokenManager {
private static final String AUTHORITIES_KEY = "auth";
@Autowired
private AuthConfigs authConfigs;
/**
* Create token
*
* @param authentication auth info
* @return token
*/
public String createToken(Authentication authentication) {
return createToken(authentication.getName());
}
public String createToken(String userName) {
long now = (new Date()).getTime();
Date validity;
validity = new Date(now + authConfigs.getTokenValidityInSeconds() * 1000L);
Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder()
.setClaims(claims)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, authConfigs.getSecretKey())
.compact();
}
/**
* Get auth Info
*
* @param token token
* @return auth info
*/
public Authentication getAuthentication(String token) {
/**
* parse the payload of token
*/
Claims claims = Jwts.parser()
.setSigningKey(authConfigs.getSecretKey())
.parseClaimsJws(token)
.getBody();
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
/**
* validate token
*
* @param token token
* @return whether valid
*/
public void validateToken(String token) {
Jwts.parser().setSigningKey(authConfigs.getSecretKey()).parseClaimsJws(token);
}
}
@@ -0,0 +1,146 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.console.filter.JwtAuthenticationTokenFilter;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.core.auth.AuthConfigs;
import com.alibaba.nacos.core.auth.AuthSystemTypes;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;
/**
* Spring security config
*
* @author Nacos
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
public static final String LOGIN_ENTRY_POINT = "/v1/auth/login";
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/";
@Autowired
private Environment env;
@Autowired
private JwtTokenManager tokenProvider;
@Autowired
private AuthConfigs authConfigs;
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Value("${nacos.security.ignore.urls}")
private String ignoreURLs;
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) {
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
ignoreURLs = "/**";
}
//
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**");
}
if (StringUtils.isNotBlank(ignoreURLs)) {
for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
web.ignoring().antMatchers(ignoreURL.trim());
}
}
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
http
.csrf().disable()
.cors() // We don't need CSRF for JWT based authentication
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers(LOGIN_ENTRY_POINT).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint());
// disable cache
http.headers().cacheControl();
http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
}
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,136 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.config.server.auth.RoleInfo;
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.console.security.nacos.users.NacosUser;
import com.alibaba.nacos.core.auth.AccessException;
import com.alibaba.nacos.core.auth.AuthManager;
import com.alibaba.nacos.core.auth.Permission;
import com.alibaba.nacos.core.auth.User;
import com.alibaba.nacos.core.utils.Loggers;
import io.jsonwebtoken.ExpiredJwtException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* Builtin access control entry of Nacos
*
* @author nkorange
* @since 1.2.0
*/
@Component
public class NacosAuthManager implements AuthManager {
private static final String TOKEN_PREFIX = "Bearer ";
@Autowired
private JwtTokenManager tokenManager;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private NacosRoleServiceImpl roleService;
@Override
public User login(Object request) throws AccessException {
HttpServletRequest req = (HttpServletRequest) request;
String token = resolveToken(req);
if (StringUtils.isBlank(token)) {
throw new AccessException("user not found!");
}
try {
tokenManager.validateToken(token);
} catch (ExpiredJwtException e) {
throw new AccessException("token expired!");
} catch (Exception e) {
throw new AccessException("token invalid!");
}
Authentication authentication = tokenManager.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
String username = authentication.getName();
NacosUser user = new NacosUser();
user.setUserName(username);
user.setToken(token);
List<RoleInfo> roleInfoList = roleService.getRoles(username);
if (roleInfoList != null) {
for (RoleInfo roleInfo : roleInfoList) {
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
user.setGlobalAdmin(true);
break;
}
}
}
return user;
}
@Override
public void auth(Permission permission, User user) throws AccessException {
if (Loggers.AUTH.isDebugEnabled()) {
Loggers.AUTH.debug("auth permission: {}, user: {}", permission, user);
}
if (!roleService.hasPermission(user.getUserName(), permission)) {
throw new AccessException("authorization failed!");
}
}
/**
* Get token from header
*/
private String resolveToken(HttpServletRequest request) throws AccessException {
String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER);
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(7);
}
bearerToken = request.getParameter(Constants.ACCESS_TOKEN);
if (StringUtils.isBlank(bearerToken)) {
String userName = request.getParameter("username");
String password = request.getParameter("password");
bearerToken = resolveTokenFromUser(userName, password);
}
return bearerToken;
}
private String resolveTokenFromUser(String userName, String rawPassword) throws AccessException {
try {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, rawPassword);
authenticationManager.authenticate(authenticationToken);
} catch (AuthenticationException e) {
throw new AccessException("unknown user!");
}
return tokenManager.createToken(userName);
}
}
@@ -0,0 +1,219 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos.roles;
import com.alibaba.nacos.config.server.auth.PermissionInfo;
import com.alibaba.nacos.config.server.auth.PermissionPersistService;
import com.alibaba.nacos.config.server.auth.RoleInfo;
import com.alibaba.nacos.config.server.auth.RolePersistService;
import com.alibaba.nacos.config.server.model.Page;
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.core.auth.AuthConfigs;
import com.alibaba.nacos.core.auth.Permission;
import com.alibaba.nacos.core.utils.Loggers;
import io.jsonwebtoken.lang.Collections;
import org.apache.commons.lang3.StringUtils;
import org.apache.mina.util.ConcurrentHashSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* Nacos builtin role service.
*
* @author nkorange
* @since 1.2.0
*/
@Service
public class NacosRoleServiceImpl {
public static final String GLOBAL_ADMIN_ROLE = "ROLE_ADMIN";
@Autowired
private AuthConfigs authConfigs;
@Autowired
private RolePersistService rolePersistService;
@Autowired
private NacosUserDetailsServiceImpl userDetailsService;
@Autowired
private PermissionPersistService permissionPersistService;
private Set<String> roleSet = new ConcurrentHashSet<>();
private Map<String, List<RoleInfo>> roleInfoMap = new ConcurrentHashMap<>();
private Map<String, List<PermissionInfo>> permissionInfoMap = new ConcurrentHashMap<>();
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
private void reload() {
try {
Page<RoleInfo> roleInfoPage = rolePersistService.getRolesByUserName(StringUtils.EMPTY, 1, Integer.MAX_VALUE);
if (roleInfoPage == null) {
return;
}
Set<String> tmpRoleSet = new HashSet<>(16);
Map<String, List<RoleInfo>> tmpRoleInfoMap = new ConcurrentHashMap<>(16);
for (RoleInfo roleInfo : roleInfoPage.getPageItems()) {
if (!tmpRoleInfoMap.containsKey(roleInfo.getUsername())) {
tmpRoleInfoMap.put(roleInfo.getUsername(), new ArrayList<>());
}
tmpRoleInfoMap.get(roleInfo.getUsername()).add(roleInfo);
tmpRoleSet.add(roleInfo.getRole());
}
Map<String, List<PermissionInfo>> tmpPermissionInfoMap = new ConcurrentHashMap<>(16);
for (String role : tmpRoleSet) {
Page<PermissionInfo> permissionInfoPage = permissionPersistService.getPermissions(role, 1, Integer.MAX_VALUE);
tmpPermissionInfoMap.put(role, permissionInfoPage.getPageItems());
}
roleSet = tmpRoleSet;
roleInfoMap = tmpRoleInfoMap;
permissionInfoMap = tmpPermissionInfoMap;
} catch (Exception e) {
Loggers.AUTH.warn("[LOAD-ROLES] load failed", e);
}
}
/**
* Determine if the user has permission of the resource.
* <p>
* Note if the user has many roles, this method returns true if any one role of the user has the
* desired permission.
*
* @param username user info
* @param permission permission to auth
* @return true if granted, false otherwise
*/
public boolean hasPermission(String username, Permission permission) {
List<RoleInfo> roleInfoList = getRoles(username);
if (Collections.isEmpty(roleInfoList)) {
return false;
}
// Global admin pass:
for (RoleInfo roleInfo : roleInfoList) {
if (GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) {
return true;
}
}
// Old global admin can pass resource 'console/':
if (permission.getResource().startsWith(NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX)) {
return false;
}
// For other roles, use a pattern match to decide if pass or not.
for (RoleInfo roleInfo : roleInfoList) {
List<PermissionInfo> permissionInfoList = getPermissions(roleInfo.getRole());
if (Collections.isEmpty(permissionInfoList)) {
continue;
}
for (PermissionInfo permissionInfo : permissionInfoList) {
String permissionResource = permissionInfo.getResource().replaceAll("\\*", ".*");
String permissionAction = permissionInfo.getAction();
if (permissionAction.contains(permission.getAction()) &&
Pattern.matches(permissionResource, permission.getResource())) {
return true;
}
}
}
return false;
}
public List<RoleInfo> getRoles(String username) {
List<RoleInfo> roleInfoList = roleInfoMap.get(username);
if (!authConfigs.isCachingEnabled()) {
Page<RoleInfo> roleInfoPage = getRolesFromDatabase(username, 1, Integer.MAX_VALUE);
if (roleInfoPage != null) {
roleInfoList = roleInfoPage.getPageItems();
}
}
return roleInfoList;
}
public Page<RoleInfo> getRolesFromDatabase(String userName, int pageNo, int pageSize) {
Page<RoleInfo> roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize);
if (roles == null) {
return new Page<>();
}
return roles;
}
public List<PermissionInfo> getPermissions(String role) {
List<PermissionInfo> permissionInfoList = permissionInfoMap.get(role);
if (!authConfigs.isCachingEnabled()) {
Page<PermissionInfo> permissionInfoPage = getPermissionsFromDatabase(role, 1, Integer.MAX_VALUE);
if (permissionInfoPage != null) {
permissionInfoList = permissionInfoPage.getPageItems();
}
}
return permissionInfoList;
}
public Page<PermissionInfo> getPermissionsByRoleFromDatabase(String role, int pageNo, int pageSize) {
return permissionPersistService.getPermissions(role, pageNo, pageSize);
}
public void addRole(String role, String username) {
if (userDetailsService.getUser(username) == null) {
throw new IllegalArgumentException("user '" + username + "' not found!");
}
if (GLOBAL_ADMIN_ROLE.equals(role)) {
throw new IllegalArgumentException("role '" + GLOBAL_ADMIN_ROLE + "' is not permitted to create!");
}
rolePersistService.addRole(role, username);
roleSet.add(role);
}
public void deleteRole(String role, String userName) {
rolePersistService.deleteRole(role, userName);
}
public void deleteRole(String role) {
rolePersistService.deleteRole(role);
roleSet.remove(role);
}
public Page<PermissionInfo> getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
Page<PermissionInfo> pageInfo = permissionPersistService.getPermissions(role, pageNo, pageSize);
if (pageInfo == null) {
return new Page<>();
}
return pageInfo;
}
public void addPermission(String role, String resource, String action) {
if (!roleSet.contains(role)) {
throw new IllegalArgumentException("role " + role + " not found!");
}
permissionPersistService.addPermission(role, resource, action);
}
public void deletePermission(String role, String resource, String action) {
permissionPersistService.deletePermission(role, resource, action);
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos.users;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.core.auth.User;
/**
* @author nkorange
* @since 1.2.0
*/
public class NacosUser extends User {
private String token;
private boolean globalAdmin = false;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isGlobalAdmin() {
return globalAdmin;
}
public void setGlobalAdmin(boolean globalAdmin) {
this.globalAdmin = globalAdmin;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos.users;
import com.alibaba.nacos.config.server.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* custem user
*
* @author wfnuser
*/
public class NacosUserDetails implements UserDetails {
private User user;
public NacosUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO: get authorities
return AuthorityUtils.commaSeparatedStringToAuthorityList("");
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.console.security.nacos.users;
import com.alibaba.nacos.config.server.auth.UserPersistService;
import com.alibaba.nacos.config.server.model.Page;
import com.alibaba.nacos.config.server.model.User;
import com.alibaba.nacos.core.auth.AuthConfigs;
import com.alibaba.nacos.core.utils.Loggers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Custem user service
*
* @author wfnuser
* @author nkorange
*/
@Service
public class NacosUserDetailsServiceImpl implements UserDetailsService {
private Map<String, User> userMap = new ConcurrentHashMap<>();
@Autowired
private UserPersistService userPersistService;
@Autowired
private AuthConfigs authConfigs;
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
private void reload() {
try {
Page<User> users = getUsersFromDatabase(1, Integer.MAX_VALUE);
if (users == null) {
return;
}
Map<String, User> map = new ConcurrentHashMap<>(16);
for (User user : users.getPageItems()) {
map.put(user.getUsername(), user);
}
userMap = map;
} catch (Exception e) {
Loggers.AUTH.warn("[LOAD-USERS] load failed", e);
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMap.get(username);
if (!authConfigs.isCachingEnabled()) {
user = userPersistService.findUserByUsername(username);
}
if (user == null) {
throw new UsernameNotFoundException(username);
}
return new NacosUserDetails(user);
}
public void updateUserPassword(String username, String password) {
userPersistService.updateUserPassword(username, password);
}
public Page<User> getUsersFromDatabase(int pageNo, int pageSize) {
return userPersistService.getUsers(pageNo, pageSize);
}
public User getUser(String username) {
User user = userMap.get(username);
if (!authConfigs.isCachingEnabled()) {
user = getUserFromDatabase(username);
}
return user;
}
public User getUserFromDatabase(String username) {
return userPersistService.findUserByUsername(username);
}
public void createUser(String username, String password) {
userPersistService.createUser(username, password);
}
public void deleteUser(String username) {
userPersistService.deleteUser(username);
}
}
@@ -0,0 +1,75 @@
import { isJsonString } from '../utils/nacosutil';
const configurationMenu = {
key: 'configurationManagementVirtual',
children: [
{
key: 'configurationManagement',
url: '/configurationManagement',
},
{
key: 'historyRollback',
url: '/historyRollback',
},
{
key: 'listeningToQuery',
url: '/listeningToQuery',
},
],
};
/**
* 权限控制相关
*/
const authorityControlMenu = {
key: 'authorityControl',
children: [
{
key: 'userList',
url: '/userManagement',
},
{
key: 'roleManagement',
url: '/rolesManagement',
},
{
key: 'privilegeManagement',
url: '/permissionsManagement',
},
],
};
export default function(model) {
const { token = '{}' } = localStorage;
const { globalAdmin } = isJsonString(token) ? JSON.parse(token) || {} : {};
return [
model === 'naming' ? undefined : configurationMenu,
{
key: 'serviceManagementVirtual',
children: [
{
key: 'serviceManagement',
url: '/serviceManagement',
},
{
key: 'subscriberList',
url: '/subscriberList',
},
],
},
{
key: 'clusterManagementVirtual',
children: [
{
key: 'clusterManagement',
url: '/clusterManagement',
},
],
},
globalAdmin ? authorityControlMenu : undefined,
{
key: 'namespace',
url: '/namespace',
},
].filter(item => item);
}
@@ -0,0 +1,121 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Field, Form, Input, Select, Dialog, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getNamespaces } from '../../../reducers/namespace';
const FormItem = Form.Item;
const { Option } = Select;
const formItemLayout = {
labelCol: { fixedSpan: 4 },
wrapperCol: { span: 19 },
};
@connect(state => ({ namespaces: state.namespace.namespaces }), { getNamespaces })
@ConfigProvider.config
class NewPermissions extends React.Component {
static displayName = 'NewPermissions';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
getNamespaces: PropTypes.func,
onOk: PropTypes.func,
onCancel: PropTypes.func,
namespaces: PropTypes.array,
};
componentDidMount() {
this.props.getNamespaces();
}
check() {
const { locale } = this.props;
const errors = {
role: locale.roleError,
resource: locale.resourceError,
action: locale.actionError,
};
const vals = Object.keys(errors).map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length === 3) {
return vals;
}
return null;
}
render() {
const { getError } = this.field;
const { visible, onOk, onCancel, locale, namespaces } = this.props;
return (
<>
<Dialog
title={locale.addPermission}
visible={visible}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label={locale.role} required help={getError('role')}>
<Input name="role" trim placeholder={locale.rolePlaceholder} />
</FormItem>
<FormItem label={locale.resource} required help={getError('resource')}>
<Select
name="resource"
placeholder={locale.resourcePlaceholder}
style={{ width: '100%' }}
>
{namespaces.map(({ namespace, namespaceShowName }) => (
<Option value={`${namespace}:*:*`}>
{namespaceShowName} {namespace ? `(${namespace})` : ''}
</Option>
))}
</Select>
</FormItem>
<FormItem label={locale.action} required help={getError('action')}>
<Select
name="action"
placeholder={locale.actionPlaceholder}
style={{ width: '100%' }}
>
<Option value="r">{locale.readOnly}(r)</Option>
<Option value="w">{locale.writeOnly}(w)</Option>
<Option value="rw">{locale.readWrite}(rw)</Option>
</Select>
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default NewPermissions;
@@ -0,0 +1,162 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getPermissions, createPermission, deletePermission } from '../../../reducers/authority';
import { getNamespaces } from '../../../reducers/namespace';
import RegionGroup from '../../../components/RegionGroup';
import NewPermissions from './NewPermissions';
import './PermissionsManagement.scss';
@connect(
state => ({
permissions: state.authority.permissions,
namespaces: state.namespace.namespaces,
}),
{ getPermissions, getNamespaces }
)
@ConfigProvider.config
class PermissionsManagement extends React.Component {
static displayName = 'PermissionsManagement';
static propTypes = {
locale: PropTypes.object,
permissions: PropTypes.object,
namespaces: PropTypes.object,
getPermissions: PropTypes.func,
getNamespaces: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
loading: true,
pageNo: 1,
pageSize: 9,
createPermission: false,
};
}
componentDidMount() {
this.getPermissions();
this.props.getNamespaces();
}
getPermissions() {
const { pageNo, pageSize } = this.state;
this.props
.getPermissions({ pageNo, pageSize })
.then(() => {
if (this.state.loading) {
this.setState({ loading: false });
}
})
.catch(() => this.setState({ loading: false }));
}
colseCreatePermission() {
this.setState({ createPermissionVisible: false });
}
getActionText(action) {
const { locale } = this.props;
return {
r: `${locale.readOnly} (r)`,
w: `${locale.writeOnly} (w)`,
rw: `${locale.readWrite} (rw)`,
}[action];
}
render() {
const { permissions, namespaces = [], locale } = this.props;
const { loading, pageSize, pageNo, createPermissionVisible } = this.state;
return (
<>
<RegionGroup left={locale.privilegeManagement} />
<div className="filter-panel">
<Button type="primary" onClick={() => this.setState({ createPermissionVisible: true })}>
{locale.addPermission}
</Button>
</div>
<Table dataSource={permissions.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
<Table.Column title={locale.role} dataIndex="role" />
<Table.Column
title={locale.resource}
dataIndex="resource"
cell={value => {
const [item = {}] = namespaces.filter(({ namespace }) => {
const [itemNamespace] = value.split(':');
return itemNamespace === namespace;
});
const { namespaceShowName = '', namespace = '' } = item;
return namespaceShowName + (namespace ? ` (${namespace})` : '');
}}
/>
<Table.Column
title={locale.action}
dataIndex="action"
cell={action => this.getActionText(action)}
/>
<Table.Column
title={locale.operation}
cell={(value, index, record) => (
<>
<Button
type="primary"
warning
onClick={() =>
Dialog.confirm({
title: locale.deletePermission,
content: locale.deletePermissionTip,
onOk: () =>
deletePermission(record).then(() => {
this.setState({ pageNo: 1 }, () => this.getPermissions());
}),
})
}
>
{locale.deletePermission}
</Button>
</>
)}
/>
</Table>
{permissions.totalCount > pageSize && (
<Pagination
className="users-pagination"
current={pageNo}
total={permissions.totalCount}
pageSize={pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.getPermissions())}
/>
)}
<NewPermissions
visible={createPermissionVisible}
onOk={permission =>
createPermission(permission).then(res => {
this.setState({ pageNo: 1 }, () => this.getPermissions());
return res;
})
}
onCancel={() => this.colseCreatePermission()}
/>
</>
);
}
}
export default PermissionsManagement;
@@ -0,0 +1,12 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import PermissionsManagement from './PermissionsManagement';
export default PermissionsManagement;
@@ -0,0 +1,7 @@
# 权限控制
> AuthorityControl
1. UserManagement => 用户管理
2. RolesManagement => 角色管理
3. PermissionsManagement => 权限管理
@@ -0,0 +1,90 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 4 },
wrapperCol: { span: 19 },
};
@ConfigProvider.config
class NewRole extends React.Component {
static displayName = 'NewRole';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func,
};
check() {
const { locale } = this.props;
const errors = {
role: locale.roleError,
username: locale.usernameError,
};
const vals = Object.keys(errors).map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length === 2) {
return vals;
}
return null;
}
render() {
const { locale } = this.props;
const { getError } = this.field;
const { visible, onOk, onCancel } = this.props;
return (
<>
<Dialog
title={locale.bindingRoles}
visible={visible}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label={locale.role} required help={getError('role')}>
<Input name="role" trim placeholder={locale.rolePlaceholder} />
</FormItem>
<FormItem label={locale.username} required help={getError('username')}>
<Input name="username" placeholder={locale.usernamePlaceholder} />
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default NewRole;
@@ -0,0 +1,130 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getRoles, createRole, deleteRole } from '../../../reducers/authority';
import RegionGroup from '../../../components/RegionGroup';
import NewRole from './NewRole';
import './RolesManagement.scss';
@connect(state => ({ roles: state.authority.roles }), { getRoles })
@ConfigProvider.config
class RolesManagement extends React.Component {
static displayName = 'RolesManagement';
static propTypes = {
locale: PropTypes.object,
roles: PropTypes.object,
getRoles: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
loading: true,
pageNo: 1,
pageSize: 9,
};
}
componentDidMount() {
this.getRoles();
}
getRoles() {
const { pageNo, pageSize } = this.state;
this.props
.getRoles({ pageNo, pageSize })
.then(() => {
if (this.state.loading) {
this.setState({ loading: false });
}
})
.catch(() => this.setState({ loading: false }));
}
colseCreateRole() {
this.setState({ createRoleVisible: false });
}
render() {
const { roles, locale } = this.props;
const { loading, pageSize, pageNo, createRoleVisible, passwordResetUser } = this.state;
return (
<>
<RegionGroup left={locale.roleManagement} />
<div className="filter-panel">
<Button type="primary" onClick={() => this.setState({ createRoleVisible: true })}>
{locale.bindingRoles}
</Button>
</div>
<Table dataSource={roles.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
<Table.Column title={locale.role} dataIndex="role" />
<Table.Column title={locale.username} dataIndex="username" />
<Table.Column
title={locale.operation}
dataIndex="role"
cell={(value, index, record) => {
if (value === 'ROLE_ADMIN') {
return null;
}
return (
<Button
type="primary"
warning
onClick={() =>
Dialog.confirm({
title: locale.deleteRole,
content: locale.deleteRoleTip,
onOk: () =>
deleteRole(record).then(() => {
this.setState({ pageNo: 1 }, () => this.getRoles());
}),
})
}
>
{locale.deleteRole}
</Button>
);
}}
/>
</Table>
{roles.totalCount > pageSize && (
<Pagination
className="users-pagination"
current={pageNo}
total={roles.totalCount}
pageSize={pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.getRoles())}
/>
)}
<NewRole
visible={createRoleVisible}
onOk={role =>
createRole(role).then(res => {
this.getRoles();
return res;
})
}
onCancel={() => this.colseCreateRole()}
/>
</>
);
}
}
export default RolesManagement;
@@ -0,0 +1,12 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import RolesManagement from './RolesManagement';
export default RolesManagement;
@@ -0,0 +1,104 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
import './UserManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 4 },
wrapperCol: { span: 19 },
};
@ConfigProvider.config
class NewUser extends React.Component {
static displayName = 'NewUser';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
onOk: PropTypes.func,
onCancel: PropTypes.func,
};
check() {
const { locale } = this.props;
const errors = {
username: locale.usernameError,
password: locale.passwordError,
rePassword: locale.rePasswordError,
};
const vals = Object.keys(errors).map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length !== 3) {
return null;
}
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
if (password !== rePassword) {
this.field.setError('rePassword', locale.rePasswordError2);
return null;
}
return vals;
}
render() {
const { locale } = this.props;
const { getError } = this.field;
const { visible, onOk, onCancel } = this.props;
return (
<>
<Dialog
title={locale.createUser}
visible={visible}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label={locale.username} required help={getError('username')}>
<Input name="username" trim placeholder={locale.usernamePlaceholder} />
</FormItem>
<FormItem label={locale.password} required help={getError('password')}>
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
</FormItem>
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
<Input
name="rePassword"
htmlType="password"
placeholder={locale.rePasswordPlaceholder}
/>
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default NewUser;
@@ -0,0 +1,104 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
import './UserManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 4 },
wrapperCol: { span: 19 },
};
@ConfigProvider.config
class PasswordReset extends React.Component {
static displayName = 'PasswordReset';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
username: PropTypes.string,
onCancel: PropTypes.func,
onOk: PropTypes.func,
};
check() {
const { locale } = this.props;
const errors = {
password: locale.passwordError,
rePassword: locale.rePasswordError,
};
const vals = Object.keys(errors).map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length !== 2) {
return null;
}
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
if (password !== rePassword) {
this.field.setError('rePassword', locale.rePasswordError2);
return null;
}
return [this.props.username, ...vals];
}
render() {
const { locale } = this.props;
const { getError } = this.field;
const { username, onOk, onCancel } = this.props;
return (
<>
<Dialog
title={locale.resetPassword}
visible={username}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label={locale.username} required>
<p>{username}</p>
</FormItem>
<FormItem label={locale.password} required help={getError('password')}>
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
</FormItem>
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
<Input
name="rePassword"
htmlType="password"
placeholder={locale.rePasswordPlaceholder}
/>
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default PasswordReset;
@@ -0,0 +1,150 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getUsers, createUser, deleteUser, passwordReset } from '../../../reducers/authority';
import RegionGroup from '../../../components/RegionGroup';
import NewUser from './NewUser';
import PasswordReset from './PasswordReset';
import './UserManagement.scss';
@connect(state => ({ users: state.authority.users }), { getUsers })
@ConfigProvider.config
class UserManagement extends React.Component {
static displayName = 'UserManagement';
static propTypes = {
locale: PropTypes.object,
users: PropTypes.object,
getUsers: PropTypes.func,
createUser: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
loading: true,
pageNo: 1,
pageSize: 9,
};
}
componentDidMount() {
this.getUsers();
}
getUsers() {
const { pageNo, pageSize } = this.state;
this.props
.getUsers({ pageNo, pageSize })
.then(() => {
if (this.state.loading) {
this.setState({ loading: false });
}
})
.catch(() => this.setState({ loading: false }));
}
colseCreateUser() {
this.setState({ createUserVisible: false });
}
render() {
const { users, locale } = this.props;
const { loading, pageSize, pageNo, createUserVisible, passwordResetUser } = this.state;
return (
<>
<RegionGroup left={locale.userManagement} />
<div className="filter-panel">
<Button type="primary" onClick={() => this.setState({ createUserVisible: true })}>
{locale.createUser}
</Button>
</div>
<Table dataSource={users.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
<Table.Column title={locale.username} dataIndex="username" />
<Table.Column
title={locale.password}
dataIndex="password"
cell={value => value.replace(/\S/g, '*')}
/>
<Table.Column
title={locale.operation}
dataIndex="username"
cell={username => (
<>
<Button
type="primary"
onClick={() => this.setState({ passwordResetUser: username })}
>
{locale.resetPassword}
</Button>
&nbsp;&nbsp;&nbsp;
<Button
type="primary"
warning
onClick={() =>
Dialog.confirm({
title: locale.deleteUser,
content: locale.deleteUserTip,
onOk: () =>
deleteUser(username).then(() => {
this.setState({ pageNo: 1 }, () => this.getUsers());
}),
})
}
>
{locale.deleteUser}
</Button>
</>
)}
/>
</Table>
{users.totalCount > pageSize && (
<Pagination
className="users-pagination"
current={pageNo}
total={users.totalCount}
pageSize={pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.getUsers())}
/>
)}
<NewUser
visible={createUserVisible}
onOk={user =>
createUser(user).then(res => {
this.setState({ pageNo: 1 }, () => this.getUsers());
return res;
})
}
onCancel={() => this.colseCreateUser()}
/>
<PasswordReset
username={passwordResetUser}
onOk={user =>
passwordReset(user).then(res => {
this.getUsers();
return res;
})
}
onCancel={() => this.setState({ passwordResetUser: undefined })}
/>
</>
);
}
}
export default UserManagement;
@@ -0,0 +1,18 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../authority.scss';
.users-pagination {
float: right;
margin-top: 20px;
}
@@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import UserManagement from './UserManagement';
export default UserManagement;
@@ -0,0 +1,17 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.filter-panel {
text-align: right;
padding: 10px 0;
}
@@ -0,0 +1,143 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Message } from '@alifd/next';
import request from '../utils/request';
import { UPDATE_USER, SIGN_IN, USER_LIST, ROLE_LIST, PERMISSIONS_LIST } from '../constants';
const initialState = {
users: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
roles: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
permissions: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
};
const successMsg = res => {
if (res.code === 200) {
Message.success(res.message);
}
return res;
};
/**
* 用户列表
* @param {*} params
*/
const getUsers = params => dispatch =>
request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data }));
/**
* 创建用户
* @param {*} param0
*/
const createUser = ([username, password]) =>
request.post('v1/auth/users', { username, password }).then(res => successMsg(res));
/**
* 删除用户
* @param {*} username
*/
const deleteUser = username =>
request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res));
/**
* 重置密码
* @param {*} param0
*/
const passwordReset = ([username, newPassword]) =>
request.put('v1/auth/users', { username, newPassword }).then(res => successMsg(res));
/**
* 角色列表
* @param {*} params
*/
const getRoles = params => dispatch =>
request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data }));
/**
* 创建角色
* @param {*} param0
*/
const createRole = ([role, username]) =>
request.post('v1/auth/roles', { role, username }).then(res => successMsg(res));
/**
* 删除角色
* @param {*} param0
*/
const deleteRole = role =>
request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res));
/**
* 权限列表
* @param {*} params
*/
const getPermissions = params => dispatch =>
request
.get('v1/auth/permissions', { params })
.then(data => dispatch({ type: PERMISSIONS_LIST, data }));
/**
* 给角色添加权限
* @param {*} param0
*/
const createPermission = ([role, resource, action]) =>
request.post('v1/auth/permissions', { role, resource, action }).then(res => successMsg(res));
/**
* 删除权限
* @param {*} param0
*/
const deletePermission = permission =>
request.delete('v1/auth/permissions', { params: permission }).then(res => successMsg(res));
export default (state = initialState, action) => {
switch (action.type) {
case USER_LIST:
return { ...state, users: { ...action.data } };
case ROLE_LIST:
return { ...state, roles: { ...action.data } };
case PERMISSIONS_LIST:
return { ...state, permissions: { ...action.data } };
default:
return state;
}
};
export {
getUsers,
createUser,
deleteUser,
passwordReset,
getRoles,
createRole,
deleteRole,
getPermissions,
createPermission,
deletePermission,
};
@@ -0,0 +1,35 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import request from '../utils/request';
import { GET_CONFIGURATION } from '../constants';
const initialState = {
configurations: [],
};
const getConfigs = params => dispatch =>
request
.get('v1/cs/configs', { params })
.then(data => dispatch({ type: GET_CONFIGURATION, data }));
export default (state = initialState, action) => {
switch (action.type) {
case GET_CONFIGURATION:
return { ...state, configurations: action.data };
default:
return state;
}
};
export { getConfigs };
@@ -0,0 +1,39 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import request from '../utils/request';
import { GET_NAMESPACES } from '../constants';
const initialState = {
namespaces: [],
};
const getNamespaces = params => dispatch =>
request.get('v1/console/namespaces', { params }).then(response => {
const { code, data } = response;
dispatch({
type: GET_NAMESPACES,
data: code === 200 ? data : [],
});
});
export default (state = initialState, action) => {
switch (action.type) {
case GET_NAMESPACES:
return { ...state, namespaces: action.data };
default:
return state;
}
};
export { getNamespaces };