You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.4 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com>
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy of
  8. # this software and associated documentation files (the "Software"), to deal in
  9. # the Software without restriction, including without limitation the rights to
  10. # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  11. # the Software, and to permit persons to whom the Software is furnished to do so,
  12. # subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in all
  15. # copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  19. # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  20. # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  21. # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. from keycloak.exceptions import KeycloakPermissionFormatError, PermissionDefinitionError
  24. class UMAPermission:
  25. """A class to conveniently assembly permissions.
  26. The class itself is callable, and will return the assembled permission.
  27. Usage example:
  28. >>> r = Resource("Users")
  29. >>> s = Scope("delete")
  30. >>> permission = r(s)
  31. >>> print(permission)
  32. 'Users#delete'
  33. """
  34. def __init__(self, permission=None, resource="", scope=""):
  35. self.resource = resource
  36. self.scope = scope
  37. if permission:
  38. if not isinstance(permission, UMAPermission):
  39. raise PermissionDefinitionError(
  40. "can't determine if '{}' is a resource or scope".format(permission)
  41. )
  42. if permission.resource:
  43. self.resource = str(permission.resource)
  44. if permission.scope:
  45. self.scope = str(permission.scope)
  46. def __str__(self):
  47. scope = self.scope
  48. if scope:
  49. scope = "#" + scope
  50. return "{}{}".format(self.resource, scope)
  51. def __eq__(self, __o: object) -> bool:
  52. return str(self) == str(__o)
  53. def __repr__(self) -> str:
  54. return self.__str__()
  55. def __hash__(self) -> int:
  56. return hash(str(self))
  57. def __call__(self, permission=None, resource="", scope="") -> object:
  58. result_resource = self.resource
  59. result_scope = self.scope
  60. if resource:
  61. result_resource = str(resource)
  62. if scope:
  63. result_scope = str(scope)
  64. if permission:
  65. if not isinstance(permission, UMAPermission):
  66. raise PermissionDefinitionError(
  67. "can't determine if '{}' is a resource or scope".format(permission)
  68. )
  69. if permission.resource:
  70. result_resource = str(permission.resource)
  71. if permission.scope:
  72. result_scope = str(permission.scope)
  73. return UMAPermission(resource=result_resource, scope=result_scope)
  74. class Resource(UMAPermission):
  75. """An UMAPermission Resource class to conveniently assembly permissions.
  76. The class itself is callable, and will return the assembled permission.
  77. """
  78. def __init__(self, resource):
  79. super().__init__(resource=resource)
  80. class Scope(UMAPermission):
  81. """An UMAPermission Scope class to conveniently assembly permissions.
  82. The class itself is callable, and will return the assembled permission.
  83. """
  84. def __init__(self, scope):
  85. super().__init__(scope=scope)
  86. class AuthStatus:
  87. """A class that represents the authorization/login status of a user associated with a token.
  88. This has to evaluate to True if and only if the user is properly authorized
  89. for the requested resource."""
  90. def __init__(self, is_logged_in, is_authorized, missing_permissions):
  91. self.is_logged_in = is_logged_in
  92. self.is_authorized = is_authorized
  93. self.missing_permissions = missing_permissions
  94. def __bool__(self):
  95. return self.is_authorized
  96. def __repr__(self):
  97. return (
  98. f"AuthStatus("
  99. f"is_authorized={self.is_authorized}, "
  100. f"is_logged_in={self.is_logged_in}, "
  101. f"missing_permissions={self.missing_permissions})"
  102. )
  103. def build_permission_param(permissions):
  104. """
  105. Transform permissions to a set, so they are usable for requests
  106. :param permissions: either str (resource#scope),
  107. iterable[str] (resource#scope),
  108. dict[str,str] (resource: scope),
  109. dict[str,iterable[str]] (resource: scopes)
  110. :return: result bool
  111. """
  112. if permissions is None or permissions == "":
  113. return set()
  114. if isinstance(permissions, str):
  115. return set((permissions,))
  116. if isinstance(permissions, UMAPermission):
  117. return set((str(permissions),))
  118. try: # treat as dictionary of permissions
  119. result = set()
  120. for resource, scopes in permissions.items():
  121. print(f"resource={resource}scopes={scopes}")
  122. if scopes is None:
  123. result.add(resource)
  124. elif isinstance(scopes, str):
  125. result.add("{}#{}".format(resource, scopes))
  126. else:
  127. try:
  128. for scope in scopes:
  129. if not isinstance(scope, str):
  130. raise KeycloakPermissionFormatError(
  131. "misbuilt permission {}".format(permissions)
  132. )
  133. result.add("{}#{}".format(resource, scope))
  134. except TypeError:
  135. raise KeycloakPermissionFormatError(
  136. "misbuilt permission {}".format(permissions)
  137. )
  138. return result
  139. except AttributeError:
  140. pass
  141. try: # treat as any other iterable of permissions
  142. result = set()
  143. for permission in permissions:
  144. if not isinstance(permission, (str, UMAPermission)):
  145. raise KeycloakPermissionFormatError("misbuilt permission {}".format(permissions))
  146. result.add(str(permission))
  147. return result
  148. except TypeError:
  149. pass
  150. raise KeycloakPermissionFormatError("misbuilt permission {}".format(permissions))