flask-appbuilder 3.2.1__py3-none-any.whl → 5.0.2__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.
- flask_appbuilder/__init__.py +2 -3
- flask_appbuilder/_compat.py +0 -1
- flask_appbuilder/actions.py +14 -14
- flask_appbuilder/api/__init__.py +741 -527
- flask_appbuilder/api/convert.py +104 -98
- flask_appbuilder/api/manager.py +14 -8
- flask_appbuilder/api/schemas.py +12 -1
- flask_appbuilder/babel/manager.py +12 -16
- flask_appbuilder/base.py +353 -280
- flask_appbuilder/basemanager.py +1 -1
- flask_appbuilder/baseviews.py +241 -164
- flask_appbuilder/charts/jsontools.py +10 -10
- flask_appbuilder/charts/views.py +56 -60
- flask_appbuilder/cli.py +115 -70
- flask_appbuilder/const.py +52 -52
- flask_appbuilder/exceptions.py +67 -5
- flask_appbuilder/fields.py +32 -23
- flask_appbuilder/fieldwidgets.py +34 -27
- flask_appbuilder/filemanager.py +33 -45
- flask_appbuilder/filters.py +11 -13
- flask_appbuilder/forms.py +31 -35
- flask_appbuilder/hooks.py +90 -0
- flask_appbuilder/menu.py +35 -10
- flask_appbuilder/models/base.py +47 -57
- flask_appbuilder/models/decorators.py +13 -13
- flask_appbuilder/models/filters.py +42 -38
- flask_appbuilder/models/generic/__init__.py +29 -29
- flask_appbuilder/models/generic/filters.py +11 -3
- flask_appbuilder/models/generic/interface.py +1 -3
- flask_appbuilder/models/group.py +37 -39
- flask_appbuilder/models/mixins.py +22 -18
- flask_appbuilder/models/sqla/__init__.py +19 -72
- flask_appbuilder/models/sqla/base.py +24 -0
- flask_appbuilder/models/sqla/base_legacy.py +132 -0
- flask_appbuilder/models/sqla/filters.py +132 -19
- flask_appbuilder/models/sqla/interface.py +390 -276
- flask_appbuilder/security/api.py +31 -35
- flask_appbuilder/security/decorators.py +181 -83
- flask_appbuilder/security/forms.py +20 -31
- flask_appbuilder/security/manager.py +715 -489
- flask_appbuilder/security/registerviews.py +29 -112
- flask_appbuilder/security/schemas.py +43 -0
- flask_appbuilder/security/sqla/apis/__init__.py +8 -0
- flask_appbuilder/security/sqla/apis/group/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/group/api.py +227 -0
- flask_appbuilder/security/sqla/apis/group/schema.py +73 -0
- flask_appbuilder/security/sqla/apis/permission/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/permission/api.py +19 -0
- flask_appbuilder/security/sqla/apis/permission_view_menu/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/permission_view_menu/api.py +16 -0
- flask_appbuilder/security/sqla/apis/role/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/role/api.py +306 -0
- flask_appbuilder/security/sqla/apis/role/schema.py +27 -0
- flask_appbuilder/security/sqla/apis/user/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/user/api.py +292 -0
- flask_appbuilder/security/sqla/apis/user/schema.py +97 -0
- flask_appbuilder/security/sqla/apis/user/validator.py +27 -0
- flask_appbuilder/security/sqla/apis/view_menu/__init__.py +1 -0
- flask_appbuilder/security/sqla/apis/view_menu/api.py +18 -0
- flask_appbuilder/security/sqla/manager.py +421 -203
- flask_appbuilder/security/sqla/models.py +192 -57
- flask_appbuilder/security/utils.py +9 -0
- flask_appbuilder/security/views.py +232 -229
- flask_appbuilder/static/.DS_Store +0 -0
- flask_appbuilder/static/appbuilder/css/ab.css +20 -12
- flask_appbuilder/static/appbuilder/css/bootstrap-datepicker/bootstrap-datepicker3.min.css +7 -0
- flask_appbuilder/static/appbuilder/css/bootstrap.min.css.map +1 -0
- flask_appbuilder/static/appbuilder/css/flags/flags16.css +249 -245
- flask_appbuilder/static/appbuilder/css/fontawesome/all.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/brands.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/fontawesome.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/regular.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/solid.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/svg-with-js.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v4-font-face.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v4-shims.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/fontawesome/v5-font-face.min.css +6 -0
- flask_appbuilder/static/appbuilder/css/images/flags16.png +0 -0
- flask_appbuilder/static/appbuilder/css/select2/select2-bootstrap.min.css +7 -0
- flask_appbuilder/static/appbuilder/css/select2/select2.min.css +1 -0
- flask_appbuilder/static/appbuilder/css/swagger/swagger-ui.css +3 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-brands-400.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-regular-400.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-solid-900.woff2 +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.ttf +0 -0
- flask_appbuilder/static/appbuilder/css/webfonts/fa-v4compatibility.woff2 +0 -0
- flask_appbuilder/static/appbuilder/js/ab.js +33 -23
- flask_appbuilder/static/appbuilder/js/ab_filters.js +91 -84
- flask_appbuilder/static/appbuilder/js/bootstrap-datepicker/bootstrap-datepicker.min.js +8 -0
- flask_appbuilder/static/appbuilder/js/jquery-latest.js +2 -2
- flask_appbuilder/static/appbuilder/js/select2/select2.min.js +2 -0
- flask_appbuilder/static/appbuilder/js/swagger-ui-bundle.js +3 -0
- flask_appbuilder/templates/appbuilder/baselib.html +9 -3
- flask_appbuilder/templates/appbuilder/general/lib.html +60 -34
- flask_appbuilder/templates/appbuilder/general/model/edit.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/edit_cascade.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/search.html +3 -2
- flask_appbuilder/templates/appbuilder/general/model/show.html +1 -1
- flask_appbuilder/templates/appbuilder/general/model/show_cascade.html +1 -1
- flask_appbuilder/templates/appbuilder/general/security/login_db.html +7 -7
- flask_appbuilder/templates/appbuilder/general/security/login_ldap.html +5 -5
- flask_appbuilder/templates/appbuilder/general/security/login_oauth.html +24 -49
- flask_appbuilder/templates/appbuilder/general/widgets/base_list.html +2 -1
- flask_appbuilder/templates/appbuilder/general/widgets/chart.html +4 -2
- flask_appbuilder/templates/appbuilder/general/widgets/direct_chart.html +4 -3
- flask_appbuilder/templates/appbuilder/general/widgets/multiple_chart.html +3 -2
- flask_appbuilder/templates/appbuilder/general/widgets/search.html +11 -10
- flask_appbuilder/templates/appbuilder/init.html +37 -43
- flask_appbuilder/templates/appbuilder/navbar_menu.html +1 -1
- flask_appbuilder/templates/appbuilder/navbar_right.html +2 -2
- flask_appbuilder/templates/appbuilder/swagger/swagger.html +22 -19
- flask_appbuilder/translations/de/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/de/LC_MESSAGES/messages.po +305 -161
- flask_appbuilder/translations/fa/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/fa/LC_MESSAGES/messages.po +802 -0
- flask_appbuilder/translations/fr/LC_MESSAGES/messages.po +461 -319
- flask_appbuilder/translations/pt_BR/LC_MESSAGES/messages.po +650 -650
- flask_appbuilder/translations/ru/LC_MESSAGES/messages.po +1 -1
- flask_appbuilder/translations/sl/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/sl/LC_MESSAGES/messages.po +690 -0
- flask_appbuilder/translations/tr/LC_MESSAGES/messages.mo +0 -0
- flask_appbuilder/translations/tr/LC_MESSAGES/messages.po +1015 -0
- flask_appbuilder/upload.py +20 -22
- flask_appbuilder/urltools.py +39 -19
- flask_appbuilder/utils/base.py +76 -0
- flask_appbuilder/utils/legacy.py +33 -0
- flask_appbuilder/utils/limit.py +20 -0
- flask_appbuilder/validators.py +73 -14
- flask_appbuilder/views.py +75 -424
- flask_appbuilder/widgets.py +50 -51
- {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/METADATA +36 -76
- flask_appbuilder-5.0.2.dist-info/RECORD +240 -0
- {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/WHEEL +1 -1
- flask_appbuilder-5.0.2.dist-info/entry_points.txt +2 -0
- Flask_AppBuilder-3.2.1.dist-info/RECORD +0 -270
- Flask_AppBuilder-3.2.1.dist-info/entry_points.txt +0 -6
- flask_appbuilder/console.py +0 -426
- flask_appbuilder/models/mongoengine/__init__.py +0 -0
- flask_appbuilder/models/mongoengine/fields.py +0 -65
- flask_appbuilder/models/mongoengine/filters.py +0 -145
- flask_appbuilder/models/mongoengine/interface.py +0 -328
- flask_appbuilder/security/mongoengine/__init__.py +0 -0
- flask_appbuilder/security/mongoengine/manager.py +0 -402
- flask_appbuilder/security/mongoengine/models.py +0 -120
- flask_appbuilder/static/appbuilder/css/font-awesome.min.css +0 -4
- flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.css +0 -9
- flask_appbuilder/static/appbuilder/datepicker/bootstrap-datepicker.js +0 -28
- flask_appbuilder/static/appbuilder/fonts/FontAwesome.otf +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.eot +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.svg +0 -2671
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.ttf +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff +0 -0
- flask_appbuilder/static/appbuilder/fonts/fontawesome-webfont.woff2 +0 -0
- flask_appbuilder/static/appbuilder/img/aol.png +0 -0
- flask_appbuilder/static/appbuilder/img/flags/flags16.png +0 -0
- flask_appbuilder/static/appbuilder/img/flickr.png +0 -0
- flask_appbuilder/static/appbuilder/img/google.png +0 -0
- flask_appbuilder/static/appbuilder/img/myopenid.png +0 -0
- flask_appbuilder/static/appbuilder/img/yahoo.png +0 -0
- flask_appbuilder/static/appbuilder/js/_google_charts.js +0 -39
- flask_appbuilder/static/appbuilder/js/html5shiv.js +0 -8
- flask_appbuilder/static/appbuilder/js/respond.min.js +0 -6
- flask_appbuilder/static/appbuilder/select2/select2-spinner.gif +0 -0
- flask_appbuilder/static/appbuilder/select2/select2.css +0 -1205
- flask_appbuilder/static/appbuilder/select2/select2.js +0 -23
- flask_appbuilder/static/appbuilder/select2/select2.png +0 -0
- flask_appbuilder/static/appbuilder/select2/select2x2.png +0 -0
- flask_appbuilder/templates/appbuilder/general/security/login_oid.html +0 -129
- flask_appbuilder/templates/appbuilder/general/security/resetpassword.html +0 -29
- flask_appbuilder/tests/__init__.py +0 -0
- flask_appbuilder/tests/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_auth_ldap.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_auth_oauth.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_ldapsearch.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/_test_oauth_registration_role.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/base.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/base.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/config_api.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/config_api.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/const.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/const.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_0_fixture.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_api.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_api.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_fab_cli.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_menu.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_menu.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mongoengine.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mvc.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_mvc.cpython-37.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-36.pyc +0 -0
- flask_appbuilder/tests/__pycache__/test_sqlalchemy.cpython-37.pyc +0 -0
- flask_appbuilder/tests/_test_auth_ldap.py +0 -1045
- flask_appbuilder/tests/_test_auth_oauth.py +0 -419
- flask_appbuilder/tests/_test_ldapsearch.py +0 -135
- flask_appbuilder/tests/_test_oauth_registration_role.py +0 -59
- flask_appbuilder/tests/app.db +0 -0
- flask_appbuilder/tests/base.py +0 -90
- flask_appbuilder/tests/config_api.py +0 -21
- flask_appbuilder/tests/const.py +0 -9
- flask_appbuilder/tests/mongoengine/__init__.py +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/mongoengine/__pycache__/models.cpython-36.pyc +0 -0
- flask_appbuilder/tests/mongoengine/models.py +0 -41
- flask_appbuilder/tests/sqla/__init__.py +0 -0
- flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/__init__.cpython-37.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/models.cpython-36.pyc +0 -0
- flask_appbuilder/tests/sqla/__pycache__/models.cpython-37.pyc +0 -0
- flask_appbuilder/tests/sqla/models.py +0 -340
- flask_appbuilder/tests/test_0_fixture.py +0 -39
- flask_appbuilder/tests/test_api.py +0 -2790
- flask_appbuilder/tests/test_fab_cli.py +0 -72
- flask_appbuilder/tests/test_menu.py +0 -122
- flask_appbuilder/tests/test_mongoengine.py +0 -572
- flask_appbuilder/tests/test_mvc.py +0 -1710
- flask_appbuilder/tests/test_sqlalchemy.py +0 -24
- flask_appbuilder/translations/__pycache__/__init__.cpython-36.pyc +0 -0
- flask_appbuilder/translations/es/LC_MESSAGES/messages.po~ +0 -582
- {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/LICENSE +0 -0
- {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.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
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from .
|
|
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
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
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
|
-
|
|
30
|
-
|
|
44
|
+
Responsible for authentication, registering security views,
|
|
45
|
+
role and permission auto management
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
def register_views(self):
|
|
84
|
-
super(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
155
|
+
Add a registration request for the user.
|
|
111
156
|
|
|
112
|
-
|
|
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(
|
|
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.
|
|
126
|
-
self.
|
|
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
|
|
130
|
-
self.
|
|
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
|
-
|
|
184
|
+
Deletes registration object from database
|
|
136
185
|
|
|
137
|
-
|
|
186
|
+
:param register_user: RegisterUser object to delete
|
|
138
187
|
"""
|
|
139
188
|
try:
|
|
140
|
-
self.
|
|
141
|
-
self.
|
|
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
|
|
145
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
216
|
-
self.
|
|
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.
|
|
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.
|
|
225
|
-
self.
|
|
226
|
-
log.info(c.LOGMSG_INF_SEC_UPD_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
|
|
229
|
-
self.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
248
|
-
self.
|
|
249
|
-
|
|
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
|
|
253
|
-
self.
|
|
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.
|
|
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.
|
|
263
|
-
self.
|
|
264
|
-
log.info(c.LOGMSG_INF_SEC_UPD_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
|
|
267
|
-
self.
|
|
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.
|
|
353
|
+
return self.session.query(self.role_model).all()
|
|
277
354
|
|
|
278
355
|
def get_public_role(self):
|
|
279
356
|
return (
|
|
280
|
-
self.
|
|
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
|
-
|
|
400
|
+
Finds and returns a Permission by name
|
|
294
401
|
"""
|
|
295
402
|
return (
|
|
296
|
-
self.
|
|
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.
|
|
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.
|
|
336
|
-
return self.
|
|
337
|
-
return self.
|
|
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.
|
|
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
|
-
|
|
536
|
+
Adds a permission to the backend, model permission
|
|
365
537
|
|
|
366
|
-
|
|
367
|
-
|
|
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.
|
|
375
|
-
self.
|
|
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
|
|
379
|
-
self.
|
|
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
|
-
|
|
556
|
+
Deletes a permission from the backend, model permission
|
|
385
557
|
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
563
|
+
log.warning(c.LOGMSG_WAR_SEC_DEL_PERMISSION, name)
|
|
392
564
|
return False
|
|
393
565
|
try:
|
|
394
566
|
pvms = (
|
|
395
|
-
self.
|
|
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
|
|
572
|
+
log.warning(c.LOGMSG_WAR_SEC_DEL_PERM_PVM, perm, pvms)
|
|
401
573
|
return False
|
|
402
|
-
self.
|
|
403
|
-
self.
|
|
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
|
|
407
|
-
self.
|
|
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
|
-
|
|
590
|
+
Finds and returns a ViewMenu by name
|
|
419
591
|
"""
|
|
420
592
|
return (
|
|
421
|
-
self.
|
|
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.
|
|
597
|
+
return self.session.query(self.viewmenu_model).all()
|
|
428
598
|
|
|
429
599
|
def add_view_menu(self, name):
|
|
430
600
|
"""
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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.
|
|
441
|
-
self.
|
|
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
|
|
445
|
-
self.
|
|
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
|
-
|
|
620
|
+
Deletes a ViewMenu from the backend
|
|
451
621
|
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
627
|
+
log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU, name)
|
|
458
628
|
return False
|
|
459
629
|
try:
|
|
460
630
|
pvms = (
|
|
461
|
-
self.
|
|
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
|
|
636
|
+
log.warning(c.LOGMSG_WAR_SEC_DEL_VIEWMENU_PVM, view_menu, pvms)
|
|
467
637
|
return False
|
|
468
|
-
self.
|
|
469
|
-
self.
|
|
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
|
|
473
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
667
|
+
Finds all permissions from ViewMenu, returns list of PermissionView
|
|
498
668
|
|
|
499
|
-
|
|
500
|
-
|
|
669
|
+
:param view_menu: ViewMenu object
|
|
670
|
+
:return: list of PermissionView objects
|
|
501
671
|
"""
|
|
502
672
|
return (
|
|
503
|
-
self.
|
|
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
|
-
|
|
680
|
+
Adds a permission on a view or menu to the backend
|
|
511
681
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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.
|
|
695
|
+
pv.view_menu, pv.permission = vm, perm
|
|
526
696
|
try:
|
|
527
|
-
self.
|
|
528
|
-
self.
|
|
529
|
-
log.info(c.LOGMSG_INF_SEC_ADD_PERMVIEW
|
|
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
|
|
533
|
-
self.
|
|
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.
|
|
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
|
|
549
|
-
|
|
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.
|
|
556
|
-
self.
|
|
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.
|
|
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
|
|
571
|
-
self.
|
|
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
|
-
|
|
756
|
+
Add permission-ViewMenu object to Role
|
|
588
757
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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.
|
|
598
|
-
self.
|
|
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
|
|
604
|
-
self.
|
|
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
|
-
|
|
775
|
+
Remove permission-ViewMenu object to Role
|
|
609
776
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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.
|
|
619
|
-
self.
|
|
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
|
|
625
|
-
self.
|
|
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()
|