flask-appbuilder 3.2.1rc1__py3-none-any.whl → 5.0.2rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. flask_appbuilder/__init__.py +2 -3
  2. flask_appbuilder/_compat.py +0 -1
  3. flask_appbuilder/actions.py +14 -14
  4. flask_appbuilder/api/__init__.py +741 -527
  5. flask_appbuilder/api/convert.py +104 -98
  6. flask_appbuilder/api/manager.py +14 -8
  7. flask_appbuilder/api/schemas.py +12 -1
  8. flask_appbuilder/babel/manager.py +12 -16
  9. flask_appbuilder/base.py +353 -280
  10. flask_appbuilder/basemanager.py +1 -1
  11. flask_appbuilder/baseviews.py +241 -164
  12. flask_appbuilder/charts/jsontools.py +10 -10
  13. flask_appbuilder/charts/views.py +56 -60
  14. flask_appbuilder/cli.py +115 -70
  15. flask_appbuilder/const.py +52 -52
  16. flask_appbuilder/exceptions.py +67 -5
  17. flask_appbuilder/fields.py +32 -23
  18. flask_appbuilder/fieldwidgets.py +34 -27
  19. flask_appbuilder/filemanager.py +33 -45
  20. flask_appbuilder/filters.py +11 -13
  21. flask_appbuilder/forms.py +31 -35
  22. flask_appbuilder/hooks.py +90 -0
  23. flask_appbuilder/menu.py +35 -10
  24. flask_appbuilder/models/base.py +47 -57
  25. flask_appbuilder/models/decorators.py +13 -13
  26. flask_appbuilder/models/filters.py +42 -38
  27. flask_appbuilder/models/generic/__init__.py +29 -29
  28. flask_appbuilder/models/generic/filters.py +11 -3
  29. flask_appbuilder/models/generic/interface.py +1 -3
  30. flask_appbuilder/models/group.py +37 -39
  31. flask_appbuilder/models/mixins.py +22 -18
  32. flask_appbuilder/models/sqla/__init__.py +19 -72
  33. flask_appbuilder/models/sqla/base.py +24 -0
  34. flask_appbuilder/models/sqla/base_legacy.py +132 -0
  35. flask_appbuilder/models/sqla/filters.py +132 -19
  36. flask_appbuilder/models/sqla/interface.py +390 -276
  37. flask_appbuilder/security/api.py +31 -35
  38. flask_appbuilder/security/decorators.py +181 -83
  39. flask_appbuilder/security/forms.py +20 -31
  40. flask_appbuilder/security/manager.py +715 -489
  41. flask_appbuilder/security/registerviews.py +29 -112
  42. flask_appbuilder/security/schemas.py +43 -0
  43. flask_appbuilder/security/sqla/apis/__init__.py +8 -0
  44. flask_appbuilder/security/sqla/apis/group/__init__.py +1 -0
  45. flask_appbuilder/security/sqla/apis/group/api.py +227 -0
  46. flask_appbuilder/security/sqla/apis/group/schema.py +73 -0
  47. flask_appbuilder/security/sqla/apis/permission/__init__.py +1 -0
  48. flask_appbuilder/security/sqla/apis/permission/api.py +19 -0
  49. flask_appbuilder/security/sqla/apis/permission_view_menu/__init__.py +1 -0
  50. flask_appbuilder/security/sqla/apis/permission_view_menu/api.py +16 -0
  51. flask_appbuilder/security/sqla/apis/role/__init__.py +1 -0
  52. flask_appbuilder/security/sqla/apis/role/api.py +306 -0
  53. flask_appbuilder/security/sqla/apis/role/schema.py +27 -0
  54. flask_appbuilder/security/sqla/apis/user/__init__.py +1 -0
  55. flask_appbuilder/security/sqla/apis/user/api.py +292 -0
  56. flask_appbuilder/security/sqla/apis/user/schema.py +97 -0
  57. flask_appbuilder/security/sqla/apis/user/validator.py +27 -0
  58. flask_appbuilder/security/sqla/apis/view_menu/__init__.py +1 -0
  59. flask_appbuilder/security/sqla/apis/view_menu/api.py +18 -0
  60. flask_appbuilder/security/sqla/manager.py +421 -203
  61. flask_appbuilder/security/sqla/models.py +192 -57
  62. flask_appbuilder/security/utils.py +9 -0
  63. flask_appbuilder/security/views.py +232 -229
  64. flask_appbuilder/static/.DS_Store +0 -0
  65. flask_appbuilder/static/appbuilder/css/ab.css +20 -12
  66. flask_appbuilder/static/appbuilder/css/bootstrap-datepicker/bootstrap-datepicker3.min.css +7 -0
  67. flask_appbuilder/static/appbuilder/css/bootstrap.min.css.map +1 -0
  68. flask_appbuilder/static/appbuilder/css/flags/flags16.css +249 -245
  69. flask_appbuilder/static/appbuilder/css/fontawesome/all.min.css +6 -0
  70. flask_appbuilder/static/appbuilder/css/fontawesome/brands.min.css +6 -0
  71. flask_appbuilder/static/appbuilder/css/fontawesome/fontawesome.min.css +6 -0
  72. flask_appbuilder/static/appbuilder/css/fontawesome/regular.min.css +6 -0
  73. flask_appbuilder/static/appbuilder/css/fontawesome/solid.min.css +6 -0
  74. flask_appbuilder/static/appbuilder/css/fontawesome/svg-with-js.min.css +6 -0
  75. flask_appbuilder/static/appbuilder/css/fontawesome/v4-font-face.min.css +6 -0
  76. flask_appbuilder/static/appbuilder/css/fontawesome/v4-shims.min.css +6 -0
  77. flask_appbuilder/static/appbuilder/css/fontawesome/v5-font-face.min.css +6 -0
  78. flask_appbuilder/static/appbuilder/css/images/flags16.png +0 -0
  79. flask_appbuilder/static/appbuilder/css/select2/select2-bootstrap.min.css +7 -0
  80. flask_appbuilder/static/appbuilder/css/select2/select2.min.css +1 -0
  81. flask_appbuilder/static/appbuilder/css/swagger/swagger-ui.css +3 -0
  82. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.ttf +0 -0
  83. flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.woff2 +0 -0
  84. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.ttf +0 -0
  85. flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.woff2 +0 -0
  86. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.ttf +0 -0
  87. flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.woff2 +0 -0
  88. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.ttf +0 -0
  89. flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.woff2 +0 -0
  90. flask_appbuilder/static/appbuilder/js/ab.js +33 -23
  91. flask_appbuilder/static/appbuilder/js/ab_filters.js +91 -84
  92. flask_appbuilder/static/appbuilder/js/bootstrap-datepicker/bootstrap-datepicker.min.js +8 -0
  93. flask_appbuilder/static/appbuilder/js/jquery-latest.js +2 -2
  94. flask_appbuilder/static/appbuilder/js/select2/select2.min.js +2 -0
  95. flask_appbuilder/static/appbuilder/js/swagger-ui-bundle.js +3 -0
  96. flask_appbuilder/templates/appbuilder/baselib.html +9 -3
  97. flask_appbuilder/templates/appbuilder/general/lib.html +60 -34
  98. flask_appbuilder/templates/appbuilder/general/model/edit.html +1 -1
  99. flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +1 -1
  100. flask_appbuilder/templates/appbuilder/general/model/search.html +3 -2
  101. flask_appbuilder/templates/appbuilder/general/model/show.html +1 -1
  102. flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +1 -1
  103. flask_appbuilder/templates/appbuilder/general/security/login_db.html +7 -7
  104. flask_appbuilder/templates/appbuilder/general/security/login_ldap.html +5 -5
  105. flask_appbuilder/templates/appbuilder/general/security/login_oauth.html +24 -49
  106. flask_appbuilder/templates/appbuilder/general/widgets/base_list.html +2 -1
  107. flask_appbuilder/templates/appbuilder/general/widgets/chart.html +4 -2
  108. flask_appbuilder/templates/appbuilder/general/widgets/direct_chart.html +4 -3
  109. flask_appbuilder/templates/appbuilder/general/widgets/multiple_chart.html +3 -2
  110. flask_appbuilder/templates/appbuilder/general/widgets/search.html +11 -10
  111. flask_appbuilder/templates/appbuilder/init.html +37 -43
  112. flask_appbuilder/templates/appbuilder/navbar_menu.html +1 -1
  113. flask_appbuilder/templates/appbuilder/navbar_right.html +2 -2
  114. flask_appbuilder/templates/appbuilder/swagger/swagger.html +22 -19
  115. flask_appbuilder/translations/de/LC_MESSAGES/messages.mo +0 -0
  116. flask_appbuilder/translations/de/LC_MESSAGES/messages.po +305 -161
  117. flask_appbuilder/translations/fa/LC_MESSAGES/messages.mo +0 -0
  118. flask_appbuilder/translations/fa/LC_MESSAGES/messages.po +802 -0
  119. flask_appbuilder/translations/fr/LC_MESSAGES/messages.po +461 -319
  120. flask_appbuilder/translations/pt_BR/LC_MESSAGES/messages.po +650 -650
  121. flask_appbuilder/translations/ru/LC_MESSAGES/messages.po +1 -1
  122. flask_appbuilder/translations/sl/LC_MESSAGES/messages.mo +0 -0
  123. flask_appbuilder/translations/sl/LC_MESSAGES/messages.po +690 -0
  124. flask_appbuilder/translations/tr/LC_MESSAGES/messages.mo +0 -0
  125. flask_appbuilder/translations/tr/LC_MESSAGES/messages.po +1015 -0
  126. flask_appbuilder/upload.py +20 -22
  127. flask_appbuilder/urltools.py +39 -19
  128. flask_appbuilder/utils/base.py +76 -0
  129. flask_appbuilder/utils/legacy.py +33 -0
  130. flask_appbuilder/utils/limit.py +20 -0
  131. flask_appbuilder/validators.py +73 -14
  132. flask_appbuilder/views.py +75 -424
  133. flask_appbuilder/widgets.py +50 -51
  134. {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/METADATA +36 -76
  135. flask_appbuilder-5.0.2rc1.dist-info/RECORD +240 -0
  136. {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/WHEEL +1 -1
  137. flask_appbuilder-5.0.2rc1.dist-info/entry_points.txt +2 -0
  138. Flask_AppBuilder-3.2.1rc1.dist-info/RECORD +0 -270
  139. Flask_AppBuilder-3.2.1rc1.dist-info/entry_points.txt +0 -6
  140. flask_appbuilder/console.py +0 -426
  141. flask_appbuilder/models/mongoengine/__init__.py +0 -0
  142. flask_appbuilder/models/mongoengine/fields.py +0 -65
  143. flask_appbuilder/models/mongoengine/filters.py +0 -145
  144. flask_appbuilder/models/mongoengine/interface.py +0 -328
  145. flask_appbuilder/security/mongoengine/__init__.py +0 -0
  146. flask_appbuilder/security/mongoengine/manager.py +0 -402
  147. flask_appbuilder/security/mongoengine/models.py +0 -120
  148. flask_appbuilder/static/appbuilder/css/font-awesome.min.css +0 -4
  149. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.css +0 -9
  150. flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.js +0 -28
  151. flask_appbuilder/static/appbuilder/fonts/FontAwesome.otf +0 -0
  152. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.eot +0 -0
  153. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.svg +0 -2671
  154. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.ttf +0 -0
  155. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff +0 -0
  156. flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff2 +0 -0
  157. flask_appbuilder/static/appbuilder/img/aol.png +0 -0
  158. flask_appbuilder/static/appbuilder/img/flags/flags16.png +0 -0
  159. flask_appbuilder/static/appbuilder/img/flickr.png +0 -0
  160. flask_appbuilder/static/appbuilder/img/google.png +0 -0
  161. flask_appbuilder/static/appbuilder/img/myopenid.png +0 -0
  162. flask_appbuilder/static/appbuilder/img/yahoo.png +0 -0
  163. flask_appbuilder/static/appbuilder/js/_google_charts.js +0 -39
  164. flask_appbuilder/static/appbuilder/js/html5shiv.js +0 -8
  165. flask_appbuilder/static/appbuilder/js/respond.min.js +0 -6
  166. flask_appbuilder/static/appbuilder/select2/select2-spinner.gif +0 -0
  167. flask_appbuilder/static/appbuilder/select2/select2.css +0 -1205
  168. flask_appbuilder/static/appbuilder/select2/select2.js +0 -23
  169. flask_appbuilder/static/appbuilder/select2/select2.png +0 -0
  170. flask_appbuilder/static/appbuilder/select2/select2x2.png +0 -0
  171. flask_appbuilder/templates/appbuilder/general/security/login_oid.html +0 -129
  172. flask_appbuilder/templates/appbuilder/general/security/resetpassword.html +0 -29
  173. flask_appbuilder/tests/__init__.py +0 -0
  174. flask_appbuilder/tests/__pycache__/__init__.cpython-36.pyc +0 -0
  175. flask_appbuilder/tests/__pycache__/__init__.cpython-37.pyc +0 -0
  176. flask_appbuilder/tests/__pycache__/_test_auth_ldap.cpython-37.pyc +0 -0
  177. flask_appbuilder/tests/__pycache__/_test_auth_oauth.cpython-37.pyc +0 -0
  178. flask_appbuilder/tests/__pycache__/_test_ldapsearch.cpython-36.pyc +0 -0
  179. flask_appbuilder/tests/__pycache__/_test_oauth_registration_role.cpython-36.pyc +0 -0
  180. flask_appbuilder/tests/__pycache__/base.cpython-36.pyc +0 -0
  181. flask_appbuilder/tests/__pycache__/base.cpython-37.pyc +0 -0
  182. flask_appbuilder/tests/__pycache__/config_api.cpython-36.pyc +0 -0
  183. flask_appbuilder/tests/__pycache__/config_api.cpython-37.pyc +0 -0
  184. flask_appbuilder/tests/__pycache__/const.cpython-36.pyc +0 -0
  185. flask_appbuilder/tests/__pycache__/const.cpython-37.pyc +0 -0
  186. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-36.pyc +0 -0
  187. flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-37.pyc +0 -0
  188. flask_appbuilder/tests/__pycache__/test_api.cpython-36.pyc +0 -0
  189. flask_appbuilder/tests/__pycache__/test_api.cpython-37.pyc +0 -0
  190. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-36.pyc +0 -0
  191. flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-37.pyc +0 -0
  192. flask_appbuilder/tests/__pycache__/test_menu.cpython-36.pyc +0 -0
  193. flask_appbuilder/tests/__pycache__/test_menu.cpython-37.pyc +0 -0
  194. flask_appbuilder/tests/__pycache__/test_mongoengine.cpython-36.pyc +0 -0
  195. flask_appbuilder/tests/__pycache__/test_mvc.cpython-36.pyc +0 -0
  196. flask_appbuilder/tests/__pycache__/test_mvc.cpython-37.pyc +0 -0
  197. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-36.pyc +0 -0
  198. flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-37.pyc +0 -0
  199. flask_appbuilder/tests/_test_auth_ldap.py +0 -1045
  200. flask_appbuilder/tests/_test_auth_oauth.py +0 -419
  201. flask_appbuilder/tests/_test_ldapsearch.py +0 -135
  202. flask_appbuilder/tests/_test_oauth_registration_role.py +0 -59
  203. flask_appbuilder/tests/app.db +0 -0
  204. flask_appbuilder/tests/base.py +0 -90
  205. flask_appbuilder/tests/config_api.py +0 -21
  206. flask_appbuilder/tests/const.py +0 -9
  207. flask_appbuilder/tests/mongoengine/__init__.py +0 -0
  208. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-36.pyc +0 -0
  209. flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-37.pyc +0 -0
  210. flask_appbuilder/tests/mongoengine/__pycache__/models.cpython-36.pyc +0 -0
  211. flask_appbuilder/tests/mongoengine/models.py +0 -41
  212. flask_appbuilder/tests/sqla/__init__.py +0 -0
  213. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-36.pyc +0 -0
  214. flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-37.pyc +0 -0
  215. flask_appbuilder/tests/sqla/__pycache__/models.cpython-36.pyc +0 -0
  216. flask_appbuilder/tests/sqla/__pycache__/models.cpython-37.pyc +0 -0
  217. flask_appbuilder/tests/sqla/models.py +0 -340
  218. flask_appbuilder/tests/test_0_fixture.py +0 -39
  219. flask_appbuilder/tests/test_api.py +0 -2790
  220. flask_appbuilder/tests/test_fab_cli.py +0 -72
  221. flask_appbuilder/tests/test_menu.py +0 -122
  222. flask_appbuilder/tests/test_mongoengine.py +0 -572
  223. flask_appbuilder/tests/test_mvc.py +0 -1710
  224. flask_appbuilder/tests/test_sqlalchemy.py +0 -24
  225. flask_appbuilder/translations/__pycache__/__init__.cpython-36.pyc +0 -0
  226. flask_appbuilder/translations/es/LC_MESSAGES/messages.po~ +0 -582
  227. {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/LICENSE +0 -0
  228. {Flask_AppBuilder-3.2.1rc1.dist-info → flask_appbuilder-5.0.2rc1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ import json
1
5
  import logging
2
- from typing import List, Optional
6
+ from typing import Dict, List, Optional, Tuple, Union
3
7
  import uuid
4
8
 
5
- from sqlalchemy import and_, func, literal
6
- from sqlalchemy.engine.reflection import Inspector
7
- from sqlalchemy.orm.exc import MultipleResultsFound
8
- from werkzeug.security import generate_password_hash
9
-
10
- from .models import (
9
+ from flask import current_app, has_app_context
10
+ from flask_appbuilder import const as c
11
+ from flask_appbuilder import Model
12
+ from flask_appbuilder.models.sqla.interface import SQLAInterface
13
+ from flask_appbuilder.security.manager import BaseSecurityManager
14
+ from flask_appbuilder.security.sqla.apis import (
15
+ GroupApi,
16
+ PermissionApi,
17
+ PermissionViewMenuApi,
18
+ RoleApi,
19
+ UserApi,
20
+ ViewMenuApi,
21
+ )
22
+ from flask_appbuilder.security.sqla.models import (
11
23
  assoc_permissionview_role,
24
+ Group,
12
25
  Permission,
13
26
  PermissionView,
14
27
  RegisterUser,
@@ -16,37 +29,48 @@ from .models import (
16
29
  User,
17
30
  ViewMenu,
18
31
  )
19
- from ..manager import BaseSecurityManager
20
- from ... import const as c
21
- from ...models.sqla import Base
22
- from ...models.sqla.interface import SQLAInterface
32
+ from sqlalchemy import and_, func, literal, update
33
+ from sqlalchemy import inspect
34
+ from sqlalchemy.orm import contains_eager
35
+ from sqlalchemy.orm.exc import MultipleResultsFound
36
+ from werkzeug.security import generate_password_hash
37
+
23
38
 
24
39
  log = logging.getLogger(__name__)
25
40
 
26
41
 
27
42
  class SecurityManager(BaseSecurityManager):
28
43
  """
29
- Responsible for authentication, registering security views,
30
- role and permission auto management
44
+ Responsible for authentication, registering security views,
45
+ role and permission auto management
31
46
 
32
- If you want to change anything just inherit and override, then
33
- pass your own security manager to AppBuilder.
47
+ If you want to change anything just inherit and override, then
48
+ pass your own security manager to AppBuilder.
34
49
  """
35
50
 
36
51
  user_model = User
37
52
  """ Override to set your own User Model """
38
53
  role_model = Role
39
54
  """ Override to set your own Role Model """
55
+ group_model = Group
40
56
  permission_model = Permission
41
57
  viewmenu_model = ViewMenu
42
58
  permissionview_model = PermissionView
43
59
  registeruser_model = RegisterUser
44
60
 
61
+ # APIs
62
+ permission_api = PermissionApi
63
+ role_api = RoleApi
64
+ user_api = UserApi
65
+ view_menu_api = ViewMenuApi
66
+ permission_view_menu_api = PermissionViewMenuApi
67
+ group_api = GroupApi
68
+
45
69
  def __init__(self, appbuilder):
46
70
  """
47
- SecurityManager contructor
48
- param appbuilder:
49
- F.A.B AppBuilder main object
71
+ SecurityManager contructor
72
+ param appbuilder:
73
+ F.A.B AppBuilder main object
50
74
  """
51
75
  super(SecurityManager, self).__init__(appbuilder)
52
76
  user_datamodel = SQLAInterface(self.user_model)
@@ -54,8 +78,6 @@ class SecurityManager(BaseSecurityManager):
54
78
  self.userdbmodelview.datamodel = user_datamodel
55
79
  elif self.auth_type == c.AUTH_LDAP:
56
80
  self.userldapmodelview.datamodel = user_datamodel
57
- elif self.auth_type == c.AUTH_OID:
58
- self.useroidmodelview.datamodel = user_datamodel
59
81
  elif self.auth_type == c.AUTH_OAUTH:
60
82
  self.useroauthmodelview.datamodel = user_datamodel
61
83
  elif self.auth_type == c.AUTH_REMOTE_USER:
@@ -69,6 +91,7 @@ class SecurityManager(BaseSecurityManager):
69
91
  )
70
92
 
71
93
  self.rolemodelview.datamodel = SQLAInterface(self.role_model)
94
+ self.groupmodelview.datamodel = SQLAInterface(self.group_model)
72
95
  self.permissionmodelview.datamodel = SQLAInterface(self.permission_model)
73
96
  self.viewmenumodelview.datamodel = SQLAInterface(self.viewmenu_model)
74
97
  self.permissionviewmodelview.datamodel = SQLAInterface(
@@ -77,39 +100,61 @@ class SecurityManager(BaseSecurityManager):
77
100
  self.create_db()
78
101
 
79
102
  @property
80
- def get_session(self):
81
- return self.appbuilder.get_session
82
-
83
- def register_views(self):
84
- super(SecurityManager, self).register_views()
85
-
86
- def create_db(self):
87
- try:
88
- engine = self.get_session.get_bind(mapper=None, clause=None)
89
- inspector = Inspector.from_engine(engine)
90
- if "ab_user" not in inspector.get_table_names():
91
- log.info(c.LOGMSG_INF_SEC_NO_DB)
92
- Base.metadata.create_all(engine)
93
- log.info(c.LOGMSG_INF_SEC_ADD_DB)
94
- super(SecurityManager, self).create_db()
95
- except Exception as e:
96
- log.error(c.LOGMSG_ERR_SEC_CREATE_DB.format(str(e)))
97
- exit(1)
98
-
99
- def find_register_user(self, registration_hash):
103
+ def session(self):
104
+ return current_app.appbuilder.session
105
+
106
+ def register_views(self) -> None:
107
+ super().register_views()
108
+
109
+ if current_app.config.get("FAB_ADD_SECURITY_API", False):
110
+ self.appbuilder.add_api(self.permission_api)
111
+ self.appbuilder.add_api(self.role_api)
112
+ self.appbuilder.add_api(self.user_api)
113
+ self.appbuilder.add_api(self.view_menu_api)
114
+ self.appbuilder.add_api(self.permission_view_menu_api)
115
+ self.appbuilder.add_api(self.group_api)
116
+
117
+ def create_db(self) -> None:
118
+ if not current_app.config.get("FAB_CREATE_DB", True):
119
+ return
120
+ # Check if an application context does not exist
121
+ if not has_app_context():
122
+ # Create a new application context
123
+ with current_app.app_context():
124
+ self._create_db()
125
+ else:
126
+ self._create_db()
127
+
128
+ def _create_db(self) -> None:
129
+ engine = self.session.get_bind(mapper=None, clause=None)
130
+ inspector = inspect(engine)
131
+ existing_tables = inspector.get_table_names()
132
+ if "ab_user" not in existing_tables or "ab_group" not in existing_tables:
133
+ log.info(c.LOGMSG_INF_SEC_NO_DB)
134
+ Model.metadata.create_all(engine)
135
+ log.info(c.LOGMSG_INF_SEC_ADD_DB)
136
+ super().create_db()
137
+
138
+ def find_register_user(self, registration_hash: str) -> Optional[RegisterUser]:
100
139
  return (
101
- self.get_session.query(self.registeruser_model)
140
+ self.session.query(self.registeruser_model)
102
141
  .filter(self.registeruser_model.registration_hash == registration_hash)
103
142
  .scalar()
104
143
  )
105
144
 
106
145
  def add_register_user(
107
- self, username, first_name, last_name, email, password="", hashed_password=""
108
- ):
146
+ self,
147
+ username: str,
148
+ first_name: str,
149
+ last_name: str,
150
+ email: str,
151
+ password: str = "",
152
+ hashed_password: str = "",
153
+ ) -> User:
109
154
  """
110
- Add a registration request for the user.
155
+ Add a registration request for the user.
111
156
 
112
- :rtype : RegisterUser
157
+ :rtype : RegisterUser
113
158
  """
114
159
  register_user = self.registeruser_model()
115
160
  register_user.username = username
@@ -119,41 +164,45 @@ class SecurityManager(BaseSecurityManager):
119
164
  if hashed_password:
120
165
  register_user.password = hashed_password
121
166
  else:
122
- register_user.password = generate_password_hash(password)
167
+ register_user.password = generate_password_hash(
168
+ password=password,
169
+ method=current_app.config.get("FAB_PASSWORD_HASH_METHOD", "scrypt"),
170
+ salt_length=current_app.config.get("FAB_PASSWORD_HASH_SALT_LENGTH", 16),
171
+ )
123
172
  register_user.registration_hash = str(uuid.uuid1())
124
173
  try:
125
- self.get_session.add(register_user)
126
- self.get_session.commit()
174
+ self.session.add(register_user)
175
+ self.session.commit()
127
176
  return register_user
128
177
  except Exception as e:
129
- log.error(c.LOGMSG_ERR_SEC_ADD_REGISTER_USER.format(str(e)))
130
- self.appbuilder.get_session.rollback()
178
+ log.error(c.LOGMSG_ERR_SEC_ADD_REGISTER_USER, e)
179
+ self.session.rollback()
131
180
  return None
132
181
 
133
182
  def del_register_user(self, register_user):
134
183
  """
135
- Deletes registration object from database
184
+ Deletes registration object from database
136
185
 
137
- :param register_user: RegisterUser object to delete
186
+ :param register_user: RegisterUser object to delete
138
187
  """
139
188
  try:
140
- self.get_session.delete(register_user)
141
- self.get_session.commit()
189
+ self.session.delete(register_user)
190
+ self.session.commit()
142
191
  return True
143
192
  except Exception as e:
144
- log.error(c.LOGMSG_ERR_SEC_DEL_REGISTER_USER.format(str(e)))
145
- self.get_session.rollback()
193
+ log.error(c.LOGMSG_ERR_SEC_DEL_REGISTER_USER, e)
194
+ self.session.rollback()
146
195
  return False
147
196
 
148
197
  def find_user(self, username=None, email=None):
149
198
  """
150
- Finds user by username or email
199
+ Finds user by username or email
151
200
  """
152
201
  if username:
153
202
  try:
154
203
  if self.auth_username_ci:
155
204
  return (
156
- self.get_session.query(self.user_model)
205
+ self.session.query(self.user_model)
157
206
  .filter(
158
207
  func.lower(self.user_model.username) == func.lower(username)
159
208
  )
@@ -161,40 +210,45 @@ class SecurityManager(BaseSecurityManager):
161
210
  )
162
211
  else:
163
212
  return (
164
- self.get_session.query(self.user_model)
213
+ self.session.query(self.user_model)
165
214
  .filter(self.user_model.username == username)
166
215
  .one_or_none()
167
216
  )
168
217
  except MultipleResultsFound:
169
- log.error(f"Multiple results found for user {username}")
218
+ log.error("Multiple results found for user %s", username)
170
219
  return None
171
220
  elif email:
172
221
  try:
173
222
  return (
174
- self.get_session.query(self.user_model)
223
+ self.session.query(self.user_model)
175
224
  .filter_by(email=email)
176
225
  .one_or_none()
177
226
  )
178
227
  except MultipleResultsFound:
179
- log.error(f"Multiple results found for user with email {email}")
228
+ log.error("Multiple results found for user with email %s", email)
180
229
  return None
181
230
 
182
231
  def get_all_users(self):
183
- return self.get_session.query(self.user_model).all()
232
+ return self.session.query(self.user_model).all()
184
233
 
185
234
  def add_user(
186
235
  self,
187
- username,
188
- first_name,
189
- last_name,
190
- email,
191
- role,
192
- password="",
193
- hashed_password="",
236
+ username: str,
237
+ first_name: str,
238
+ last_name: str,
239
+ email: str,
240
+ role: Union[List[Role], Role, None] = None,
241
+ password: str = "",
242
+ hashed_password: str = "",
243
+ groups: Optional[List[Group]] = None,
194
244
  ):
195
245
  """
196
- Generic function to create user
246
+ Generic function to create user
197
247
  """
248
+ roles = []
249
+ if role:
250
+ roles = role if isinstance(role, list) else [role]
251
+
198
252
  try:
199
253
  user = self.user_model()
200
254
  user.first_name = first_name
@@ -202,35 +256,54 @@ class SecurityManager(BaseSecurityManager):
202
256
  user.username = username
203
257
  user.email = email
204
258
  user.active = True
205
- user.roles = role if isinstance(role, list) else [role]
259
+ user.roles = roles
260
+ user.groups = groups or []
206
261
  if hashed_password:
207
262
  user.password = hashed_password
208
263
  else:
209
- user.password = generate_password_hash(password)
210
- self.get_session.add(user)
211
- self.get_session.commit()
212
- log.info(c.LOGMSG_INF_SEC_ADD_USER.format(username))
264
+ user.password = generate_password_hash(
265
+ password=password,
266
+ method=current_app.config.get("FAB_PASSWORD_HASH_METHOD", "scrypt"),
267
+ salt_length=current_app.config.get(
268
+ "FAB_PASSWORD_HASH_SALT_LENGTH", 16
269
+ ),
270
+ )
271
+ self.session.add(user)
272
+ self.session.commit()
273
+ log.info(c.LOGMSG_INF_SEC_ADD_USER, username)
213
274
  return user
214
275
  except Exception as e:
215
- log.error(c.LOGMSG_ERR_SEC_ADD_USER.format(str(e)))
216
- self.get_session.rollback()
276
+ log.error(c.LOGMSG_ERR_SEC_ADD_USER, e)
277
+ self.session.rollback()
217
278
  return False
218
279
 
219
280
  def count_users(self):
220
- return self.get_session.query(func.count(self.user_model.id)).scalar()
281
+ return self.session.query(func.count(self.user_model.id)).scalar()
221
282
 
222
283
  def update_user(self, user):
223
284
  try:
224
- self.get_session.merge(user)
225
- self.get_session.commit()
226
- log.info(c.LOGMSG_INF_SEC_UPD_USER.format(user))
285
+ self.session.merge(user)
286
+ self.session.commit()
287
+ log.info(c.LOGMSG_INF_SEC_UPD_USER, user)
227
288
  except Exception as e:
228
- log.error(c.LOGMSG_ERR_SEC_UPD_USER.format(str(e)))
229
- self.get_session.rollback()
289
+ log.error(c.LOGMSG_ERR_SEC_UPD_USER, e)
290
+ self.session.rollback()
230
291
  return False
231
292
 
232
293
  def get_user_by_id(self, pk):
233
- return self.get_session.query(self.user_model).get(pk)
294
+ return self.session.get(self.user_model, pk)
295
+
296
+ def get_first_user(self) -> "User":
297
+ return self.session.query(self.user_model).first()
298
+
299
+ def noop_user_update(self, user: "User") -> None:
300
+ stmt = (
301
+ update(self.user_model)
302
+ .where(self.user_model.id == user.id)
303
+ .values(login_count=user.login_count)
304
+ )
305
+ self.session.execute(stmt)
306
+ self.session.commit()
234
307
 
235
308
  """
236
309
  -----------------------
@@ -238,50 +311,84 @@ class SecurityManager(BaseSecurityManager):
238
311
  -----------------------
239
312
  """
240
313
 
241
- def add_role(self, name: str) -> Optional[Role]:
314
+ def add_role(
315
+ self, name: str, permissions: Optional[List[PermissionView]] = None
316
+ ) -> Optional[Role]:
317
+ if not permissions:
318
+ permissions = []
319
+
242
320
  role = self.find_role(name)
243
321
  if role is None:
244
322
  try:
245
323
  role = self.role_model()
246
324
  role.name = name
247
- self.get_session.add(role)
248
- self.get_session.commit()
249
- log.info(c.LOGMSG_INF_SEC_ADD_ROLE.format(name))
325
+ role.permissions = permissions
326
+ self.session.add(role)
327
+ self.session.commit()
328
+ log.info(c.LOGMSG_INF_SEC_ADD_ROLE, name)
250
329
  return role
251
330
  except Exception as e:
252
- log.error(c.LOGMSG_ERR_SEC_ADD_ROLE.format(str(e)))
253
- self.get_session.rollback()
331
+ log.error(c.LOGMSG_ERR_SEC_ADD_ROLE, e)
332
+ self.session.rollback()
254
333
  return role
255
334
 
256
335
  def update_role(self, pk, name: str) -> Optional[Role]:
257
- role = self.get_session.query(self.role_model).get(pk)
336
+ role = self.session.query(self.role_model).get(pk)
258
337
  if not role:
259
338
  return
260
339
  try:
261
340
  role.name = name
262
- self.get_session.merge(role)
263
- self.get_session.commit()
264
- log.info(c.LOGMSG_INF_SEC_UPD_ROLE.format(role))
341
+ self.session.merge(role)
342
+ self.session.commit()
343
+ log.info(c.LOGMSG_INF_SEC_UPD_ROLE, role)
265
344
  except Exception as e:
266
- log.error(c.LOGMSG_ERR_SEC_UPD_ROLE.format(str(e)))
267
- self.get_session.rollback()
345
+ log.error(c.LOGMSG_ERR_SEC_UPD_ROLE, e)
346
+ self.session.rollback()
268
347
  return
269
348
 
270
349
  def find_role(self, name):
271
- return (
272
- self.get_session.query(self.role_model).filter_by(name=name).one_or_none()
273
- )
350
+ return self.session.query(self.role_model).filter_by(name=name).one_or_none()
274
351
 
275
352
  def get_all_roles(self):
276
- return self.get_session.query(self.role_model).all()
353
+ return self.session.query(self.role_model).all()
277
354
 
278
355
  def get_public_role(self):
279
356
  return (
280
- self.get_session.query(self.role_model)
357
+ self.session.query(self.role_model)
281
358
  .filter_by(name=self.auth_role_public)
282
359
  .one_or_none()
283
360
  )
284
361
 
362
+ def find_group(self, name: str) -> Group:
363
+ return self.session.query(self.group_model).filter_by(name=name).one_or_none()
364
+
365
+ def add_group(
366
+ self,
367
+ name: str,
368
+ label: str,
369
+ description: str,
370
+ roles: Optional[List[Role]] = None,
371
+ users: Optional[List[User]] = None,
372
+ ) -> Optional[Group]:
373
+ group = self.find_group(name)
374
+ if group is not None:
375
+ return group
376
+ try:
377
+ group = self.group_model()
378
+ group.name = name
379
+ group.label = label
380
+ group.description = description
381
+ group.roles = roles or []
382
+ group.users = users or []
383
+
384
+ self.session.add(group)
385
+ self.session.commit()
386
+ log.info(c.LOGMSG_INF_SEC_ADD_ROLE, name)
387
+ return group
388
+ except Exception as e:
389
+ log.error(c.LOGMSG_ERR_SEC_ADD_GROUP, e)
390
+ self.session.rollback()
391
+
285
392
  def get_public_permissions(self):
286
393
  role = self.get_public_role()
287
394
  if role:
@@ -290,12 +397,10 @@ class SecurityManager(BaseSecurityManager):
290
397
 
291
398
  def find_permission(self, name):
292
399
  """
293
- Finds and returns a Permission by name
400
+ Finds and returns a Permission by name
294
401
  """
295
402
  return (
296
- self.get_session.query(self.permission_model)
297
- .filter_by(name=name)
298
- .one_or_none()
403
+ self.session.query(self.permission_model).filter_by(name=name).one_or_none()
299
404
  )
300
405
 
301
406
  def exist_permission_on_roles(
@@ -311,7 +416,7 @@ class SecurityManager(BaseSecurityManager):
311
416
  :return: Boolean
312
417
  """
313
418
  q = (
314
- self.appbuilder.get_session.query(self.permissionview_model)
419
+ self.session.query(self.permissionview_model)
315
420
  .join(
316
421
  assoc_permissionview_role,
317
422
  and_(
@@ -332,15 +437,15 @@ class SecurityManager(BaseSecurityManager):
332
437
  .exists()
333
438
  )
334
439
  # Special case for MSSQL/Oracle (works on PG and MySQL > 8)
335
- if self.appbuilder.get_session.bind.dialect.name in ("mssql", "oracle"):
336
- return self.appbuilder.get_session.query(literal(True)).filter(q).scalar()
337
- return self.appbuilder.get_session.query(q).scalar()
440
+ if self.session.get_bind().name in ("mssql", "oracle"):
441
+ return self.session.query(literal(True)).filter(q).scalar()
442
+ return self.session.query(q).scalar()
338
443
 
339
444
  def find_roles_permission_view_menus(
340
445
  self, permission_name: str, role_ids: List[int]
341
446
  ):
342
447
  return (
343
- self.appbuilder.get_session.query(self.permissionview_model)
448
+ self.session.query(self.permissionview_model)
344
449
  .join(
345
450
  assoc_permissionview_role,
346
451
  and_(
@@ -359,52 +464,119 @@ class SecurityManager(BaseSecurityManager):
359
464
  )
360
465
  ).all()
361
466
 
467
+ def get_user_roles_permissions(self, user) -> Dict[str, List[Tuple[str, str]]]:
468
+ """
469
+ Utility method for fetching all roles and permissions for a specific user.
470
+ Example of the returned data:
471
+ ```
472
+ {
473
+ 'Admin': [
474
+ ('can_this_form_get', 'ResetPasswordView'),
475
+ ('can_this_form_post', 'ResetPasswordView'),
476
+ ...
477
+ ]
478
+ 'EmptyRole': [],
479
+ }
480
+ ```
481
+ """
482
+ if not user.roles and not user.groups:
483
+ raise AttributeError("User object does not have roles or groups")
484
+
485
+ result: Dict[str, List[Tuple[str, str]]] = {}
486
+ db_roles_ids = []
487
+ roles = self.get_user_roles(user)
488
+ for role in roles:
489
+ # Make sure all db roles are included on the result
490
+ result[role.name] = []
491
+ if role.name in self.builtin_roles:
492
+ for permission in self.builtin_roles[role.name]:
493
+ result[role.name].append((permission[1], permission[0]))
494
+ else:
495
+ db_roles_ids.append(role.id)
496
+
497
+ permission_views = (
498
+ self.session.query(PermissionView)
499
+ .join(Permission)
500
+ .join(ViewMenu)
501
+ .join(PermissionView.role)
502
+ .filter(Role.id.in_(db_roles_ids))
503
+ .options(contains_eager(PermissionView.permission))
504
+ .options(contains_eager(PermissionView.view_menu))
505
+ .options(contains_eager(PermissionView.role))
506
+ ).all()
507
+
508
+ for permission_view in permission_views:
509
+ for role_item in permission_view.role:
510
+ if role_item.name in result:
511
+ result[role_item.name].append(
512
+ (
513
+ permission_view.permission.name,
514
+ permission_view.view_menu.name,
515
+ )
516
+ )
517
+ return result
518
+
519
+ def get_db_role_permissions(self, role_id: int) -> List[PermissionView]:
520
+ """
521
+ Get all DB permissions from a role (one single query)
522
+ """
523
+ return (
524
+ self.session.query(PermissionView)
525
+ .join(Permission)
526
+ .join(ViewMenu)
527
+ .join(PermissionView.role)
528
+ .filter(Role.id == role_id)
529
+ .options(contains_eager(PermissionView.permission))
530
+ .options(contains_eager(PermissionView.view_menu))
531
+ .all()
532
+ )
533
+
362
534
  def add_permission(self, name):
363
535
  """
364
- Adds a permission to the backend, model permission
536
+ Adds a permission to the backend, model permission
365
537
 
366
- :param name:
367
- name of the permission: 'can_add','can_edit' etc...
538
+ :param name:
539
+ name of the permission: 'can_add','can_edit' etc...
368
540
  """
369
541
  perm = self.find_permission(name)
370
542
  if perm is None:
371
543
  try:
372
544
  perm = self.permission_model()
373
545
  perm.name = name
374
- self.get_session.add(perm)
375
- self.get_session.commit()
546
+ self.session.add(perm)
547
+ self.session.commit()
376
548
  return perm
377
549
  except Exception as e:
378
- log.error(c.LOGMSG_ERR_SEC_ADD_PERMISSION.format(str(e)))
379
- self.get_session.rollback()
550
+ log.error(c.LOGMSG_ERR_SEC_ADD_PERMISSION, e)
551
+ self.session.rollback()
380
552
  return perm
381
553
 
382
554
  def del_permission(self, name: str) -> bool:
383
555
  """
384
- Deletes a permission from the backend, model permission
556
+ Deletes a permission from the backend, model permission
385
557
 
386
- :param name:
387
- name of the permission: 'can_add','can_edit' etc...
558
+ :param name:
559
+ name of the permission: 'can_add','can_edit' etc...
388
560
  """
389
561
  perm = self.find_permission(name)
390
562
  if not perm:
391
- log.warning(c.LOGMSG_WAR_SEC_DEL_PERMISSION.format(name))
563
+ log.warning(c.LOGMSG_WAR_SEC_DEL_PERMISSION, name)
392
564
  return False
393
565
  try:
394
566
  pvms = (
395
- self.get_session.query(self.permissionview_model)
567
+ self.session.query(self.permissionview_model)
396
568
  .filter(self.permissionview_model.permission == perm)
397
569
  .all()
398
570
  )
399
571
  if pvms:
400
- log.warning(c.LOGMSG_WAR_SEC_DEL_PERM_PVM.format(perm, pvms))
572
+ log.warning(c.LOGMSG_WAR_SEC_DEL_PERM_PVM, perm, pvms)
401
573
  return False
402
- self.get_session.delete(perm)
403
- self.get_session.commit()
574
+ self.session.delete(perm)
575
+ self.session.commit()
404
576
  return True
405
577
  except Exception as e:
406
- log.error(c.LOGMSG_ERR_SEC_DEL_PERMISSION.format(str(e)))
407
- self.get_session.rollback()
578
+ log.error(c.LOGMSG_ERR_SEC_DEL_PERMISSION, e)
579
+ self.session.rollback()
408
580
  return False
409
581
 
410
582
  """
@@ -415,62 +587,60 @@ class SecurityManager(BaseSecurityManager):
415
587
 
416
588
  def find_view_menu(self, name):
417
589
  """
418
- Finds and returns a ViewMenu by name
590
+ Finds and returns a ViewMenu by name
419
591
  """
420
592
  return (
421
- self.get_session.query(self.viewmenu_model)
422
- .filter_by(name=name)
423
- .one_or_none()
593
+ self.session.query(self.viewmenu_model).filter_by(name=name).one_or_none()
424
594
  )
425
595
 
426
596
  def get_all_view_menu(self):
427
- return self.get_session.query(self.viewmenu_model).all()
597
+ return self.session.query(self.viewmenu_model).all()
428
598
 
429
599
  def add_view_menu(self, name):
430
600
  """
431
- Adds a view or menu to the backend, model view_menu
432
- param name:
433
- name of the view menu to add
601
+ Adds a view or menu to the backend, model view_menu
602
+ param name:
603
+ name of the view menu to add
434
604
  """
435
605
  view_menu = self.find_view_menu(name)
436
606
  if view_menu is None:
437
607
  try:
438
608
  view_menu = self.viewmenu_model()
439
609
  view_menu.name = name
440
- self.get_session.add(view_menu)
441
- self.get_session.commit()
610
+ self.session.add(view_menu)
611
+ self.session.commit()
442
612
  return view_menu
443
613
  except Exception as e:
444
- log.error(c.LOGMSG_ERR_SEC_ADD_VIEWMENU.format(str(e)))
445
- self.get_session.rollback()
614
+ log.error(c.LOGMSG_ERR_SEC_ADD_VIEWMENU, e)
615
+ self.session.rollback()
446
616
  return view_menu
447
617
 
448
618
  def del_view_menu(self, name: str) -> bool:
449
619
  """
450
- Deletes a ViewMenu from the backend
620
+ Deletes a ViewMenu from the backend
451
621
 
452
- :param name:
453
- name of the ViewMenu
622
+ :param name:
623
+ name of the ViewMenu
454
624
  """
455
625
  view_menu = self.find_view_menu(name)
456
626
  if not view_menu:
457
- log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU.format(name))
627
+ log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU, name)
458
628
  return False
459
629
  try:
460
630
  pvms = (
461
- self.get_session.query(self.permissionview_model)
631
+ self.session.query(self.permissionview_model)
462
632
  .filter(self.permissionview_model.view_menu == view_menu)
463
633
  .all()
464
634
  )
465
635
  if pvms:
466
- log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU_PVM.format(view_menu, pvms))
636
+ log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU_PVM, view_menu, pvms)
467
637
  return False
468
- self.get_session.delete(view_menu)
469
- self.get_session.commit()
638
+ self.session.delete(view_menu)
639
+ self.session.commit()
470
640
  return True
471
641
  except Exception as e:
472
- log.error(c.LOGMSG_ERR_SEC_DEL_PERMISSION.format(str(e)))
473
- self.get_session.rollback()
642
+ log.error(c.LOGMSG_ERR_SEC_DEL_PERMISSION, e)
643
+ self.session.rollback()
474
644
  return False
475
645
 
476
646
  """
@@ -481,38 +651,38 @@ class SecurityManager(BaseSecurityManager):
481
651
 
482
652
  def find_permission_view_menu(self, permission_name, view_menu_name):
483
653
  """
484
- Finds and returns a PermissionView by names
654
+ Finds and returns a PermissionView by names
485
655
  """
486
656
  permission = self.find_permission(permission_name)
487
657
  view_menu = self.find_view_menu(view_menu_name)
488
658
  if permission and view_menu:
489
659
  return (
490
- self.get_session.query(self.permissionview_model)
660
+ self.session.query(self.permissionview_model)
491
661
  .filter_by(permission=permission, view_menu=view_menu)
492
662
  .one_or_none()
493
663
  )
494
664
 
495
665
  def find_permissions_view_menu(self, view_menu):
496
666
  """
497
- Finds all permissions from ViewMenu, returns list of PermissionView
667
+ Finds all permissions from ViewMenu, returns list of PermissionView
498
668
 
499
- :param view_menu: ViewMenu object
500
- :return: list of PermissionView objects
669
+ :param view_menu: ViewMenu object
670
+ :return: list of PermissionView objects
501
671
  """
502
672
  return (
503
- self.get_session.query(self.permissionview_model)
673
+ self.session.query(self.permissionview_model)
504
674
  .filter_by(view_menu_id=view_menu.id)
505
675
  .all()
506
676
  )
507
677
 
508
678
  def add_permission_view_menu(self, permission_name, view_menu_name):
509
679
  """
510
- Adds a permission on a view or menu to the backend
680
+ Adds a permission on a view or menu to the backend
511
681
 
512
- :param permission_name:
513
- name of the permission to add: 'can_add','can_edit' etc...
514
- :param view_menu_name:
515
- name of the view menu to add
682
+ :param permission_name:
683
+ name of the permission to add: 'can_add','can_edit' etc...
684
+ :param view_menu_name:
685
+ name of the view menu to add
516
686
  """
517
687
  if not (permission_name and view_menu_name):
518
688
  return None
@@ -522,15 +692,15 @@ class SecurityManager(BaseSecurityManager):
522
692
  vm = self.add_view_menu(view_menu_name)
523
693
  perm = self.add_permission(permission_name)
524
694
  pv = self.permissionview_model()
525
- pv.view_menu_id, pv.permission_id = vm.id, perm.id
695
+ pv.view_menu, pv.permission = vm, perm
526
696
  try:
527
- self.get_session.add(pv)
528
- self.get_session.commit()
529
- log.info(c.LOGMSG_INF_SEC_ADD_PERMVIEW.format(str(pv)))
697
+ self.session.add(pv)
698
+ self.session.commit()
699
+ log.info(c.LOGMSG_INF_SEC_ADD_PERMVIEW, pv)
530
700
  return pv
531
701
  except Exception as e:
532
- log.error(c.LOGMSG_ERR_SEC_ADD_PERMVIEW.format(str(e)))
533
- self.get_session.rollback()
702
+ log.error(c.LOGMSG_ERR_SEC_ADD_PERMVIEW, e)
703
+ self.session.rollback()
534
704
 
535
705
  def del_permission_view_menu(self, permission_name, view_menu_name, cascade=True):
536
706
  if not (permission_name and view_menu_name):
@@ -539,36 +709,35 @@ class SecurityManager(BaseSecurityManager):
539
709
  if not pv:
540
710
  return
541
711
  roles_pvs = (
542
- self.get_session.query(self.role_model)
712
+ self.session.query(self.role_model)
543
713
  .filter(self.role_model.permissions.contains(pv))
544
714
  .first()
545
715
  )
546
716
  if roles_pvs:
547
717
  log.warning(
548
- c.LOGMSG_WAR_SEC_DEL_PERMVIEW.format(
549
- view_menu_name, permission_name, roles_pvs
550
- )
718
+ c.LOGMSG_WAR_SEC_DEL_PERMVIEW,
719
+ view_menu_name,
720
+ permission_name,
721
+ roles_pvs,
551
722
  )
552
723
  return
553
724
  try:
554
725
  # delete permission on view
555
- self.get_session.delete(pv)
556
- self.get_session.commit()
726
+ self.session.delete(pv)
727
+ self.session.commit()
557
728
  # if no more permission on permission view, delete permission
558
729
  if not cascade:
559
730
  return
560
731
  if (
561
- not self.get_session.query(self.permissionview_model)
732
+ not self.session.query(self.permissionview_model)
562
733
  .filter_by(permission=pv.permission)
563
734
  .all()
564
735
  ):
565
736
  self.del_permission(pv.permission.name)
566
- log.info(
567
- c.LOGMSG_INF_SEC_DEL_PERMVIEW.format(permission_name, view_menu_name)
568
- )
737
+ log.info(c.LOGMSG_INF_SEC_DEL_PERMVIEW, permission_name, view_menu_name)
569
738
  except Exception as e:
570
- log.error(c.LOGMSG_ERR_SEC_DEL_PERMVIEW.format(str(e)))
571
- self.get_session.rollback()
739
+ log.error(c.LOGMSG_ERR_SEC_DEL_PERMVIEW, e)
740
+ self.session.rollback()
572
741
 
573
742
  def exist_permission_on_views(self, lst, item):
574
743
  for i in lst:
@@ -584,42 +753,91 @@ class SecurityManager(BaseSecurityManager):
584
753
 
585
754
  def add_permission_role(self, role, perm_view):
586
755
  """
587
- Add permission-ViewMenu object to Role
756
+ Add permission-ViewMenu object to Role
588
757
 
589
- :param role:
590
- The role object
591
- :param perm_view:
592
- The PermissionViewMenu object
758
+ :param role:
759
+ The role object
760
+ :param perm_view:
761
+ The PermissionViewMenu object
593
762
  """
594
763
  if perm_view and perm_view not in role.permissions:
595
764
  try:
596
765
  role.permissions.append(perm_view)
597
- self.get_session.merge(role)
598
- self.get_session.commit()
599
- log.info(
600
- c.LOGMSG_INF_SEC_ADD_PERMROLE.format(str(perm_view), role.name)
601
- )
766
+ self.session.merge(role)
767
+ self.session.commit()
768
+ log.info(c.LOGMSG_INF_SEC_ADD_PERMROLE, perm_view, role.name)
602
769
  except Exception as e:
603
- log.error(c.LOGMSG_ERR_SEC_ADD_PERMROLE.format(str(e)))
604
- self.get_session.rollback()
770
+ log.error(c.LOGMSG_ERR_SEC_ADD_PERMROLE, e)
771
+ self.session.rollback()
605
772
 
606
773
  def del_permission_role(self, role, perm_view):
607
774
  """
608
- Remove permission-ViewMenu object to Role
775
+ Remove permission-ViewMenu object to Role
609
776
 
610
- :param role:
611
- The role object
612
- :param perm_view:
613
- The PermissionViewMenu object
777
+ :param role:
778
+ The role object
779
+ :param perm_view:
780
+ The PermissionViewMenu object
614
781
  """
615
782
  if perm_view in role.permissions:
616
783
  try:
617
784
  role.permissions.remove(perm_view)
618
- self.get_session.merge(role)
619
- self.get_session.commit()
620
- log.info(
621
- c.LOGMSG_INF_SEC_DEL_PERMROLE.format(str(perm_view), role.name)
622
- )
785
+ self.session.merge(role)
786
+ self.session.commit()
787
+ log.info(c.LOGMSG_INF_SEC_DEL_PERMROLE, perm_view, role.name)
623
788
  except Exception as e:
624
- log.error(c.LOGMSG_ERR_SEC_DEL_PERMROLE.format(str(e)))
625
- self.get_session.rollback()
789
+ log.error(c.LOGMSG_ERR_SEC_DEL_PERMROLE, e)
790
+ self.session.rollback()
791
+
792
+ def export_roles(
793
+ self, path: Optional[str] = None, indent: Optional[Union[int, str]] = None
794
+ ) -> None:
795
+ """Exports roles to JSON file."""
796
+ log.error("BIND URL: %s", self.session.get_bind().url)
797
+ timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
798
+ filename = path or f"roles_export_{timestamp}.json"
799
+
800
+ serialized_roles: List[Dict[str, List[Dict[str, str]]]] = []
801
+
802
+ for role in self.get_all_roles():
803
+ serialized_role = {"name": role.name, "permissions": []}
804
+ for pvm in role.permissions:
805
+ permission = pvm.permission
806
+ view_menu = pvm.view_menu
807
+ permission_view_menu = {
808
+ "permission": {"name": permission.name},
809
+ "view_menu": {"name": view_menu.name},
810
+ }
811
+ serialized_role["permissions"].append(permission_view_menu)
812
+ serialized_roles.append(serialized_role)
813
+
814
+ with open(filename, "w") as fd:
815
+ fd.write(json.dumps(serialized_roles, indent=indent))
816
+
817
+ def import_roles(self, path: str) -> None:
818
+ """Imports roles from JSON file."""
819
+
820
+ session = self.session()
821
+
822
+ with open(path, "r") as fd:
823
+ roles_json = json.loads(fd.read())
824
+
825
+ roles = []
826
+
827
+ for role_kwargs in roles_json:
828
+ role = self.add_role(role_kwargs["name"])
829
+ permission_view_menus = [
830
+ self.add_permission_view_menu(
831
+ permission_name=pvm_kwargs["permission"]["name"],
832
+ view_menu_name=pvm_kwargs["view_menu"]["name"],
833
+ )
834
+ for pvm_kwargs in role_kwargs["permissions"]
835
+ ]
836
+
837
+ for permission_view_menu in permission_view_menus:
838
+ if permission_view_menu not in role.permissions:
839
+ role.permissions.append(permission_view_menu)
840
+ roles.append(role)
841
+
842
+ session.add_all(roles)
843
+ session.commit()