-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
[BUGFIX]: Fixing permission rollup database/schema to table in SupersetFilter #4004
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,13 @@ | |
FRONTEND_CONF_KEYS = ('SUPERSET_WEBSERVER_TIMEOUT',) | ||
|
||
|
||
ALL_DATASOURCE_ACCESS = 'all_datasource_access' | ||
ALL_DATABASE_ACCESS = 'all_database_access' | ||
DATABASE_ACCESS = 'database_access' | ||
SCHEMA_ACCESS = 'schema_access' | ||
DATASOURCE_ACCESS = 'datasource_access' | ||
|
||
|
||
def get_error_msg(): | ||
if conf.get('SHOW_STACKTRACE'): | ||
error_msg = traceback.format_exc() | ||
|
@@ -76,26 +83,26 @@ def can_access(self, permission_name, view_name, user=None): | |
|
||
def all_datasource_access(self, user=None): | ||
return self.can_access( | ||
'all_datasource_access', 'all_datasource_access', user=user) | ||
ALL_DATASOURCE_ACCESS, ALL_DATASOURCE_ACCESS, user=user) | ||
|
||
def database_access(self, database, user=None): | ||
return ( | ||
self.can_access( | ||
'all_database_access', 'all_database_access', user=user) or | ||
self.can_access('database_access', database.perm, user=user) | ||
ALL_DATABASE_ACCESS, ALL_DATABASE_ACCESS, user=user) or | ||
self.can_access(DATABASE_ACCESS, database.perm, user=user) | ||
) | ||
|
||
def schema_access(self, datasource, user=None): | ||
return ( | ||
self.database_access(datasource.database, user=user) or | ||
self.all_datasource_access(user=user) or | ||
self.can_access('schema_access', datasource.schema_perm, user=user) | ||
self.can_access(SCHEMA_ACCESS, datasource.schema_perm, user=user) | ||
) | ||
|
||
def datasource_access(self, datasource, user=None): | ||
return ( | ||
self.schema_access(datasource, user=user) or | ||
self.can_access('datasource_access', datasource.perm, user=user) | ||
self.can_access(DATASOURCE_ACCESS, datasource.perm, user=user) | ||
) | ||
|
||
def datasource_access_by_name( | ||
|
@@ -104,13 +111,13 @@ def datasource_access_by_name( | |
return True | ||
|
||
schema_perm = utils.get_schema_perm(database, schema) | ||
if schema and self.can_access('schema_access', schema_perm): | ||
if schema and self.can_access(SCHEMA_ACCESS, schema_perm): | ||
return True | ||
|
||
datasources = ConnectorRegistry.query_datasources_by_name( | ||
db.session, database, datasource_name, schema=schema) | ||
for datasource in datasources: | ||
if self.can_access('datasource_access', datasource.perm): | ||
if self.can_access(DATASOURCE_ACCESS, datasource.perm): | ||
return True | ||
return False | ||
|
||
|
@@ -138,7 +145,7 @@ def user_datasource_perms(self): | |
for perm in r.permissions: | ||
if ( | ||
perm.permission and | ||
'datasource_access' == perm.permission.name): | ||
DATASOURCE_ACCESS == perm.permission.name): | ||
datasource_perms.add(perm.view_menu.name) | ||
return datasource_perms | ||
|
||
|
@@ -149,7 +156,7 @@ def schemas_accessible_by_user(self, database, schemas): | |
subset = set() | ||
for schema in schemas: | ||
schema_perm = utils.get_schema_perm(database, schema) | ||
if self.can_access('schema_access', schema_perm): | ||
if self.can_access(SCHEMA_ACCESS, schema_perm): | ||
subset.add(schema) | ||
|
||
perms = self.user_datasource_perms() | ||
|
@@ -173,7 +180,7 @@ def accessible_by_user(self, database, datasource_names, schema=None): | |
|
||
if schema: | ||
schema_perm = utils.get_schema_perm(database, schema) | ||
if self.can_access('schema_access', schema_perm): | ||
if self.can_access(SCHEMA_ACCESS, schema_perm): | ||
return datasource_names | ||
|
||
user_perms = self.user_datasource_perms() | ||
|
@@ -299,13 +306,16 @@ def get_user_roles(self): | |
return get_user_roles() | ||
|
||
def get_all_permissions(self): | ||
"""Returns a set of tuples with the perm name and view menu name""" | ||
perms = set() | ||
"""Returns a map of view menu name to permission name""" | ||
permissions = dict() | ||
for role in self.get_user_roles(): | ||
for perm_view in role.permissions: | ||
t = (perm_view.permission.name, perm_view.view_menu.name) | ||
perms.add(t) | ||
return perms | ||
for perm in role.permissions: | ||
view_name = perm.view_menu.name | ||
perm_name = perm.permission.name | ||
perm_set = permissions.get(view_name, set()) | ||
perm_set.add(perm_name) | ||
permissions[perm.view_menu.name] = perm_set | ||
return permissions | ||
|
||
def has_role(self, role_name_or_list): | ||
"""Whether the user has this role name""" | ||
|
@@ -316,28 +326,52 @@ def has_role(self, role_name_or_list): | |
|
||
def has_perm(self, permission_name, view_menu_name): | ||
"""Whether the user has this perm""" | ||
return (permission_name, view_menu_name) in self.get_all_permissions() | ||
return permission_name in self.get_all_permissions().get( | ||
view_menu_name, {}) | ||
|
||
def get_view_menus(self, permission_name): | ||
"""Returns the details of view_menus for a perm name""" | ||
vm = set() | ||
for perm_name, vm_name in self.get_all_permissions(): | ||
if perm_name == permission_name: | ||
for vm_name, perm_names in list(self.get_all_permissions().items()): | ||
if permission_name in perm_names: | ||
vm.add(vm_name) | ||
return vm | ||
|
||
def has_all_datasource_access(self): | ||
return ( | ||
self.has_role(['Admin', 'Alpha']) or | ||
self.has_perm('all_datasource_access', 'all_datasource_access')) | ||
self.has_perm(ALL_DATASOURCE_ACCESS, ALL_DATASOURCE_ACCESS)) | ||
|
||
def datasources_with_access(self): | ||
datasources = ConnectorRegistry.get_all_datasources(db.session) | ||
permissions = self.get_all_permissions() | ||
if (ALL_DATASOURCE_ACCESS in permissions.get( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fabianmenges per my comment in #4737 I'm concerned that this could make the A possible performance improvement would for the case of a role where it contains either |
||
ALL_DATASOURCE_ACCESS, {}) or | ||
ALL_DATABASE_ACCESS in permissions.get( | ||
ALL_DATABASE_ACCESS, {})): | ||
return list(datasources) | ||
else: | ||
filtered = list() | ||
for ds in datasources: | ||
if (ds.database and | ||
DATABASE_ACCESS in permissions.get( | ||
ds.database.perm, {})): | ||
filtered.append(ds) | ||
if (ds.schema_perm | ||
and SCHEMA_ACCESS in permissions.get(ds.schema_perm, {})): | ||
filtered.append(ds) | ||
if DATASOURCE_ACCESS in permissions.get(ds.perm, {}): | ||
filtered.append(ds) | ||
return filtered | ||
|
||
|
||
class DatasourceFilter(SupersetFilter): | ||
|
||
def apply(self, query, func): # noqa | ||
if self.has_all_datasource_access(): | ||
return query | ||
perms = self.get_view_menus('datasource_access') | ||
# TODO(bogdan): add `schema_access` support here | ||
|
||
perms = [d.perm for d in self.datasources_with_access()] | ||
return query.filter(self.model.perm.in_(perms)) | ||
|
||
|
||
|
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 should be possible to avoid doing 2 database roundtrips when loading the slices or dashboard lists. If that doesn't work the second best option is to
@memoize
this method.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 can probably refactor and get rid of one call.
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 this is not easy, for the
DatasourceFilter
I can refactor to something like this:But that does not work for slices, since slices don't have a foreign key relationship to the datasource they reference (because we have different tables for Druid and SQL datasources).
If I'm memoizing the call (lets say for
datasources_with_access
), I have to handle cache invalidation when a new datasources is added. Cache invalidation does not seem to be implemented right now so I don't really want to pen that can of worms for this fix.I think the best I can do is refactor the
DatasourceFilter
and leave theSliceFilter
andDasboardFilter
as is.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 should be possible even without the FK, may be tricky though. You'll probably have to left join to
LEFT JOIN
to both tables and look for whether the it joins to either.