/* * 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.controller; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.common.ActionTypes; import com.alibaba.nacos.auth.common.AuthConfigs; import com.alibaba.nacos.auth.common.AuthSystemTypes; import com.alibaba.nacos.auth.exception.AccessException; import com.alibaba.nacos.common.model.RestResult; import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.Objects; import com.alibaba.nacos.config.server.auth.RoleInfo; import com.alibaba.nacos.config.server.model.User; import com.alibaba.nacos.config.server.utils.RequestUtil; import com.alibaba.nacos.security.nacos.JwtTokenManager; import com.alibaba.nacos.security.nacos.NacosAuthConfig; import com.alibaba.nacos.security.nacos.NacosAuthManager; import com.alibaba.nacos.security.nacos.roles.NacosRoleServiceImpl; import com.alibaba.nacos.security.nacos.users.NacosUser; import com.alibaba.nacos.security.nacos.users.NacosUserDetailsServiceImpl; import com.alibaba.nacos.utils.PasswordEncoderUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; 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.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; 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 JwtTokenManager jwtTokenManager; @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 RestResultUtils.success("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 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 RestResultUtils.success("delete user ok!"); } /** * Update an user. * @param username username of user * @param newPassword new password of user * @param response http response * @param request http request * @return ok if update succeed * @throws IllegalArgumentException if user not exist or oldPassword is incorrect * @since 1.2.0 */ @PutMapping @Secured(resource = NacosAuthConfig.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE) public Object updateUser(@RequestParam String username, @RequestParam String newPassword, HttpServletResponse response, HttpServletRequest request) throws IOException { // admin or same user if (!hasPermission(username, request)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "authorization failed!"); } User user = userDetailsService.getUserFromDatabase(username); if (user == null) { throw new IllegalArgumentException("user " + username + " not exist!"); } userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); return RestResultUtils.success("update user ok!"); } private boolean hasPermission(String username, HttpServletRequest request) { if (!authConfigs.isAuthEnabled()) { return true; } if (Objects.isNull(request.getAttribute(RequestUtil.NACOS_USER_KEY))) { return false; } NacosUser user = (NacosUser) request.getAttribute(RequestUtil.NACOS_USER_KEY); // admin if (user.isGlobalAdmin()) { return true; } // same user return user.getUserName().equals(username); } /** * 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 * *

* 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()) || AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { NacosUser user = (NacosUser) authManager.login(request); response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, NacosAuthConfig.TOKEN_PREFIX + user.getToken()); ObjectNode result = JacksonUtils.createEmptyJsonNode(); result.put(Constants.ACCESS_TOKEN, user.getToken()); result.put(Constants.TOKEN_TTL, authConfigs.getTokenValidityInSeconds()); result.put(Constants.GLOBAL_ADMIN, user.isGlobalAdmin()); result.put(Constants.USERNAME, user.getUserName()); return result; } // create Authentication class through username and password, the implement class // is UsernamePasswordAuthenticationToken UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); try { // use the method authenticate of AuthenticationManager(default implement is // ProviderManager) to valid Authentication Authentication authentication = authenticationManager.authenticate(authenticationToken); // bind SecurityContext to Authentication SecurityContextHolder.getContext().setAuthentication(authentication); // generate Token String token = jwtTokenManager.createToken(authentication); // write Token to Http header response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, "Bearer " + token); return RestResultUtils.success("Bearer " + token); } catch (BadCredentialsException authentication) { return RestResultUtils.failed(HttpStatus.UNAUTHORIZED.value(), null, "Login failed"); } } /** * Update password. * @param oldPassword old password * @param newPassword new password * @return Code 200 if update successfully, Code 401 if old password invalid, * otherwise 500 */ @PutMapping("/password") @Deprecated public RestResult updatePassword(@RequestParam(value = "oldPassword") String oldPassword, @RequestParam(value = "newPassword") String newPassword) { 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)); return RestResultUtils.success("Update password success"); } return RestResultUtils.failed(HttpStatus.UNAUTHORIZED.value(), "Old password is invalid"); } catch (Exception e) { return RestResultUtils.failed(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Update userpassword failed"); } } /** * Fuzzy matching username. * @param username username * @return Matched username */ @GetMapping("/search") @Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) public List searchUsersLikeUsername(@RequestParam String username) { return userDetailsService.findUserLikeUsername(username); } }