-
Notifications
You must be signed in to change notification settings - Fork 331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#2869] feat(core): Add the core logic of authorization #3050
Conversation
53f1324
to
2574f78
Compare
@jerryshao This pull request is ready to review. |
@jerryshao I have removed version filter. |
return Utils.ok( | ||
new GroupResponse( | ||
DTOConverters.toDTO(accessControlManager.getGroup(metalake, group)))); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to wrap these two operations into one tree lock?
identifier, | ||
LockType.READ, | ||
() -> { | ||
AuthorizationUtils.checkIsMetalakeOwner(identifier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the creator only has the permission to do this operation? What if the owner is not available any more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should have a different role for metalake admins to have the metalake operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By referring other systems, you should have a series of role and privileges for admin users, like create metalake, access metalake, create/drop role/user/groups. Otherwise, your current solution has lots of limitations.
DTOConverters.toDTO( | ||
accessControlManager.addMetalakeAdmin(request.getName()))))); | ||
() -> { | ||
AuthorizationUtils.checkIsServiceAdmin(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you do this in a filter, not just adding it one by one interface?
// Filter the metalakes which aren't created by the user | ||
List<Metalake> filteredMetalakes = Lists.newArrayList(); | ||
for (Metalake metalake : orignMetalakes) { | ||
if (metalake.auditInfo().creator().equals(currentUser)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, why only the creator has permission to list metalakes, what if the creator is leaving, how to handle this scenario?
@jerryshao Could you help me review this pull request? |
|
||
@Override | ||
public boolean supportsSecurableObjectType(SecurableObject.Type type) { | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add some comment here andy below for grant privilege?
* object is only used for metalake admin. You can't grant any privilege to this securable object. | ||
* You can't bind this securable object to any role, too. | ||
* object is reserved for system. You can't grant any privilege to this securable object. You | ||
* can't bind this securable object to any role, too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There you mentioned that "You can give the securable object the privileges CREATE METALAKE
", but here you mentioned that "You can't grant any privilege to this securable object". I think these two sentences are self-contradictory, you should have a more accurate description.
Besides, why you can't bind this securable object to a role?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for users, users can't grant any privilege to allMetalakes. But system pre-created a role binds allMetalakes. AllMetalakes isn't used for the normal user.
We shouldn't allow user to use allMetalakes. It can inflluence the privileges of all metalakes.It's too dangerous.
@@ -48,8 +48,18 @@ class UserGroupManager { | |||
} | |||
|
|||
User addUser(String metalake, String name) throws UserAlreadyExistsException { | |||
|
|||
AuthorizationUtils.checkMetalakeExists(metalake); | |||
AuthorizationUtils.checkPermission( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a best practice to checkPermission everywhere in this package, why can't you do this in a filter before the request actually executed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation here is so broken and split into two groups of API, which makes the change so hard to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a best practice to checkPermission everywhere in this package, why can't you do this in a filter before the request actually executed?
Some operations will execute some extra actions after the operations are executed For example, list operation. We should filter the entities which we don't have any privilege.
For example, create operation. We should create some system roles for these operations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's due to your design model problem, you're not implementing a consistent role base model, which makes the code so broken, in which you differentiate service user, admin user and normal user, each user has a different implementation logic to handle.
Anyway, I don't like the current implementation, it is not a good code implementation.
} | ||
} | ||
|
||
public static boolean isNotAllowedToList( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method name is so weird.
} | ||
} | ||
|
||
public static boolean isNotAllowedForAnyPrivileges( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to name the method to "isNotXXX", it's no so intuitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I usually use !xxxxx. IDEA suggest me use isNotxxx to replace it.
@@ -57,16 +49,195 @@ public static NameIdentifier ofUser(String metalake, String user) { | |||
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, Entity.USER_SCHEMA_NAME, user); | |||
} | |||
|
|||
public static NameIdentifier ofMetalakeAdminRole() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metalake admin is not a role, it is a user with some predefined roles.
ba4e52a
to
33c0244
Compare
*/ | ||
String SYSTEM_METALAKE_MANAGE_USER_ROLE = "system_role_metalake_system_manage_user"; | ||
|
||
/** The reserved the prefix of the role names */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reserved role name's prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to The reserved role names' prefix.
core/src/main/java/com/datastrato/gravitino/authorization/AuthorizationUtils.java
Outdated
Show resolved
Hide resolved
} | ||
} | ||
} catch (NoSuchUserException noSuchUserException) { | ||
// If one user doesn't exist in the metalake, the user doesn't have any privilege. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ok.
continue; | ||
} | ||
|
||
if (role.hasPrivilegeWithCondition(object, privilege.name(), Privilege.Condition.ALLOW)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering the following case:
Privileges of object
is: Create table: allow, create database: allow
Privileges of current user: create table: allow, create table: deny
So the final result is true????
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
try { | ||
store.put(userEntity, false /* overwritten */); | ||
return userEntity; | ||
if (isServiceAdmin(user)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the user is a service admin, do we still need to add roles SYSTEM_METALAKE_MANAGE_USER_ROLE
and METALAKE_CREATE_ROLE
to it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because we used the role to execute authorization operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just did a cursory review, I found that you have many changes in authorization
package, but looks like you doesn't have many UTs to cover all the changes. I would suggest you add more tests to cover the changes you made.
String SYSTEM_METALAKE_MANAGE_USER_ROLE = "system_role_metalake_system_manage_user"; | ||
|
||
/** The reserved the prefix of the role names */ | ||
String SYSTEM_RESERVED_ROLE_NAME_PREFIX = "system_role"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would better to move these definitions to Role
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved to Role.
NameIdentifier systemMetalake = NameIdentifier.of(Entity.SYSTEM_METALAKE_RESERVED_NAME); | ||
if (!metalakeManager.metalakeExists(systemMetalake)) { | ||
metalakeManager.createMetalake(systemMetalake, null, Collections.emptyMap()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd better have a new method to do the system-level metalake, catalog and other creations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because AccessManager need system metalake. I extract a method in this class.
For system catalog and other creation, I can create a new class to handle them.
@@ -294,6 +286,17 @@ public boolean deleteRole(String metalake, String role) throws NoSuchMetalakeExc | |||
return doWithNonAdminLock(() -> roleManager.deleteRole(metalake, role)); | |||
} | |||
|
|||
public List<RoleEntity> getRolesByUserFromMetalake(String metalake, String currentUser) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
listRolesByUser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed.
return true; | ||
} else { | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should clearly define the behavior of false
. in Gravitino, false
only means not existed, here is it suitable to return false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the role which creates the metalake doesn't exist, we return will false.
return userEntity; | ||
if (isServiceAdmin(user)) { | ||
if (hasUserMetalakeCreateRole(user)) { | ||
throw new EntityAlreadyExistsException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so.
LOG.warn("User {} in the metalake admin already exists", user, e); | ||
throw new UserAlreadyExistsException("User %s in the metalake admin already exists", user); | ||
LOG.warn("The metalake admin {} has been added before", user, e); | ||
throw new RoleAlreadyExistsException("The metalake admin %s has been added before", user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you throw "EntityAlreadyExistsException" and convert to "RoleAlreadyExistsException", why don't you directly throw "RoleAlreadyExistsException"?
return AuthorizationUtils.ofUser(Entity.SYSTEM_METALAKE_RESERVED_NAME, user); | ||
} | ||
|
||
private boolean hasCreateMetalakeRole(String user) throws IOException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasMetakeCreateRole
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed.
Entity.EntityType.USER); | ||
|
||
for (UserEntity userEntity : userEntities) { | ||
// Case 1: If the user is the service admin, we should the system metalake manage user role |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"we should" what? Please fix the grammar issue.
privileges.add(Privileges.RemoveUser.allow()); | ||
} | ||
|
||
return SecurableObjects.ofMetalake(Entity.SYSTEM_METALAKE_RESERVED_NAME, privileges); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it uses "system" metalake name, not "*"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we need to create a system metalake object with user privilege to check the permission. Please explain more here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add the comment
System metalake will store the privileges which don't belong any concrete metalake. `*` means all the metalakes. We don't have a concrete metalake named `*`. So we use system metlake to store the privileges about `*`.
@@ -155,6 +162,23 @@ public Namespace namespace() { | |||
return namespace; | |||
} | |||
|
|||
public boolean hasPrivilegeWithCondition( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest not put privilege checking logic in this object class, you'd create a new method class to implement such logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the logic to AuthorizationUtils.
I can add some new uts for AuthorizationUtils.satisfyPrivileges. In my origin thought, I want to cover the cases in the uts and ITS of the filter. |
@@ -76,7 +93,16 @@ User addMetalakeAdmin(String user) { | |||
|
|||
boolean removeMetalakeAdmin(String user) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removeUserFromMetalakeAdmin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is a good name.
return store.delete(ofMetalakeAdmin(user), Entity.EntityType.USER); | ||
} | ||
|
||
if (hasMetalakeCreateRole(user)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, if the code goes here, the user is the service admin. Am I right? Why do we need to judge whether the user has the role MetalakeCreateRole
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I return the correct value whether the MetalakeCreateRole is removed successfully.
|
||
for (UserEntity userEntity : userEntities) { | ||
// Case 1: If the user is the service admin, we should add the system metalake manage user | ||
// role |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Format the comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
} | ||
} catch (NoSuchUserException noSuchUserException) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, Maybe we can narrow the try/catch block by the following code.
List<RoleEntity> roles;
try {
roles = accessControlManager.listRolesByUser(metalake, currentUser);
} catch( XXXX) {
return false;
}
...
doCheckPermissin()
...
} | ||
} | ||
} | ||
} catch (NoSuchUserException noSuchUserException) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may need to add the following comments.
- Any privilege is satisfied for the object privileges to return true.
- We only check the allowed privileges and only the user have the allow privileges can do some operations, we will not check if a user has the denied privileges.
What changes were proposed in this pull request?
Add the core logic of authorization.
Why are the changes needed?
Fix: #2869
Does this PR introduce any user-facing change?
No.
How was this patch tested?
Add new uts.