Skip to content
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

[WIP] Support role management #7346

Closed
wants to merge 58 commits into from

Conversation

arhimondr
Copy link
Member

@amrutagokhale
Copy link
Member

Can you add documentation for the two commands?

Copy link
Member

@maciejgrzybek maciejgrzybek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Squash "Add product tests for CREATE/DROP role" (1341f67a6bffa16236bb7dcf4a677ddb5632d925)
with "Implement CREATE/DROP role for Hive connector" (66eb6fb8018d1c186f803aa4b8bfb84690262414). They don't make sense separately.

assertStatement("CREATE ROLE role3 IN catalog1", new CreateRole("role3", Optional.empty(), Optional.of("catalog1")));
assertStatement("CREATE ROLE \"role\" WITH ADMIN \"admin\" IN \"catalog\"", new CreateRole("role", Optional.of("admin"), Optional.of("catalog")));
assertStatement("CREATE ROLE \"ro le\" WITH ADMIN \"ad min\" IN \"ca talog\"", new CreateRole("ro le", Optional.of("ad min"), Optional.of("ca talog")));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for non-letter chars in role, admin and catalog (depending on if they're accepted or not, expect failure here or proper CreateRole object). E.g. role[] or (admin) etc.

assertStatement("DROP ROLE role1 IN catalog", new DropRole("role1", Optional.of("catalog")));
assertStatement("DROP ROLE \"role\" IN \"catalog\"", new DropRole("role", Optional.of("catalog")));
assertStatement("DROP ROLE \"ro le\" IN \"ca talog\"", new DropRole("ro le", Optional.of("ca talog")));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto non-letter chars.

@@ -253,6 +253,16 @@
Optional<ResolvedIndex> resolveIndex(Session session, TableHandle tableHandle, Set<ColumnHandle> indexableColumns, Set<ColumnHandle> outputColumns, TupleDomain<ColumnHandle> tupleDomain);

/**
* Create role
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain any constraints on grantor maybe? Are there any? What if grantor is empty? This comment is useless currently.

{
Optional<CatalogMetadata> catalogMetadata = getOptionalCatalogMetadata(session, catalog);
if (!catalogMetadata.isPresent()) {
return ImmutableSet.of();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not throw here? If catalog does not exist but was used, it's an exceptional situation, isn't it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken the approach from listSchemaNames method

throw new PrestoException(NOT_SUPPORTED, "WITH ADMIN statement is not supported");
}
// roles are case insensitive in Hive
String roleLowerCase = role.toLowerCase();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify locale, so we use the same as in other places, instead of JVMs defaults.

@@ -218,6 +219,17 @@ private CachingHiveMetastore(ExtendedHiveMetastore delegate, ExecutorService exe
return loadTablePrivileges(key.getUser(), key.getDatabase(), key.getTable());
}
}, executor));

rolesCache = newCacheBuilder(expiresAfterWriteMillis, refreshMills, maximumSize)
.build(asyncReloading(new CacheLoader<String, Set<String>>()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not <Void, Set<String>>?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only valid value for Void type is null. But null is not allowed to be a key in Guava cache.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You never refer to the key in cache in this case, do you?

public void createRole(String role, String grantor)
throws TException
{
// No-op
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not mimic e.g. createTable's behavior - to throw the UnsupportedOperationException?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this mock in cache test. I don't want it to fail.

@@ -547,6 +550,34 @@ public synchronized void alterPartition(String databaseName, String tableName, P
}

@Override
public synchronized void createRole(String role, String grantor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not create a synchronized object shared between roles methods instead of having synchronized method?
You don't interfere with e.g. getPartitionNames or alterPartition so you don't need synchronization with all synchronized methods in this class.

@@ -409,6 +413,30 @@ public synchronized void alterPartition(String databaseName, String tableName, P
}

@Override
public synchronized void createRole(String role, String grantor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto synchronization

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't address this comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed that comment for FileBasedMetastore, but i think it doesn't matter here, because it is inmemory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't insist.

@Test(groups = {HIVE_CONNECTOR, ROLES, AUTHORIZATION, PROFILE_SPECIFIC_TESTS})
public void testAccessControl()
throws Exception
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention here that alice is non-admin user?

@arhimondr arhimondr changed the title Support role management [WIP] Support role management Feb 15, 2017
@arhimondr arhimondr force-pushed the support_role_management branch 3 times, most recently from e1331c4 to 049a900 Compare February 16, 2017 03:10
Copy link
Member

@maciejgrzybek maciejgrzybek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments

}

private final Type type;
private final String name;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional<String> instead? (for CURRENT_USER and CURRENET_ROLE)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've started to implement this, but it just introduces additional code with no value. This field is completely private. We never expose that null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe annotate it with @Nullable at least?

@@ -264,6 +265,18 @@
Optional<ResolvedIndex> resolveIndex(Session session, TableHandle tableHandle, Set<ColumnHandle> indexableColumns, Set<ColumnHandle> outputColumns, TupleDomain<ColumnHandle> tupleDomain);

/**
* Creates the specified role in the specified connector.
*
* @param grantor represent the principal specified by WITH ADMIN statement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

represents

void createRole(Session session, String role, Optional<PrestoPrincipal> grantor, String catalog);

/**
* Drops the specified role in the specified connector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/connector/catalog/

@@ -277,6 +277,11 @@
void dropRole(Session session, String role, String catalog);

/**
* List available roles in specified connector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/connector/catalog/
to be consistent with parameter name

@@ -218,6 +219,17 @@ private CachingHiveMetastore(ExtendedHiveMetastore delegate, ExecutorService exe
return loadTablePrivileges(key.getUser(), key.getDatabase(), key.getTable());
}
}, executor));

rolesCache = newCacheBuilder(expiresAfterWriteMillis, refreshMills, maximumSize)
.build(asyncReloading(new CacheLoader<String, Set<String>>()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You never refer to the key in cache in this case, do you?

@@ -409,6 +413,30 @@ public synchronized void alterPartition(String databaseName, String tableName, P
}

@Override
public synchronized void createRole(String role, String grantor)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't address this comment.

@Override
public void checkCanCreateRole(TransactionId transactionId, Identity identity, String role, Optional<PrestoPrincipal> grantor, String catalogName)
{
requireNonNull(identity, "identity is null");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need them here? You don't store those variables. NPE will be thrown immediately a couple lines below anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is done in all the methods in this class. I just wanted to keep it consistent.

@arhimondr arhimondr force-pushed the support_role_management branch 8 times, most recently from 1f0ebf9 to ff62efb Compare February 23, 2017 18:52
@arhimondr arhimondr force-pushed the support_role_management branch 6 times, most recently from 761f38f to c7cae56 Compare March 2, 2017 17:02
@arhimondr
Copy link
Member Author

Depends on #6629

@@ -309,6 +335,10 @@ private boolean getGrantOptionForPrivilege(ConnectorTransactionHandle transactio

private boolean checkTablePermission(ConnectorTransactionHandle transaction, Identity identity, SchemaTableName tableName, HivePrivilege... requiredPrivileges)
{
if (tableName.equals(ROLES) && !isAdmin(transaction, identity)) {
Copy link
Member

@cawallin cawallin Mar 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an exception is an antipattern for information schema queries -- it would be better to filter out the roles table in the filterTables() method if the user isn't admin.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, makes total sense

Amruta Gokhale added 2 commits March 20, 2017 10:45
The grantOption flag in PrivilegeInfo represents
the WITH GRANT OPTION clause in GRANT.

Also add UPDATE privilege to the SPI Privilege enum.
cawallin and others added 27 commits March 20, 2017 15:49
For SHOW ROLES, issue the query:
select role_name as "Role" from catalog.information_schema.roles;
Instead of select * from information_schema.roles, SHOW CURRENT ROLES
rewrites to select * from information_schema.enabled_roles.

All users can see what roles they're currently using, so no need
for access control checks.
Currently the only database permission we support is OWNERSHIP.
Instead of creating that permission, and checking if it is granted
it is more readable to just call `isDatabaseOwner` directly.
Admin user has all the available permissions for all the entities
implicitly. So it may be considered as a database and table "owner"
for all tables and databases. Also it has all the SELECT, INSERT, DELETE
permissions implicitly.
hasGrantOptionForPrivilege cannot be used in security checks for createView
because it doesn't consider the session role.
Verify that role set with `SET ROLE` is considering during the access check.
@arhimondr
Copy link
Member Author

arhimondr commented Mar 27, 2017

Blocked on #6629

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants