-
Notifications
You must be signed in to change notification settings - Fork 447
Expand file tree
/
Copy pathpermissions_item.py
More file actions
153 lines (120 loc) · 6 KB
/
permissions_item.py
File metadata and controls
153 lines (120 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import xml.etree.ElementTree as ET
from typing import Optional
from defusedxml.ElementTree import fromstring
from tableauserverclient.models.exceptions import UnknownGranteeTypeError, UnpopulatedPropertyError
from tableauserverclient.models.group_item import GroupItem
from tableauserverclient.models.groupset_item import GroupSetItem
from tableauserverclient.models.reference_item import ResourceReference
from tableauserverclient.models.user_item import UserItem
from tableauserverclient.helpers.logging import logger
class Permission:
class Mode:
Allow = "Allow"
Deny = "Deny"
def __repr__(self):
return "<Enum Mode: Allow | Deny>"
class Capability:
AddComment = "AddComment"
ChangeHierarchy = "ChangeHierarchy"
ChangePermissions = "ChangePermissions"
Connect = "Connect"
Delete = "Delete"
Execute = "Execute"
ExportData = "ExportData"
ExportImage = "ExportImage"
ExportXml = "ExportXml"
Filter = "Filter"
ProjectLeader = "ProjectLeader"
Read = "Read"
ShareView = "ShareView"
ViewComments = "ViewComments"
ViewUnderlyingData = "ViewUnderlyingData"
VizqlDataApiAccess = "VizqlDataApiAccess"
WebAuthoring = "WebAuthoring"
Write = "Write"
RunExplainData = "RunExplainData"
CreateRefreshMetrics = "CreateRefreshMetrics"
SaveAs = "SaveAs"
PulseMetricDefine = "PulseMetricDefine"
def __repr__(self):
return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
class PermissionsRule:
def __init__(self, grantee: ResourceReference, capabilities: dict[str, str]) -> None:
self.grantee = grantee
self.capabilities = capabilities
def __repr__(self):
return f"<PermissionsRule grantee={self.grantee}, capabilities={self.capabilities}>"
def __eq__(self, other: object) -> bool:
if not hasattr(other, "grantee") or not hasattr(other, "capabilities"):
return False
return self.grantee == other.grantee and self.capabilities == other.capabilities
def __and__(self, other: "PermissionsRule") -> "PermissionsRule":
if self.grantee != other.grantee:
raise ValueError("Cannot AND two permissions rules with different grantees")
if self.capabilities == other.capabilities:
return self
capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
new_capabilities = {}
for capability in capabilities:
if (self.capabilities.get(capability), other.capabilities.get(capability)) == (
Permission.Mode.Allow,
Permission.Mode.Allow,
):
new_capabilities[capability] = Permission.Mode.Allow
elif Permission.Mode.Deny in (self.capabilities.get(capability), other.capabilities.get(capability)):
new_capabilities[capability] = Permission.Mode.Deny
return PermissionsRule(self.grantee, new_capabilities)
def __or__(self, other: "PermissionsRule") -> "PermissionsRule":
if self.grantee != other.grantee:
raise ValueError("Cannot OR two permissions rules with different grantees")
if self.capabilities == other.capabilities:
return self
capabilities = {*self.capabilities.keys(), *other.capabilities.keys()}
new_capabilities = {}
for capability in capabilities:
if Permission.Mode.Allow in (self.capabilities.get(capability), other.capabilities.get(capability)):
new_capabilities[capability] = Permission.Mode.Allow
elif (self.capabilities.get(capability), other.capabilities.get(capability)) == (
Permission.Mode.Deny,
Permission.Mode.Deny,
):
new_capabilities[capability] = Permission.Mode.Deny
return PermissionsRule(self.grantee, new_capabilities)
@classmethod
def from_response(cls, resp, ns=None) -> list["PermissionsRule"]:
parsed_response = 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: dict[str, str] = {}
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")
if name is None or mode is None:
logger.error(f"Capability was not valid: {capability_xml}")
raise UnpopulatedPropertyError()
else:
capability_dict[name] = mode
rule = PermissionsRule(grantee, capability_dict)
rules.append(rule)
return rules
@staticmethod
def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[dict[str, str]]) -> ResourceReference:
"""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)
elif grantee_type == "groupSet":
grantee = GroupSetItem.as_reference(grantee_id)
else:
raise UnknownGranteeTypeError(f"No support for grantee type of {grantee_type}")
return grantee