KYLIN-2703 supports managing access rights for project and cube through apache ranger
Signed-off-by: Li Yang <liy...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/kylin/repo Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/108e93c6 Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/108e93c6 Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/108e93c6 Branch: refs/heads/security_update Commit: 108e93c604d89b83d40986ebf50d1024412ab8e4 Parents: e3e361d Author: peng.jianhua <peng.jian...@zte.com.cn> Authored: Wed Sep 6 17:50:51 2017 +0800 Committer: Li Yang <liy...@apache.org> Committed: Fri Sep 15 22:41:24 2017 +0800 ---------------------------------------------------------------------- build/deploy/context.xml | 2 +- .../apache/kylin/common/KylinConfigBase.java | 5 + .../kylin/metadata/project/ProjectManager.java | 9 ++ .../kylin/rest/controller/AccessController.java | 44 +++++++- .../kylin/rest/security/AclEntityType.java | 1 - .../kylin/rest/security/AclPermission.java | 2 - .../rest/security/ExternalAclProvider.java | 104 +++++++++++++++++ .../security/KylinAclPermissionEvaluator.java | 111 +++++++++++++++++++ .../kylin/rest/util/AclPermissionUtil.java | 39 +++++++ server/src/main/resources/kylinSecurity.xml | 4 +- webapp/app/js/services/kylinProperties.js | 8 ++ webapp/app/partials/cubes/cube_detail.html | 3 +- .../app/partials/projects/project_detail.html | 3 +- 13 files changed, 323 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/build/deploy/context.xml ---------------------------------------------------------------------- diff --git a/build/deploy/context.xml b/build/deploy/context.xml index 38c6ec8..4650d27 100644 --- a/build/deploy/context.xml +++ b/build/deploy/context.xml @@ -17,7 +17,7 @@ ~ limitations under the License. --> <!-- The contents of this file will be loaded for each web application --> -<Context> +<Context allowLinking="true"> <!-- Default set of monitored resources --> <WatchedResource>WEB-INF/web.xml</WatchedResource> http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java ---------------------------------------------------------------------- diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java index c120c4b..c74c093 100644 --- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java +++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java @@ -1194,6 +1194,10 @@ abstract public class KylinConfigBase implements Serializable { public int getServerUserCacheMaxEntries() { return Integer.valueOf(this.getOptional("kylin.server.auth-user-cache.max-entries", "100")); } + + public String getExternalAclProvider() { + return getOptional("kylin.server.external-acl-provider", ""); + } // ============================================================================ // WEB @@ -1238,4 +1242,5 @@ abstract public class KylinConfigBase implements Serializable { public String getPerfLoggerClassName() { return getOptional("kylin.metrics.perflogger-class", "org.apache.kylin.common.metrics.perflog.PerfLogger"); } + } http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java ---------------------------------------------------------------------- diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java index bbbca4f..ee51fa2 100644 --- a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java +++ b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java @@ -442,6 +442,15 @@ public class ProjectManager { return projects; } + public ProjectInstance getProjectByUuid(String uuid) { + Collection<ProjectInstance> copy = new ArrayList<ProjectInstance>(projectMap.values()); + for (ProjectInstance project : copy) { + if (uuid.equals(project.getUuid())) + return project; + } + return null; + } + public ExternalFilterDesc getExternalFilterDesc(String project, String extFilter) { return l2Cache.getExternalFilterDesc(project, extFilter); } http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java index a88c342..cd39cb1 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java +++ b/server-base/src/main/java/org/apache/kylin/rest/controller/AccessController.java @@ -19,18 +19,26 @@ package org.apache.kylin.rest.controller; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import org.apache.kylin.common.persistence.AclEntity; +import org.apache.kylin.common.util.Pair; import org.apache.kylin.rest.request.AccessRequest; import org.apache.kylin.rest.response.AccessEntryResponse; +import org.apache.kylin.rest.security.AclPermission; import org.apache.kylin.rest.security.AclPermissionFactory; +import org.apache.kylin.rest.security.ExternalAclProvider; import org.apache.kylin.rest.service.AccessService; +import org.apache.kylin.rest.service.UserService; +import org.apache.kylin.rest.util.AclPermissionUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -50,6 +58,10 @@ public class AccessController extends BasicController { @Qualifier("accessService") private AccessService accessService; + @Autowired + @Qualifier("userService") + private UserService userService; + /** * Get access entry list of a domain object * @@ -59,11 +71,35 @@ public class AccessController extends BasicController { */ @RequestMapping(value = "/{type}/{uuid}", method = { RequestMethod.GET }, produces = { "application/json" }) @ResponseBody - public List<AccessEntryResponse> getAccessEntities(@PathVariable String type, @PathVariable String uuid) { - AclEntity ae = accessService.getAclEntity(type, uuid); - Acl acl = accessService.getAcl(ae); + public List<AccessEntryResponse> getAccessEntities(@PathVariable String type, @PathVariable String uuid) throws IOException { + ExternalAclProvider eap = ExternalAclProvider.getInstance(); - return accessService.generateAceResponses(acl); + if (eap != null) { + List<AccessEntryResponse> ret = new ArrayList<>(); + List<Pair<String, AclPermission>> acl = eap.getAcl(type, uuid); + if (acl != null) { + for (Pair<String, AclPermission> p : acl) { + PrincipalSid sid = new PrincipalSid(p.getFirst()); + ret.add(new AccessEntryResponse(null, sid, p.getSecond(), true)); + } + } else { + // in case getAcl() does not work, try checkPermission() as a fall back + for (UserDetails user : userService.listUsers()) { + PrincipalSid sid = new PrincipalSid(user.getUsername()); + List<String> authorities = AclPermissionUtil.transformAuthorities(user.getAuthorities()); + for (Permission p : AclPermissionFactory.getPermissions()) { + if (eap.checkPermission(user.getUsername(), authorities, type, uuid, p)) { + ret.add(new AccessEntryResponse(null, sid, p, true)); + } + } + } + } + return ret; + } else { + AclEntity ae = accessService.getAclEntity(type, uuid); + Acl acl = accessService.getAcl(ae); + return accessService.generateAceResponses(acl); + } } /** http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/security/AclEntityType.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/AclEntityType.java b/server-base/src/main/java/org/apache/kylin/rest/security/AclEntityType.java index 69965f8..62ba5da 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/security/AclEntityType.java +++ b/server-base/src/main/java/org/apache/kylin/rest/security/AclEntityType.java @@ -18,7 +18,6 @@ package org.apache.kylin.rest.security; /** - * Created by xiefan on 17-4-14. */ public interface AclEntityType { static final String CUBE_INSTANCE = "CubeInstance"; http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/security/AclPermission.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/AclPermission.java b/server-base/src/main/java/org/apache/kylin/rest/security/AclPermission.java index 4e2e182..7d493d1 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/security/AclPermission.java +++ b/server-base/src/main/java/org/apache/kylin/rest/security/AclPermission.java @@ -22,8 +22,6 @@ import org.springframework.security.acls.domain.BasePermission; import org.springframework.security.acls.model.Permission; /** - * @author xduo - * */ public class AclPermission extends BasePermission { http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/security/ExternalAclProvider.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/ExternalAclProvider.java b/server-base/src/main/java/org/apache/kylin/rest/security/ExternalAclProvider.java new file mode 100644 index 0000000..395f44d --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/ExternalAclProvider.java @@ -0,0 +1,104 @@ +/* + * 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.security; + +import java.util.List; + +import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.common.util.ClassUtil; +import org.apache.kylin.common.util.Pair; +import org.springframework.security.acls.model.Permission; + +/** + */ +abstract public class ExternalAclProvider { + + private static boolean inited = false; + private static ExternalAclProvider singleton = null; + + public static ExternalAclProvider getInstance() { + if (inited) + return singleton; + + synchronized (ExternalAclProvider.class) { + if (inited) + return singleton; + + String cls = KylinConfig.getInstanceFromEnv().getExternalAclProvider(); + if (cls != null && cls.length() > 0) { + singleton = (ExternalAclProvider) ClassUtil.newInstance(cls); + singleton.init(); + } + + inited = true; + return singleton; + } + } + + // ============================================================================ + + public final static String CUBE_ADMIN = "CUBE ADMIN"; + public final static String CUBE_EDIT = "CUBE EDIT"; + public final static String CUBE_OPERATION = "CUBE OPERATION"; + public final static String CUBE_QUERY = "CUBE QUERY"; + + // used by ranger ExternalAclProvider + public static String transformPermission(Permission p) { + String permString = null; + if (AclPermission.ADMINISTRATION.equals(p)) { + permString = CUBE_ADMIN; + } else if (AclPermission.MANAGEMENT.equals(p)) { + permString = CUBE_EDIT; + } else if (AclPermission.OPERATION.equals(p)) { + permString = CUBE_OPERATION; + } else if (AclPermission.READ.equals(p)) { + permString = CUBE_QUERY; + } else { + permString = p.getPattern(); + } + return permString; + } + + // ============================================================================ + + abstract public void init(); + + /** + * Checks if a user has permission on an entity. + * + * @param user + * @param userRoles + * @param entityType String constants defined in AclEntityType + * @param entityUuid + * @param permission + * + * @return true if has permission + */ + abstract public boolean checkPermission(String user, List<String> userRoles, // + String entityType, String entityUuid, Permission permission); + + /** + * Returns all granted permissions on specified entity. + * + * @param entityType String constants defined in AclEntityType + * @param entityUuid + * @return a list of (user/role, permission) + */ + abstract public List<Pair<String, AclPermission>> getAcl(String entityType, String entityUuid); + +} http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/security/KylinAclPermissionEvaluator.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/KylinAclPermissionEvaluator.java b/server-base/src/main/java/org/apache/kylin/rest/security/KylinAclPermissionEvaluator.java new file mode 100644 index 0000000..b677537 --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/KylinAclPermissionEvaluator.java @@ -0,0 +1,111 @@ +/* + * 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.security; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; + +import org.apache.kylin.common.persistence.AclEntity; +import org.apache.kylin.rest.service.AclService; +import org.apache.kylin.rest.util.AclPermissionUtil; +import org.springframework.security.acls.AclPermissionEvaluator; +import org.springframework.security.acls.domain.PermissionFactory; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.core.Authentication; + +public class KylinAclPermissionEvaluator extends AclPermissionEvaluator { + + private PermissionFactory kylinPermissionFactory; + + public KylinAclPermissionEvaluator(AclService aclService, PermissionFactory permissionFactory) { + super(aclService); + super.setPermissionFactory(permissionFactory); + this.kylinPermissionFactory = permissionFactory; + } + + @Override + public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { + ExternalAclProvider eap = ExternalAclProvider.getInstance(); + if (eap == null) + return super.hasPermission(authentication, targetDomainObject, permission); + + if (targetDomainObject == null) { + return false; + } + + AclEntity e = (AclEntity) targetDomainObject; + return checkExternalPermission(eap, authentication, e.getClass().getSimpleName(), e.getId(), permission); + } + + private boolean checkExternalPermission(ExternalAclProvider eap, Authentication authentication, String entityType, + String entityUuid, Object permission) { + + String currentUser = authentication.getName(); + List<String> authorities = AclPermissionUtil.transformAuthorities(authentication.getAuthorities()); + List<Permission> kylinPermissions = resolveKylinPermission(permission); + + for (Permission p : kylinPermissions) { + if (eap.checkPermission(currentUser, authorities, entityType, entityUuid, p)) + return true; + } + return false; + } + + private List<Permission> resolveKylinPermission(Object permission) { + if (permission instanceof Integer) { + return Arrays.asList(kylinPermissionFactory.buildFromMask(((Integer) permission).intValue())); + } + + if (permission instanceof Permission) { + return Arrays.asList((Permission) permission); + } + + if (permission instanceof Permission[]) { + return Arrays.asList((Permission[]) permission); + } + + if (permission instanceof String) { + String permString = (String) permission; + Permission p; + + try { + p = kylinPermissionFactory.buildFromName(permString); + } catch (IllegalArgumentException notfound) { + p = kylinPermissionFactory.buildFromName(permString.toUpperCase()); + } + + if (p != null) { + return Arrays.asList(p); + } + + } + throw new IllegalArgumentException("Unsupported permission: " + permission); + } + + @Override + public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, + Object permission) { + ExternalAclProvider eap = ExternalAclProvider.getInstance(); + if (eap == null) + return super.hasPermission(authentication, targetId, targetType, permission); + + return checkExternalPermission(eap, authentication, targetType, targetId.toString(), permission); + } +} http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..a687d4d --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/util/AclPermissionUtil.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +package org.apache.kylin.rest.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; + +public class AclPermissionUtil { + + public static List<String> transformAuthorities(Collection<? extends GrantedAuthority> authorities) { + List<String> ret = new ArrayList<String>(); + for (GrantedAuthority auth : authorities) { + if (!authorities.contains(auth.getAuthority())) { + ret.add(auth.getAuthority()); + } + } + return ret; + } + +} http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/server/src/main/resources/kylinSecurity.xml ---------------------------------------------------------------------- diff --git a/server/src/main/resources/kylinSecurity.xml b/server/src/main/resources/kylinSecurity.xml index ce068d7..856fb5e 100644 --- a/server/src/main/resources/kylinSecurity.xml +++ b/server/src/main/resources/kylinSecurity.xml @@ -38,9 +38,9 @@ <property name="permissionEvaluator" ref="permissionEvaluator"/> </bean> - <bean id="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator"> + <bean id="permissionEvaluator" class="org.apache.kylin.rest.security.KylinAclPermissionEvaluator"> <constructor-arg ref="aclService"/> - <property name="permissionFactory" ref="aclPermissionFactory"/> + <constructor-arg ref="aclPermissionFactory"/> </bean> <bean id="aclAuthorizationStrategy" http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/webapp/app/js/services/kylinProperties.js ---------------------------------------------------------------------- diff --git a/webapp/app/js/services/kylinProperties.js b/webapp/app/js/services/kylinProperties.js index 645ed2f..a2af4ed 100644 --- a/webapp/app/js/services/kylinProperties.js +++ b/webapp/app/js/services/kylinProperties.js @@ -107,5 +107,13 @@ KylinApp.service('kylinConfig', function (AdminService, $log) { } } + this.isExternalAclEnabled = function() { + var status = this.getProperty("kylin.server.external-acl-provider").trim(); + if (status == '') { + return false; + } + return true; + } + }); http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/webapp/app/partials/cubes/cube_detail.html ---------------------------------------------------------------------- diff --git a/webapp/app/partials/cubes/cube_detail.html b/webapp/app/partials/cubes/cube_detail.html index 0113c12..51f7dee 100755 --- a/webapp/app/partials/cubes/cube_detail.html +++ b/webapp/app/partials/cubes/cube_detail.html @@ -29,7 +29,8 @@ ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission(cube, 16) && !newAccess"> <a href="" ng-click="cube.visiblePage='json';">JSON(Cube)</a> </li> - <li class="{{cube.visiblePage=='access'? 'active':''}}"> + <li class="{{cube.visiblePage=='access'? 'active':''}}" + ng-if="!kylinConfig.isExternalAclEnabled()"> <a href="" ng-click="cube.visiblePage='access';listAccess(cube, 'CubeInstance');">Access</a> </li> <li class="{{cube.visiblePage=='notification'? 'active':''}}" http://git-wip-us.apache.org/repos/asf/kylin/blob/108e93c6/webapp/app/partials/projects/project_detail.html ---------------------------------------------------------------------- diff --git a/webapp/app/partials/projects/project_detail.html b/webapp/app/partials/projects/project_detail.html index 5cfc091..7112da6 100644 --- a/webapp/app/partials/projects/project_detail.html +++ b/webapp/app/partials/projects/project_detail.html @@ -21,7 +21,8 @@ <li class="{{project.visiblePage=='cubes'? 'active':''}}"> <a href="" ng-click="project.visiblePage='cubes';">Cubes</a> </li> - <li class="{{project.visiblePage=='access'? 'active':''}}"> + <li class="{{project.visiblePage=='access'? 'active':''}}" + ng-if="!kylinConfig.isExternalAclEnabled()"> <a href="" ng-click="project.visiblePage='access';listAccess(project, 'ProjectInstance');">Access</a> </li> <li class="{{project.visiblePage=='config'? 'active':''}}">