This is an automated email from the ASF dual-hosted git repository. billyliu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/kylin.git
commit 62bfc5f70e9553f0c95fe160dbf63f8d20ec3d52 Author: Li Yang <liy...@apache.org> AuthorDate: Sat Jan 27 22:03:50 2018 +0800 Refactor ACL, AclService, AccessService --- .../kylin/common/persistence/JsonSerializer.java | 11 +- .../localmeta/acl/legacy-test-domain-object | 31 +++ .../apache/kylin/rest/request/AccessRequest.java | 6 +- .../kylin/rest/security/springacl/AceImpl.java | 184 +++++++++++++ .../kylin/rest/security/springacl/AclRecord.java | 276 +++++++++++++++++++ .../springacl/LegacyAceInfo.java} | 26 +- .../rest/security/springacl/MutableAclRecord.java | 123 +++++++++ .../security/springacl/ObjectIdentityImpl.java | 134 +++++++++ .../{service => security/springacl}/SidInfo.java | 31 ++- .../apache/kylin/rest/service/AccessService.java | 263 +++++++----------- .../org/apache/kylin/rest/service/AclService.java | 303 +++++++-------------- .../kylin/rest/service/AclTableMigrationTool.java | 30 +- .../kylin/rest/service/DomainObjectInfo.java | 52 ---- .../rest/controller/AccessControllerTest.java | 10 +- .../kylin/rest/service/AccessServiceTest.java | 41 ++- .../apache/kylin/rest/service/AclServiceTest.java | 166 +++++++++++ 16 files changed, 1189 insertions(+), 498 deletions(-) diff --git a/core-common/src/main/java/org/apache/kylin/common/persistence/JsonSerializer.java b/core-common/src/main/java/org/apache/kylin/common/persistence/JsonSerializer.java index 8f0cc51..508b2ab 100644 --- a/core-common/src/main/java/org/apache/kylin/common/persistence/JsonSerializer.java +++ b/core-common/src/main/java/org/apache/kylin/common/persistence/JsonSerializer.java @@ -30,11 +30,17 @@ import org.apache.kylin.common.util.JsonUtil; public class JsonSerializer<T extends RootPersistentEntity> implements Serializer<T> { Class<T> clz; + boolean compact = false; public JsonSerializer(Class<T> clz) { this.clz = clz; } + public JsonSerializer(Class<T> clz, boolean compact) { + this.clz = clz; + this.compact = compact; + } + @Override public T deserialize(DataInputStream in) throws IOException { return JsonUtil.readValue(in, clz); @@ -42,6 +48,9 @@ public class JsonSerializer<T extends RootPersistentEntity> implements Serialize @Override public void serialize(T obj, DataOutputStream out) throws IOException { - JsonUtil.writeValueIndent(out, obj); + if (compact) + JsonUtil.writeValue(out, obj); + else + JsonUtil.writeValueIndent(out, obj); } } diff --git a/examples/test_case_data/localmeta/acl/legacy-test-domain-object b/examples/test_case_data/localmeta/acl/legacy-test-domain-object new file mode 100644 index 0000000..46cae53 --- /dev/null +++ b/examples/test_case_data/localmeta/acl/legacy-test-domain-object @@ -0,0 +1,31 @@ +{ + "domainObjectInfo" : { + "id" : "legacy-test-domain-object", + "type" : "org.apache.kylin.rest.service.AclServiceTest$MockAclEntity" + }, + "parentDomainObjectInfo" : null, + "ownerInfo" : { + "sid" : "ADMIN", + "principal" : true + }, + "entriesInheriting" : true, + "allAceInfo" : { + "MODELER" : { + "sidInfo" : { + "sid" : "MODELER", + "principal" : true + }, + "permissionMask" : 1 + }, + "ADMIN" : { + "sidInfo" : { + "sid" : "ADMIN", + "principal" : true + }, + "permissionMask" : 16 + } + }, + "uuid" : null, + "last_modified" : 1517017738360, + "version" : "2.3.0.20505" +} \ No newline at end of file diff --git a/server-base/src/main/java/org/apache/kylin/rest/request/AccessRequest.java b/server-base/src/main/java/org/apache/kylin/rest/request/AccessRequest.java index 0ff57b2..ba93fa7 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/request/AccessRequest.java +++ b/server-base/src/main/java/org/apache/kylin/rest/request/AccessRequest.java @@ -24,7 +24,7 @@ package org.apache.kylin.rest.request; */ public class AccessRequest { - private Long accessEntryId; + private int accessEntryId; private String permission; private String sid; private boolean principal; @@ -32,11 +32,11 @@ public class AccessRequest { public AccessRequest() { } - public Long getAccessEntryId() { + public int getAccessEntryId() { return accessEntryId; } - public void setAccessEntryId(Long accessEntryId) { + public void setAccessEntryId(int accessEntryId) { this.accessEntryId = accessEntryId; } diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AceImpl.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AceImpl.java new file mode 100644 index 0000000..8976a19 --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AceImpl.java @@ -0,0 +1,184 @@ +/* + * 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. +*/ + +/** + * Mimic org.springframework.security.acls.domain.ObjectIdentityImpl + * Make it Jackson friendly. + */ +package org.apache.kylin.rest.security.springacl; + +import java.io.Serializable; +import java.util.Comparator; + +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@SuppressWarnings("serial") +@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class AceImpl implements AccessControlEntry { + + public static final Comparator<AceImpl> SID_ORDER = new Comparator<AceImpl>() { + @Override + public int compare(AceImpl o1, AceImpl o2) { + if (o1.sidOfAuthority == null) { + return o2.sidOfAuthority == null ? o1.sidOfPrincipal.compareTo(o2.sidOfPrincipal) : 1; + } else { + return o2.sidOfAuthority == null ? -1 : o1.sidOfAuthority.compareTo(o2.sidOfAuthority); + } + } + }; + + // ~ Instance fields + // ================================================================================================ + + @JsonProperty("p") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String sidOfPrincipal; + @JsonProperty("a") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String sidOfAuthority; + @JsonProperty("m") + private int permissionMask; + + // non-persistent fields + private AclRecord acl; + private int indexInAcl; + private Sid sid; + private Permission perm; + + // ~ Constructors + // =================================================================================================== + + // for Jackson + public AceImpl() { + } + + public AceImpl(LegacyAceInfo legacy) { + this(legacy.getSidInfo(), legacy.getPermissionMask()); + } + + public AceImpl(Sid sid, Permission perm) { + this(new SidInfo(sid), perm == null ? 0 : perm.getMask()); + } + + public AceImpl(SidInfo sidInfo, int permMask) { + if (sidInfo.isPrincipal()) + sidOfPrincipal = sidInfo.getSid(); + else + sidOfAuthority = sidInfo.getSid(); + + permissionMask = permMask; + } + + void init(AclRecord acl, int index) { + this.acl = acl; + this.indexInAcl = index; + } + + // ~ Methods + // ======================================================================================================== + + @Override + public Acl getAcl() { + return acl; + } + + @Override + public Serializable getId() { + return indexInAcl; + } + + @Override + public Permission getPermission() { + if (perm == null) { + perm = acl.aclPermissionFactory.buildFromMask(permissionMask); + } + return perm; + } + + public int getPermissionMask() { + return permissionMask; + } + + void setPermission(Permission perm) { + this.permissionMask = perm.getMask(); + this.perm = null; + } + + @Override + public Sid getSid() { + if (sid == null) { + if (sidOfPrincipal != null) + sid = new PrincipalSid(sidOfPrincipal); + else if (sidOfAuthority != null) + sid = new GrantedAuthoritySid(sidOfAuthority); + else + throw new IllegalStateException(); + } + return sid; + } + + @Override + public boolean isGranting() { + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + permissionMask; + result = prime * result + ((sidOfAuthority == null) ? 0 : sidOfAuthority.hashCode()); + result = prime * result + ((sidOfPrincipal == null) ? 0 : sidOfPrincipal.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AceImpl other = (AceImpl) obj; + if (permissionMask != other.permissionMask) + return false; + if (sidOfAuthority == null) { + if (other.sidOfAuthority != null) + return false; + } else if (!sidOfAuthority.equals(other.sidOfAuthority)) + return false; + if (sidOfPrincipal == null) { + if (other.sidOfPrincipal != null) + return false; + } else if (!sidOfPrincipal.equals(other.sidOfPrincipal)) + return false; + return true; + } + +} diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AclRecord.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AclRecord.java new file mode 100644 index 0000000..3fff632 --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/AclRecord.java @@ -0,0 +1,276 @@ +/* + * 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.springacl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.kylin.common.persistence.RootPersistentEntity; +import org.springframework.security.acls.domain.PermissionFactory; +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.OwnershipAcl; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.PermissionGrantingStrategy; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.acls.model.UnloadedSidException; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + */ +@SuppressWarnings("serial") +@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class AclRecord extends RootPersistentEntity implements Acl, OwnershipAcl { + + // ~ Instance fields + // ================================================================================================ + + @JsonProperty("domainObjectInfo") + private ObjectIdentityImpl domainObjectInfo; + @JsonProperty("parentDomainObjectInfo") + private ObjectIdentityImpl parentDomainObjectInfo; + @JsonProperty("ownerInfo") + private SidInfo ownerInfo; + @JsonProperty("entriesInheriting") + private boolean entriesInheriting; + @JsonProperty("entries") + private List<AceImpl> entries; // the list is ordered by SID, so that grant/revoke by SID is fast + @JsonProperty("allAceInfo") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Map<String, LegacyAceInfo> legacyAceInfo; + + // non-persistent fields + PermissionFactory aclPermissionFactory; + private PermissionGrantingStrategy permissionGrantingStrategy; + private Acl parentAcl; + + // ~ Constructor / Getter / Setter + // ================================================================================================ + + // for Jackson + public AclRecord() { + } + + // new empty ACL + public AclRecord(ObjectIdentity oid, Sid owner) { + this.domainObjectInfo = new ObjectIdentityImpl(oid); + this.ownerInfo = new SidInfo(owner); + } + + public void init(Acl parentAcl, PermissionFactory aclPermissionFactory, + PermissionGrantingStrategy permissionGrantingStrategy) { + this.aclPermissionFactory = aclPermissionFactory; + this.permissionGrantingStrategy = permissionGrantingStrategy; + this.parentAcl = parentAcl; + + if (entries == null) + entries = new ArrayList<>(); + + // convert legacy ace + if (legacyAceInfo != null) { + for (LegacyAceInfo legacy : legacyAceInfo.values()) { + entries.add(new AceImpl(legacy)); + } + Collections.sort(entries, AceImpl.SID_ORDER); + legacyAceInfo = null; + } + + for (int i = 0; i < entries.size(); i++) { + entries.get(i).init(this, i); + } + } + + public SidInfo getOwnerInfo() { + return ownerInfo; + } + + public void setOwnerInfo(SidInfo ownerInfo) { + this.ownerInfo = ownerInfo; + } + + public boolean isEntriesInheriting() { + return entriesInheriting; + } + + public void setEntriesInheriting(boolean entriesInheriting) { + this.entriesInheriting = entriesInheriting; + } + + public ObjectIdentityImpl getDomainObjectInfo() { + return domainObjectInfo; + } + + public void setDomainObjectInfo(ObjectIdentityImpl domainObjectInfo) { + this.domainObjectInfo = domainObjectInfo; + } + + public ObjectIdentityImpl getParentDomainObjectInfo() { + return parentDomainObjectInfo; + } + + public void setParentDomainObjectInfo(ObjectIdentityImpl parentDomainObjectInfo) { + this.parentDomainObjectInfo = parentDomainObjectInfo; + } + + public Map<String, LegacyAceInfo> getAllAceInfo() { + return legacyAceInfo; + } + + public void setAllAceInfo(Map<String, LegacyAceInfo> allAceInfo) { + this.legacyAceInfo = allAceInfo; + } + + // ~ Methods + // ======================================================================================================== + + @Override + public ObjectIdentity getObjectIdentity() { + return domainObjectInfo; + } + + @Override + public Sid getOwner() { + return ownerInfo.getSidObj(); + } + + @Override + public void setOwner(Sid newOwner) { + ownerInfo = new SidInfo(newOwner); + } + + @Override + public Acl getParentAcl() { + return parentAcl; + } + + @Override + public void setParent(Acl newParent) { + AclRecord newP = newParent instanceof MutableAclRecord // + ? ((MutableAclRecord) newParent).getAclRecord() + : (AclRecord) newParent; + parentDomainObjectInfo = newP.domainObjectInfo; + parentAcl = newP; + } + + @Override + public List<AccessControlEntry> getEntries() { + return new ArrayList<AccessControlEntry>(entries); + } + + public AccessControlEntry getAccessControlEntryAt(int entryIndex) { + return entries.get(entryIndex); + } + + public Permission getPermission(Sid sid) { + synchronized (entries) { + int p = Collections.binarySearch(entries, new AceImpl(sid, null), AceImpl.SID_ORDER); + if (p >= 0) { + return entries.get(p).getPermission(); + } + return null; + } + } + + public void upsertAce(Permission permission, Sid sid) { + Assert.notNull(sid, "Sid required"); + + AceImpl ace = new AceImpl(sid, permission); + synchronized (entries) { + int p = Collections.binarySearch(entries, ace, AceImpl.SID_ORDER); + if (p >= 0) { + if (permission == null) // null permission means delete + entries.remove(p); + else + entries.get(p).setPermission(permission); + } else { + if (permission != null) { // if not delete + ace.init(this, entries.size()); + entries.add(-p - 1, ace); + } + } + } + } + + public void deleteAce(Sid sid) { + upsertAce(null, sid); + } + + @Override + public void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) + throws NotFoundException { + Assert.state(granting, "Granting must be true"); + + // entries are strictly ordered, given index is ignored + upsertAce(permission, sid); + } + + @Override + public void updateAce(int aceIndex, Permission permission) throws NotFoundException { + verifyAceIndexExists(aceIndex); + + synchronized (entries) { + AceImpl ace = entries.get(aceIndex); + ace.setPermission(permission); + } + } + + @Override + public void deleteAce(int aceIndex) throws NotFoundException { + verifyAceIndexExists(aceIndex); + + synchronized (entries) { + entries.remove(aceIndex); + } + } + + private void verifyAceIndexExists(int aceIndex) { + if (aceIndex < 0) { + throw new NotFoundException("aceIndex must be greater than or equal to zero"); + } + if (aceIndex >= this.entries.size()) { + throw new NotFoundException("aceIndex must refer to an index of the AccessControlEntry list. " + + "List size is " + entries.size() + ", index was " + aceIndex); + } + } + + @Override + public boolean isGranted(List<Permission> permission, List<Sid> sids, boolean administrativeMode) + throws NotFoundException, UnloadedSidException { + Assert.notEmpty(sids, "SIDs required"); + Assert.notEmpty(permission, "Permissions required"); + + return permissionGrantingStrategy.isGranted(this, permission, sids, administrativeMode); + } + + @Override + public boolean isSidLoaded(List<Sid> sids) { + // don't support sid filtering yet + return true; + } + +} diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/AceInfo.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/LegacyAceInfo.java similarity index 68% rename from server-base/src/main/java/org/apache/kylin/rest/service/AceInfo.java rename to server-base/src/main/java/org/apache/kylin/rest/security/springacl/LegacyAceInfo.java index 0be1019..478cae3 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/AceInfo.java +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/LegacyAceInfo.java @@ -16,21 +16,28 @@ * limitations under the License. */ -package org.apache.kylin.rest.service; +package org.apache.kylin.rest.security.springacl; import org.springframework.security.acls.model.AccessControlEntry; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonProperty; + /** - * Created by xiefan on 17-5-2. */ -class AceInfo { +@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class LegacyAceInfo { + + @JsonProperty("sidInfo") private SidInfo sidInfo; + @JsonProperty("permissionMask") private int permissionMask; - public AceInfo() { + public LegacyAceInfo() { } - public AceInfo(AccessControlEntry ace) { + public LegacyAceInfo(AccessControlEntry ace) { super(); this.sidInfo = new SidInfo(ace.getSid()); this.permissionMask = ace.getPermission().getMask(); @@ -40,16 +47,7 @@ class AceInfo { return sidInfo; } - public void setSidInfo(SidInfo sidInfo) { - this.sidInfo = sidInfo; - } - public int getPermissionMask() { return permissionMask; } - - public void setPermissionMask(int permissionMask) { - this.permissionMask = permissionMask; - } - } diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/springacl/MutableAclRecord.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/MutableAclRecord.java new file mode 100644 index 0000000..9ad099c --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/MutableAclRecord.java @@ -0,0 +1,123 @@ +/* + * 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.springacl; + +import java.io.Serializable; +import java.util.List; + +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.Acl; +import org.springframework.security.acls.model.MutableAcl; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.OwnershipAcl; +import org.springframework.security.acls.model.Permission; +import org.springframework.security.acls.model.Sid; +import org.springframework.security.acls.model.UnloadedSidException; + +/** + * A thin wrapper around AclRecord to work around the conflict between MutableAcl.getId() and AclEntity.getId() + * having different return types. + */ +@SuppressWarnings("serial") +public class MutableAclRecord implements Acl, MutableAcl, OwnershipAcl { + + private final AclRecord acl; + + public MutableAclRecord(AclRecord acl) { + this.acl = acl; + } + + public AclRecord getAclRecord() { + return acl; + } + + @Override + public Serializable getId() { + return acl.getDomainObjectInfo().getIdentifier(); + } + + @Override + public ObjectIdentity getObjectIdentity() { + return acl.getObjectIdentity(); + } + + @Override + public Sid getOwner() { + return acl.getOwner(); + } + + @Override + public Acl getParentAcl() { + return acl.getParentAcl(); + } + + @Override + public boolean isEntriesInheriting() { + return acl.isEntriesInheriting(); + } + + @Override + public void setOwner(Sid newOwner) { + acl.setOwner(newOwner); + } + + @Override + public void setEntriesInheriting(boolean entriesInheriting) { + acl.setEntriesInheriting(entriesInheriting); + } + + @Override + public void setParent(Acl newParent) { + acl.setParent(newParent); + } + + @Override + public List<AccessControlEntry> getEntries() { + return acl.getEntries(); + } + + @Override + public void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) + throws NotFoundException { + acl.insertAce(atIndexLocation, permission, sid, granting); + } + + @Override + public void updateAce(int aceIndex, Permission permission) throws NotFoundException { + acl.updateAce(aceIndex, permission); + } + + @Override + public void deleteAce(int aceIndex) throws NotFoundException { + acl.deleteAce(aceIndex); + } + + @Override + public boolean isGranted(List<Permission> permission, List<Sid> sids, boolean administrativeMode) + throws NotFoundException, UnloadedSidException { + return acl.isGranted(permission, sids, administrativeMode); + } + + @Override + public boolean isSidLoaded(List<Sid> sids) { + return acl.isSidLoaded(sids); + } + +} diff --git a/server-base/src/main/java/org/apache/kylin/rest/security/springacl/ObjectIdentityImpl.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/ObjectIdentityImpl.java new file mode 100644 index 0000000..a1dc9c6 --- /dev/null +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/ObjectIdentityImpl.java @@ -0,0 +1,134 @@ +/* + * 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.springacl; + +import java.io.Serializable; + +import org.apache.kylin.common.persistence.AclEntity; +import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Mimic org.springframework.security.acls.domain.ObjectIdentityImpl + * Make it Jackson friendly. + */ +@SuppressWarnings("serial") +@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class ObjectIdentityImpl implements ObjectIdentity { + // ~ Instance fields + // ================================================================================================ + + @JsonProperty("type") + private String type; + @JsonProperty("id") + private String identifier; + + // ~ Constructors + // =================================================================================================== + + // for Jackson + public ObjectIdentityImpl() { + } + + public ObjectIdentityImpl(ObjectIdentity oid) { + this(oid.getType(), String.valueOf(oid.getIdentifier())); + } + + public ObjectIdentityImpl(String type, String identifier) { + Assert.hasText(type, "Type required"); + Assert.notNull(identifier, "identifier required"); + + this.identifier = identifier; + this.type = type; + } + + public ObjectIdentityImpl(AclEntity ae) { + Assert.notNull(ae, "ACL entity required"); + this.type = ae.getClass().getName(); + this.identifier = ae.getId(); + } + + // ~ Methods + // ======================================================================================================== + + /** + * Important so caching operates properly. + * <p> + * Considers an object of the same class equal if it has the same + * <code>classname</code> and <code>id</code> properties. + * <p> + * Numeric identities (Integer and Long values) are considered equal if they are + * numerically equal. Other serializable types are evaluated using a simple equality. + * + * @param arg0 object to compare + * + * @return <code>true</code> if the presented object matches this object + */ + public boolean equals(Object arg0) { + if (arg0 == null || !(arg0 instanceof ObjectIdentity)) { + return false; + } + + ObjectIdentity other = (ObjectIdentity) arg0; + + if (!identifier.equals(other.getIdentifier())) { + return false; + } + + return type.equals(other.getType()); + } + + public Serializable getIdentifier() { + return identifier; + } + + public String getId() { + return identifier; + } + + public String getType() { + return type; + } + + /** + * Important so caching operates properly. + * + * @return the hash + */ + public int hashCode() { + int code = 31; + code ^= this.type.hashCode(); + code ^= this.identifier.hashCode(); + + return code; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getName()).append("["); + sb.append("Type: ").append(this.type); + sb.append("; Identifier: ").append(this.identifier).append("]"); + + return sb.toString(); + } +} diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/SidInfo.java b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/SidInfo.java similarity index 67% rename from server-base/src/main/java/org/apache/kylin/rest/service/SidInfo.java rename to server-base/src/main/java/org/apache/kylin/rest/security/springacl/SidInfo.java index 0a89449..af39341 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/SidInfo.java +++ b/server-base/src/main/java/org/apache/kylin/rest/security/springacl/SidInfo.java @@ -16,19 +16,29 @@ * limitations under the License. */ -package org.apache.kylin.rest.service; +package org.apache.kylin.rest.security.springacl; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.Sid; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonProperty; + /** - * Created by xiefan on 17-5-2. */ -class SidInfo { +@JsonAutoDetect(fieldVisibility = Visibility.NONE, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class SidInfo { + + @JsonProperty("sid") private String sid; + @JsonProperty("principal") private boolean isPrincipal; + private transient Sid sidObj; + + // for Jackson public SidInfo() { } @@ -39,23 +49,22 @@ class SidInfo { } else if (sid instanceof GrantedAuthoritySid) { this.sid = ((GrantedAuthoritySid) sid).getGrantedAuthority(); this.isPrincipal = false; - } + } else + throw new IllegalStateException(); } public String getSid() { return sid; } - public void setSid(String sid) { - this.sid = sid; - } - public boolean isPrincipal() { return isPrincipal; } - public void setPrincipal(boolean isPrincipal) { - this.isPrincipal = isPrincipal; + public Sid getSidObj() { + if (sidObj == null) { + sidObj = isPrincipal ? new PrincipalSid(sid) : new GrantedAuthoritySid(sid); + } + return sidObj; } - } diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/AccessService.java b/server-base/src/main/java/org/apache/kylin/rest/service/AccessService.java index 8498ecd..74a87c8 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/AccessService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/AccessService.java @@ -40,17 +40,20 @@ import org.apache.kylin.rest.msg.MsgPicker; import org.apache.kylin.rest.response.AccessEntryResponse; import org.apache.kylin.rest.security.AclEntityFactory; import org.apache.kylin.rest.security.AclEntityType; +import org.apache.kylin.rest.security.springacl.AclRecord; +import org.apache.kylin.rest.security.springacl.MutableAclRecord; +import org.apache.kylin.rest.security.springacl.ObjectIdentityImpl; +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.acls.domain.BasePermission; import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AlreadyExistsException; -import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Permission; @@ -64,11 +67,11 @@ import org.springframework.transaction.annotation.Transactional; import com.google.common.base.Preconditions; /** - * @author xduo - * */ @Component("accessService") public class AccessService { + @SuppressWarnings("unused") + private static final Logger logger = LoggerFactory.getLogger(AccessService.class); @Autowired @Qualifier("aclService") @@ -77,15 +80,15 @@ public class AccessService { // ~ Methods to manage acl life circle of domain objects ~ @Transactional - public Acl init(AclEntity ae, Permission initPermission) { - Acl acl = null; - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); + public MutableAclRecord init(AclEntity ae, Permission initPermission) { + MutableAclRecord acl = null; + ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae); try { // Create acl record for secured domain object. - acl = aclService.createAcl(objectIdentity); + acl = (MutableAclRecord) aclService.createAcl(objectIdentity); } catch (AlreadyExistsException e) { - acl = (MutableAcl) aclService.readAclById(objectIdentity); + acl = aclService.readAcl(objectIdentity); } if (null != initPermission) { @@ -99,7 +102,7 @@ public class AccessService { @Transactional @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#ae, 'ADMINISTRATION')") - public Acl grant(AclEntity ae, Permission permission, Sid sid) { + public MutableAclRecord grant(AclEntity ae, Permission permission, Sid sid) { Message msg = MsgPicker.getMsg(); if (ae == null) @@ -109,150 +112,83 @@ public class AccessService { if (sid == null) throw new BadRequestException(msg.getSID_REQUIRED()); - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); - MutableAcl acl = null; - + MutableAclRecord acl = null; try { - acl = (MutableAcl) aclService.readAclById(objectIdentity); + acl = aclService.readAcl(new ObjectIdentityImpl(ae)); } catch (NotFoundException e) { - acl = (MutableAcl) init(ae, null); - } - - int indexOfAce = -1; - for (int i = 0; i < acl.getEntries().size(); i++) { - AccessControlEntry ace = acl.getEntries().get(i); - - if (ace.getSid().equals(sid)) { - indexOfAce = i; - } + acl = init(ae, null); } - if (indexOfAce != -1) { - secureOwner(acl, indexOfAce); - acl.updateAce(indexOfAce, permission); - } else { - acl.insertAce(acl.getEntries().size(), permission, sid, true); - } - - acl = aclService.updateAcl(acl); + secureOwner(acl, sid); - return acl; + return aclService.upsertAce(acl, sid, permission); } @Transactional @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#ae, 'ADMINISTRATION')") - public Acl update(AclEntity ae, Long accessEntryId, Permission newPermission) { + public MutableAclRecord update(AclEntity ae, int accessEntryIndex, Permission newPermission) { Message msg = MsgPicker.getMsg(); if (ae == null) throw new BadRequestException(msg.getACL_DOMAIN_NOT_FOUND()); - if (accessEntryId == null) - throw new BadRequestException(msg.getACE_ID_REQUIRED()); if (newPermission == null) throw new BadRequestException(msg.getACL_PERMISSION_REQUIRED()); - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); - MutableAcl acl = (MutableAcl) aclService.readAclById(objectIdentity); - - int indexOfAce = -1; - for (int i = 0; i < acl.getEntries().size(); i++) { - AccessControlEntry ace = acl.getEntries().get(i); - if (ace.getId().equals(accessEntryId)) { - indexOfAce = i; - break; - } - } - - if (indexOfAce != -1) { - secureOwner(acl, indexOfAce); + MutableAclRecord acl = aclService.readAcl(new ObjectIdentityImpl(ae)); + Sid sid = acl.getAclRecord().getAccessControlEntryAt(accessEntryIndex).getSid(); - try { - acl.updateAce(indexOfAce, newPermission); - acl = aclService.updateAcl(acl); - } catch (NotFoundException e) { - //do nothing? - } - } + secureOwner(acl, sid); - return acl; + return aclService.upsertAce(acl, sid, newPermission); } @Transactional @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#ae, 'ADMINISTRATION')") - public Acl revoke(AclEntity ae, Long accessEntryId) { + public MutableAclRecord revoke(AclEntity ae, int accessEntryIndex) { Message msg = MsgPicker.getMsg(); if (ae == null) throw new BadRequestException(msg.getACL_DOMAIN_NOT_FOUND()); - if (accessEntryId == null) - throw new BadRequestException(msg.getACE_ID_REQUIRED()); - MutableAcl acl = (MutableAcl) getAcl(ae); - int indexOfAce = getIndexOfAce(accessEntryId, acl); - acl = deleteAndUpdate(acl, indexOfAce); - return acl; - } + MutableAclRecord acl = aclService.readAcl(new ObjectIdentityImpl(ae)); + Sid sid = acl.getAclRecord().getAccessControlEntryAt(accessEntryIndex).getSid(); - private int getIndexOfAce(Long accessEntryId, MutableAcl acl) { - int indexOfAce = -1; - List<AccessControlEntry> aces = acl.getEntries(); - for (int i = 0; i < aces.size(); i++) { - if (aces.get(i).getId().equals(accessEntryId)) { - indexOfAce = i; - break; - } - } - return indexOfAce; - } + secureOwner(acl, sid); - private MutableAcl deleteAndUpdate(MutableAcl acl, int indexOfAce) { - if (indexOfAce != -1) { - secureOwner(acl, indexOfAce); - try { - acl.deleteAce(indexOfAce); - acl = aclService.updateAcl(acl); - } catch (NotFoundException e) { - throw new RuntimeException("Revoke acl fail." + e.getMessage()); - } - } - return acl; + return aclService.upsertAce(acl, sid, null); } - @Deprecated + /** + * The method is not used at the moment + */ @Transactional public void inherit(AclEntity ae, AclEntity parentAe) { Message msg = MsgPicker.getMsg(); - if (ae == null) { + if (ae == null) throw new BadRequestException(msg.getACL_DOMAIN_NOT_FOUND()); - } - if (parentAe == null) { + if (parentAe == null) throw new BadRequestException(msg.getPARENT_ACL_NOT_FOUND()); - } - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); - MutableAcl acl = null; + MutableAclRecord acl = null; try { - acl = (MutableAcl) aclService.readAclById(objectIdentity); + acl = aclService.readAcl(new ObjectIdentityImpl(ae)); } catch (NotFoundException e) { - acl = (MutableAcl) init(ae, null); + acl = init(ae, null); } - ObjectIdentity parentObjectIdentity = new ObjectIdentityImpl(parentAe.getClass(), parentAe.getId()); - MutableAcl parentAcl = null; + MutableAclRecord parentAcl = null; try { - parentAcl = (MutableAcl) aclService.readAclById(parentObjectIdentity); + parentAcl = aclService.readAcl(new ObjectIdentityImpl(parentAe)); } catch (NotFoundException e) { - parentAcl = (MutableAcl) init(parentAe, null); + parentAcl = init(parentAe, null); } if (null == acl || null == parentAcl) { return; } - acl.setEntriesInheriting(true); - acl.setParent(parentAcl); - aclService.updateAcl(acl); + aclService.inherit(acl, parentAcl); } @Transactional @@ -268,7 +204,7 @@ public class AccessService { if (ae.getId() == null) return; - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); + ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae); try { aclService.deleteAcl(objectIdentity, deleteChildren); @@ -287,20 +223,17 @@ public class AccessService { return AclEntityFactory.createAclEntity(entityType, uuid); } - @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + - " or hasPermission(#ae, 'ADMINISTRATION')" + - " or hasPermission(#ae, 'MANAGEMENT')" + - " or hasPermission(#ae, 'OPERATION')" + - " or hasPermission(#ae, 'READ')") - public Acl getAcl(AclEntity ae) { + @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#ae, 'ADMINISTRATION')" + + " or hasPermission(#ae, 'MANAGEMENT')" + " or hasPermission(#ae, 'OPERATION')" + + " or hasPermission(#ae, 'READ')") + public MutableAclRecord getAcl(AclEntity ae) { if (null == ae) { return null; } - ObjectIdentity objectIdentity = new ObjectIdentityImpl(ae.getClass(), ae.getId()); - Acl acl = null; + MutableAclRecord acl = null; try { - acl = (MutableAcl) aclService.readAclById(objectIdentity); + acl = aclService.readAcl(new ObjectIdentityImpl(ae)); } catch (NotFoundException e) { //do nothing? } @@ -316,7 +249,8 @@ public class AccessService { } } - public List<AccessEntryResponse> generateAceResponsesByFuzzMatching(Acl acl, String nameSeg, boolean isCaseSensitive) { + public List<AccessEntryResponse> generateAceResponsesByFuzzMatching(Acl acl, String nameSeg, + boolean isCaseSensitive) { if (null == acl) { return Collections.emptyList(); } @@ -357,10 +291,10 @@ public class AccessService { List<String> result = new ArrayList<>(); for (AccessControlEntry ace : acl.getEntries()) { String name = null; - if (type.equalsIgnoreCase("user") && ace.getSid() instanceof PrincipalSid) { + if (type.equalsIgnoreCase(MetadataConstants.TYPE_USER) && ace.getSid() instanceof PrincipalSid) { name = ((PrincipalSid) ace.getSid()).getPrincipal(); } - if (type.equalsIgnoreCase("group") && ace.getSid() instanceof GrantedAuthoritySid) { + if (type.equalsIgnoreCase(MetadataConstants.TYPE_GROUP) && ace.getSid() instanceof GrantedAuthoritySid) { name = ((GrantedAuthoritySid) ace.getSid()).getGrantedAuthority(); } if (!StringUtils.isBlank(name)) { @@ -376,14 +310,16 @@ public class AccessService { * @param acl * @param indexOfAce */ - private void secureOwner(MutableAcl acl, int indexOfAce) { + private void secureOwner(MutableAclRecord acl, Sid sid) { Message msg = MsgPicker.getMsg(); - // Can't revoke admin permission from domain object owner - if (acl.getOwner().equals(acl.getEntries().get(indexOfAce).getSid()) - && BasePermission.ADMINISTRATION.equals(acl.getEntries().get(indexOfAce).getPermission())) { + AclRecord record = acl.getAclRecord(); + if (record.getOwner().equals(sid) == false) + return; + + // prevent changing owner's admin permission + if (BasePermission.ADMINISTRATION.equals(record.getPermission(sid))) throw new ForbiddenException(msg.getREVOKE_ADMIN_PERMISSION()); - } } public Object generateAllAceResponses(Acl acl) { @@ -400,32 +336,35 @@ public class AccessService { } public void revokeProjectPermission(String name, String type) { - //revoke user's project permission - List<ProjectInstance> projectInstances = ProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).listAllProjects(); + Sid sid = null; + if (type.equalsIgnoreCase(MetadataConstants.TYPE_USER)) { + sid = new PrincipalSid(name); + } else if (type.equalsIgnoreCase(MetadataConstants.TYPE_GROUP)) { + sid = new GrantedAuthoritySid(name); + } else { + return; + } + + // revoke user's project permission + List<ProjectInstance> projectInstances = ProjectManager.getInstance(KylinConfig.getInstanceFromEnv()) + .listAllProjects(); for (ProjectInstance pi : projectInstances) { // after KYLIN-2760, only project ACL will work, so entity type is always ProjectInstance. AclEntity ae = getAclEntity("ProjectInstance", pi.getUuid()); - MutableAcl acl = (MutableAcl) getAcl(ae); + MutableAclRecord acl = getAcl(ae); if (acl == null) { return; } - List<AccessControlEntry> aces = acl.getEntries(); - if (aces == null) { - return; - } - int indexOfAce = -1; - for (int i = 0; i < aces.size(); i++) { - if (needRevoke(aces.get(i).getSid(), name, type)) { - indexOfAce = i; - break; - } + Permission perm = acl.getAclRecord().getPermission(sid); + if (perm != null) { + secureOwner(acl, sid); + aclService.upsertAce(acl, sid, null); } - deleteAndUpdate(acl, indexOfAce); } } - + public String getUserPermissionInPrj(String project) { String grantedPermission = ""; List<String> groups = getGroupsFromCurrentUser(); @@ -435,30 +374,31 @@ public class AccessService { // {user/group:permission} Map<String, Integer> projectPermissions = getProjectPermission(project); - Integer greaterPermission = projectPermissions.get(SecurityContextHolder.getContext().getAuthentication().getName()); + Integer greaterPermission = projectPermissions + .get(SecurityContextHolder.getContext().getAuthentication().getName()); for (String group : groups) { Integer groupPerm = projectPermissions.get(group); greaterPermission = Preconditions.checkNotNull(getGreaterPerm(groupPerm, greaterPermission)); } switch (greaterPermission) { - case 16: - grantedPermission = "ADMINISTRATION"; - break; - case 32: - grantedPermission = "MANAGEMENT"; - break; - case 64: - grantedPermission = "OPERATION"; - break; - case 1: - grantedPermission = "READ"; - break; - case 0: - grantedPermission = "EMPTY"; - break; - default: - throw new RuntimeException("invalid permission state:" + greaterPermission); + case 16: + grantedPermission = "ADMINISTRATION"; + break; + case 32: + grantedPermission = "MANAGEMENT"; + break; + case 64: + grantedPermission = "OPERATION"; + break; + case 1: + grantedPermission = "READ"; + break; + case 0: + grantedPermission = "EMPTY"; + break; + default: + throw new RuntimeException("invalid permission state:" + greaterPermission); } return grantedPermission; } @@ -488,7 +428,8 @@ public class AccessService { private List<String> getGroupsFromCurrentUser() { List<String> groups = new ArrayList<>(); - Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); + Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication() + .getAuthorities(); for (GrantedAuthority auth : authorities) { groups.add(auth.getAuthority()); @@ -522,14 +463,4 @@ public class AccessService { } return null; } - - private boolean needRevoke(Sid sid, String name, String type) { - if (type.equals(MetadataConstants.TYPE_USER) && sid instanceof PrincipalSid) { - return ((PrincipalSid) sid).getPrincipal().equals(name); - } else if (type.equals(MetadataConstants.TYPE_GROUP) && sid instanceof GrantedAuthoritySid) { - return ((GrantedAuthoritySid) sid).getGrantedAuthority().equals(name); - } else { - return false; - } - } } diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/AclService.java b/server-base/src/main/java/org/apache/kylin/rest/service/AclService.java index 1eab0e5..73a6fb2 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/AclService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/AclService.java @@ -19,7 +19,6 @@ package org.apache.kylin.rest.service; import java.io.IOException; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -29,25 +28,19 @@ import java.util.Map; 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.RootPersistentEntity; import org.apache.kylin.common.persistence.Serializer; import org.apache.kylin.rest.exception.BadRequestException; import org.apache.kylin.rest.exception.InternalErrorException; import org.apache.kylin.rest.msg.Message; import org.apache.kylin.rest.msg.MsgPicker; +import org.apache.kylin.rest.security.springacl.AclRecord; +import org.apache.kylin.rest.security.springacl.MutableAclRecord; +import org.apache.kylin.rest.security.springacl.ObjectIdentityImpl; 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.acls.domain.AccessControlEntryImpl; -import org.springframework.security.acls.domain.AclAuthorizationStrategy; -import org.springframework.security.acls.domain.AclImpl; -import org.springframework.security.acls.domain.AuditLogger; -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PermissionFactory; import org.springframework.security.acls.domain.PrincipalSid; -import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.ChildrenExistException; @@ -55,29 +48,21 @@ import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; +import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.util.FieldUtils; import org.springframework.stereotype.Component; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; - @Component("aclService") public class AclService implements MutableAclService { - private static final Logger logger = LoggerFactory.getLogger(AclService.class); - private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); - - private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl"); - public static final String DIR_PREFIX = "/acl/"; + public static final Serializer<AclRecord> SERIALIZER = new JsonSerializer<>(AclRecord.class, true); - public static final Serializer<AclRecord> SERIALIZER = new JsonSerializer<>(AclRecord.class); + // ============================================================================ @Autowired protected PermissionGrantingStrategy permissionGrantingStrategy; @@ -85,21 +70,15 @@ public class AclService implements MutableAclService { @Autowired protected PermissionFactory aclPermissionFactory; - @Autowired - protected AclAuthorizationStrategy aclAuthorizationStrategy; + // @Autowired + // protected AclAuthorizationStrategy aclAuthorizationStrategy; - @Autowired - protected AuditLogger auditLogger; + // @Autowired + // protected AuditLogger auditLogger; protected ResourceStore aclStore; - @Autowired - @Qualifier("userService") - protected UserService userService; - public AclService() throws IOException { - fieldAces.setAccessible(true); - fieldAcl.setAccessible(true); aclStore = ResourceStore.getStore(KylinConfig.getInstanceFromEnv()); } @@ -107,13 +86,12 @@ public class AclService implements MutableAclService { public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) { List<ObjectIdentity> oids = new ArrayList<ObjectIdentity>(); try { - List<AclRecord> allAclRecords = aclStore.getAllResources(String.valueOf(DIR_PREFIX), AclRecord.class, - SERIALIZER); + List<AclRecord> allAclRecords = aclStore.getAllResources(DIR_PREFIX, AclRecord.class, SERIALIZER); for (AclRecord record : allAclRecords) { - DomainObjectInfo parent = record.getParentDomainObjectInfo(); - if (parent != null && parent.getId().equals(String.valueOf(parentIdentity.getIdentifier()))) { - DomainObjectInfo child = record.getDomainObjectInfo(); - oids.add(new ObjectIdentityImpl(child.getType(), child.getId())); + ObjectIdentityImpl parent = record.getParentDomainObjectInfo(); + if (parent != null && parent.equals(parentIdentity)) { + ObjectIdentityImpl child = record.getDomainObjectInfo(); + oids.add(child); } } return oids; @@ -122,6 +100,10 @@ public class AclService implements MutableAclService { } } + public MutableAclRecord readAcl(ObjectIdentity oid) throws NotFoundException { + return (MutableAclRecord) readAclById(oid); + } + @Override public Acl readAclById(ObjectIdentity object) throws NotFoundException { Map<ObjectIdentity, Acl> aclsMap = readAclsById(Arrays.asList(object), null); @@ -145,31 +127,22 @@ public class AclService implements MutableAclService { @Override public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> oids, List<Sid> sids) throws NotFoundException { - Message msg = MsgPicker.getMsg(); - Map<ObjectIdentity, Acl> aclMaps = new HashMap<ObjectIdentity, Acl>(); + Map<ObjectIdentity, Acl> aclMaps = new HashMap<>(); try { for (ObjectIdentity oid : oids) { - AclRecord record = aclStore.getResource(getQueryKeyById(String.valueOf(oid.getIdentifier())), - AclRecord.class, SERIALIZER); - if (record != null && record.getOwnerInfo() != null) { - SidInfo owner = record.getOwnerInfo(); - Sid ownerSid = owner.isPrincipal() ? new PrincipalSid(owner.getSid()) : new GrantedAuthoritySid(owner.getSid()); - boolean entriesInheriting = record.isEntriesInheriting(); - - Acl parentAcl = null; - DomainObjectInfo parent = record.getParentDomainObjectInfo(); - if (parent != null) { - ObjectIdentity parentObject = new ObjectIdentityImpl(parent.getType(), parent.getId()); - parentAcl = readAclById(parentObject, null); - } - - AclImpl acl = new AclImpl(oid, oid.getIdentifier(), aclAuthorizationStrategy, permissionGrantingStrategy, parentAcl, null, entriesInheriting, ownerSid); - genAces(sids, record, acl); - - aclMaps.put(oid, acl); - } else { + AclRecord record = aclStore.getResource(resourceKey(oid), AclRecord.class, SERIALIZER); + if (record == null) { + Message msg = MsgPicker.getMsg(); throw new NotFoundException(String.format(msg.getACL_INFO_NOT_FOUND(), oid)); } + + Acl parentAcl = null; + if (record.isEntriesInheriting() && record.getParentDomainObjectInfo() != null) + parentAcl = readAclById(record.getParentDomainObjectInfo()); + + record.init(parentAcl, aclPermissionFactory, permissionGrantingStrategy); + + aclMaps.put(oid, new MutableAclRecord(record)); } return aclMaps; } catch (IOException e) { @@ -179,22 +152,16 @@ public class AclService implements MutableAclService { @Override public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { - Acl acl = null; - - try { - acl = readAclById(objectIdentity); - } catch (NotFoundException e) { - //do nothing? - } - if (null != acl) { - throw new AlreadyExistsException("ACL of " + objectIdentity + " exists!"); - } - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - PrincipalSid sid = new PrincipalSid(auth); try { - AclRecord record = new AclRecord(new DomainObjectInfo(objectIdentity), null, new SidInfo(sid), true, null); - aclStore.putResource(getQueryKeyById(String.valueOf(objectIdentity.getIdentifier())), record, 0, - SERIALIZER); + if (aclStore.exists(resourceKey(objectIdentity))) { + throw new AlreadyExistsException("ACL of " + objectIdentity + " exists!"); + } + + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + PrincipalSid owner = new PrincipalSid(auth); + AclRecord record = new AclRecord(objectIdentity, owner); + record.init(null, aclPermissionFactory, permissionGrantingStrategy); + aclStore.putResource(resourceKey(objectIdentity), record, 0, SERIALIZER); logger.debug("ACL of " + objectIdentity + " created successfully."); } catch (IOException e) { throw new InternalErrorException(e); @@ -204,178 +171,92 @@ public class AclService implements MutableAclService { @Override public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { - Message msg = MsgPicker.getMsg(); try { List<ObjectIdentity> children = findChildren(objectIdentity); if (!deleteChildren && children.size() > 0) { + Message msg = MsgPicker.getMsg(); throw new BadRequestException(String.format(msg.getIDENTITY_EXIST_CHILDREN(), objectIdentity)); } for (ObjectIdentity oid : children) { deleteAcl(oid, deleteChildren); } - aclStore.deleteResource(getQueryKeyById(String.valueOf(objectIdentity.getIdentifier()))); + aclStore.deleteResource(resourceKey(String.valueOf(objectIdentity.getIdentifier()))); logger.debug("ACL of " + objectIdentity + " deleted successfully."); } catch (IOException e) { throw new InternalErrorException(e); } } + // Try use the updateAclWithRetry() method family whenever possible @Override public MutableAcl updateAcl(MutableAcl mutableAcl) throws NotFoundException { - Message msg = MsgPicker.getMsg(); try { - readAclById(mutableAcl.getObjectIdentity()); - } catch (NotFoundException e) { - throw e; - } - - try { - String id = getQueryKeyById(String.valueOf(mutableAcl.getObjectIdentity().getIdentifier())); - AclRecord record = aclStore.getResource(id, AclRecord.class, SERIALIZER); - if (mutableAcl.getParentAcl() != null) { - record.setParentDomainObjectInfo(new DomainObjectInfo(mutableAcl.getParentAcl().getObjectIdentity())); - } - - if (record.getAllAceInfo() == null) { - record.setAllAceInfo(new HashMap<String, AceInfo>()); - } - Map<String, AceInfo> allAceInfo = record.getAllAceInfo(); - allAceInfo.clear(); - for (AccessControlEntry ace : mutableAcl.getEntries()) { - if (ace.getSid() instanceof PrincipalSid) { - PrincipalSid psid = (PrincipalSid) ace.getSid(); - String userName = psid.getPrincipal(); - if (!userService.userExists(userName)) - logger.error("Grant project access error," + String.format(msg.getUSER_NOT_EXIST(), userName)); - } - AceInfo aceInfo = new AceInfo(ace); - allAceInfo.put(String.valueOf(aceInfo.getSidInfo().getSid()), aceInfo); - } - aclStore.putResourceWithoutCheck(id, record, System.currentTimeMillis(), SERIALIZER); + AclRecord record = ((MutableAclRecord) mutableAcl).getAclRecord(); + String resPath = resourceKey(mutableAcl.getObjectIdentity()); + aclStore.putResource(resPath, record, System.currentTimeMillis(), SERIALIZER); logger.debug("ACL of " + mutableAcl.getObjectIdentity() + " updated successfully."); } catch (IOException e) { throw new InternalErrorException(e); } - return (MutableAcl) readAclById(mutableAcl.getObjectIdentity()); + return mutableAcl; } - protected void genAces(List<Sid> sids, AclRecord record, AclImpl acl) throws JsonParseException, JsonMappingException, IOException { - List<AceInfo> aceInfos = new ArrayList<AceInfo>(); - Map<String, AceInfo> allAceInfos = record.getAllAceInfo(); - if (allAceInfos != null) { - if (sids != null) { - // Just return aces in sids - for (Sid sid : sids) { - String sidName = null; - if (sid instanceof PrincipalSid) { - sidName = ((PrincipalSid) sid).getPrincipal(); - } else if (sid instanceof GrantedAuthoritySid) { - sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); - } - AceInfo aceInfo = allAceInfos.get(sidName); - if (aceInfo != null) { - aceInfos.add(aceInfo); - } - } - } else { - aceInfos.addAll(allAceInfos.values()); - } - } - - List<AccessControlEntry> newAces = new ArrayList<AccessControlEntry>(); - for (int i = 0; i < aceInfos.size(); i++) { - AceInfo aceInfo = aceInfos.get(i); - - if (null != aceInfo) { - Sid sid = aceInfo.getSidInfo().isPrincipal() ? new PrincipalSid(aceInfo.getSidInfo().getSid()) : new GrantedAuthoritySid(aceInfo.getSidInfo().getSid()); - AccessControlEntry ace = new AccessControlEntryImpl(Long.valueOf(i), acl, sid, aclPermissionFactory.buildFromMask(aceInfo.getPermissionMask()), true, false, false); - newAces.add(ace); + // a NULL permission means to delete the ace + public MutableAclRecord upsertAce(MutableAclRecord acl, final Sid sid, final Permission perm) { + return updateAclWithRetry(acl, new AclRecordUpdater() { + @Override + public void update(AclRecord record) { + record.upsertAce(perm, sid); } - } - - this.setAces(acl, newAces); - } - - private void setAces(AclImpl acl, List<AccessControlEntry> aces) { - try { - fieldAces.set(acl, aces); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Could not set AclImpl entries", e); - } - } - - public static String getQueryKeyById(String id) { - return DIR_PREFIX + id; - } -} - -@SuppressWarnings("serial") -class AclRecord extends RootPersistentEntity { - - @JsonProperty() - private DomainObjectInfo domainObjectInfo; - - @JsonProperty() - private DomainObjectInfo parentDomainObjectInfo; - - @JsonProperty() - private SidInfo ownerInfo; - - @JsonProperty() - private boolean entriesInheriting; - - @JsonProperty() - private Map<String, AceInfo> allAceInfo; - - public AclRecord() { + }); } - - public AclRecord(DomainObjectInfo domainObjectInfo, DomainObjectInfo parentDomainObjectInfo, SidInfo ownerInfo, boolean entriesInheriting, Map<String, AceInfo> allAceInfo) { - this.domainObjectInfo = domainObjectInfo; - this.parentDomainObjectInfo = parentDomainObjectInfo; - this.ownerInfo = ownerInfo; - this.entriesInheriting = entriesInheriting; - this.allAceInfo = allAceInfo; - } - - public SidInfo getOwnerInfo() { - return ownerInfo; - } - - public void setOwnerInfo(SidInfo ownerInfo) { - this.ownerInfo = ownerInfo; - } - - public boolean isEntriesInheriting() { - return entriesInheriting; - } - - public void setEntriesInheriting(boolean entriesInheriting) { - this.entriesInheriting = entriesInheriting; + + public MutableAclRecord inherit(MutableAclRecord acl, final MutableAclRecord parentAcl) { + return updateAclWithRetry(acl, new AclRecordUpdater() { + @Override + public void update(AclRecord record) { + record.setEntriesInheriting(true); + record.setParent(parentAcl); + } + }); } - public DomainObjectInfo getDomainObjectInfo() { - return domainObjectInfo; + public interface AclRecordUpdater { + void update(AclRecord record); } - public void setDomainObjectInfo(DomainObjectInfo domainObjectInfo) { - this.domainObjectInfo = domainObjectInfo; - } + private MutableAclRecord updateAclWithRetry(MutableAclRecord acl, AclRecordUpdater updater) { + int retry = 7; + while (retry-- > 0) { + AclRecord record = acl.getAclRecord(); + + updater.update(record); + String resPath = resourceKey(record.getObjectIdentity()); + try { + aclStore.putResource(resPath, record, System.currentTimeMillis(), SERIALIZER); + return acl; // here we are done + + } catch (IllegalStateException ise) { + if (retry <= 0) { + logger.error("Retry is out, till got error, abandoning...", ise); + throw ise; + } - public DomainObjectInfo getParentDomainObjectInfo() { - return parentDomainObjectInfo; - } + logger.warn("Write conflict to update ACL " + resPath + " retry remaining " + retry + ", will retry..."); + acl = readAcl(acl.getObjectIdentity()); - public void setParentDomainObjectInfo(DomainObjectInfo parentDomainObjectInfo) { - this.parentDomainObjectInfo = parentDomainObjectInfo; + } catch (IOException e) { + throw new InternalErrorException(e); + } + } + throw new RuntimeException("should not reach here"); } - public Map<String, AceInfo> getAllAceInfo() { - return allAceInfo; + public static String resourceKey(ObjectIdentity domainObjId) { + return resourceKey(String.valueOf(domainObjId.getIdentifier())); } - public void setAllAceInfo(Map<String, AceInfo> allAceInfo) { - this.allAceInfo = allAceInfo; + public static String resourceKey(String domainObjId) { + return DIR_PREFIX + domainObjId; } - } diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/AclTableMigrationTool.java b/server-base/src/main/java/org/apache/kylin/rest/service/AclTableMigrationTool.java index 333d98e..33957ab 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/AclTableMigrationTool.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/AclTableMigrationTool.java @@ -40,6 +40,10 @@ import org.apache.kylin.common.persistence.StringEntity; import org.apache.kylin.common.util.Bytes; import org.apache.kylin.rest.security.AclConstant; import org.apache.kylin.rest.security.ManagedUser; +import org.apache.kylin.rest.security.springacl.AclRecord; +import org.apache.kylin.rest.security.springacl.LegacyAceInfo; +import org.apache.kylin.rest.security.springacl.ObjectIdentityImpl; +import org.apache.kylin.rest.security.springacl.SidInfo; import org.apache.kylin.rest.util.Serializer; import org.apache.kylin.storage.hbase.HBaseConnection; import org.apache.kylin.storage.hbase.HBaseResourceStore; @@ -53,10 +57,10 @@ public class AclTableMigrationTool { private static final Serializer<SidInfo> sidSerializer = new Serializer<SidInfo>(SidInfo.class); - private static final Serializer<DomainObjectInfo> domainObjSerializer = new Serializer<DomainObjectInfo>( - DomainObjectInfo.class); + private static final Serializer<ObjectIdentityImpl> domainObjSerializer = new Serializer<ObjectIdentityImpl>( + ObjectIdentityImpl.class); - private static final Serializer<AceInfo> aceSerializer = new Serializer<AceInfo>(AceInfo.class); + private static final Serializer<LegacyAceInfo> aceSerializer = new Serializer<LegacyAceInfo>(LegacyAceInfo.class); private static final Serializer<UserGrantedAuthority[]> ugaSerializer = new Serializer<UserGrantedAuthority[]>( UserGrantedAuthority[].class); @@ -124,13 +128,13 @@ public class AclTableMigrationTool { Result result = rs.next(); while (result != null) { AclRecord record = new AclRecord(); - DomainObjectInfo object = getDomainObjectInfoFromRs(result); + ObjectIdentityImpl object = getDomainObjectInfoFromRs(result); record.setDomainObjectInfo(object); record.setParentDomainObjectInfo(getParentDomainObjectInfoFromRs(result)); record.setOwnerInfo(getOwnerSidInfo(result)); record.setEntriesInheriting(getInheriting(result)); record.setAllAceInfo(getAllAceInfo(result)); - store.putResourceWithoutCheck(AclService.getQueryKeyById(object.getId()), record, + store.putResourceWithoutCheck(AclService.resourceKey(object.getId()), record, System.currentTimeMillis(), AclService.SERIALIZER); result = rs.next(); } @@ -192,18 +196,16 @@ public class AclTableMigrationTool { } - private DomainObjectInfo getDomainObjectInfoFromRs(Result result) { + private ObjectIdentityImpl getDomainObjectInfoFromRs(Result result) { String type = new String(result.getValue(Bytes.toBytes(AclConstant.ACL_INFO_FAMILY), Bytes.toBytes(AclConstant.ACL_INFO_FAMILY_TYPE_COLUMN))); String id = new String(result.getRow()); - DomainObjectInfo newInfo = new DomainObjectInfo(); - newInfo.setId(id); - newInfo.setType(type); + ObjectIdentityImpl newInfo = new ObjectIdentityImpl(type, id); return newInfo; } - private DomainObjectInfo getParentDomainObjectInfoFromRs(Result result) throws IOException { - DomainObjectInfo parentInfo = domainObjSerializer.deserialize(result.getValue( + private ObjectIdentityImpl getParentDomainObjectInfoFromRs(Result result) throws IOException { + ObjectIdentityImpl parentInfo = domainObjSerializer.deserialize(result.getValue( Bytes.toBytes(AclConstant.ACL_INFO_FAMILY), Bytes.toBytes(AclConstant.ACL_INFO_FAMILY_PARENT_COLUMN))); return parentInfo; } @@ -220,14 +222,14 @@ public class AclTableMigrationTool { return owner; } - private Map<String, AceInfo> getAllAceInfo(Result result) throws IOException { - Map<String, AceInfo> allAceInfoMap = new HashMap<>(); + private Map<String, LegacyAceInfo> getAllAceInfo(Result result) throws IOException { + Map<String, LegacyAceInfo> allAceInfoMap = new HashMap<>(); NavigableMap<byte[], byte[]> familyMap = result.getFamilyMap(Bytes.toBytes(AclConstant.ACL_ACES_FAMILY)); if (familyMap != null && !familyMap.isEmpty()) { for (Map.Entry<byte[], byte[]> entry : familyMap.entrySet()) { String sid = new String(entry.getKey()); - AceInfo aceInfo = aceSerializer.deserialize(entry.getValue()); + LegacyAceInfo aceInfo = aceSerializer.deserialize(entry.getValue()); if (null != aceInfo) { allAceInfoMap.put(sid, aceInfo); } diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/DomainObjectInfo.java b/server-base/src/main/java/org/apache/kylin/rest/service/DomainObjectInfo.java deleted file mode 100644 index f07a65e..0000000 --- a/server-base/src/main/java/org/apache/kylin/rest/service/DomainObjectInfo.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.service; - -import org.springframework.security.acls.model.ObjectIdentity; - - -class DomainObjectInfo { - private String id; - private String type; - - public DomainObjectInfo() { - } - - public DomainObjectInfo(ObjectIdentity oid) { - super(); - this.id = (String) oid.getIdentifier(); - this.type = oid.getType(); - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } -} 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 16bc971..217b54c 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 @@ -121,9 +121,9 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy aes = accessController.grant(CUBE_INSTANCE, "a24ca905-1fc6-4f67-985c-38fa5aeafd92", accessRequest); Assert.assertTrue(aes.size() == 1); - Long aeId = null; + int aeId = 0; for (AccessEntryResponse ae : aes) { - aeId = (Long) ae.getId(); + aeId = (Integer) ae.getId(); } Assert.assertNotNull(aeId); @@ -134,7 +134,7 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy aes = accessController.update(CUBE_INSTANCE, "a24ca905-1fc6-4f67-985c-38fa5aeafd92", accessRequest); Assert.assertTrue(aes.size() == 1); for (AccessEntryResponse ae : aes) { - aeId = (Long) ae.getId(); + aeId = (Integer) ae.getId(); } Assert.assertNotNull(aeId); @@ -167,7 +167,7 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy //revoke auth swichToAdmin(); AccessRequest request = getAccessRequest(ANALYST, READ, true); - request.setAccessEntryId((Long) aes.get(0).getId()); + request.setAccessEntryId((Integer) aes.get(0).getId()); accessController.revoke(PROJECT_INSTANCE, project.getUuid(), request); swichToAnalyst(); projects = projectController.getProjects(10000, 0); @@ -215,7 +215,7 @@ public class AccessControllerTest extends ServiceTestBase implements AclEntityTy //correct } swichToAdmin(); - accessRequest.setAccessEntryId((Long) aes.get(0).getId()); + accessRequest.setAccessEntryId((Integer) aes.get(0).getId()); accessController.revoke(PROJECT_INSTANCE, projects.get(0).getUuid(), accessRequest); swichToAnalyst(); try { diff --git a/server/src/test/java/org/apache/kylin/rest/service/AccessServiceTest.java b/server/src/test/java/org/apache/kylin/rest/service/AccessServiceTest.java index 3b9dca2..8f18ed1 100644 --- a/server/src/test/java/org/apache/kylin/rest/service/AccessServiceTest.java +++ b/server/src/test/java/org/apache/kylin/rest/service/AccessServiceTest.java @@ -31,7 +31,9 @@ import org.apache.kylin.metadata.project.ProjectInstance; 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.service.AclServiceTest.MockAclEntity; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -43,7 +45,6 @@ import org.springframework.security.acls.model.Sid; import com.fasterxml.jackson.core.JsonProcessingException; /** - * @author xduo */ public class AccessServiceTest extends ServiceTestBase { @@ -74,9 +75,9 @@ public class AccessServiceTest extends ServiceTestBase { Assert.assertNotNull(adminSid); Assert.assertNotNull(AclPermissionFactory.getPermissions()); - AclEntity ae = new MockAclEntity("test-domain-object"); + AclEntity ae = new AclServiceTest.MockAclEntity("test-domain-object"); accessService.clean(ae, true); - AclEntity attachedEntity = new MockAclEntity("attached-domain-object"); + AclEntity attachedEntity = new AclServiceTest.MockAclEntity("attached-domain-object"); accessService.clean(attachedEntity, true); // test getAcl @@ -97,12 +98,12 @@ public class AccessServiceTest extends ServiceTestBase { acl = accessService.grant(ae, AclPermission.ADMINISTRATION, modeler); Assert.assertEquals(accessService.generateAceResponses(acl).size(), 2); - Long modelerEntryId = null; + int modelerEntryId = 0; for (AccessControlEntry ace : acl.getEntries()) { PrincipalSid sid = (PrincipalSid) ace.getSid(); if (sid.getPrincipal().equals("MODELER")) { - modelerEntryId = (Long) ace.getId(); + modelerEntryId = (Integer) ace.getId(); Assert.assertTrue(ace.getPermission() == AclPermission.ADMINISTRATION); } } @@ -116,7 +117,7 @@ public class AccessServiceTest extends ServiceTestBase { PrincipalSid sid = (PrincipalSid) ace.getSid(); if (sid.getPrincipal().equals("MODELER")) { - modelerEntryId = (Long) ace.getId(); + modelerEntryId = (Integer) ace.getId(); Assert.assertTrue(ace.getPermission() == AclPermission.READ); } } @@ -147,21 +148,19 @@ public class AccessServiceTest extends ServiceTestBase { Assert.assertNull(attachedEntityAcl); } - public class MockAclEntity implements AclEntity { - - private String id; - - /** - * @param id - */ - public MockAclEntity(String id) { - super(); - this.id = id; - } - - @Override - public String getId() { - return id; + @Ignore + @Test + public void test100000Entries() throws JsonProcessingException { + MockAclEntity ae = new MockAclEntity("100000Entries"); + long time = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + if (i % 10 == 0) { + long now = System.currentTimeMillis(); + System.out.println((now - time) + " ms for last 10 entries, total " + i); + time = now; + } + Sid sid = accessService.getSid("USER" + i, true); + accessService.grant(ae, AclPermission.OPERATION, sid); } } } diff --git a/server/src/test/java/org/apache/kylin/rest/service/AclServiceTest.java b/server/src/test/java/org/apache/kylin/rest/service/AclServiceTest.java new file mode 100644 index 0000000..ad0d524 --- /dev/null +++ b/server/src/test/java/org/apache/kylin/rest/service/AclServiceTest.java @@ -0,0 +1,166 @@ +/* + * 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.service; + +import java.util.List; + +import org.apache.kylin.common.persistence.AclEntity; +import org.apache.kylin.rest.security.AclPermission; +import org.apache.kylin.rest.security.springacl.AceImpl; +import org.apache.kylin.rest.security.springacl.AclRecord; +import org.apache.kylin.rest.security.springacl.MutableAclRecord; +import org.apache.kylin.rest.security.springacl.ObjectIdentityImpl; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.acls.domain.GrantedAuthoritySid; +import org.springframework.security.acls.domain.PrincipalSid; +import org.springframework.security.acls.model.AccessControlEntry; +import org.springframework.security.acls.model.AlreadyExistsException; +import org.springframework.security.acls.model.NotFoundException; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + */ +public class AclServiceTest extends ServiceTestBase { + + @Autowired + @Qualifier("aclService") + AclService aclService; + + @Test + public void testReadFromLegacy() throws Exception { + ObjectIdentityImpl oid = oid("legacy-test-domain-object"); + MutableAclRecord acl = aclService.readAcl(oid); + { + AclRecord rec = acl.getAclRecord(); + Assert.assertEquals(oid, rec.getDomainObjectInfo()); + Assert.assertNull(rec.getParentDomainObjectInfo()); + Assert.assertNull(rec.getParentAcl()); + Assert.assertEquals(new PrincipalSid("ADMIN"), rec.getOwner()); + Assert.assertEquals(true, rec.isEntriesInheriting()); + + List<AccessControlEntry> entries = rec.getEntries(); + Assert.assertEquals(2, entries.size()); + AceImpl e0 = (AceImpl) entries.get(0); + Assert.assertEquals(rec, e0.getAcl()); + Assert.assertEquals(Integer.valueOf(0), e0.getId()); + Assert.assertEquals(new PrincipalSid("ADMIN"), e0.getSid()); + Assert.assertEquals(16, e0.getPermissionMask()); + AceImpl e1 = (AceImpl) entries.get(1); + Assert.assertEquals(rec, e1.getAcl()); + Assert.assertEquals(Integer.valueOf(1), e1.getId()); + Assert.assertEquals(new PrincipalSid("MODELER"), e1.getSid()); + Assert.assertEquals(1, e1.getPermissionMask()); + } + + aclService.upsertAce(acl, new GrantedAuthoritySid("G1"), AclPermission.MANAGEMENT); + MutableAclRecord newAcl = aclService.readAcl(oid); + { + AclRecord rec = newAcl.getAclRecord(); + List<AccessControlEntry> entries = rec.getEntries(); + Assert.assertEquals(3, entries.size()); + AceImpl e0 = (AceImpl) entries.get(0); + Assert.assertEquals(rec, e0.getAcl()); + Assert.assertEquals(Integer.valueOf(0), e0.getId()); + Assert.assertEquals(new GrantedAuthoritySid("G1"), e0.getSid()); + Assert.assertEquals(32, e0.getPermissionMask()); + } + } + + @Test + public void testBasics() throws Exception { + switchToAdmin(); + ObjectIdentityImpl parentOid = oid("parent-obj"); + MutableAclRecord parentAcl = (MutableAclRecord) aclService.createAcl(parentOid); + + switchToAnalyst(); + ObjectIdentityImpl childOid = oid("child-obj"); + MutableAclRecord childAcl = (MutableAclRecord) aclService.createAcl(childOid); + MutableAclRecord childAclOutdated = aclService.readAcl(childOid); + + // test create on existing acl + try { + aclService.createAcl(childOid); + Assert.fail(); + } catch (AlreadyExistsException ex) { + // expected + } + + // inherit parent + childAcl = aclService.inherit(childAcl, parentAcl); + Assert.assertEquals(parentOid, childAcl.getAclRecord().getParentDomainObjectInfo()); + Assert.assertEquals(null, childAclOutdated.getAclRecord().getParentDomainObjectInfo()); + + // update permission on an outdated ACL, retry should keep things going + PrincipalSid user1 = new PrincipalSid("user1"); + MutableAclRecord childAcl2 = aclService.upsertAce(childAclOutdated, user1, AclPermission.ADMINISTRATION); + Assert.assertEquals(parentOid, childAcl2.getAclRecord().getParentDomainObjectInfo()); + Assert.assertEquals(AclPermission.ADMINISTRATION, childAcl2.getAclRecord().getPermission(user1)); + + // remove permission + MutableAclRecord childAcl3 = aclService.upsertAce(childAcl2, user1, null); + Assert.assertEquals(0, childAcl3.getAclRecord().getEntries().size()); + + // delete ACL + aclService.deleteAcl(parentOid, true); + + try { + aclService.readAcl(childOid); + Assert.fail(); + } catch (NotFoundException ex) { + // expected + } + } + + private void switchToAdmin() { + Authentication adminAuth = new TestingAuthenticationToken("ADMIN", "ADMIN", "ROLE_ADMIN"); + SecurityContextHolder.getContext().setAuthentication(adminAuth); + } + + private void switchToAnalyst() { + Authentication analystAuth = new TestingAuthenticationToken("ANALYST", "ANALYST", "ROLE_ANALYST"); + SecurityContextHolder.getContext().setAuthentication(analystAuth); + } + + private ObjectIdentityImpl oid(String oid) { + return new ObjectIdentityImpl(new MockAclEntity(oid)); + } + + public static class MockAclEntity implements AclEntity { + + private String id; + + /** + * @param id + */ + public MockAclEntity(String id) { + super(); + this.id = id; + } + + @Override + public String getId() { + return id; + } + } +} -- To stop receiving notification emails like this one, please contact billy...@apache.org.