From 9a0879cc1c8cc2e235d5336216814d92f142352c Mon Sep 17 00:00:00 2001 From: jon-wei Date: Tue, 7 May 2019 13:27:56 -0700 Subject: [PATCH 1/3] Add simpler permissions option to BasicAuthorizer GET APIs --- .../extensions-core/druid-basic-security.md | 132 +++++++++++++- .../endpoint/BasicAuthorizerResource.java | 10 +- .../BasicAuthorizerResourceHandler.java | 4 +- ...dinatorBasicAuthorizerResourceHandler.java | 103 ++++++++--- ...DefaultBasicAuthorizerResourceHandler.java | 5 +- .../entity/BasicAuthorizerRoleFull.java | 4 + ...icAuthorizerRoleSimplifiedPermissions.java | 165 ++++++++++++++++++ .../entity/BasicAuthorizerUserFull.java | 4 + ...thorizerUserFullSimplifiedPermissions.java | 81 +++++++++ ...oordinatorBasicAuthorizerResourceTest.java | 140 ++++++++++++--- 10 files changed, 584 insertions(+), 64 deletions(-) create mode 100644 extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleSimplifiedPermissions.java create mode 100644 extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFullSimplifiedPermissions.java diff --git a/docs/content/development/extensions-core/druid-basic-security.md b/docs/content/development/extensions-core/druid-basic-security.md index 28eff1fca9f3..4282f910db37 100644 --- a/docs/content/development/extensions-core/druid-basic-security.md +++ b/docs/content/development/extensions-core/druid-basic-security.md @@ -172,6 +172,87 @@ Return a list of all user names. `GET(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})` Return the name and role information of the user with name {userName} +Example output: +```json +{ + "name": "druid2", + "roles": [ + "druidRole" + ] +} +``` + +This API supports the following flags: +- `?full`: The response will also include the full information for each role currently assigned to the user. + +Example output: +```json +{ + "name": "druid2", + "roles": [ + { + "name": "druidRole", + "permissions": [ + { + "resourceAction": { + "resource": { + "name": "A", + "type": "DATASOURCE" + }, + "action": "READ" + }, + "resourceNamePattern": "A" + }, + { + "resourceAction": { + "resource": { + "name": "C", + "type": "CONFIG" + }, + "action": "WRITE" + }, + "resourceNamePattern": "C" + } + ] + } + ] +} +``` + +The output format of this API when `?full` is specified is deprecated and in later versions will be switched to the output format used when both `?full` and `?simplifyPermissions` flag is set. + +The `resourceNamePattern` is a compiled version of the resource name regex. It is redundant and complicates the use of this API for clients such as frontends that edit the authorization configuration, as the permission format in this output does not match the format used for adding permissions to a role. + +- `?full?simplifyPermissions`: When both `?full` and `?simplifyPermissions` are set, the permissions in the output will contain only a list of `resourceAction` objects, without the extraneous `resourceNamePattern` field. + +```json +{ + "name": "druid2", + "roles": [ + { + "name": "druidRole", + "users": null, + "permissions": [ + { + "resource": { + "name": "A", + "type": "DATASOURCE" + }, + "action": "READ" + }, + { + "resource": { + "name": "C", + "type": "CONFIG" + }, + "action": "WRITE" + } + ] + } + ] +} +``` + `POST(/druid-ext/basic-security/authorization/db/{authorizerName}/users/{userName})` Create a new user with name {userName} @@ -184,7 +265,56 @@ Delete the user with name {userName} Return a list of all role names. `GET(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName})` -Return name and permissions for the role named {roleName} +Return name and permissions for the role named {roleName}. + +Example output: +```json +{ + "name": "druidRole2", + "permissions": [ + { + "resourceAction": { + "resource": { + "name": "E", + "type": "DATASOURCE" + }, + "action": "WRITE" + }, + "resourceNamePattern": "E" + } + ] +} +``` + +The default output format of this API is deprecated and in later versions will be switched to the output format used when the `?simplifyPermissions` flag is set. The `resourceNamePattern` is a compiled version of the resource name regex. It is redundant and complicates the use of this API for clients such as frontends that edit the authorization configuration, as the permission format in this output does not match the format used for adding permissions to a role. + +This API supports the following flags: + +- `?full`: The output will contain an extra `users` list, containing the users that currently have this role. + +```json +"users":["druid"] +``` + +- `?simplifyPermissions`: The permissions in the output will contain only a list of `resourceAction` objects, without the extraneous `resourceNamePattern` field. The `users` field will be null when `?full` is not specified. + +Example output: +```json +{ + "name": "druidRole2", + "users": null, + "permissions": [ + { + "resource": { + "name": "E", + "type": "DATASOURCE" + }, + "action": "WRITE" + } + ] +} +``` + `POST(/druid-ext/basic-security/authorization/db/{authorizerName}/roles/{roleName})` Create a new role with name {roleName}. diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResource.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResource.java index eadd72a07a50..99a112602c8b 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResource.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResource.java @@ -123,10 +123,11 @@ public Response getUser( @Context HttpServletRequest req, @PathParam("authorizerName") final String authorizerName, @PathParam("userName") final String userName, - @QueryParam("full") String full + @QueryParam("full") String full, + @QueryParam("simplifyPermissions") String simplifyPermissions ) { - return resourceHandler.getUser(authorizerName, userName, full != null); + return resourceHandler.getUser(authorizerName, userName, full != null, simplifyPermissions != null); } /** @@ -208,10 +209,11 @@ public Response getRole( @Context HttpServletRequest req, @PathParam("authorizerName") final String authorizerName, @PathParam("roleName") final String roleName, - @QueryParam("full") String full + @QueryParam("full") String full, + @QueryParam("simplifyPermissions") String simplifyPermissions ) { - return resourceHandler.getRole(authorizerName, roleName, full != null); + return resourceHandler.getRole(authorizerName, roleName, full != null, simplifyPermissions != null); } /** diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResourceHandler.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResourceHandler.java index ad69d7b6c11e..214da87c4c18 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResourceHandler.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/BasicAuthorizerResourceHandler.java @@ -34,7 +34,7 @@ public interface BasicAuthorizerResourceHandler // coordinator methods Response getAllUsers(String authorizerName); - Response getUser(String authorizerName, String userName, boolean isFull); + Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions); Response createUser(String authorizerName, String userName); @@ -42,7 +42,7 @@ public interface BasicAuthorizerResourceHandler Response getAllRoles(String authorizerName); - Response getRole(String authorizerName, String roleName, boolean isFull); + Response getRole(String authorizerName, String roleName, boolean isFull, boolean simplifyPermissions); Response createRole(String authorizerName, String roleName); diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java index 6bd951fccdc8..ef1f651d047b 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java @@ -31,8 +31,10 @@ import org.apache.druid.security.basic.authorization.db.updater.BasicAuthorizerMetadataStorageUpdater; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull; +import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleSimplifiedPermissions; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFull; +import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFullSimplifiedPermissions; import org.apache.druid.security.basic.authorization.entity.UserAndRoleMap; import org.apache.druid.server.security.Authorizer; import org.apache.druid.server.security.AuthorizerMapper; @@ -92,7 +94,7 @@ public Response getAllUsers(String authorizerName) } @Override - public Response getUser(String authorizerName, String userName, boolean isFull) + public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions) { final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName); if (authorizer == null) { @@ -100,7 +102,7 @@ public Response getUser(String authorizerName, String userName, boolean isFull) } if (isFull) { - return getUserFull(authorizerName, userName); + return getUserFull(authorizerName, userName, simplifyPermissions); } else { return getUserSimple(authorizerName, userName); } @@ -157,7 +159,7 @@ public Response getAllRoles(String authorizerName) } @Override - public Response getRole(String authorizerName, String roleName, boolean isFull) + public Response getRole(String authorizerName, String roleName, boolean isFull, boolean simplifyPermissions) { final BasicRoleBasedAuthorizer authorizer = authorizerMap.get(authorizerName); if (authorizer == null) { @@ -165,9 +167,9 @@ public Response getRole(String authorizerName, String roleName, boolean isFull) } if (isFull) { - return getRoleFull(authorizerName, roleName); + return getRoleFull(authorizerName, roleName, simplifyPermissions); } else { - return getRoleSimple(authorizerName, roleName); + return getRoleSimple(authorizerName, roleName, simplifyPermissions); } } @@ -335,7 +337,7 @@ private Response getUserSimple(String authorizerName, String userName) } } - private Response getUserFull(String authorizerName, String userName) + private Response getUserFull(String authorizerName, String userName, boolean simplifyPermissions) { Map userMap = BasicAuthUtils.deserializeAuthorizerUserMap( objectMapper, @@ -353,25 +355,65 @@ private Response getUserFull(String authorizerName, String userName) throw new BasicSecurityDBResourceException("User [%s] does not exist.", userName); } - Set roles = new HashSet<>(); - for (String roleName : user.getRoles()) { - BasicAuthorizerRole role = roleMap.get(roleName); - if (role == null) { - log.error("User [%s] had role [%s], but role was not found.", userName, roleName); - } else { - roles.add(role); - } + if (simplifyPermissions) { + Set roles = getRolesForUserWithSimplifiedPermissions(user, roleMap); + BasicAuthorizerUserFullSimplifiedPermissions fullUser = new BasicAuthorizerUserFullSimplifiedPermissions( + userName, + roles + ); + return Response.ok(fullUser).build(); + } else { + Set roles = getRolesForUser(user, roleMap); + BasicAuthorizerUserFull fullUser = new BasicAuthorizerUserFull(userName, roles); + return Response.ok(fullUser).build(); } - - BasicAuthorizerUserFull fullUser = new BasicAuthorizerUserFull(userName, roles); - return Response.ok(fullUser).build(); } catch (BasicSecurityDBResourceException e) { return makeResponseForBasicSecurityDBResourceException(e); } } - private Response getRoleSimple(String authorizerName, String roleName) + private Set getRolesForUserWithSimplifiedPermissions( + BasicAuthorizerUser user, + Map roleMap + ) + { + Set roles = new HashSet<>(); + for (String roleName : user.getRoles()) { + BasicAuthorizerRole role = roleMap.get(roleName); + if (role == null) { + log.error("User [%s] had role [%s], but role was not found.", user.getName(), roleName); + } else { + BasicAuthorizerRoleSimplifiedPermissions roleWithSimplifiedPermissions = new BasicAuthorizerRoleSimplifiedPermissions( + role.getName(), + null, + BasicAuthorizerRoleSimplifiedPermissions.convertPermissions(role.getPermissions()) + ); + roles.add(roleWithSimplifiedPermissions); + } + } + return roles; + } + + private Set getRolesForUser( + BasicAuthorizerUser user, + Map roleMap + ) + { + Set roles = new HashSet<>(); + for (String roleName : user.getRoles()) { + BasicAuthorizerRole role = roleMap.get(roleName); + if (role == null) { + log.error("User [%s] had role [%s], but role was not found.", user.getName(), roleName); + } else { + roles.add(role); + } + } + return roles; + } + + + private Response getRoleSimple(String authorizerName, String roleName, boolean simplifyPermissions) { Map roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap( objectMapper, @@ -383,14 +425,19 @@ private Response getRoleSimple(String authorizerName, String roleName) if (role == null) { throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName); } - return Response.ok(role).build(); + + if (simplifyPermissions) { + return Response.ok(new BasicAuthorizerRoleSimplifiedPermissions(role, null)).build(); + } else { + return Response.ok(role).build(); + } } catch (BasicSecurityDBResourceException e) { return makeResponseForBasicSecurityDBResourceException(e); } } - private Response getRoleFull(String authorizerName, String roleName) + private Response getRoleFull(String authorizerName, String roleName, boolean simplifyPermissions) { Map roleMap = BasicAuthUtils.deserializeAuthorizerRoleMap( objectMapper, @@ -414,12 +461,16 @@ private Response getRoleFull(String authorizerName, String roleName) if (role == null) { throw new BasicSecurityDBResourceException("Role [%s] does not exist.", roleName); } - BasicAuthorizerRoleFull roleFull = new BasicAuthorizerRoleFull( - roleName, - users, - role.getPermissions() - ); - return Response.ok(roleFull).build(); + if (simplifyPermissions) { + return Response.ok(new BasicAuthorizerRoleSimplifiedPermissions(role, users)).build(); + } else { + BasicAuthorizerRoleFull roleFull = new BasicAuthorizerRoleFull( + roleName, + users, + role.getPermissions() + ); + return Response.ok(roleFull).build(); + } } catch (BasicSecurityDBResourceException e) { return makeResponseForBasicSecurityDBResourceException(e); diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/DefaultBasicAuthorizerResourceHandler.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/DefaultBasicAuthorizerResourceHandler.java index 94a63b6f85a5..e6c663bce562 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/DefaultBasicAuthorizerResourceHandler.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/DefaultBasicAuthorizerResourceHandler.java @@ -63,7 +63,6 @@ public DefaultBasicAuthorizerResourceHandler( } } - @Override public Response getAllUsers(String authorizerName) { @@ -71,7 +70,7 @@ public Response getAllUsers(String authorizerName) } @Override - public Response getUser(String authorizerName, String userName, boolean isFull) + public Response getUser(String authorizerName, String userName, boolean isFull, boolean simplifyPermissions) { return NOT_FOUND_RESPONSE; } @@ -95,7 +94,7 @@ public Response getAllRoles(String authorizerName) } @Override - public Response getRole(String authorizerName, String roleName, boolean isFull) + public Response getRole(String authorizerName, String roleName, boolean isFull, boolean simplifyPermissions) { return NOT_FOUND_RESPONSE; } diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleFull.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleFull.java index 79ced60bb186..178682981af6 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleFull.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleFull.java @@ -26,6 +26,10 @@ import java.util.List; import java.util.Set; +/** + * Please see the class javadocs on {@link BasicAuthorizerRoleSimplifiedPermissions} for details on the deprecation. + */ +@Deprecated public class BasicAuthorizerRoleFull { private final String name; diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleSimplifiedPermissions.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleSimplifiedPermissions.java new file mode 100644 index 000000000000..d4c2ae595bb9 --- /dev/null +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerRoleSimplifiedPermissions.java @@ -0,0 +1,165 @@ +/* + * 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.druid.security.basic.authorization.entity; + + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Lists; +import org.apache.druid.server.security.ResourceAction; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The POST API for setting permissions on a role represents permissions as ResourceAction options. + * + * However, the BasicAuthorizerRole object in the metadata store has a different representation for permissions, where + * the permission object keeps a compiled regex pattern for matching. + * + * If we return this role object directly, it is somewhat inconvenient for callers of the API. The returned permissions + * format does not match the permissions format for update APIs. If the client (e.g. a UI for editing roles) has a + * workflow where the client retrieves existing roles, edits them, and resubmits for updates, this imposes + * extra work on the client. + * + * This class is used to return role information with permissions represented as ResourceAction objects to match + * the update APIs. + * + * The compiled regex pattern is not useful for users, so the response formats that contain the permission+regex are + * now deprecated. In the future user-facing APIs should only return role information with the simplfied permissions + * format. + */ +public class BasicAuthorizerRoleSimplifiedPermissions +{ + private final String name; + private final Set users; + private final List permissions; + + @JsonCreator + public BasicAuthorizerRoleSimplifiedPermissions( + @JsonProperty("name") String name, + @JsonProperty("users") Set users, + @JsonProperty("permissions") List permissions + ) + { + this.name = name; + this.users = users; + this.permissions = permissions == null ? new ArrayList<>() : permissions; + } + + public BasicAuthorizerRoleSimplifiedPermissions( + BasicAuthorizerRole role, + Set users + ) + { + this( + role.getName(), + users, + convertPermissions(role.getPermissions()) + ); + } + + public BasicAuthorizerRoleSimplifiedPermissions( + BasicAuthorizerRoleFull role + ) + { + this( + role.getName(), + role.getUsers(), + convertPermissions(role.getPermissions()) + ); + } + + @JsonProperty + public String getName() + { + return name; + } + + @JsonProperty + public List getPermissions() + { + return permissions; + } + + @JsonProperty + public Set getUsers() + { + return users; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BasicAuthorizerRoleSimplifiedPermissions that = (BasicAuthorizerRoleSimplifiedPermissions) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + if (getUsers() != null ? !getUsers().equals(that.getUsers()) : that.getUsers() != null) { + return false; + } + return getPermissions() != null ? getPermissions().equals(that.getPermissions()) : that.getPermissions() == null; + + } + + @Override + public int hashCode() + { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getUsers() != null ? getUsers().hashCode() : 0); + result = 31 * result + (getPermissions() != null ? getPermissions().hashCode() : 0); + return result; + } + + public static List convertPermissions( + List permissions + ) + { + return Lists.transform( + permissions, + (permission) -> { + return permission.getResourceAction(); + } + ); + } + + public static Set convertRoles( + Set roles + ) + { + final HashSet newRoles = new HashSet<>(); + for (BasicAuthorizerRole role : roles) { + newRoles.add( + new BasicAuthorizerRoleSimplifiedPermissions(role, null) + ); + } + return newRoles; + } +} diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFull.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFull.java index a772bd089d03..c48e1285d071 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFull.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFull.java @@ -25,6 +25,10 @@ import java.util.HashSet; import java.util.Set; +/** + * Please see the class javadocs on {@link BasicAuthorizerRoleSimplifiedPermissions} for details on the deprecation. + */ +@Deprecated public class BasicAuthorizerUserFull { private final String name; diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFullSimplifiedPermissions.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFullSimplifiedPermissions.java new file mode 100644 index 000000000000..37fe6d6330ab --- /dev/null +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/entity/BasicAuthorizerUserFullSimplifiedPermissions.java @@ -0,0 +1,81 @@ +/* + * 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.druid.security.basic.authorization.entity; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashSet; +import java.util.Set; + +public class BasicAuthorizerUserFullSimplifiedPermissions +{ + private final String name; + private final Set roles; + + @JsonCreator + public BasicAuthorizerUserFullSimplifiedPermissions( + @JsonProperty("name") String name, + @JsonProperty("roles") Set roles + ) + { + this.name = name; + this.roles = roles == null ? new HashSet<>() : roles; + } + + @JsonProperty + public String getName() + { + return name; + } + + @JsonProperty + public Set getRoles() + { + return roles; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + BasicAuthorizerUserFullSimplifiedPermissions that = (BasicAuthorizerUserFullSimplifiedPermissions) o; + + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { + return false; + } + return getRoles() != null ? getRoles().equals(that.getRoles()) : that.getRoles() == null; + + } + + @Override + public int hashCode() + { + int result = getName() != null ? getName().hashCode() : 0; + result = 31 * result + (getRoles() != null ? getRoles().hashCode() : 0); + return result; + } +} diff --git a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authorization/CoordinatorBasicAuthorizerResourceTest.java b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authorization/CoordinatorBasicAuthorizerResourceTest.java index ef10dcc30dee..156273c53162 100644 --- a/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authorization/CoordinatorBasicAuthorizerResourceTest.java +++ b/extensions-core/druid-basic-security/src/test/java/org/apache/druid/security/authorization/CoordinatorBasicAuthorizerResourceTest.java @@ -36,8 +36,10 @@ import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerPermission; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRole; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleFull; +import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerRoleSimplifiedPermissions; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUser; import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFull; +import org.apache.druid.security.basic.authorization.entity.BasicAuthorizerUserFullSimplifiedPermissions; import org.apache.druid.server.security.Action; import org.apache.druid.server.security.AuthorizerMapper; import org.apache.druid.server.security.Resource; @@ -243,7 +245,7 @@ public void testCreateDeleteUser() Response response = resource.createUser(req, AUTHORIZER_NAME, "druid"); Assert.assertEquals(200, response.getStatus()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerUser expectedUser = new BasicAuthorizerUser( @@ -259,7 +261,7 @@ public void testCreateDeleteUser() Assert.assertEquals(400, response.getStatus()); Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(400, response.getStatus()); Assert.assertEquals(errorMapWithMsg("User [druid] does not exist."), response.getEntity()); } @@ -270,7 +272,7 @@ public void testCreateDeleteRole() Response response = resource.createRole(req, AUTHORIZER_NAME, "druidRole"); Assert.assertEquals(200, response.getStatus()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of()); @@ -283,7 +285,7 @@ public void testCreateDeleteRole() Assert.assertEquals(400, response.getStatus()); Assert.assertEquals(errorMapWithMsg("Role [druidRole] does not exist."), response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(400, response.getStatus()); Assert.assertEquals(errorMapWithMsg("Role [druidRole] does not exist."), response.getEntity()); } @@ -300,7 +302,7 @@ public void testRoleAssignment() response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid", "druidRole"); Assert.assertEquals(200, response.getStatus()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerUser expectedUser = new BasicAuthorizerUser( @@ -309,7 +311,7 @@ public void testRoleAssignment() ); Assert.assertEquals(expectedUser, response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of()); Assert.assertEquals(expectedRole, response.getEntity()); @@ -317,7 +319,7 @@ public void testRoleAssignment() response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", "druidRole"); Assert.assertEquals(200, response.getStatus()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(200, response.getStatus()); expectedUser = new BasicAuthorizerUser( "druid", @@ -325,7 +327,7 @@ public void testRoleAssignment() ); Assert.assertEquals(expectedUser, response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedRole, response.getEntity()); } @@ -348,7 +350,7 @@ public void testDeleteAssignedRole() response = resource.assignRoleToUser(req, AUTHORIZER_NAME, "druid2", "druidRole"); Assert.assertEquals(200, response.getStatus()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerUser expectedUser = new BasicAuthorizerUser( "druid", @@ -356,7 +358,7 @@ public void testDeleteAssignedRole() ); Assert.assertEquals(expectedUser, response.getEntity()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerUser expectedUser2 = new BasicAuthorizerUser( "druid2", @@ -364,7 +366,7 @@ public void testDeleteAssignedRole() ); Assert.assertEquals(expectedUser2, response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", ImmutableList.of()); Assert.assertEquals(expectedRole, response.getEntity()); @@ -372,7 +374,7 @@ public void testDeleteAssignedRole() response = resource.deleteRole(req, AUTHORIZER_NAME, "druidRole"); Assert.assertEquals(200, response.getStatus()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", null, null); Assert.assertEquals(200, response.getStatus()); expectedUser = new BasicAuthorizerUser( "druid", @@ -380,7 +382,7 @@ public void testDeleteAssignedRole() ); Assert.assertEquals(expectedUser, response.getEntity()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", null, null); Assert.assertEquals(200, response.getStatus()); expectedUser2 = new BasicAuthorizerUser( "druid2", @@ -408,7 +410,7 @@ public void testRolesAndPerms() Assert.assertEquals(400, response.getStatus()); Assert.assertEquals(errorMapWithMsg("Role [wrongRole] does not exist."), response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); BasicAuthorizerRole expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(perms)); Assert.assertEquals(expectedRole, response.getEntity()); @@ -422,7 +424,7 @@ public void testRolesAndPerms() response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", newPerms); Assert.assertEquals(200, response.getStatus()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); expectedRole = new BasicAuthorizerRole("druidRole", BasicAuthorizerPermission.makePermissionList(newPerms)); Assert.assertEquals(expectedRole, response.getEntity()); @@ -430,7 +432,7 @@ public void testRolesAndPerms() response = resource.setRolePermissions(req, AUTHORIZER_NAME, "druidRole", null); Assert.assertEquals(200, response.getStatus()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, null); Assert.assertEquals(200, response.getStatus()); expectedRole = new BasicAuthorizerRole("druidRole", null); Assert.assertEquals(expectedRole, response.getEntity()); @@ -486,14 +488,30 @@ public void testUsersRolesAndPerms() Set expectedRoles = Sets.newHashSet(expectedRole, expectedRole2); BasicAuthorizerUserFull expectedUserFull = new BasicAuthorizerUserFull("druid", expectedRoles); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull, response.getEntity()); + BasicAuthorizerUserFullSimplifiedPermissions expectedUserFullSimplifiedPermissions = + new BasicAuthorizerUserFullSimplifiedPermissions( + "druid", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedRoles) + ); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions, response.getEntity()); BasicAuthorizerUserFull expectedUserFull2 = new BasicAuthorizerUserFull("druid2", expectedRoles); - response = resource.getUser(req, AUTHORIZER_NAME, "druid2", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull2, response.getEntity()); + BasicAuthorizerUserFullSimplifiedPermissions expectedUserFullSimplifiedPermissions2 = + new BasicAuthorizerUserFullSimplifiedPermissions( + "druid2", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedRoles) + ); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions2, response.getEntity()); Set expectedUserSet = Sets.newHashSet("druid", "druid2"); BasicAuthorizerRoleFull expectedRoleFull = new BasicAuthorizerRoleFull( @@ -501,19 +519,50 @@ public void testUsersRolesAndPerms() expectedUserSet, BasicAuthorizerPermission.makePermissionList(perms) ); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", ""); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedRoleFull, response.getEntity()); + BasicAuthorizerRoleSimplifiedPermissions expectedRoleSimplifiedPerms = new BasicAuthorizerRoleSimplifiedPermissions( + "druidRole", + expectedUserSet, + perms + ); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms, response.getEntity()); + expectedRoleSimplifiedPerms = new BasicAuthorizerRoleSimplifiedPermissions( + "druidRole", + null, + perms + ); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", null, ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms, response.getEntity()); BasicAuthorizerRoleFull expectedRoleFull2 = new BasicAuthorizerRoleFull( "druidRole2", expectedUserSet, BasicAuthorizerPermission.makePermissionList(perms2) ); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", ""); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedRoleFull2, response.getEntity()); - + BasicAuthorizerRoleSimplifiedPermissions expectedRoleSimplifiedPerms2 = new BasicAuthorizerRoleSimplifiedPermissions( + "druidRole2", + expectedUserSet, + perms2 + ); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms2, response.getEntity()); + expectedRoleSimplifiedPerms2 = new BasicAuthorizerRoleSimplifiedPermissions( + "druidRole2", + null, + perms2 + ); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", null, ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms2, response.getEntity()); perms = ImmutableList.of( new ResourceAction(new Resource("A", ResourceType.DATASOURCE), Action.READ), @@ -535,14 +584,28 @@ public void testUsersRolesAndPerms() expectedRoles = Sets.newHashSet(expectedRole, expectedRole2); expectedUserFull = new BasicAuthorizerUserFull("druid", expectedRoles); expectedUserFull2 = new BasicAuthorizerUserFull("druid2", expectedRoles); + expectedUserFullSimplifiedPermissions = new BasicAuthorizerUserFullSimplifiedPermissions( + "druid", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedRoles) + ); + expectedUserFullSimplifiedPermissions2 = new BasicAuthorizerUserFullSimplifiedPermissions( + "druid2", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedRoles) + ); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull, response.getEntity()); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions, response.getEntity()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid2", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull2, response.getEntity()); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions2, response.getEntity()); response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid", "druidRole"); Assert.assertEquals(200, response.getStatus()); @@ -550,7 +613,6 @@ public void testUsersRolesAndPerms() response = resource.unassignRoleFromUser(req, AUTHORIZER_NAME, "druid2", "druidRole2"); Assert.assertEquals(200, response.getStatus()); - expectedUserFull = new BasicAuthorizerUserFull("druid", Sets.newHashSet(expectedRole2)); expectedUserFull2 = new BasicAuthorizerUserFull("druid2", Sets.newHashSet(expectedRole)); expectedRoleFull = new BasicAuthorizerRoleFull( @@ -563,22 +625,44 @@ public void testUsersRolesAndPerms() Sets.newHashSet("druid"), BasicAuthorizerPermission.makePermissionList(perms2) ); + expectedUserFullSimplifiedPermissions = new BasicAuthorizerUserFullSimplifiedPermissions( + "druid", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedUserFull.getRoles()) + ); + expectedUserFullSimplifiedPermissions2 = new BasicAuthorizerUserFullSimplifiedPermissions( + "druid2", + BasicAuthorizerRoleSimplifiedPermissions.convertRoles(expectedUserFull2.getRoles()) + ); + expectedRoleSimplifiedPerms = new BasicAuthorizerRoleSimplifiedPermissions(expectedRoleFull); + expectedRoleSimplifiedPerms2 = new BasicAuthorizerRoleSimplifiedPermissions(expectedRoleFull2); - response = resource.getUser(req, AUTHORIZER_NAME, "druid", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull, response.getEntity()); + response = resource.getUser(req, AUTHORIZER_NAME, "druid", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions, response.getEntity()); - response = resource.getUser(req, AUTHORIZER_NAME, "druid2", ""); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedUserFull2, response.getEntity()); + response = resource.getUser(req, AUTHORIZER_NAME, "druid2", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedUserFullSimplifiedPermissions2, response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", ""); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedRoleFull, response.getEntity()); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms, response.getEntity()); - response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", ""); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", null); Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(expectedRoleFull2, response.getEntity()); + response = resource.getRole(req, AUTHORIZER_NAME, "druidRole2", "", ""); + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(expectedRoleSimplifiedPerms2, response.getEntity()); } private static Map errorMapWithMsg(String errorMsg) From 333902d0a7d948a6a11948034cf211bc428c22c7 Mon Sep 17 00:00:00 2001 From: Jonathan Wei Date: Tue, 14 May 2019 13:28:00 -0700 Subject: [PATCH 2/3] Adjust log message Co-Authored-By: Himanshu --- .../endpoint/CoordinatorBasicAuthorizerResourceHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java index ef1f651d047b..aa67bfb622bd 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java @@ -404,7 +404,7 @@ private Set getRolesForUser( for (String roleName : user.getRoles()) { BasicAuthorizerRole role = roleMap.get(roleName); if (role == null) { - log.error("User [%s] had role [%s], but role was not found.", user.getName(), roleName); + log.error("User [%s] had role [%s], but role object was not found.", user.getName(), roleName); } else { roles.add(role); } From 4cae5cc24f0eb0c036b33e3ff2a82348642e5be0 Mon Sep 17 00:00:00 2001 From: Jonathan Wei Date: Tue, 14 May 2019 13:28:08 -0700 Subject: [PATCH 3/3] Adjust log message Co-Authored-By: Himanshu --- .../endpoint/CoordinatorBasicAuthorizerResourceHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java index aa67bfb622bd..3a3cd139d525 100644 --- a/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java +++ b/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/endpoint/CoordinatorBasicAuthorizerResourceHandler.java @@ -382,7 +382,7 @@ private Set getRolesForUserWithSimplif for (String roleName : user.getRoles()) { BasicAuthorizerRole role = roleMap.get(roleName); if (role == null) { - log.error("User [%s] had role [%s], but role was not found.", user.getName(), roleName); + log.error("User [%s] had role [%s], but role object was not found.", user.getName(), roleName); } else { BasicAuthorizerRoleSimplifiedPermissions roleWithSimplifiedPermissions = new BasicAuthorizerRoleSimplifiedPermissions( role.getName(),