10000 Permissions Support 2.0 by t8y8 · Pull Request #429 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Permissions Support 2.0 #429

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

Merged
merged 22 commits into from
Jul 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
GroupItem, JobItem, BackgroundJobItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, PersonalAccessTokenAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem, \
SubscriptionItem, Target
SubscriptionItem, Target, PermissionsRule, Permission
from .server import RequestOptions, CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \
Server, ServerResponseError, MissingRequiredFieldError, NotSignedInError, Pager
from ._version import get_versions
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
from .view_item import ViewItem
from .workbook_item import WorkbookItem
from .subscription_item import SubscriptionItem
from .permissions_item import PermissionsRule, Permission
12 changes: 12 additions & 0 deletions tableauserverclient/models/datasource_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@ def __init__(self, project_id, name=None):
self.project_id = project_id
self.tags = set()

self._permissions = None

@property
def connections(self):
if self._connections is None:
error = 'Datasource item must be populated with connections first.'
raise UnpopulatedPropertyError(error)
return self._connections()

@property
def permissions(self):
if self._permissions is None:
error = "Project item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._permissions()

@property
def content_url(self):
return self._content_url
Expand Down Expand Up @@ -84,6 +93,9 @@ def updated_at(self):
def _set_connections(self, connections):
self._connections = connections

def _set_permissions(self, permissions):
self._permissions = permissions

def _parse_common_elements(self, datasource_xml, ns):
if not isinstance(datasource_xml, ET.Element):
datasource_xml = ET.fromstring(datasource_xml).find('.//t:datasource', namespaces=ns)
Expand Down
4 changes: 4 additions & 0 deletions tableauserverclient/models/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
class UnpopulatedPropertyError(Exception):
pass


class UnknownGranteeTypeError(Exception):
pass
13 changes: 12 additions & 1 deletion tableauserverclient/models/group_item.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import xml.etree.ElementTree as ET
from .exceptions import UnpopulatedPropertyError
from .property_decorators import property_not_empty
from .reference_item import ResourceReference


class GroupItem(object):
def __init__(self, name):

tag_name = 'group'

def __init__(self, name=None):
self._domain_name = None
self._id = None
self._users = None
Expand Down Expand Up @@ -35,6 +39,9 @@ def users(self):
# Each call to `.users` should create a new pager, this just runs the callable
return self._users()

def to_reference(self):
return ResourceReference(id_=self.id, tag_name=self.tag_name)

def _set_users(self, users):
self._users = users

Expand All @@ -53,3 +60,7 @@ def from_response(cls, resp, ns):
group_item._domain_name = domain_elem.get('name', None)
all_group_items.append(group_item)
return all_group_items

@staticmethod
def as_reference(id_):
return ResourceReference(id_, GroupItem.tag_name)
93 changes: 93 additions & 0 deletions tableauserverclient/models/permissions_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import xml.etree.ElementTree as ET
import logging

from .exceptions import UnknownGranteeTypeError
from .user_item import UserItem
from .group_item import GroupItem

logger = logging.getLogger('tableau.models.permissions_item')


class Permission:

class Mode:
Allow = 'Allow'
Deny = 'Deny'

class Capability:
AddComment = 'AddComment'
ChangeHierarchy = 'ChangeHierarchy'
ChangePermissions = 'ChangePermissions'
Connect = 'Connect'
Delete = 'Delete'
ExportData = 'ExportData'
ExportImage = 'ExportImage'
ExportXml = 'ExportXml'
Filter = 'Filter'
ProjectLeader = 'ProjectLeader'
Read = 'Read'
ShareView = 'ShareView'
ViewComments = 'ViewComments'
ViewUnderlyingData = 'ViewUnderlyingData'
WebAuthoring = 'WebAuthoring'
Write = 'Write'

class Resource:
Workbook = 'workbook'
Datasource = 'datasource'
Flow = 'flow'


class PermissionsRule(object):

def __init__(self, grantee, capabilities):
self.grantee = grantee
self.capabilities = capabilities

@classmethod
def from_response(cls, resp, ns=None):
parsed_response = ET.fromstring(resp)

rules = []
permissions_rules_list_xml = parsed_response.findall('.//t:granteeCapabilities',
namespaces=ns)

for grantee_capability_xml in permissions_rules_list_xml:
capability_dict = {}

grantee = PermissionsRule._parse_grantee_element(grantee_capability_xml, ns)

for capability_xml in grantee_capability_xml.findall(
'.//t:capabilities/t:capability', namespaces=ns):
name = capability_xml.get('name')
mode = capability_xml.get('mode')

capability_dict[name] = mode

rule = PermissionsRule(grantee,
capability_dict)
rules.append(rule)

return rules

@staticmethod
def _parse_grantee_element(grantee_capability_xml, ns):
"""Use Xpath magic and some string splitting to get the right object type from the xml"""

# Get the first element in the tree with an 'id' attribute
grantee_element = grantee_capability_xml.findall('.//*[@id]', namespaces=ns).pop()
grantee_id = grantee_element.get('id', None)
grantee_type = grantee_element.tag.split('}').pop()

if grantee_id is None:
logger.error('Cannot find grantee type in response')
raise UnknownGranteeTypeError()

if grantee_type == 'user':
grantee = UserItem.as_reference(grantee_id)
elif grantee_type == 'group':
grantee = GroupItem.as_reference(grantee_id)
else:
raise UnknownGranteeTypeError("No support for grantee type of {}".format(grantee_type))

return grantee
43 changes: 43 additions & 0 deletions tableauserverclient/models/project_item.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import xml.etree.ElementTree as ET

from .permissions_item import Permission

from .property_decorators import property_is_enum, property_not_empty
from .exceptions import UnpopulatedPropertyError


class ProjectItem(object):
Expand All @@ -15,10 +19,43 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N
self.content_permissions = content_permissions
self.parent_id = parent_id

self._permissions = None
self._default_workbook_permissions = None
self._default_datasource_permissions = None
self._default_flow_permissions = None

@property
def content_permissions(self):
return self._content_permissions

@property
def permissions(self):
if self._permissions is None:
error = "Project item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._permissions()

@property
def default_datasource_permissions(self):
if self._default_datasource_permissions is None:
error = "Project item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._default_datasource_permissions()

@property
def default_workbook_permissions(self):
if self._default_workbook_permissions is None:
error = "Project item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._default_workbook_permissions()

@property
def default_flow_permissions(self):
if self._default_flow_permissions is None:
error = "Project item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._default_flow_permissions()

@content_permissions.setter
@property_is_enum(ContentPermissions)
def content_permissions(self, value):
Expand Down Expand Up @@ -61,6 +98,12 @@ def _set_values(self, project_id, name, description, content_permissions, parent
if parent_id:
self.parent_id = parent_id

def _set_permissions(self, permissions):
self._permissions = permissions

def _set_default_permissions(self, permissions, content_type):
setattr(self, "_default_{content}_permissions".format(content=content_type), permissions)

@classmethod
def from_response(cls, resp, ns):
all_project_items = list()
Expand Down
21 changes: 21 additions & 0 deletions tableauserverclient/models/reference_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class ResourceReference(object):

def __init__(self, id_, tag_name):
self.id = id_
self.tag_name = tag_name

@property
def id(self):
return self._id

@id.setter
def id(self, value):
self._id = value

@property
def tag_name(self):
return self._tag_name

@tag_name.setter
def tag_name(self, value):
self._tag_name = value
13 changes: 12 additions & 1 deletion tableauserverclient/models/user_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
from .exceptions import UnpopulatedPropertyError
from .property_decorators import property_is_enum, property_not_empty, property_not_nullable
from ..datetime_helpers import parse_datetime
from .reference_item import ResourceReference


class UserItem(object):

tag_name = 'user'

class Roles:
Interactor = 'Interactor'
Publisher = 'Publisher'
Expand All @@ -30,7 +34,7 @@ class Auth:
SAML = 'SAML'
ServerDefault = 'ServerDefault'

def __init__(self, name, site_role, auth_setting=None):
def __init__(self, name=None, site_role=None, auth_setting=None):
self._auth_setting = None
self._domain_name = None
self._external_auth_user_id = None
Expand Down Expand Up @@ -94,6 +98,9 @@ def workbooks(self):
raise UnpopulatedPropertyError(error)
return self._workbooks()

def to_reference(self):
return ResourceReference(id_=self.id, tag_name=self.tag_name)

def _set_workbooks(self, workbooks):
self._workbooks = workbooks

Expand Down Expand Up @@ -140,6 +147,10 @@ def from_response(cls, resp, ns):
all_user_items.append(user_item)
return all_user_items

@staticmethod
def as_reference(id_):
return ResourceReference(id_, UserItem.tag_name)

@staticmethod
def _parse_element(user_xml, ns):
id = user_xml.get('id', None)
Expand Down
12 changes: 12 additions & 0 deletions tableauserverclient/models/workbook_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .property_decorators import property_not_nullable, property_is_boolean, property_is_materialized_views_config
from .tag_item import TagItem
from .view_item import ViewItem
from .permissions_item import PermissionsRule
from ..datetime_helpers import parse_datetime
import copy

Expand All @@ -27,6 +28,7 @@ def __init__(self, project_id, name=None, show_tabs=False):
self.tags = set()
self.materialized_views_config = {'materialized_views_enabled': None,
'run_materialization_now': None}
self._permissions = None

@property
def connections(self):
Expand All @@ -35,6 +37,13 @@ def connections(self):
raise UnpopulatedPropertyError(error)
return self._connections()

@property
def permissions(self):
if self._permissions is None:
error = "Workbook item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._permissions()

@property
def content_url(self):
return self._content_url
Expand Down Expand Up @@ -120,6 +129,9 @@ def materialized_views_config(self, value):
def _set_connections(self, connections):
self._connections = connections

def _set_permissions(self, permissions):
self._permissions = permissions

def _set_views(self, views):
self._views = views

Expand Down
2 changes: 1 addition & 1 deletion tableauserverclient/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .sort import Sort
from .. import ConnectionItem, DatasourceItem, JobItem, BackgroundJobItem, \
GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\
UserItem, ViewItem, WorkbookItem, TaskItem, SubscriptionItem
UserItem, ViewItem, WorkbookItem, TaskItem, SubscriptionItem, PermissionsRule, Permission
from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \
Sites, Users, Views, Workbooks, Subscriptions, ServerResponseError, \
MissingRequiredFieldError
Expand Down
Loading
0