You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by ni...@apache.org on 2019/08/15 03:08:26 UTC
[kylin] branch master updated: KYLIN-4122 Add kylin user and group
manage modules
This is an automated email from the ASF dual-hosted git repository.
nic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git
The following commit(s) were added to refs/heads/master by this push:
new 4651737 KYLIN-4122 Add kylin user and group manage modules
4651737 is described below
commit 465173758773f947339a6568f5546afbb25551d0
Author: luguosheng1314 <55...@qq.com>
AuthorDate: Wed Aug 7 15:38:23 2019 +0800
KYLIN-4122 Add kylin user and group manage modules
---
.../org/apache/kylin/metadata/acl/UserGroup.java | 59 ++++
.../rest/controller/KylinUserGroupController.java | 103 +++++-
.../kylin/rest/controller/UserController.java | 308 ++++++++++++++++
.../kylin/rest/request/PasswdChangeRequest.java | 62 ++++
.../apache/kylin/rest/security/ManagedUser.java | 10 +-
.../kylin/rest/service/KylinUserGroupService.java | 168 ++++++++-
.../kylin/rest/service/KylinUserService.java | 43 +++
...UserGroupService.java => UserGroupService.java} | 61 ++--
.../org/apache/kylin/rest/service/UserService.java | 10 +-
.../apache/kylin/rest/util/AclPermissionUtil.java | 6 +-
.../org/apache/kylin/rest/util/PagingUtil.java | 29 +-
.../java/org/apache/kylin/rest/DebugTomcat.java | 10 +-
server/src/main/resources/kylinSecurity.xml | 34 +-
.../rest/controller/AccessControllerTest.java | 23 +-
.../kylin/rest/controller/UserControllerTest.java | 50 +--
.../apache/kylin/rest/service/ServiceTestBase.java | 26 +-
.../apache/kylin/rest/util/ValidateUtilTest.java | 11 +-
webapp/app/index.html | 4 +-
webapp/app/js/app.js | 2 +-
webapp/app/js/controllers/admin.js | 13 +-
webapp/app/js/controllers/userGroup.js | 387 +++++++++++++++++++++
webapp/app/js/directives/directives.js | 2 +-
webapp/app/js/services/kylinProperties.js | 9 +-
webapp/app/js/{app.js => services/userGroup.js} | 12 +-
webapp/app/js/services/users.js | 12 +-
webapp/app/js/{app.js => utils/response.js} | 29 +-
webapp/app/less/app.less | 3 +
webapp/app/partials/admin/admin.html | 14 +-
webapp/app/partials/admin/change_pwd.html | 78 +++++
webapp/app/partials/admin/group.html | 93 +++++
webapp/app/partials/admin/group_assign.html | 87 +++++
webapp/app/partials/admin/group_create.html | 48 +++
webapp/app/partials/admin/user.html | 108 ++++++
webapp/app/partials/admin/user_assign.html | 87 +++++
webapp/app/partials/admin/user_create.html | 73 ++++
webapp/app/partials/common/access.html | 5 +-
36 files changed, 1940 insertions(+), 139 deletions(-)
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/acl/UserGroup.java b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/UserGroup.java
new file mode 100644
index 0000000..10712c2
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/acl/UserGroup.java
@@ -0,0 +1,59 @@
+/*
+ * 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.kylin.metadata.acl;
+
+import java.util.List;
+import java.util.TreeSet;
+
+import org.apache.kylin.common.persistence.RootPersistentEntity;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.Lists;
+
+@SuppressWarnings("serial")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
+public class UserGroup extends RootPersistentEntity {
+ @JsonProperty
+ private TreeSet<String> groups = new TreeSet<>();
+
+ public List<String> getAllGroups() {
+ return Lists.newArrayList(this.groups);
+ }
+
+ public boolean exists(String name) {
+ return groups.contains(name);
+ }
+
+ public UserGroup add(String name) {
+ if (groups.contains(name)) {
+ throw new RuntimeException("Operation failed, group:" + name + " already exists");
+ }
+ this.groups.add(name);
+ return this;
+ }
+
+ public UserGroup delete(String name) {
+ if (!groups.contains(name)) {
+ throw new RuntimeException("Operation failed, group:" + name + " does not exists");
+ }
+ this.groups.remove(name);
+ return this;
+ }
+}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/KylinUserGroupController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/KylinUserGroupController.java
index 5cfc869..06159ae 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/controller/KylinUserGroupController.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/controller/KylinUserGroupController.java
@@ -19,28 +19,123 @@
package org.apache.kylin.rest.controller;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
-import org.apache.kylin.rest.service.IUserGroupService;
+import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.common.util.StringUtil;
+import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.exception.InternalErrorException;
+import org.apache.kylin.rest.response.EnvelopeResponse;
+import org.apache.kylin.rest.response.ResponseCode;
+import org.apache.kylin.rest.security.ManagedUser;
+import org.apache.kylin.rest.service.UserGroupService;
+import org.apache.kylin.rest.util.PagingUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
+import com.google.common.collect.Sets;
+
@Controller
@RequestMapping(value = "/user_group")
public class KylinUserGroupController extends BasicController {
@Autowired
@Qualifier("userGroupService")
- private IUserGroupService userGroupService;
+ private UserGroupService userGroupService;
+
+ @RequestMapping(value = "/groups", method = { RequestMethod.GET }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse<String> listUserAuthorities(
+ @RequestParam(value = "project", required = false) String project,
+ @RequestParam(value = "name", required = false) String name,
+ @RequestParam(value = "isFuzzMatch", required = false) boolean isFuzzMatch,
+ @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset,
+ @RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit) throws IOException {
+ HashMap<String, Object> data = new HashMap<>();
+ Map<String, List<String>> userGroupMap = userGroupService.getGroupMembersMap();
+ Map<String, Set<String>> usersWithGroup = new HashMap<>();
+ List<String> groupsByFuzzyMatching = getManagedGroupsByFuzzyMatching(name, isFuzzMatch, getAllGroups(project));
+ List<String> subList = PagingUtil.cutPage(groupsByFuzzyMatching, offset, limit);
+ for (String g : subList) {
+ List<String> userNames = userGroupMap.get(g);
+ usersWithGroup.put(g, (userNames == null ? Sets.newHashSet() : Sets.newHashSet(userNames)));
+ }
+ data.put("groups", usersWithGroup);
+ data.put("size", groupsByFuzzyMatching.size());
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, data, "");
+ }
+
+ @RequestMapping(value = "/{name:.+}", method = { RequestMethod.POST }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse<String> addUserGroup(@PathVariable String name) throws IOException {
+ userGroupService.addGroup(name);
+ return new EnvelopeResponse<>(ResponseCode.CODE_SUCCESS, null, "");
+ }
+
+ @RequestMapping(value = "/{name:.+}", method = { RequestMethod.DELETE }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse<String> delUserGroup(@PathVariable String name) throws IOException {
+ if (StringUtils.equalsIgnoreCase(name, Constant.GROUP_ALL_USERS)
+ || StringUtils.equalsIgnoreCase(name, Constant.ROLE_ADMIN)) {
+ throw new InternalErrorException("Can not delete group " + name);
+ }
+ userGroupService.deleteGroup(name);
+ return new EnvelopeResponse<>(ResponseCode.CODE_SUCCESS, null, "");
+ }
- @RequestMapping(value = "/groups", method = {RequestMethod.GET}, produces = {"application/json"})
+ //move users in/out from groups
+ @RequestMapping(value = "/users/{name:.+}", method = { RequestMethod.POST, RequestMethod.PUT }, produces = {
+ "application/json" })
@ResponseBody
- public List<String> listUserAuthorities(@RequestParam(value = "project") String project) throws IOException {
+ public EnvelopeResponse<String> addOrDelUsers(@PathVariable String name, @RequestBody List<String> users)
+ throws IOException {
+ if (StringUtil.equals(name, Constant.ROLE_ADMIN) && users.size() == 0) {
+ throw new InternalErrorException("role_admin must have at least one user");
+ }
+ userGroupService.modifyGroupUsers(name, users);
+ return new EnvelopeResponse<>(ResponseCode.CODE_SUCCESS, null, "");
+ }
+
+ //move users in/out from groups
+ @RequestMapping(value = "/users/{name:.+}", method = { RequestMethod.GET }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse<String> getUsersByGroup(@PathVariable String name) throws IOException {
+ HashMap<String, Object> data = new HashMap<>();
+ List<ManagedUser> users = userGroupService.getGroupMembersByName(name);
+ data.put("users", users);
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, data, "");
+ }
+
+ private List<String> getAllGroups(String project) throws IOException {
return userGroupService.listAllAuthorities(project);
}
+
+ private List<String> getManagedGroupsByFuzzyMatching(String nameSeg, boolean isFuzzyMatch, List<String> groups) {
+ //for name fuzzy matching
+ if (StringUtils.isBlank(nameSeg)) {
+ return groups;
+ }
+
+ List<String> groupsByFuzzyMatching = new ArrayList<>();
+ for (String u : groups) {
+ if (!isFuzzyMatch && StringUtils.equals(u, nameSeg)) {
+ groupsByFuzzyMatching.add(u);
+ }
+ if (isFuzzyMatch && StringUtils.containsIgnoreCase(u, nameSeg)) {
+ groupsByFuzzyMatching.add(u);
+ }
+ }
+ return groupsByFuzzyMatching;
+ }
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/UserController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/UserController.java
index 884b949..d1ba18b 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/controller/UserController.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/controller/UserController.java
@@ -18,17 +18,50 @@
package org.apache.kylin.rest.controller;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+import javax.annotation.PostConstruct;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.exception.BadRequestException;
+import org.apache.kylin.rest.exception.ForbiddenException;
+import org.apache.kylin.rest.request.PasswdChangeRequest;
+import org.apache.kylin.rest.response.EnvelopeResponse;
+import org.apache.kylin.rest.response.ResponseCode;
+import org.apache.kylin.rest.security.ManagedUser;
+import org.apache.kylin.rest.service.AccessService;
+import org.apache.kylin.rest.service.UserGroupService;
import org.apache.kylin.rest.service.UserService;
+import org.apache.kylin.rest.util.AclEvaluate;
+import org.apache.kylin.rest.util.PagingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.google.common.collect.Lists;
/**
* Handle user authentication request to protected kylin rest resources by
@@ -42,10 +75,34 @@ import org.springframework.web.bind.annotation.RequestMethod;
public class UserController extends BasicController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
+
+ private static final SimpleGrantedAuthority ALL_USERS_AUTH = new SimpleGrantedAuthority(Constant.GROUP_ALL_USERS);
+
+ private static final String ADMIN = "ADMIN";
+ private static final String ANALYST = "ANALYST";
+ private static final String MODELER = "MODELER";
+ private static final String ADMIN_DEFAULT = "KYLIN";
+ private static final String ACTIVE_PROFILES_NAME = "spring.profiles.active";
+
@Autowired
@Qualifier("userService")
UserService userService;
+ @Autowired
+ private AclEvaluate aclEvaluate;
+
+ @Autowired
+ @Qualifier("accessService")
+ private AccessService accessService;
+
+ @Autowired
+ @Qualifier("userGroupService")
+ private UserGroupService userGroupService;
+
+ private Pattern passwordPattern;
+ private Pattern bcryptPattern;
+ private BCryptPasswordEncoder pwdEncoder;
+
@RequestMapping(value = "/authentication", method = RequestMethod.POST, produces = { "application/json" })
public UserDetails authenticate() {
UserDetails userDetails = authenticatedUser();
@@ -72,4 +129,255 @@ public class UserController extends BasicController {
return null;
}
+
+ @PostConstruct
+ public void init() throws IOException {
+ passwordPattern = Pattern.compile("^(?=.*\\d)(?=.*[a-zA-Z])(?=.*[~!@#$%^&*(){}|:\"<>?\\[\\];',./`]).{8,}$");
+ bcryptPattern = Pattern.compile("\\A\\$2a?\\$\\d\\d\\$[./0-9A-Za-z]{53}");
+ pwdEncoder = new BCryptPasswordEncoder();
+
+ List<ManagedUser> all = userService.listUsers();
+ logger.info("All {} users", all.size());
+ if (all.isEmpty() && "testing".equals(System.getProperty(ACTIVE_PROFILES_NAME))) {
+ create(ADMIN, new ManagedUser(ADMIN, ADMIN_DEFAULT, true, Constant.ROLE_ADMIN, Constant.GROUP_ALL_USERS));
+ create(ANALYST, new ManagedUser(ANALYST, ANALYST, true, Constant.GROUP_ALL_USERS));
+ create(MODELER, new ManagedUser(MODELER, MODELER, true, Constant.GROUP_ALL_USERS));
+ }
+
+ }
+
+ private void checkProfileEditAllowed() {
+ String activeProfiles = System.getProperty(ACTIVE_PROFILES_NAME);
+ if (!"testing".equals(activeProfiles) && !"custom".equals(activeProfiles)) {
+ throw new BadRequestException("Action not allowed!");
+ }
+ }
+
+ @RequestMapping(value = "/{userName:.+}", method = { RequestMethod.POST }, produces = { "application/json" })
+ @ResponseBody
+ @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
+ //do not use aclEvaluate, if getManagedUsersByFuzzMatching there's no users and will come into init() and will call save.
+ public ManagedUser create(@PathVariable("userName") String userName, @RequestBody ManagedUser user) {
+ checkProfileEditAllowed();
+
+ if (StringUtils.equals(getPrincipal(), user.getUsername()) && user.isDisabled()) {
+ throw new ForbiddenException("Action not allowed!");
+ }
+
+ checkUserName(userName);
+
+ user.setUsername(userName);
+ user.setPassword(pwdEncode(user.getPassword()));
+
+ logger.info("Creating {}", user);
+
+ completeAuthorities(user);
+ userService.createUser(user);
+ return get(userName);
+ }
+
+ @RequestMapping(value = "/{userName:.+}", method = { RequestMethod.PUT }, produces = { "application/json" })
+ @ResponseBody
+ @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
+ //do not use aclEvaluate, if there's no users and will come into init() and will call save.
+ public ManagedUser save(@PathVariable("userName") String userName, @RequestBody ManagedUser user) {
+ checkProfileEditAllowed();
+
+ if (StringUtils.equals(getPrincipal(), user.getUsername()) && user.isDisabled()) {
+ throw new ForbiddenException("Action not allowed!");
+ }
+
+ checkUserName(userName);
+
+ user.setUsername(userName);
+
+ // merge with existing user
+ try {
+ ManagedUser existing = get(userName);
+ if (existing != null) {
+ if (user.getPassword() == null)
+ user.setPassword(existing.getPassword());
+ if (user.getAuthorities() == null || user.getAuthorities().isEmpty())
+ user.setGrantedAuthorities(existing.getAuthorities());
+ }
+ } catch (UsernameNotFoundException ex) {
+ // that is OK, we create new
+ }
+ logger.info("Saving {}", user);
+
+ completeAuthorities(user);
+ userService.updateUser(user);
+ return get(userName);
+ }
+
+ @RequestMapping(value = "/password", method = { RequestMethod.PUT }, produces = { "application/json" })
+ @ResponseBody
+ //change passwd
+ public EnvelopeResponse save(@RequestBody PasswdChangeRequest user) {
+
+ checkProfileEditAllowed();
+
+ if (!this.isAdmin() && !StringUtils.equals(getPrincipal(), user.getUsername())) {
+ throw new ForbiddenException("Permission Denied");
+ }
+ ManagedUser existing = get(user.getUsername());
+ checkUserName(user.getUsername());
+ checkNewPwdRule(user.getNewPassword());
+
+ if (existing != null) {
+ if (!this.isAdmin() && !pwdEncoder.matches(user.getPassword(), existing.getPassword())) {
+ throw new BadRequestException("pwd update error");
+ }
+
+ existing.setPassword(pwdEncode(user.getNewPassword()));
+ existing.setDefaultPassword(false);
+ logger.info("update password for user {}", user);
+
+ completeAuthorities(existing);
+ userService.updateUser(existing);
+
+ // update authentication
+ if (StringUtils.equals(getPrincipal(), user.getUsername())) {
+ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(existing,
+ user.getNewPassword(), existing.getAuthorities());
+ token.setDetails(SecurityContextHolder.getContext().getAuthentication().getDetails());
+ SecurityContextHolder.getContext().setAuthentication(token);
+ }
+ }
+
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, get(user.getUsername()), "");
+ }
+
+ private String pwdEncode(String pwd) {
+ if (bcryptPattern.matcher(pwd).matches())
+ return pwd;
+
+ return pwdEncoder.encode(pwd);
+ }
+
+ private void checkUserName(String userName) {
+ if (userName == null || userName.isEmpty())
+ throw new BadRequestException("empty user name");
+ }
+
+ private void checkNewPwdRule(String newPwd) {
+ if (!checkPasswordLength(newPwd)) {
+ throw new BadRequestException("password length need more then 8 chars");
+ }
+
+ if (!checkPasswordCharacter(newPwd)) {
+ throw new BadRequestException("pwd update error");
+ }
+ }
+
+ private boolean checkPasswordLength(String password) {
+ return !(password == null || password.length() < 8);
+ }
+
+ private boolean checkPasswordCharacter(String password) {
+ return passwordPattern.matcher(password).matches();
+ }
+
+ @RequestMapping(value = "/{userName:.+}", method = { RequestMethod.GET }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse getUser(@PathVariable("userName") String userName) {
+
+ if (!this.isAdmin() && !StringUtils.equals(getPrincipal(), userName)) {
+ throw new ForbiddenException("...");
+ }
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, get(userName), "");
+ }
+
+ private ManagedUser get(@Nullable String userName) {
+ checkUserName(userName);
+
+ UserDetails details = userService.loadUserByUsername(userName);
+ if (details == null)
+ return null;
+ return (ManagedUser) details;
+ }
+
+ @RequestMapping(value = "/users", method = { RequestMethod.GET }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse listAllUsers(@RequestParam(value = "project", required = false) String project,
+ @RequestParam(value = "name", required = false) String name,
+ @RequestParam(value = "groupName", required = false) String groupName,
+ @RequestParam(value = "isFuzzMatch", required = false) boolean isFuzzMatch,
+ @RequestParam(value = "offset", required = false, defaultValue = "0") Integer offset,
+ @RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit) throws IOException {
+ if (project == null) {
+ aclEvaluate.checkIsGlobalAdmin();
+ } else {
+ aclEvaluate.checkProjectAdminPermission(project);
+ }
+ HashMap<String, Object> data = new HashMap<>();
+ List<ManagedUser> usersByFuzzMatching = userService.listUsers(name, groupName, isFuzzMatch);
+ List<ManagedUser> subList = PagingUtil.cutPage(usersByFuzzMatching, offset, limit);
+ //LDAP users dose not have authorities
+ for (ManagedUser u : subList) {
+ userService.completeUserInfo(u);
+ }
+ data.put("users", subList);
+ data.put("size", usersByFuzzMatching.size());
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, data, "");
+ }
+
+ @RequestMapping(value = "/{userName:.+}", method = { RequestMethod.DELETE }, produces = { "application/json" })
+ @ResponseBody
+ public EnvelopeResponse delete(@PathVariable("userName") String userName) throws IOException {
+
+ checkProfileEditAllowed();
+
+ if (StringUtils.equals(getPrincipal(), userName)) {
+ throw new ForbiddenException("...");
+ }
+
+ //delete user's project ACL
+ accessService.revokeProjectPermission(userName, MetadataConstants.TYPE_USER);
+
+ //delete user's table/row/column ACL
+ // ACLOperationUtil.delLowLevelACL(userName, MetadataConstants.TYPE_USER);
+
+ checkUserName(userName);
+ userService.deleteUser(userName);
+ return new EnvelopeResponse(ResponseCode.CODE_SUCCESS, userName, "");
+ }
+
+ private void completeAuthorities(ManagedUser managedUser) {
+ List<SimpleGrantedAuthority> detailRoles = Lists.newArrayList(managedUser.getAuthorities());
+ for (SimpleGrantedAuthority authority : detailRoles) {
+ try {
+ if (!userGroupService.exists(authority.getAuthority())) {
+ throw new BadRequestException(String.format(Locale.ROOT,
+ "user's authority:%s is not found in user group", authority.getAuthority()));
+ }
+ } catch (IOException e) {
+ logger.error("Get user group error: {}", e.getMessage());
+ }
+ }
+ if (!detailRoles.contains(ALL_USERS_AUTH)) {
+ detailRoles.add(ALL_USERS_AUTH);
+ }
+ managedUser.setGrantedAuthorities(detailRoles);
+ }
+
+ private String getPrincipal() {
+ String userName = null;
+
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication == null) {
+ return null;
+ }
+
+ Object principal = authentication.getPrincipal();
+
+ if (principal instanceof UserDetails) {
+ userName = ((UserDetails) principal).getUsername();
+ } else if (authentication.getDetails() instanceof UserDetails) {
+ userName = ((UserDetails) authentication.getDetails()).getUsername();
+ } else {
+ userName = principal.toString();
+ }
+ return userName;
+ }
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/request/PasswdChangeRequest.java b/server-base/src/main/java/org/apache/kylin/rest/request/PasswdChangeRequest.java
new file mode 100644
index 0000000..c1eb008
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/request/PasswdChangeRequest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.kylin.rest.request;
+
+import java.io.Serializable;
+
+public class PasswdChangeRequest implements Serializable {
+ private String username;
+ private String password;
+ private String newPassword;
+
+ public PasswdChangeRequest() {
+
+ }
+
+ // only for test now
+ public PasswdChangeRequest(String username, String password, String newPassword) {
+ this.username = username;
+ this.password = password;
+ this.newPassword = newPassword;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getNewPassword() {
+ return newPassword;
+ }
+
+ public void setNewPassword(String newPassword) {
+ this.newPassword = newPassword;
+ }
+
+}
\ No newline at end of file
diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/ManagedUser.java b/server-base/src/main/java/org/apache/kylin/rest/security/ManagedUser.java
index 0d5ce60..3a4f244 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/security/ManagedUser.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/security/ManagedUser.java
@@ -156,18 +156,24 @@ public class ManagedUser extends RootPersistentEntity implements UserDetails {
}
}
- public void addAuthoritie(String auth) {
+ public void addAuthorities(String auth) {
if (this.authorities == null) {
this.authorities = Lists.newArrayList();
}
authorities.add(new SimpleGrantedAuthority(auth));
}
- public void removeAuthoritie(String auth) {
+ public void removeAuthorities(String auth) {
Preconditions.checkNotNull(this.authorities);
authorities.remove(new SimpleGrantedAuthority(auth));
}
+ public void clearAuthenticateFailedRecord() {
+ this.wrongTime = 0;
+ this.locked = false;
+ this.lockedTime = 0;
+ }
+
public boolean isDisabled() {
return disabled;
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java b/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java
index 9165e06..033353f 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java
@@ -18,18 +18,39 @@
package org.apache.kylin.rest.service;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.JsonSerializer;
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.common.persistence.WriteConflictException;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.apache.kylin.metadata.acl.UserGroup;
+import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.security.ManagedUser;
import org.apache.kylin.rest.util.AclEvaluate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
-public class KylinUserGroupService implements IUserGroupService {
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class KylinUserGroupService extends UserGroupService {
+ public static final Logger logger = LoggerFactory.getLogger(KylinUserGroupService.class);
+ private ResourceStore store = ResourceStore.getStore(KylinConfig.getInstanceFromEnv());
+ private static final String PATH = "/user_group/";
+ private static final Serializer<UserGroup> USER_GROUP_SERIALIZER = new JsonSerializer<>(UserGroup.class);
@Autowired
@Qualifier("userService")
private UserService userService;
@@ -37,12 +58,6 @@ public class KylinUserGroupService implements IUserGroupService {
@Autowired
private AclEvaluate aclEvaluate;
- @Override
- public List<String> listAllAuthorities(String project) throws IOException {
- aclEvaluate.checkProjectAdminPermission(project);
- return getAllUserAuthorities();
- }
-
List<String> getAllUserAuthorities() throws IOException {
List<String> all = new ArrayList<>();
for (UserDetails user : userService.listUsers()) {
@@ -57,6 +72,135 @@ public class KylinUserGroupService implements IUserGroupService {
@Override
public boolean exists(String name) throws IOException {
- return getAllUserAuthorities().contains(name);
+ return getUserGroup().getAllGroups().contains(name);
+ }
+
+ @Autowired
+ @Qualifier("accessService")
+ private AccessService accessService;
+
+ @PostConstruct
+ public void init() throws IOException, InterruptedException {
+ int retry = 100;
+ while (retry > 0) {
+ UserGroup userGroup = getUserGroup();
+ if (!userGroup.exists(Constant.GROUP_ALL_USERS)) {
+ userGroup.add(Constant.GROUP_ALL_USERS);
+ }
+ if (!userGroup.exists(Constant.ROLE_ADMIN)) {
+ userGroup.add(Constant.ROLE_ADMIN);
+ }
+ if (!userGroup.exists(Constant.ROLE_MODELER)) {
+ userGroup.add(Constant.ROLE_MODELER);
+ }
+ if (!userGroup.exists(Constant.ROLE_ANALYST)) {
+ userGroup.add(Constant.ROLE_ANALYST);
+ }
+
+ try {
+ store.checkAndPutResource(PATH, userGroup, USER_GROUP_SERIALIZER);
+ return;
+ } catch (WriteConflictException e) {
+ logger.info("Find WriteConflictException, sleep 100 ms.", e);
+ Thread.sleep(100L);
+ retry--;
+ }
+ }
+ logger.error("Failed to update user group's metadata.");
+ }
+
+ private UserGroup getUserGroup() throws IOException {
+ UserGroup userGroup = store.getResource(PATH, USER_GROUP_SERIALIZER);
+ if (userGroup == null) {
+ userGroup = new UserGroup();
+ }
+ return userGroup;
+ }
+
+ @Override
+ protected List<String> getAllUserGroups() throws IOException {
+ return getUserGroup().getAllGroups();
+ }
+
+ @Override
+ public Map<String, List<String>> getGroupMembersMap() throws IOException {
+ Map<String, List<String>> result = Maps.newHashMap();
+ List<ManagedUser> users = userService.listUsers();
+ for (ManagedUser user : users) {
+ for (SimpleGrantedAuthority authority : user.getAuthorities()) {
+ String role = authority.getAuthority();
+ List<String> usersInGroup = result.get(role);
+ if (usersInGroup == null) {
+ result.put(role, Lists.newArrayList(user.getUsername()));
+ } else {
+ usersInGroup.add(user.getUsername());
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<ManagedUser> getGroupMembersByName(String name) throws IOException {
+ List<ManagedUser> users = userService.listUsers();
+ for (Iterator<ManagedUser> it = users.iterator(); it.hasNext();) {
+ ManagedUser user = it.next();
+ if (!user.getAuthorities().contains(new SimpleGrantedAuthority(name))) {
+ it.remove();
+ }
+ }
+ return users;
+ }
+
+ @Override
+ public void addGroup(String name) throws IOException {
+ aclEvaluate.checkIsGlobalAdmin();
+ UserGroup userGroup = getUserGroup();
+ store.checkAndPutResource(PATH, userGroup.add(name), USER_GROUP_SERIALIZER);
+ }
+
+ @Override
+ public void deleteGroup(String name) throws IOException {
+ aclEvaluate.checkIsGlobalAdmin();
+ // remove retained user group in all users
+ List<ManagedUser> managedUsers = userService.listUsers();
+ for (ManagedUser managedUser : managedUsers) {
+ if (managedUser.getAuthorities().contains(new SimpleGrantedAuthority(name))) {
+ managedUser.removeAuthorities(name);
+ userService.updateUser(managedUser);
+ }
+ }
+ //delete group's project ACL
+ accessService.revokeProjectPermission(name, MetadataConstants.TYPE_GROUP);
+ //delete group's table/row/column ACL
+ // ACLOperationUtil.delLowLevelACL(name, MetadataConstants.TYPE_GROUP);
+
+ store.checkAndPutResource(PATH, getUserGroup().delete(name), USER_GROUP_SERIALIZER);
+ }
+
+ //user's group information is stored by user its own.Object user group does not hold user's ref.
+ @Override
+ public void modifyGroupUsers(String groupName, List<String> users) throws IOException {
+ aclEvaluate.checkIsGlobalAdmin();
+ List<String> groupUsers = new ArrayList<>();
+ for (ManagedUser user : getGroupMembersByName(groupName)) {
+ groupUsers.add(user.getUsername());
+ }
+ List<String> moveInUsers = Lists.newArrayList(users);
+ List<String> moveOutUsers = Lists.newArrayList(groupUsers);
+ moveInUsers.removeAll(groupUsers);
+ moveOutUsers.removeAll(users);
+
+ for (String in : moveInUsers) {
+ ManagedUser managedUser = (ManagedUser) userService.loadUserByUsername(in);
+ managedUser.addAuthorities(groupName);
+ userService.updateUser(managedUser);
+ }
+
+ for (String out : moveOutUsers) {
+ ManagedUser managedUser = (ManagedUser) userService.loadUserByUsername(out);
+ managedUser.removeAuthorities(groupName);
+ userService.updateUser(managedUser);
+ }
}
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserService.java b/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
index eea8cd7..ea97118 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserService.java
@@ -25,6 +25,7 @@ import java.util.Locale;
import javax.annotation.PostConstruct;
+import org.apache.commons.lang.StringUtils;
import org.apache.kylin.common.KylinConfig;
import org.apache.kylin.common.persistence.JsonSerializer;
import org.apache.kylin.common.persistence.ResourceStore;
@@ -35,8 +36,10 @@ import org.apache.kylin.rest.msg.Message;
import org.apache.kylin.rest.msg.MsgPicker;
import org.apache.kylin.rest.security.KylinUserManager;
import org.apache.kylin.rest.security.ManagedUser;
+import org.apache.kylin.rest.util.AclEvaluate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -46,6 +49,8 @@ import com.google.common.base.Preconditions;
public class KylinUserService implements UserService {
private Logger logger = LoggerFactory.getLogger(KylinUserService.class);
+ @Autowired
+ private AclEvaluate aclEvaluate;
public static final String DIR_PREFIX = "/user/";
@@ -131,6 +136,18 @@ public class KylinUserService implements UserService {
}
@Override
+ public List<ManagedUser> listUsers(String userName, Boolean isFuzzMatch) throws IOException {
+ List<ManagedUser> userList = getKylinUserManager().list();
+ return getManagedUsersByFuzzMatching(userName, isFuzzMatch, userList, null);
+ }
+
+ @Override
+ public List<ManagedUser> listUsers(String userName, String groupName, Boolean isFuzzMatch) throws IOException {
+ List<ManagedUser> userList = getKylinUserManager().list();
+ return getManagedUsersByFuzzMatching(userName, isFuzzMatch, userList, groupName);
+ }
+
+ @Override
public List<String> listAdminUsers() throws IOException {
List<String> adminUsers = new ArrayList<>();
for (ManagedUser managedUser : listUsers()) {
@@ -152,4 +169,30 @@ public class KylinUserService implements UserService {
private KylinUserManager getKylinUserManager() {
return KylinUserManager.getInstance(KylinConfig.getInstanceFromEnv());
}
+
+ private List<ManagedUser> getManagedUsersByFuzzMatching(String nameSeg, boolean isFuzzMatch,
+ List<ManagedUser> userList, String groupName) {
+ aclEvaluate.checkIsGlobalAdmin();
+ //for name fuzzy matching
+ if (StringUtils.isBlank(nameSeg) && StringUtils.isBlank(groupName)) {
+ return userList;
+ }
+
+ List<ManagedUser> usersByFuzzyMatching = new ArrayList<>();
+ for (ManagedUser u : userList) {
+ if (!isFuzzMatch && StringUtils.equals(u.getUsername(), nameSeg) && isUserInGroup(u, groupName)) {
+ usersByFuzzyMatching.add(u);
+ }
+ if (isFuzzMatch && StringUtils.containsIgnoreCase(u.getUsername(), nameSeg)
+ && isUserInGroup(u, groupName)) {
+ usersByFuzzyMatching.add(u);
+ }
+
+ }
+ return usersByFuzzyMatching;
+ }
+
+ private boolean isUserInGroup(ManagedUser user, String groupName) {
+ return StringUtils.isBlank(groupName) || user.getAuthorities().contains(new SimpleGrantedAuthority(groupName));
+ }
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java b/server-base/src/main/java/org/apache/kylin/rest/service/UserGroupService.java
similarity index 53%
copy from server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java
copy to server-base/src/main/java/org/apache/kylin/rest/service/UserGroupService.java
index 9165e06..5f23f51 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/KylinUserGroupService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/UserGroupService.java
@@ -6,57 +6,60 @@
* 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.kylin.rest.service;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
+import org.apache.kylin.rest.security.ManagedUser;
import org.apache.kylin.rest.util.AclEvaluate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-public class KylinUserGroupService implements IUserGroupService {
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public abstract class UserGroupService extends BasicService implements IUserGroupService {
@Autowired
- @Qualifier("userService")
- private UserService userService;
+ AclEvaluate aclEvaluate;
@Autowired
- private AclEvaluate aclEvaluate;
+ @Qualifier("userService")
+ UserService userService;
- @Override
+ // add param project to check user's permission
public List<String> listAllAuthorities(String project) throws IOException {
- aclEvaluate.checkProjectAdminPermission(project);
- return getAllUserAuthorities();
- }
-
- List<String> getAllUserAuthorities() throws IOException {
- List<String> all = new ArrayList<>();
- for (UserDetails user : userService.listUsers()) {
- for (GrantedAuthority auth : user.getAuthorities()) {
- if (!all.contains(auth.getAuthority())) {
- all.add(auth.getAuthority());
- }
- }
+ if (project == null) {
+ aclEvaluate.checkIsGlobalAdmin();
+ } else {
+ aclEvaluate.checkProjectAdminPermission(project);
}
- return all;
+ return getAllUserGroups();
}
- @Override
public boolean exists(String name) throws IOException {
- return getAllUserAuthorities().contains(name);
+ return getAllUserGroups().contains(name);
}
+
+ public abstract Map<String, List<String>> getGroupMembersMap() throws IOException;
+
+ public abstract List<ManagedUser> getGroupMembersByName(String name) throws IOException;
+
+ protected abstract List<String> getAllUserGroups() throws IOException;
+
+ public abstract void addGroup(String name) throws IOException;
+
+ public abstract void deleteGroup(String name) throws IOException;
+
+ //user's group information is stored by user its own.Object user group does not hold user's ref.
+ public abstract void modifyGroupUsers(String groupName, List<String> users) throws IOException;
}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/UserService.java b/server-base/src/main/java/org/apache/kylin/rest/service/UserService.java
index 21c4cf9..90107a1 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/UserService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/UserService.java
@@ -18,12 +18,12 @@
package org.apache.kylin.rest.service;
-import java.io.IOException;
-import java.util.List;
-
import org.apache.kylin.rest.security.ManagedUser;
import org.springframework.security.provisioning.UserDetailsManager;
+import java.io.IOException;
+import java.util.List;
+
public interface UserService extends UserDetailsManager {
boolean isEvictCacheFlag();
@@ -32,6 +32,10 @@ public interface UserService extends UserDetailsManager {
List<ManagedUser> listUsers() throws IOException;
+ List<ManagedUser> listUsers(String userName, Boolean isFuzzyMatch) throws IOException;
+
+ List<ManagedUser> listUsers(String userName, String groupName, Boolean isFuzzyMatch) throws IOException;
+
List<String> listAdminUsers() throws IOException;
//For performance consideration, list all users may be incomplete(eg. not load user's authorities until authorities has benn used).
diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java b/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java
index acf0f77..a0f2d0f 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java
@@ -6,15 +6,15 @@
* 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.kylin.rest.util;
diff --git a/webapp/app/js/app.js b/server-base/src/main/java/org/apache/kylin/rest/util/PagingUtil.java
similarity index 56%
copy from webapp/app/js/app.js
copy to server-base/src/main/java/org/apache/kylin/rest/util/PagingUtil.java
index e505ffc..ebc0d1d 100644
--- a/webapp/app/js/app.js
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/PagingUtil.java
@@ -16,5 +16,30 @@
* limitations under the License.
*/
-//Kylin Application Module
-KylinApp = angular.module('kylin', ['ngRoute', 'ngResource', 'ngGrid', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.grouping', 'ui.bootstrap', 'ui.ace', 'base64', 'angularLocalStorage', 'localytics.directives', 'treeControl', 'ngLoadingRequest', 'oitozero.ngSweetAlert', 'ngCookies', 'angular-underscore', 'ngAnimate', 'ui.sortable', 'angularBootstrapNavTree', 'toggle-switch', 'ngSanitize', 'ui.select', 'ui.bootstrap.datetimepicker', 'nvd3', 'ngTagsInput']);
+package org.apache.kylin.rest.util;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PagingUtil {
+
+ public static <T> List<T> cutPage(List<T> full, int offset, int limit) {
+ if (full == null)
+ return null;
+
+ int begin = offset;
+ int end = offset + limit;
+
+ return cut(full, begin, end);
+ }
+
+ private static <T> List<T> cut(List<T> full, int begin, int end) {
+ if (begin >= full.size())
+ return Collections.emptyList();
+
+ if (end > full.size())
+ end = full.size();
+
+ return full.subList(begin, end);
+ }
+}
diff --git a/server/src/main/java/org/apache/kylin/rest/DebugTomcat.java b/server/src/main/java/org/apache/kylin/rest/DebugTomcat.java
index f611403..a238a8f 100644
--- a/server/src/main/java/org/apache/kylin/rest/DebugTomcat.java
+++ b/server/src/main/java/org/apache/kylin/rest/DebugTomcat.java
@@ -18,11 +18,6 @@
package org.apache.kylin.rest;
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
import org.apache.catalina.Context;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.StandardServer;
@@ -34,6 +29,11 @@ import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Shell;
import org.apache.kylin.common.KylinConfig;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
public class DebugTomcat {
public static void setupDebugEnv() {
diff --git a/server/src/main/resources/kylinSecurity.xml b/server/src/main/resources/kylinSecurity.xml
index fe1aeec..a7197ff 100644
--- a/server/src/main/resources/kylinSecurity.xml
+++ b/server/src/main/resources/kylinSecurity.xml
@@ -47,13 +47,13 @@
class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
- <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
+ <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN"/>
</bean>
- <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
+ <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN"/>
</bean>
- <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
+ <bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="ROLE_ADMIN"/>
</bean>
</list>
@@ -68,9 +68,9 @@
<constructor-arg ref="auditLogger"/>
</bean>
- <bean id="userService" class="org.apache.kylin.rest.service.KylinUserService" />
+ <bean id="userService" class="org.apache.kylin.rest.service.KylinUserService"/>
- <bean id="userGroupService" class="org.apache.kylin.rest.service.KylinUserGroupService" />
+ <bean id="userGroupService" class="org.apache.kylin.rest.service.KylinUserGroupService"/>
<beans profile="ldap,saml">
<bean id="ldapSource"
@@ -202,15 +202,7 @@
<constructor-arg>
<bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
- <bean class="org.springframework.security.provisioning.InMemoryUserDetailsManager">
- <constructor-arg>
- <util:list
- value-type="org.springframework.security.core.userdetails.User">
- <ref bean="adminUser"></ref>
- <ref bean="modelerUser"></ref>
- <ref bean="analystUser"></ref>
- </util:list>
- </constructor-arg>
+ <bean class="org.apache.kylin.rest.service.KylinUserService">
</bean>
</property>
@@ -242,7 +234,7 @@
<scr:intercept-url pattern="/api/metadata*/**" access="isAuthenticated()"/>
<scr:intercept-url pattern="/api/**/metrics" access="permitAll"/>
<scr:intercept-url pattern="/api/cache*/**" access="permitAll"/>
- <scr:intercept-url pattern="/api/streaming_coordinator/**" access="permitAll" />
+ <scr:intercept-url pattern="/api/streaming_coordinator/**" access="permitAll"/>
<scr:intercept-url pattern="/api/service_discovery/state/is_active_job_node" access="permitAll"/>
<scr:intercept-url pattern="/api/cubes/src/tables" access="hasAnyRole('ROLE_ANALYST')"/>
<scr:intercept-url pattern="/api/cubes*/**" access="isAuthenticated()"/>
@@ -255,8 +247,9 @@
<scr:intercept-url pattern="/api/tables/**/snapshotLocalCache/**" access="permitAll"/>
<scr:intercept-url pattern="/api/**" access="isAuthenticated()"/>
- <scr:form-login login-page="/login" />
- <scr:logout invalidate-session="true" delete-cookies="JSESSIONID" logout-url="/j_spring_security_logout" logout-success-url="/." />
+ <scr:form-login login-page="/login"/>
+ <scr:logout invalidate-session="true" delete-cookies="JSESSIONID" logout-url="/j_spring_security_logout"
+ logout-success-url="/."/>
<scr:session-management session-fixation-protection="newSession"/>
</scr:http>
</beans>
@@ -290,7 +283,7 @@
<scr:intercept-url pattern="/api/metadata*/**" access="isAuthenticated()"/>
<scr:intercept-url pattern="/api/**/metrics" access="permitAll"/>
<scr:intercept-url pattern="/api/cache*/**" access="permitAll"/>
- <scr:intercept-url pattern="/api/streaming_coordinator/**" access="permitAll" />
+ <scr:intercept-url pattern="/api/streaming_coordinator/**" access="permitAll"/>
<scr:intercept-url pattern="/api/cubes/src/tables" access="hasAnyRole('ROLE_ANALYST')"/>
<scr:intercept-url pattern="/api/cubes*/**" access="isAuthenticated()"/>
<scr:intercept-url pattern="/api/models*/**" access="isAuthenticated()"/>
@@ -302,8 +295,9 @@
<scr:intercept-url pattern="/api/tables/**/snapshotLocalCache/**" access="permitAll"/>
<scr:intercept-url pattern="/api/**" access="isAuthenticated()"/>
- <scr:form-login login-page="/login" />
- <scr:logout invalidate-session="true" delete-cookies="JSESSIONID" logout-url="/j_spring_security_logout" logout-success-url="/." />
+ <scr:form-login login-page="/login"/>
+ <scr:logout invalidate-session="true" delete-cookies="JSESSIONID" logout-url="/j_spring_security_logout"
+ logout-success-url="/."/>
<scr:session-management session-fixation-protection="newSession"/>
</scr:http>
diff --git a/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java b/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
index dea37f5..d0f4c66 100644
--- a/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/controller/AccessControllerTest.java
@@ -18,13 +18,6 @@
package org.apache.kylin.rest.controller;
-import static junit.framework.TestCase.fail;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.List;
-
import org.apache.kylin.cube.CubeInstance;
import org.apache.kylin.metadata.project.ProjectInstance;
import org.apache.kylin.rest.request.AccessRequest;
@@ -34,9 +27,9 @@ import org.apache.kylin.rest.security.AclEntityType;
import org.apache.kylin.rest.security.AclPermissionType;
import org.apache.kylin.rest.security.ManagedUser;
import org.apache.kylin.rest.service.CubeService;
-import org.apache.kylin.rest.service.IUserGroupService;
import org.apache.kylin.rest.service.ProjectService;
import org.apache.kylin.rest.service.ServiceTestBase;
+import org.apache.kylin.rest.service.UserGroupService;
import org.apache.kylin.rest.service.UserService;
import org.junit.Assert;
import org.junit.Before;
@@ -48,6 +41,13 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
+import java.io.IOException;
+import java.util.List;
+
+import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
/**
* @author xduo
*/
@@ -81,7 +81,7 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy
@Autowired
@Qualifier("userGroupService")
- private IUserGroupService userGroupService;
+ private UserGroupService userGroupService;
@Before
public void setup() throws Exception {
@@ -97,6 +97,11 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy
List<ProjectInstance> projects = projectController.getProjects(10000, 0);
assertTrue(projects.size() > 0);
ProjectInstance project = projects.get(0);
+ userGroupService.addGroup("g1");
+ userGroupService.addGroup("g2");
+ userGroupService.addGroup("g3");
+ userGroupService.addGroup("g4");
+
ManagedUser user = new ManagedUser("u", "kylin", false, "all_users", "g1", "g2", "g3", "g4");
userService.createUser(user);
diff --git a/server/src/test/java/org/apache/kylin/rest/controller/UserControllerTest.java b/server/src/test/java/org/apache/kylin/rest/controller/UserControllerTest.java
index f6b4ae1..2c21e0a 100644
--- a/server/src/test/java/org/apache/kylin/rest/controller/UserControllerTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/controller/UserControllerTest.java
@@ -18,50 +18,50 @@
package org.apache.kylin.rest.controller;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
import org.apache.kylin.rest.constant.Constant;
import org.apache.kylin.rest.security.ManagedUser;
import org.apache.kylin.rest.service.ServiceTestBase;
import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
-import org.springframework.security.authentication.TestingAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
+import java.io.IOException;
+
/**
* @author xduo
*/
public class UserControllerTest extends ServiceTestBase {
+ @Autowired
private UserController userController;
- @BeforeClass
- public static void setupResource() {
- staticCreateTestMetadata();
- List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
- ManagedUser user = new ManagedUser("ADMIN", "ADMIN", false, authorities);
- Authentication authentication = new TestingAuthenticationToken(user, "ADMIN", Constant.ROLE_ADMIN);
- SecurityContextHolder.getContext().setAuthentication(authentication);
- }
+ private ManagedUser userAdmin = new ManagedUser("ADMIN", "KYLIN", false, Constant.ROLE_ADMIN);
- @Before
- public void setup() throws Exception {
- super.setup();
+ private ManagedUser userModeler = new ManagedUser("MODELER", "MODELER", false, Constant.ROLE_MODELER);
- userController = new UserController();
+ @Test
+ public void testLogin() throws IOException {
+ logInWithUser(userAdmin);
+ UserDetails userDetail = userController.authenticatedUser();
+ ManagedUser user = (ManagedUser) userDetail;
+ Assert.assertTrue(user.equals(userAdmin));
}
@Test
- public void testBasics() throws IOException {
- UserDetails user = userController.authenticate();
- Assert.assertNotNull(user);
- Assert.assertTrue(user.getUsername().equals("ADMIN"));
+ public void testLoginAsAnotherUser() throws IOException {
+ logInWithUser(userModeler);
+ UserDetails userDetail = userController.authenticate();
+ ManagedUser user = (ManagedUser) userDetail;
+ Assert.assertTrue(user.equals(userModeler));
+ }
+
+ private void logInWithUser(ManagedUser user) {
+ UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, user.getPassword(),
+ user.getAuthorities());
+ token.setDetails(SecurityContextHolder.getContext().getAuthentication().getDetails());
+ SecurityContextHolder.getContext().setAuthentication(token);
}
}
diff --git a/server/src/test/java/org/apache/kylin/rest/service/ServiceTestBase.java b/server/src/test/java/org/apache/kylin/rest/service/ServiceTestBase.java
index ee5cbd1..36a36cd 100644
--- a/server/src/test/java/org/apache/kylin/rest/service/ServiceTestBase.java
+++ b/server/src/test/java/org/apache/kylin/rest/service/ServiceTestBase.java
@@ -20,6 +20,7 @@ package org.apache.kylin.rest.service;
import java.io.IOException;
import java.util.Arrays;
+import java.util.List;
import org.apache.curator.test.TestingServer;
import org.apache.kylin.common.KylinConfig;
@@ -58,6 +59,10 @@ public class ServiceTestBase extends LocalFileMetadataTestCase {
@Qualifier("userService")
UserService userService;
+ @Autowired
+ @Qualifier("userGroupService")
+ UserGroupService userGroupService;
+
@BeforeClass
public static void setupResource() throws Exception {
staticCreateTestMetadata();
@@ -76,9 +81,23 @@ public class ServiceTestBase extends LocalFileMetadataTestCase {
@Before
public void setup() throws Exception {
this.createTestMetadata();
-
+ Authentication authentication = new TestingAuthenticationToken("ADMIN", "ADMIN", Constant.ROLE_ADMIN);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
KylinConfig config = KylinConfig.getInstanceFromEnv();
Broadcaster.getInstance(config).notifyClearAll();
+ List<String> userGroups = userGroupService.getAllUserGroups();
+ if (!userGroups.contains(Constant.GROUP_ALL_USERS)) {
+ userGroupService.addGroup(Constant.GROUP_ALL_USERS);
+ }
+ if (!userGroups.contains(Constant.ROLE_ADMIN)) {
+ userGroupService.addGroup(Constant.ROLE_ADMIN);
+ }
+ if (!userGroups.contains(Constant.ROLE_MODELER)) {
+ userGroupService.addGroup(Constant.ROLE_MODELER);
+ }
+ if (!userGroups.contains(Constant.ROLE_ANALYST)) {
+ userGroupService.addGroup(Constant.ROLE_ANALYST);
+ }
if (!userService.userExists("ADMIN")) {
userService.createUser(new ManagedUser("ADMIN", "KYLIN", false, Arrays.asList(//
@@ -88,14 +107,15 @@ public class ServiceTestBase extends LocalFileMetadataTestCase {
if (!userService.userExists("MODELER")) {
userService.createUser(new ManagedUser("MODELER", "MODELER", false, Arrays.asList(//
- new SimpleGrantedAuthority(Constant.ROLE_ANALYST),
- new SimpleGrantedAuthority(Constant.ROLE_MODELER))));
+ new SimpleGrantedAuthority(Constant.ROLE_ANALYST),
+ new SimpleGrantedAuthority(Constant.ROLE_MODELER))));
}
if (!userService.userExists("ANALYST")) {
userService.createUser(new ManagedUser("ANALYST", "ANALYST", false, Arrays.asList(//
new SimpleGrantedAuthority(Constant.ROLE_ANALYST))));
}
+
}
@After
diff --git a/server/src/test/java/org/apache/kylin/rest/util/ValidateUtilTest.java b/server/src/test/java/org/apache/kylin/rest/util/ValidateUtilTest.java
index 62bd203..c35420b 100644
--- a/server/src/test/java/org/apache/kylin/rest/util/ValidateUtilTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/util/ValidateUtilTest.java
@@ -18,11 +18,7 @@
package org.apache.kylin.rest.util;
-import static org.apache.kylin.metadata.MetadataConstants.TYPE_GROUP;
-import static org.apache.kylin.metadata.MetadataConstants.TYPE_USER;
-
-import java.io.IOException;
-
+import com.google.common.collect.Lists;
import org.apache.kylin.common.persistence.RootPersistentEntity;
import org.apache.kylin.rest.security.AclPermission;
import org.apache.kylin.rest.service.AccessService;
@@ -32,7 +28,10 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
-import com.google.common.collect.Lists;
+import java.io.IOException;
+
+import static org.apache.kylin.metadata.MetadataConstants.TYPE_GROUP;
+import static org.apache.kylin.metadata.MetadataConstants.TYPE_USER;
public class ValidateUtilTest extends ServiceTestBase {
private final String PROJECT = "default";
diff --git a/webapp/app/index.html b/webapp/app/index.html
index eac7909..193f903 100644
--- a/webapp/app/index.html
+++ b/webapp/app/index.html
@@ -155,6 +155,7 @@
<script src="js/services/hybridInstance.js"></script>
<script src="js/services/dashboard.js"></script>
<script src="js/services/instance.js"></script>
+<script src="js/services/userGroup.js"></script>
<script src="js/model/cubeConfig.js"></script>
<script src="js/model/jobConfig.js"></script>
@@ -182,6 +183,7 @@
<script src="js/services/badQuery.js"></script>
<script src="js/utils/utils.js"></script>
<script src="js/utils/liquidFillGauge.js"></script>
+<script src="js/utils/response.js"></script>
<script src="js/controllers/page.js"></script>
<script src="js/controllers/index.js"></script>
<script src="js/controllers/access.js"></script>
@@ -224,7 +226,7 @@
<script src="js/controllers/streamingBalanceAssignGroup.js"></script>
<script src="js/controllers/adminStreaming.js"></script>
<script src="js/controllers/instances.js"></script>
-
+<script src="js/controllers/userGroup.js"></script>
<!-- endref -->
<!-- ref:remove -->
diff --git a/webapp/app/js/app.js b/webapp/app/js/app.js
index e505ffc..a7f43b7 100644
--- a/webapp/app/js/app.js
+++ b/webapp/app/js/app.js
@@ -17,4 +17,4 @@
*/
//Kylin Application Module
-KylinApp = angular.module('kylin', ['ngRoute', 'ngResource', 'ngGrid', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.grouping', 'ui.bootstrap', 'ui.ace', 'base64', 'angularLocalStorage', 'localytics.directives', 'treeControl', 'ngLoadingRequest', 'oitozero.ngSweetAlert', 'ngCookies', 'angular-underscore', 'ngAnimate', 'ui.sortable', 'angularBootstrapNavTree', 'toggle-switch', 'ngSanitize', 'ui.select', 'ui.bootstrap.datetimepicker', 'nvd3', 'ngTagsInput']);
+KylinApp = angular.module('kylin', ['ngRoute', 'ngResource', 'ngGrid', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.grouping', 'ui.bootstrap', 'ui.bootstrap.pagination', 'ui.ace', 'base64', 'angularLocalStorage', 'localytics.directives', 'treeControl', 'ngLoadingRequest', 'oitozero.ngSweetAlert', 'ngCookies', 'angular-underscore', 'ngAnimate', 'ui.sortable', 'angularBootstrapNavTree', 'toggle-switch', 'ngSanitize', 'ui.select', 'ui.bootstrap.datetimepicker', 'nvd3', 'ngTagsInput']);
diff --git a/webapp/app/js/controllers/admin.js b/webapp/app/js/controllers/admin.js
index fb0dac2..9fce648 100644
--- a/webapp/app/js/controllers/admin.js
+++ b/webapp/app/js/controllers/admin.js
@@ -21,7 +21,18 @@
KylinApp.controller('AdminCtrl', function ($scope, AdminService, CacheService, TableService, loadingRequest, MessageService, ProjectService, $modal, SweetAlert,kylinConfig,ProjectModel,$window, MessageBox) {
$scope.configStr = "";
$scope.envStr = "";
-
+ $scope.active = {
+ tab_instance: true
+ }
+ $scope.tabData = {}
+ $scope.activateTab = function(tab) {
+ $scope.active = {}; //reset
+ $scope.active[tab] = true;
+ }
+ $scope.$on('change.active', function(event, data) {
+ $scope.activateTab(data.activeTab);
+ $scope.tabData.groupName = data.groupName
+ });
$scope.isCacheEnabled = function(){
return kylinConfig.isCacheEnabled();
}
diff --git a/webapp/app/js/controllers/userGroup.js b/webapp/app/js/controllers/userGroup.js
new file mode 100644
index 0000000..d2c4b6d
--- /dev/null
+++ b/webapp/app/js/controllers/userGroup.js
@@ -0,0 +1,387 @@
+/*
+ * 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.
+*/
+
+'use strict';
+
+KylinApp
+ .controller('UserGroupCtrl', function ($scope, kylinConfig, UserGroupService, ProjectModel, SweetAlert, $modal, UserService, ResponseUtil) {
+ $scope.grouploading = false;
+ $scope.userloading = false;
+ $scope.dialogActionLoading = false;
+ $scope.groups = [];
+ $scope.groupsTotal = 0;
+ $scope.users = [];
+ $scope.usersTotal = 0;
+ kylinConfig.init().$promise.then(function() {
+ $scope.securityType = kylinConfig.getSecurityType();
+ $scope.allowUseUserAndGroupModule = ['testing', 'custom'].indexOf($scope.securityType) >= 0;
+ })
+ $scope.page = {
+ curpage: kylinConfig.page.offset,
+ limit: kylinConfig.page.limit
+ }
+ $scope.editPage = {
+ curpage: kylinConfig.page.offset,
+ limit: kylinConfig.page.limit
+ }
+ $scope.selectGroups = {};
+ $scope.selectUsers = {};
+
+
+ $scope.filter = {
+ name: '',
+ groupName: ''
+ };
+ var createChangePwdMeta = function () {
+ return {
+ repeatPassword: '',
+ newPassword:''
+ }
+ }
+ var createUserMeta = function () {
+ return {
+ name: '',
+ password: '',
+ isAdmin: false,
+ authorities: ["ALL_USERS"]
+ }
+ }
+ var createGroupMeta = function () {
+ return {
+ name: ''
+ }
+ }
+ var createFilter = function (offset, limit, pageObj) {
+ offset = (!!offset) ? offset : pageObj.curpage;
+ if (pageObj) {
+ pageObj.curpage = offset;
+ }
+ offset = offset - 1;
+ limit = (!!limit) ? limit : pageObj.limit;
+ return {
+ offset: offset * limit,
+ limit: (offset + 1) * limit,
+ name: $scope.filter.name,
+ groupName: $scope.tabData.groupName,
+ isFuzzMatch: true,
+ project: ProjectModel.selectedProject
+ };
+ }
+ $scope.showUserListByGroup = function (groupName) {
+ $scope.$emit('change.active', {
+ activeTab: 'tab_users',
+ groupName: groupName
+ });
+ };
+ $scope.removeGroupFilter = function () {
+ $scope.$emit('change.active', {
+ activeTab: 'tab_users',
+ groupName: ''
+ });
+ $scope.listUsers();
+ }
+ $scope.user = createUserMeta();
+ $scope.group = createGroupMeta();
+ $scope.getGroupList = function (offset, limit, pageObj) {
+ var queryParam = createFilter(offset, limit, pageObj);
+ $scope.grouploading = true;
+ UserGroupService.listGroups(queryParam, function (res) {
+ $scope.groups = res && res.data.groups || [];
+ $scope.groupsTotal = res.data.size || 0;
+ $scope.grouploading = false;
+ }, function (res) {
+ $scope.grouploading = false;
+ ResponseUtil.handleError(res);
+ });
+ }
+ $scope.getUserList = function (offset, limit, pageObj) {
+ var queryParam = createFilter(offset, limit, pageObj);
+ $scope.grouploading = true;
+ UserService.listUsers(queryParam, function (res) {
+ $scope.users = res && res.data.users || [];
+ $scope.usersTotal = res.data.size || 0;
+ $scope.userloading = false;
+ }, function (res) {
+ $scope.userloading = false;
+ ResponseUtil.handleError(res);
+ });
+ }
+ $scope.listUsers = function (offset, limit) {
+ $scope.getUserList(offset, limit, $scope.page);
+ }
+ $scope.listGroups = function (offset, limit) {
+ $scope.getGroupList(offset, limit, $scope.page);
+ }
+ $scope.listEditUsers = function (offset, limit) {
+ $scope.getUserList(offset, limit, $scope.editPage);
+ }
+ $scope.listEditGroups = function (offset, limit) {
+ $scope.getGroupList(offset, limit, $scope.editPage);
+ }
+
+ $scope.delUser = function (userName) {
+ SweetAlert.swal({
+ title: '',
+ text: "Are you sure to delete the user " + userName + "?",
+ showCancelButton: true,
+ confirmButtonColor: '#DD6B55',
+ confirmButtonText: "Yes",
+ closeOnConfirm: true
+ }, function(isConfirm) {
+ if(isConfirm){
+ UserService.delUser({
+ userName: userName
+ }, function () {
+ SweetAlert.swal('Delete successfuly', null, 'success');
+ $scope.listUsers($scope.page.curpage);
+ }, function (e) {
+ ResponseUtil.handleError(e);
+ })
+ }
+ })
+ }
+ var updateUser = function (user, isDisable) {
+ let updateUser = angular.extend({}, user)
+ updateUser.disabled = isDisable
+ UserService.updateUser({userName: updateUser.username}, updateUser, function () {
+ $scope.listUsers($scope.page.curpage);
+ SweetAlert.swal('User status update successfuly', null, 'success');
+ }, function (e) {
+ ResponseUtil.handleError(e);
+ })
+ }
+ $scope.disableUser = function (user) {
+ updateUser(user, true);
+ }
+ $scope.enableUser = function (user) {
+ updateUser(user, false);
+ }
+ $scope.delGroup = function (groupName) {
+ SweetAlert.swal({
+ title: '',
+ text: "Are you sure to delete the goup " + groupName + "?",
+ showCancelButton: true,
+ confirmButtonColor: '#DD6B55',
+ confirmButtonText: "Yes",
+ closeOnConfirm: true
+ }, function(isConfirm) {
+ if(isConfirm){
+ UserGroupService.delGroup({
+ group: groupName
+ }, function () {
+ SweetAlert.swal('Delete successfuly', null, 'success');
+ $scope.listGroups($scope.page.curpage);
+ }, function (res) {
+ ResponseUtil.handleError(res);
+ })
+ }
+ })
+ }
+ $scope.isAllchecked = function(selectedItems, items) {
+ if (items && items.hasOwnProperty('length')) {
+ for(let i = 0; i < items.length; i++) {
+ var item = typeof items[i] === "object" ? items[i].username : items[i];
+ if (!selectedItems[item]) {
+ return false;
+ }
+ }
+ } else {
+ for (let i in items) {
+ if (!selectedItems[i]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ $scope.selectAllUsers = function(element){
+ setTimeout(function() {
+ $scope.isChecked = element.checked;
+ $scope.users.forEach((u) => {
+ $scope.selectUsers[u.username] = $scope.isChecked;
+ })
+ $scope.$apply()
+ }, 1);
+ }
+ $scope.selectAllGroups = function(element){
+ setTimeout(function() {
+ $scope.isChecked = element.checked;
+ for (let g in $scope.groups) {
+ $scope.selectGroups[g] = $scope.isChecked;
+ }
+ $scope.$apply()
+ }, 1);
+ }
+ $scope.selectGroup = function (groupName) {
+ $scope.selectGroups[groupName] = !$scope.selectGroups[groupName];
+ }
+ var userEditCtr = function ($scope, $modalInstance, UserService,SweetAlert, kylinConfig) {
+ $scope.userPattern = /^[\w.@]+$/;
+ $scope.groupPattern = /^[\w.@]+$/;
+ $scope.pwdPattern = /^(?=.*\d)(?=.*[a-z])(?=.*[~!@#$%^&*(){}|:"<>?[\];',./`]).{8,}$/;
+ $scope.saveAssignGroup = function () {
+ $scope.user.authorities = [];
+ for (let i in $scope.selectGroups) {
+ if ($scope.selectGroups[i]) {
+ $scope.user.authorities.push(i);
+ }
+ }
+ $scope.dialogActionLoading = true;
+ UserService.assignGroup({
+ userName: $scope.user.name
+ }, $scope.user, function () {
+ $scope.dialogActionLoading = false;
+ $modalInstance.dismiss('cancel');
+ SweetAlert.swal('Assign successfuly', null, 'success');
+ $scope.listUsers($scope.page.curpage);
+ }, function (res){
+ $scope.dialogActionLoading = false;
+ ResponseUtil.handleError(res);
+ })
+ }
+
+ $scope.saveAssignUser = function () {
+ let users = []
+ for (let i in $scope.selectUsers) {
+ if ($scope.selectUsers[i]) {
+ users.push(i);
+ }
+ }
+ $scope.dialogActionLoading = true;
+ UserGroupService.assignUsers({
+ group: $scope.group.name
+ }, users, function () {
+ $scope.dialogActionLoading = false;
+ $modalInstance.dismiss('cancel');
+ SweetAlert.swal('Assign successfuly', null, 'success');
+ $scope.listGroups($scope.page.curpage);
+ }, function (res){
+ $scope.dialogActionLoading = false;
+ ResponseUtil.handleError(res);
+ })
+ }
+
+ $scope.cancel = function () {
+ $modalInstance.dismiss('cancel');
+ }
+ $scope.saveUser = function () {
+ $scope.dialogActionLoading = true;
+ if ($scope.user.isAdmin) {
+ $scope.user.authorities.push('ROLE_ADMIN');
+ }
+ UserService.addUser({
+ userName: $scope.user.name
+ }, $scope.user, function() {
+ $scope.dialogActionLoading = false;
+ $modalInstance.dismiss('cancel');
+ SweetAlert.swal('Add user successfuly', null, 'success');
+ $scope.listUsers();
+ }, function (res){
+ $scope.dialogActionLoading = false;
+ ResponseUtil.handleError(res)
+ })
+ }
+ $scope.saveGroup = function () {
+ $scope.dialogActionLoading = true;
+ UserGroupService.addGroup({
+ group: $scope.group.name
+ }, null, function () {
+ $modalInstance.dismiss('cancel');
+ SweetAlert.swal('Add group successfuly', null, 'success');
+ $scope.listGroups();
+ $scope.dialogActionLoading = false;
+ }, function (res){
+ $scope.dialogActionLoading = false;
+ ResponseUtil.handleError(res)
+ })
+ }
+ $scope.saveNewPassword = function () {
+ $scope.dialogActionLoading = true;
+ UserService.changePwd($scope.changePwdUser, function () {
+ $modalInstance.dismiss('cancel');
+ SweetAlert.swal('Change password successfuly', null, 'success');
+ $scope.listUsers();
+ $scope.dialogActionLoading = false;
+ }, function (e) {
+ $scope.dialogActionLoading = false;
+ ResponseUtil.handleError(e);
+ })
+ }
+ }
+ $scope.createUser = function () {
+ $scope.user = createUserMeta();
+ $modal.open({
+ templateUrl: 'addUser.html',
+ controller: userEditCtr,
+ scope: $scope
+ });
+ }
+ $scope.createGroup = function () {
+ $scope.group = createGroupMeta();
+ $modal.open({
+ templateUrl: 'addGroup.html',
+ controller: userEditCtr,
+ scope: $scope
+ });
+ }
+ $scope.assignToGroup = function (userName, authorities) {
+ $scope.listEditGroups();
+ $scope.user.name = userName;
+ $scope.selectGroups = {};
+ authorities.forEach(auth => {
+ $scope.selectGroups[auth.authority] = true;
+ })
+ $modal.open({
+ templateUrl: 'assignGroup.html',
+ controller: userEditCtr,
+ scope: $scope
+ });
+ }
+ $scope.assignToUser = function (groupName) {
+ $scope.listEditUsers();
+ $scope.group.name = groupName;
+ UserGroupService.getUsersByGroup({
+ group: groupName
+ }, {}, function(res) {
+ ResponseUtil.handleSuccess(res, function(data) {
+ let groupUsers = data.users || []
+ $scope.selectUsers = {};
+ groupUsers.forEach(function(user){
+ $scope.selectUsers[user.username] = true;
+ })
+ $modal.open({
+ templateUrl: 'assignUser.html',
+ controller: userEditCtr,
+ scope: $scope
+ });
+ })
+ })
+ }
+ $scope.changePwdUser = createChangePwdMeta()
+ $scope.changePwd = function (user) {
+ $scope.changePwdUser.username = user.username;
+ $scope.changePwdUser.password = user.password;
+ $scope.changePwdUser.repeatPassword = '';
+ $scope.changePwdUser.newPassword = '';
+ $modal.open({
+ templateUrl: 'changePwd.html',
+ controller: userEditCtr,
+ scope: $scope
+ });
+ }
+ });
diff --git a/webapp/app/js/directives/directives.js b/webapp/app/js/directives/directives.js
index 1999cb8..1ba9687 100644
--- a/webapp/app/js/directives/directives.js
+++ b/webapp/app/js/directives/directives.js
@@ -99,7 +99,7 @@ KylinApp.directive('kylinPagination', function ($parse, $q) {
}
};
})
- .directive('loading', function ($parse, $q) {
+.directive('loading', function ($parse, $q) {
return {
restrict: 'E',
scope: {},
diff --git a/webapp/app/js/services/kylinProperties.js b/webapp/app/js/services/kylinProperties.js
index 3c7a66f..fed071b 100644
--- a/webapp/app/js/services/kylinProperties.js
+++ b/webapp/app/js/services/kylinProperties.js
@@ -21,7 +21,6 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
var timezone;
var deployEnv;
-
this.init = function () {
return AdminService.publicConfig({}, function (config) {
_config = config.config;
@@ -170,5 +169,13 @@ KylinApp.service('kylinConfig', function (AdminService, $log) {
}
return this.sourceType;
}
+ this.getSecurityType = function () {
+ this.securityType = this.getProperty("kylin.security.profile").trim();
+ return this.securityType;
+ }
+ this.page = {
+ offset: 1,
+ limit: 15
+ }
});
diff --git a/webapp/app/js/app.js b/webapp/app/js/services/userGroup.js
similarity index 58%
copy from webapp/app/js/app.js
copy to webapp/app/js/services/userGroup.js
index e505ffc..22172d6 100644
--- a/webapp/app/js/app.js
+++ b/webapp/app/js/services/userGroup.js
@@ -16,5 +16,13 @@
* limitations under the License.
*/
-//Kylin Application Module
-KylinApp = angular.module('kylin', ['ngRoute', 'ngResource', 'ngGrid', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.grouping', 'ui.bootstrap', 'ui.ace', 'base64', 'angularLocalStorage', 'localytics.directives', 'treeControl', 'ngLoadingRequest', 'oitozero.ngSweetAlert', 'ngCookies', 'angular-underscore', 'ngAnimate', 'ui.sortable', 'angularBootstrapNavTree', 'toggle-switch', 'ngSanitize', 'ui.select', 'ui.bootstrap.datetimepicker', 'nvd3', 'ngTagsInput']);
+KylinApp.factory('UserGroupService', ['$resource', function ($resource, config) {
+ return $resource(Config.service.url + 'user_group/:action/:group', {}, {
+ listGroups: {method: 'GET', params: {action:'groups'}, isArray: false},
+ addGroup: {method: 'POST', params: {}, isArray: false},
+ delGroup: {method: 'DELETE', params: {}, isArray: false},
+ editGroup: {method: 'PUT', params: {}, isArray: false},
+ assignUsers: {method: 'PUT', params: {action: 'users'}, isArray: false},
+ getUsersByGroup: {method: 'GET', params: {action:'users'}, isArray: false}
+ });
+}]);
diff --git a/webapp/app/js/services/users.js b/webapp/app/js/services/users.js
index bccd414..97d5de4 100644
--- a/webapp/app/js/services/users.js
+++ b/webapp/app/js/services/users.js
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-KylinApp.service('UserService', function ($http, $q) {
+KylinApp.service('UserService', function ($resource) {
var curUser = {};
this.getCurUser = function () {
@@ -43,4 +43,14 @@ KylinApp.service('UserService', function ($http, $q) {
this.getHomePage = function () {
return this.isAuthorized()? "/models" : "/login";
}
+
+ var apiService = $resource(Config.service.url + 'user/:action/:userName', {}, {
+ listUsers: {method: 'GET', params: {action: 'users'}, isArray: false},
+ delUser: {method: 'DELETE', isArray: false},
+ addUser: {method: 'POST', params: {} ,isArray: false},
+ changePwd: {method: 'PUT', params: {action: 'password'}, isArray: false},
+ updateUser: {method: 'PUT', params: {}, isArray: false},
+ assignGroup: {method: 'PUT', params: {}, isArray: false}
+ });
+ angular.extend(this, apiService)
});
diff --git a/webapp/app/js/app.js b/webapp/app/js/utils/response.js
similarity index 51%
copy from webapp/app/js/app.js
copy to webapp/app/js/utils/response.js
index e505ffc..5ebf5a6 100644
--- a/webapp/app/js/app.js
+++ b/webapp/app/js/utils/response.js
@@ -16,5 +16,30 @@
* limitations under the License.
*/
-//Kylin Application Module
-KylinApp = angular.module('kylin', ['ngRoute', 'ngResource', 'ngGrid', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.grouping', 'ui.bootstrap', 'ui.ace', 'base64', 'angularLocalStorage', 'localytics.directives', 'treeControl', 'ngLoadingRequest', 'oitozero.ngSweetAlert', 'ngCookies', 'angular-underscore', 'ngAnimate', 'ui.sortable', 'angularBootstrapNavTree', 'toggle-switch', 'ngSanitize', 'ui.select', 'ui.bootstrap.datetimepicker', 'nvd3', 'ngTagsInput']);
+'use strict';
+
+/* utils */
+
+KylinApp.factory('ResponseUtil', function (SweetAlert) {
+ return {
+ handleError: function (e, cb) {
+ if (typeof cb === 'function') {
+ return cb(e)
+ }
+ if (e.data&& e.data.exception){
+ var message =e.data.exception;
+ var msg = !!(message) ? message : 'Failed to take action.';
+ SweetAlert.swal('Oops...', msg, 'error');
+ } else{
+ SweetAlert.swal('Oops...', "Failed to take action.", 'error');
+ }
+ },
+ // use in standard api response res = {data: {}, code:000, msg: ''}
+ handleSuccess: function (res, cb) {
+ let data = res && res.data || {}
+ if (typeof cb === 'function') {
+ return cb(data, res.code, res.msg)
+ }
+ }
+ }
+});
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index 35631c9..483fd74 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -1136,4 +1136,7 @@ tags-input .tags .tag-item {
.receiver-stats-modal .modal-dialog{
margin-top: 100px;
max-width: 600px;
+}
+.pagination{
+ cursor: pointer;
}
\ No newline at end of file
diff --git a/webapp/app/partials/admin/admin.html b/webapp/app/partials/admin/admin.html
index 5512386..fb5acaf 100644
--- a/webapp/app/partials/admin/admin.html
+++ b/webapp/app/partials/admin/admin.html
@@ -18,16 +18,22 @@
<div class="row" style="padding-top:10px;padding-left: 5px;">
<div ng-class="row">
- <tabset>
- <tab heading="Configuration" select="getEnv();getConfig();">
+ <tabset active="activeTab">
+ <tab active="active['tab_config']" heading="Configuration" select="getEnv();getConfig();">
<div class="col-xs-12" ng-include src="'partials/admin/config.html'"></div>
</tab>
- <tab ng-if="isCuratorScheduler()" heading="Instances" select="list()" ng-controller="InstanceCtrl">
+ <tab active="active['tab_instance']" ng-if="isCuratorScheduler()" heading="Instances" select="list()" ng-controller="InstanceCtrl">
<div class="col-xs-12" ng-include src="'partials/admin/instances.html'"></div>
</tab>
- <tab heading="Streaming" select="listReplicaSet()" ng-controller="AdminStreamingCtrl">
+ <tab active="active['tab_streaming']" heading="Streaming" select="listReplicaSet()" ng-controller="AdminStreamingCtrl">
<div class="col-xs-12" ng-include src="'partials/admin/streaming.html'"></div>
</tab>
+ <tab active="active['tab_users']" heading="User" select="listUsers()" ng-controller="UserGroupCtrl">
+ <div class="col-xs-12" ng-include src="'partials/admin/user.html'"></div>
+ </tab>
+ <tab active="active['tab_groups']"heading="Group" select="listGroups()" ng-controller="UserGroupCtrl">
+ <div class="col-xs-12" ng-include src="'partials/admin/group.html'"></div>
+ </tab>
</tabset>
</div>
</div>
diff --git a/webapp/app/partials/admin/change_pwd.html b/webapp/app/partials/admin/change_pwd.html
new file mode 100644
index 0000000..6ae1679
--- /dev/null
+++ b/webapp/app/partials/admin/change_pwd.html
@@ -0,0 +1,78 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<script type="text/ng-template" id="changePwd.html">
+ <ng-form name="change_pwd_form">
+ <div class="modal-header">
+ <h4>Change Password</h4>
+ </div>
+ <input name="username" type="text" style="display:none"/>
+ <input name="password" type="password" style="width:1px;height:0;border:none"/>
+ <div class="modal-body" style="background-color: white">
+ <div class="form-group">
+ <label><b>User Name</b></label>
+ <div class="clearfix">
+ <input name="name_input" type="text" class="form-control" readonly ng-model="changePwdUser.username" required
+ placeholder="You can use letters, numbers, and underscore characters '_'"
+ ng-maxlength=100 ng-pattern="userPattern"/>
+ <span class="text-warning"
+ ng-if="change_pwd_form.name_input.$error.required && change_pwd_form.name_input.$dirty"
+ > The project name is required</span>
+ <span class="text-warning"
+ ng-if="change_pwd_form.name_input.$invalid && change_pwd_form.name_input.$dirty && !change_pwd_form.name_input.$error.required"
+ > The project name is invalid</span>
+ </div>
+ </div>
+ <div class="form-group">
+ <label><b>User New Password</b></label>
+ <div class="clearfix">
+ <input required ng-pattern="pwdPattern" name="pwd_input" type="password" class="form-control"
+ placeholder="The password should contain at least one number, letter and special character(~!@#$%^&*(){}|:"\<\>?[];\',./`)."
+ ng-model="changePwdUser.newPassword"/>
+ <span class="text-warning"
+ ng-if="change_pwd_form.pwd_input.$error.required && change_pwd_form.pwd_input.$dirty"
+ > The password is required</span>
+ <span class="text-warning"
+ ng-if="change_pwd_form.pwd_input.$invalid && change_pwd_form.pwd_input.$dirty && !change_pwd_form.pwd_input.$error.required"
+ > The password is invalid</span>
+ </div>
+ </div>
+ <div class="form-group">
+ <label><b>Confirm New Password</b></label>
+ <div class="clearfix">
+ <input required name="pwd_new_input" type="password" class="form-control"
+ placeholder="The password should contain at least one number, letter and special character(~!@#$%^&*(){}|:"\<\>?[];\',./`)."
+ ng-model="changePwdUser.repeatPassword"/>
+ <span class="text-warning"
+ ng-if="user_create_form.pwd_new_input.$error.required && user_create_form.pwd_new_input.$dirty"
+ > The password is required</span>
+ <span class="text-warning"
+ ng-if="changePwdUser.repeatPassword && changePwdUser.newPassword !== changePwdUser.repeatPassword"
+ > Password and confirm password are not the same.</span>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="cancel()">Close</button>
+ <button class="btn btn-success" ng-click="saveNewPassword()" ng-disabled="(changePwdUser.newPassword !== changePwdUser.repeatPassword) || change_pwd_form.$invalid || dialogActionLoading">
+ Submit
+ </button>
+ </div>
+ </ng-form>
+</script>
+
diff --git a/webapp/app/partials/admin/group.html b/webapp/app/partials/admin/group.html
new file mode 100644
index 0000000..a674d4c
--- /dev/null
+++ b/webapp/app/partials/admin/group.html
@@ -0,0 +1,93 @@
+<!--
+* 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.
+-->
+<div class="container">
+ <div class="row" style="margin-top:15px;" ng-if="!allowUseUserAndGroupModule">User management does not apply to the current security configuration, go to the correct permissions management page for editing.</div>
+ <div class="row" style="margin-top:15px;" ng-if="allowUseUserAndGroupModule">
+
+ <!--No Instances-->
+ <div ng-if="!grouploading && groupsTotal == 0">
+ <div no-result text="No user group"></div>
+ </div>
+ <!--Loading Instances-->
+ <div ng-if="grouploading">
+ <loading text="Loading user group ..."></loading>
+ </div>
+ <div class="row">
+ <div class="col-xs-9">
+ <label class="table-header-text">Group List</label>
+ </div>
+ <div class="col-xs-3">
+ <form style="float:right;" >
+ <span class="input-icon input-icon-right nav-search">
+ <input type="text" placeholder="Search by group name" class="nav-search-input" ng-model="filter.name" />
+ <i class="ace-icon fa fa-search blue" ng-click="listGroups();"></i>
+ </span>
+ </form>
+ </div>
+ </div>
+ <button class="btn btn-primary btn-sm" ng-click="createGroup()"><i class="fa fa-plus"></i> Group</button>
+ <!--Queries Table Content-->
+ <table ng-if="groupsTotal > 0" style="margin-top:20px;table-layout:fixed;" class="table table-striped table-bordered table-hover dataTable no-footer">
+ <thead>
+ <tr style="cursor: pointer">
+ <th width="7%">
+ ID
+ </th>
+ <th>
+ Group Name
+ </th>
+ <th>
+ User Count
+ </th>
+ <th width="17%">
+ Actions
+ </th>
+ </tr>
+ </thead>
+ <tbody class="odd table table-striped table-bordered table-hover dataTable no-footer">
+ <tr ng-repeat="(group, value) in groups" style="cursor: pointer">
+ <td>
+ {{$index+1 + page.limit * (page.curpage - 1)}}
+ </td>
+ <td>
+ {{group}}
+ </td>
+ <td>
+ <a ng-click="showUserListByGroup(group)">{{value.length}}</a>
+ </td>
+ <td>
+ <button type="button" tooltip="Add user to group" class="btn btn-default btn-xs" ng-if="group!=='ALL_USERS'" ng-click="assignToUser(group)">
+ <i class="fa fa-user-plus fa-fw"></i>
+ </button>
+ <button type="button" tooltip="Delete group" ng-if="group!=='ROLE_ADMIN' && group!=='ALL_USERS'" class="btn btn-default btn-xs" ng-click="delGroup(group)">
+ <i class="fa fa-trash-o fa-fw"></i>
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="row">
+ <div class="col-xs-12">
+ <pagination total-items="groupsTotal" boundary-link-numbers="true" rotate="false" boundary-links="true" force-ellipses="true" page="page.curpage" max-size="10" items-per-page="page.limit" on-select-page="listGroups(page)" class="pagination-sm"></pagination>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div ng-include="'partials/admin/group_create.html'"></div>
+<div ng-include="'partials/admin/user_assign.html'"></div>
diff --git a/webapp/app/partials/admin/group_assign.html b/webapp/app/partials/admin/group_assign.html
new file mode 100644
index 0000000..9fd82e8
--- /dev/null
+++ b/webapp/app/partials/admin/group_assign.html
@@ -0,0 +1,87 @@
+<!--
+* 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.
+-->
+
+<script type="text/ng-template" id="assignGroup.html">
+ <ng-form name="group_create_form">
+ <div class="modal-header">
+ <h4>New Group</h4>
+ </div>
+ <div class="modal-body" style="background-color: white">
+ <div class="container">
+ <div class="row" style="margin-top:15px;">
+
+ <!--No Instances-->
+ <div ng-if="!loading && groupsTotal==0">
+ <div no-result text="No user group"></div>
+ </div>
+ <!--Loading Instances-->
+ <div ng-if="loading">
+ <loading text="Loading user group ..."></loading>
+ </div>
+ <div class="row">
+ <div class="col-xs-9">
+ <label class="table-header-text">Group List</label>
+ </div>
+ <div class="col-xs-3">
+ <form style="float:right;" >
+ <span class="input-icon input-icon-right nav-search">
+ <input type="text" placeholder="Search by group name" class="nav-search-input" ng-model="filter.name" />
+ <i class="ace-icon fa fa-search blue" ng-click="listEditGroups();"></i>
+ </span>
+ </form>
+ </div>
+ </div>
+ <table ng-if="groupsTotal > 0" style="margin-top:20px;table-layout:fixed;" class="table table-striped table-bordered table-hover dataTable no-footer">
+ <thead>
+ <tr style="cursor: pointer">
+ <th width="7%">
+ <input type="checkbox" onchange="angular.element(this).scope().selectAllGroups(this)" ng-checked="isAllchecked(selectGroups,groups)" >
+ </th>
+ <th >
+ Group Name
+ </th>
+ </tr>
+ </thead>
+ <tbody class="odd table table-striped table-bordered table-hover dataTable no-footer">
+ <tr ng-repeat="(group, val) in groups" style="cursor: pointer">
+ <td>
+ <input type="checkbox" ng-model="selectGroups[group]" >
+ </td>
+ <td>
+ {{group}}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="row" v-if="">
+ <div class="col-xs-12">
+ <pagination total-items="groupsTotal" boundary-link-numbers="true" rotate="false" boundary-links="true" force-ellipses="true" page="editPage.curpage" max-size="10" items-per-page="editPage.limit" on-select-page="listEditGroups(page)" class="pagination-sm"></pagination>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="cancel()">Close</button>
+ <button class="btn btn-success" ng-click="saveAssignGroup()" ng-disabled="group_create_form.$invalid || dialogActionLoading">
+ Submit
+ </button>
+ </div>
+ </ng-form>
+</script>
+
diff --git a/webapp/app/partials/admin/group_create.html b/webapp/app/partials/admin/group_create.html
new file mode 100644
index 0000000..e838245
--- /dev/null
+++ b/webapp/app/partials/admin/group_create.html
@@ -0,0 +1,48 @@
+<!--
+* 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.
+-->
+
+<script type="text/ng-template" id="addGroup.html">
+ <ng-form name="group_create_form">
+ <div class="modal-header">
+ <h4>New Group</h4>
+ </div>
+ <div class="modal-body" style="background-color: white">
+ <div class="form-group">
+ <label><b>Group Name</b></label>
+ <div class="clearfix">
+ <input name="name_input" type="text" class="form-control" ng-model="group.name" required
+ placeholder="You can use letters, numbers, and underscore characters '_'"
+ ng-maxlength=100 ng-pattern="groupPattern"/>
+ <span class="text-warning"
+ ng-if="group_create_form.name_input.$error.required && group_create_form.name_input.$dirty"
+ > The group name is required</span>
+ <span class="text-warning"
+ ng-if="group_create_form.name_input.$invalid && group_create_form.name_input.$dirty && !group_create_form.name_input.$error.required"
+ > The project name is invalid</span>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="cancel()">Close</button>
+ <button class="btn btn-success" ng-click="saveGroup()" ng-disabled="group_create_form.$invalid || dialogActionLoading">
+ Submit
+ </button>
+ </div>
+ </ng-form>
+</script>
+
diff --git a/webapp/app/partials/admin/user.html b/webapp/app/partials/admin/user.html
new file mode 100644
index 0000000..e0c4529
--- /dev/null
+++ b/webapp/app/partials/admin/user.html
@@ -0,0 +1,108 @@
+<!--
+* 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.
+-->
+<div class="container">
+ <div class="row" style="margin-top:15px;" ng-if="!allowUseUserAndGroupModule">User management does not apply to the current security configuration, go to the correct permissions management page for editing.</div>
+ <div class="row" style="margin-top:15px;" ng-if="allowUseUserAndGroupModule">
+ <!--No Instances-->
+ <div ng-if="!userloading && usersTotal == 0">
+ <div no-result text="No user"></div>
+ </div>
+ <!--Loading Instances-->
+ <div ng-if="userloading">
+ <loading text="Loading user list ..."></loading>
+ </div>
+ <div class="row">
+ <div class="col-xs-9">
+ <label class="table-header-text">User List</label>
+ </div>
+ <div class="col-xs-3">
+ <form style="float:right;" >
+ <span class="input-icon input-icon-right nav-search">
+ <input type="text" placeholder="Search by user name" class="nav-search-input" ng-model="filter.name" />
+ <i class="ace-icon fa fa-search blue" ng-click="listUsers();"></i>
+ </span>
+ </form>
+ </div>
+ </div>
+ <button class="btn btn-primary btn-sm" ng-click="createUser()"><i class="fa fa-plus"></i> User</button>
+ <span class="label label-success" style="float:right;margin-top:10px;" ng-if="tabData.groupName">Group Name: {{tabData.groupName}} <i class="fa fa-close" style="cursor: pointer;" ng-click="removeGroupFilter()"></i></span>
+ <!--Queries Table Content-->
+ <table ng-if="usersTotal > 0" style="margin-top:20px;table-layout: fixed;" class="table table-striped table-bordered table-hover table-condensed dataTable no-footer">
+ <thead>
+ <tr style="cursor: pointer">
+ <th width="7%">
+ ID
+ </th>
+ <th width="15%">
+ User Name
+ </th>
+ <th>
+ Group
+ </th>
+ <th width="8%">
+ Status
+ </th>
+ <th width="15%">
+ Actions
+ </th>
+ </tr>
+ </thead>
+ <tbody class="odd table table-striped table-bordered table-hover dataTable no-footer">
+ <tr ng-repeat="(i, user) in users" style="cursor: pointer">
+ <td>
+ {{i+1 + page.limit * (page.curpage - 1)}}
+ </td>
+ <td>
+ {{user.username}}
+ </td>
+ <th>
+ <span class="label label-primary" style="margin-left:4px;display:inline-block" ng-repeat="auth in user.authorities">{{auth.authority}}</span>
+ </th>
+ <th >
+ <span ng-if="!user.disabled" class="label label-success">Enabled</span>
+ <span ng-if="user.disabled" class="label label-warning">Disabled</span>
+ </th>
+ <td>
+ <div class="btn-group">
+ <button type="button" tooltip="Assign group" class="btn btn-default btn-xs" ng-if="user.username!='ADMIN'" ng-click="assignToGroup(user.username, user.authorities)">
+ <i class="fa fa-group fa-fw"></i>
+ </button>
+ <button type="button" tooltip="Delete user" class="btn btn-default btn-xs" ng-if="user.username!='ADMIN'" ng-click="delUser(user.username)">
+ <i class="fa fa-trash-o fa-fw"></i>
+ </button>
+ <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">Actions<span class="ace-icon fa fa-caret-down icon-on-right"></span></button>
+ <ul class="dropdown-menu" role="menu" style="right:0;left:auto;">
+ <li ng-if="user.disabled"><a ng-click="enableUser(user)">Enable</a></li>
+ <li ng-if="!user.disabled"><a ng-click="disableUser(user)">Disable</a></li>
+ <li><a ng-click="changePwd(user)">Change Password</a></li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="row" ng-if="users.length>0">
+ <div class="col-xs-12">
+ <pagination total-items="usersTotal" boundary-link-numbers="true" rotate="false" boundary-links="true" force-ellipses="true" page="page.curpage" max-size="10" items-per-page="page.limit" on-select-page="listUsers(page)" class="pagination-sm"></pagination>
+ </div>
+ </div>
+ </div>
+</div>
+<div ng-include="'partials/admin/user_create.html'"></div>
+<div ng-include="'partials/admin/change_pwd.html'"></div>
+<div ng-include="'partials/admin/group_assign.html'"></div>
\ No newline at end of file
diff --git a/webapp/app/partials/admin/user_assign.html b/webapp/app/partials/admin/user_assign.html
new file mode 100644
index 0000000..70ac67d
--- /dev/null
+++ b/webapp/app/partials/admin/user_assign.html
@@ -0,0 +1,87 @@
+<!--
+* 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.
+-->
+
+<script type="text/ng-template" id="assignUser.html">
+ <ng-form name="group_create_form">
+ <div class="modal-header">
+ <h4>Select User</h4>
+ </div>
+ <div class="modal-body" style="background-color: white">
+ <div class="container">
+ <div class="row" style="margin-top:15px;">
+
+ <!--No Instances-->
+ <div ng-if="!loading && usersTotal == 0">
+ <div no-result text="No user group"></div>
+ </div>
+ <!--Loading Instances-->
+ <div ng-if="loading">
+ <loading text="Loading user group ..."></loading>
+ </div>
+ <div class="row">
+ <div class="col-xs-9">
+ <label class="table-header-text">User List</label>
+ </div>
+ <div class="col-xs-3">
+ <form style="float:right;" >
+ <span class="input-icon input-icon-right nav-search">
+ <input type="text" placeholder="Search by user name" class="nav-search-input" ng-model="filter.name" />
+ <i class="ace-icon fa fa-search blue" ng-click="listEditUsers();"></i>
+ </span>
+ </form>
+ </div>
+ </div>
+ <table ng-if="usersTotal > 0" style="margin-top:20px;table-layout:fixed;" class="table table-striped table-bordered table-hover dataTable no-footer">
+ <thead>
+ <tr style="cursor: pointer">
+ <th width="7%">
+ <input type="checkbox" onchange="angular.element(this).scope().selectAllUsers(this)" ng-checked="isAllchecked(selectUsers, users)" >
+ </th>
+ <th >
+ User Name
+ </th>
+ </tr>
+ </thead>
+ <tbody class="odd table table-striped table-bordered table-hover dataTable no-footer">
+ <tr ng-repeat="user in users" style="cursor: pointer">
+ <td>
+ <input type="checkbox" ng-model="selectUsers[user.username]" >
+ </td>
+ <td>
+ {{user.username}}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div class="row" v-if="">
+ <div class="col-xs-12">
+ <pagination total-items="usersTotal" boundary-link-numbers="true" rotate="false" boundary-links="true" force-ellipses="true" page="editPage.curpage" max-size="10" items-per-page="editPage.limit" on-select-page="listEditUsers(page)" class="pagination-sm"></pagination>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="cancel()">Close</button>
+ <button class="btn btn-success" ng-click="saveAssignUser()" ng-disabled="group_create_form.$invalid || dialogActionLoading">
+ Submit
+ </button>
+ </div>
+ </ng-form>
+</script>
+
diff --git a/webapp/app/partials/admin/user_create.html b/webapp/app/partials/admin/user_create.html
new file mode 100644
index 0000000..4c60060
--- /dev/null
+++ b/webapp/app/partials/admin/user_create.html
@@ -0,0 +1,73 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<script type="text/ng-template" id="addUser.html">
+ <ng-form name="user_create_form">
+ <div class="modal-header">
+ <h4>New User</h4>
+ </div>
+ <input name="username" type="text" style="display:none"/>
+ <input name="password" type="password" style="width:1px;height:0;border:none"/>
+ <div class="modal-body" style="background-color: white">
+ <div class="form-group">
+ <label><b>User Name</b></label>
+ <div class="clearfix">
+ <input name="name_input" type="text" class="form-control" ng-model="user.name" required
+ placeholder="You can use letters, numbers, and underscore characters '_'"
+ ng-maxlength=100 ng-pattern="userPattern"/>
+ <span class="text-warning"
+ ng-if="user_create_form.name_input.$error.required && user_create_form.name_input.$dirty"
+ > The project name is required</span>
+ <span class="text-warning"
+ ng-if="user_create_form.name_input.$invalid && user_create_form.name_input.$dirty && !user_create_form.name_input.$error.required"
+ > The project name is invalid</span>
+ </div>
+ </div>
+ <div class="form-group">
+ <label><b>User Password</b></label>
+ <div class="clearfix">
+ <input ng-pattern="pwdPattern" required name="pwd_input" type="password" class="form-control" placeholder="The password should contain at least one number, letter and special character(~!@#$%^&*(){}|:"\<\>?[];\',./`)." ng-model="user.password"/>
+ <span class="text-warning"
+ ng-if="user_create_form.pwd_input.$error.required && user_create_form.pwd_input.$dirty"
+ > The password is required</span>
+ <span class="text-warning"
+ ng-if="user_create_form.pwd_input.$invalid && user_create_form.pwd_input.$dirty && !user_create_form.pwd_input.$error.required"
+ > The password is invalid</span>
+ </div>
+ </div>
+ <div class="form-group">
+ <label><b>User Role</b></label>
+ <div class="clearfix">
+ <label>
+ <input type="radio" name="isadmin" ng-model="user.isAdmin" ng-value="true"/> Administrator
+ </label>
+ <label>
+ <input type="radio" name="isadmin" ng-model="user.isAdmin" ng-value="false"/> User
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" ng-click="cancel()">Close</button>
+ <button class="btn btn-success" ng-click="saveUser()" ng-disabled="user_create_form.$invalid || dialogActionLoading">
+ Submit
+ </button>
+ </div>
+ </ng-form>
+</script>
+
diff --git a/webapp/app/partials/common/access.html b/webapp/app/partials/common/access.html
index ff54ed9..3b62e68 100644
--- a/webapp/app/partials/common/access.html
+++ b/webapp/app/partials/common/access.html
@@ -41,11 +41,12 @@
<td style="width: 40%" >
<label><b>Name </b> </label>
<input ng-model="newAccess.sid" ng-if="newAccess.principal==true" placeholder=" User NT Account..." style="width: 80%" />
+ <input ng-model="newAccess.sid" ng-if="newAccess.principal==false" placeholder=" User NT Account..." style="width: 80%" />
- <select chosen ng-model="newAccess.sid" ng-if="newAccess.principal==false" style="width: 80%"
+ <!-- <select chosen ng-model="newAccess.sid" ng-if="newAccess.principal==false" style="width: 80%"
ng-options="authority as authority for authority in authorities">
<option value=""></option>
- </select>
+ </select> -->
</td>
<td >
<label><b>Permission </b> </label>