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
|
@@ -2,10 +2,10 @@ __author__ = "Daniel Gaspar"
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
|
-
from flask import flash, redirect, request,
|
|
5
|
+
from flask import current_app, flash, redirect, request, url_for
|
|
6
6
|
from flask_babel import lazy_gettext
|
|
7
7
|
|
|
8
|
-
from .forms import
|
|
8
|
+
from .forms import RegisterUserDBForm
|
|
9
9
|
from .. import const as c
|
|
10
10
|
from .._compat import as_unicode
|
|
11
11
|
from ..validators import Unique
|
|
@@ -24,29 +24,29 @@ def get_first_last_name(fullname):
|
|
|
24
24
|
|
|
25
25
|
class BaseRegisterUser(PublicFormView):
|
|
26
26
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
Make your own user registration view and inherit from this class if you
|
|
28
|
+
want to implement a completely different registration process. If not,
|
|
29
|
+
just inherit from RegisterUserDBView or RegisterUserOAuthView depending on
|
|
30
|
+
your authentication method.
|
|
31
|
+
then override SecurityManager property that defines the class to use::
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
from flask_appbuilder.security.registerviews import RegisterUserDBView
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
class MyRegisterUserDBView(BaseRegisterUser):
|
|
36
|
+
email_template = 'register_mail.html'
|
|
37
|
+
...
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
class MySecurityManager(SecurityManager):
|
|
41
|
+
registeruserdbview = MyRegisterUserDBView
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
When instantiating AppBuilder set your own SecurityManager class::
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
appbuilder = AppBuilder(
|
|
46
|
+
app,
|
|
47
|
+
db.session,
|
|
48
|
+
security_manager_class=MySecurityManager
|
|
49
|
+
)
|
|
50
50
|
"""
|
|
51
51
|
|
|
52
52
|
route_base = "/register"
|
|
@@ -69,14 +69,14 @@ class BaseRegisterUser(PublicFormView):
|
|
|
69
69
|
|
|
70
70
|
def send_email(self, register_user):
|
|
71
71
|
"""
|
|
72
|
-
|
|
72
|
+
Method for sending the registration Email to the user
|
|
73
73
|
"""
|
|
74
74
|
try:
|
|
75
75
|
from flask_mail import Mail, Message
|
|
76
76
|
except Exception:
|
|
77
77
|
log.error("Install Flask-Mail to use User registration")
|
|
78
78
|
return False
|
|
79
|
-
mail = Mail(
|
|
79
|
+
mail = Mail(current_app)
|
|
80
80
|
msg = Message()
|
|
81
81
|
msg.subject = self.email_subject
|
|
82
82
|
url = url_for(
|
|
@@ -95,7 +95,7 @@ class BaseRegisterUser(PublicFormView):
|
|
|
95
95
|
try:
|
|
96
96
|
mail.send(msg)
|
|
97
97
|
except Exception as e:
|
|
98
|
-
log.error("Send email exception:
|
|
98
|
+
log.error("Send email exception: %s", e)
|
|
99
99
|
return False
|
|
100
100
|
return True
|
|
101
101
|
|
|
@@ -120,13 +120,13 @@ class BaseRegisterUser(PublicFormView):
|
|
|
120
120
|
@expose("/activation/<string:activation_hash>")
|
|
121
121
|
def activation(self, activation_hash):
|
|
122
122
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
Endpoint to expose an activation url, this url
|
|
124
|
+
is sent to the user by email, when accessed the user is inserted
|
|
125
|
+
and activated
|
|
126
126
|
"""
|
|
127
127
|
reg = self.appbuilder.sm.find_register_user(activation_hash)
|
|
128
128
|
if not reg:
|
|
129
|
-
log.error(c.LOGMSG_ERR_SEC_NO_REGISTER_HASH
|
|
129
|
+
log.error(c.LOGMSG_ERR_SEC_NO_REGISTER_HASH, activation_hash)
|
|
130
130
|
flash(as_unicode(self.false_error_message), "danger")
|
|
131
131
|
return redirect(self.appbuilder.get_url_for_index)
|
|
132
132
|
if not self.appbuilder.sm.add_user(
|
|
@@ -164,7 +164,7 @@ class BaseRegisterUser(PublicFormView):
|
|
|
164
164
|
|
|
165
165
|
class RegisterUserDBView(BaseRegisterUser):
|
|
166
166
|
"""
|
|
167
|
-
|
|
167
|
+
View for Registering a new user, auth db mode
|
|
168
168
|
"""
|
|
169
169
|
|
|
170
170
|
form = RegisterUserDBForm
|
|
@@ -185,95 +185,12 @@ class RegisterUserDBView(BaseRegisterUser):
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
class RegisterUserOIDView(BaseRegisterUser):
|
|
189
|
-
"""
|
|
190
|
-
View for Registering a new user, auth OID mode
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
route_base = "/register"
|
|
194
|
-
|
|
195
|
-
form = RegisterUserOIDForm
|
|
196
|
-
default_view = "form_oid_post"
|
|
197
|
-
|
|
198
|
-
@expose("/formoidone", methods=["GET", "POST"])
|
|
199
|
-
def form_oid_post(self, flag=True):
|
|
200
|
-
if flag:
|
|
201
|
-
self.oid_login_handler(self.form_oid_post, self.appbuilder.sm.oid)
|
|
202
|
-
form = LoginForm_oid()
|
|
203
|
-
if form.validate_on_submit():
|
|
204
|
-
session["remember_me"] = form.remember_me.data
|
|
205
|
-
return self.appbuilder.sm.oid.try_login(
|
|
206
|
-
form.openid.data, ask_for=["email", "fullname"]
|
|
207
|
-
)
|
|
208
|
-
resp = session.pop("oid_resp", None)
|
|
209
|
-
if resp:
|
|
210
|
-
self._init_vars()
|
|
211
|
-
form = self.form.refresh()
|
|
212
|
-
self.form_get(form)
|
|
213
|
-
form.username.data = resp.email
|
|
214
|
-
first_name, last_name = get_first_last_name(resp.fullname)
|
|
215
|
-
form.first_name.data = first_name
|
|
216
|
-
form.last_name.data = last_name
|
|
217
|
-
form.email.data = resp.email
|
|
218
|
-
widgets = self._get_edit_widget(form=form)
|
|
219
|
-
# self.update_redirect()
|
|
220
|
-
return self.render_template(
|
|
221
|
-
self.form_template,
|
|
222
|
-
title=self.form_title,
|
|
223
|
-
widgets=widgets,
|
|
224
|
-
form_action="form",
|
|
225
|
-
appbuilder=self.appbuilder,
|
|
226
|
-
)
|
|
227
|
-
else:
|
|
228
|
-
flash(as_unicode(self.error_message), "warning")
|
|
229
|
-
return redirect(self.get_redirect())
|
|
230
|
-
|
|
231
|
-
def oid_login_handler(self, f, oid):
|
|
232
|
-
"""
|
|
233
|
-
Hackish method to make use of oid.login_handler decorator.
|
|
234
|
-
"""
|
|
235
|
-
from flask_openid import OpenIDResponse, SessionWrapper
|
|
236
|
-
from openid.consumer.consumer import CANCEL, Consumer, SUCCESS
|
|
237
|
-
|
|
238
|
-
if request.args.get("openid_complete") != u"yes":
|
|
239
|
-
return f(False)
|
|
240
|
-
consumer = Consumer(SessionWrapper(self), oid.store_factory())
|
|
241
|
-
openid_response = consumer.complete(
|
|
242
|
-
request.args.to_dict(), oid.get_current_url()
|
|
243
|
-
)
|
|
244
|
-
if openid_response.status == SUCCESS:
|
|
245
|
-
return self.after_login(OpenIDResponse(openid_response, []))
|
|
246
|
-
elif openid_response.status == CANCEL:
|
|
247
|
-
oid.signal_error(u"The request was cancelled")
|
|
248
|
-
return redirect(oid.get_current_url())
|
|
249
|
-
oid.signal_error(u"OpenID authentication error")
|
|
250
|
-
return redirect(oid.get_current_url())
|
|
251
|
-
|
|
252
|
-
def after_login(self, resp):
|
|
253
|
-
"""
|
|
254
|
-
Method that adds the return OpenID response object on the session
|
|
255
|
-
this session key will be deleted
|
|
256
|
-
"""
|
|
257
|
-
session["oid_resp"] = resp
|
|
258
|
-
|
|
259
|
-
def form_get(self, form):
|
|
260
|
-
self.add_form_unique_validations(form)
|
|
261
|
-
|
|
262
|
-
def form_post(self, form):
|
|
263
|
-
self.add_registration(
|
|
264
|
-
username=form.username.data,
|
|
265
|
-
first_name=form.first_name.data,
|
|
266
|
-
last_name=form.last_name.data,
|
|
267
|
-
email=form.email.data,
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
271
188
|
class RegisterUserOAuthView(BaseRegisterUser):
|
|
272
189
|
"""
|
|
273
|
-
|
|
190
|
+
View for Registering a new user, auth OAuth mode
|
|
274
191
|
"""
|
|
275
192
|
|
|
276
|
-
form =
|
|
193
|
+
form = RegisterUserDBForm
|
|
277
194
|
|
|
278
195
|
def form_get(self, form):
|
|
279
196
|
self.add_form_unique_validations(form)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from flask import current_app
|
|
4
|
+
from flask_appbuilder.const import (
|
|
5
|
+
API_SECURITY_PROVIDER_DB,
|
|
6
|
+
API_SECURITY_PROVIDER_LDAP,
|
|
7
|
+
AUTH_DB,
|
|
8
|
+
AUTH_LDAP,
|
|
9
|
+
)
|
|
10
|
+
from marshmallow import fields, Schema, ValidationError
|
|
11
|
+
from marshmallow.validate import Length, OneOf
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
provider_to_auth_type = {"db": AUTH_DB, "ldap": AUTH_LDAP}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_password(value: Union[bytes, bytearray, str]) -> None:
|
|
18
|
+
if value and sum(value.encode()) == 0:
|
|
19
|
+
raise ValidationError("Password null is not allowed")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_provider(value: Union[bytes, bytearray, str]) -> None:
|
|
23
|
+
if not current_app.appbuilder.sm.api_login_allow_multiple_providers:
|
|
24
|
+
provider_name = current_app.appbuilder.sm.auth_type_provider_name
|
|
25
|
+
if provider_name and provider_name != value:
|
|
26
|
+
raise ValidationError("Alternative authentication provider is not allowed")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LoginPost(Schema):
|
|
30
|
+
username = fields.String(required=True, allow_none=False, validate=Length(min=1))
|
|
31
|
+
password = fields.String(
|
|
32
|
+
validate=[Length(min=1), validate_password], required=True, allow_none=False
|
|
33
|
+
)
|
|
34
|
+
provider = fields.String(
|
|
35
|
+
validate=[
|
|
36
|
+
OneOf([API_SECURITY_PROVIDER_DB, API_SECURITY_PROVIDER_LDAP]),
|
|
37
|
+
validate_provider,
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
refresh = fields.Boolean(required=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
login_post = LoginPost()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from flask_appbuilder.security.sqla.apis.group import GroupApi # noqa: F401
|
|
2
|
+
from flask_appbuilder.security.sqla.apis.permission import PermissionApi # noqa: F401
|
|
3
|
+
from flask_appbuilder.security.sqla.apis.permission_view_menu import ( # noqa: F401
|
|
4
|
+
PermissionViewMenuApi,
|
|
5
|
+
)
|
|
6
|
+
from flask_appbuilder.security.sqla.apis.role import RoleApi # noqa: F401
|
|
7
|
+
from flask_appbuilder.security.sqla.apis.user import UserApi # noqa: F401
|
|
8
|
+
from flask_appbuilder.security.sqla.apis.view_menu import ViewMenuApi # noqa: F401
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import GroupApi # noqa: F401
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from flask import request
|
|
2
|
+
from flask_appbuilder import ModelRestApi
|
|
3
|
+
from flask_appbuilder.api import expose, safe
|
|
4
|
+
from flask_appbuilder.const import API_RESULT_RES_KEY
|
|
5
|
+
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
|
6
|
+
from flask_appbuilder.security.decorators import permission_name, protect
|
|
7
|
+
from flask_appbuilder.security.sqla.apis.group.schema import (
|
|
8
|
+
GroupPostSchema,
|
|
9
|
+
GroupPutSchema,
|
|
10
|
+
)
|
|
11
|
+
from flask_appbuilder.security.sqla.models import Group, Role, User
|
|
12
|
+
from marshmallow import ValidationError
|
|
13
|
+
from sqlalchemy.exc import IntegrityError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GroupApi(ModelRestApi):
|
|
17
|
+
resource_name = "security/groups"
|
|
18
|
+
openapi_spec_tag = "Security Groups"
|
|
19
|
+
class_permission_name = "Group"
|
|
20
|
+
datamodel = SQLAInterface(Group)
|
|
21
|
+
allow_browser_login = True
|
|
22
|
+
|
|
23
|
+
list_columns = [
|
|
24
|
+
"id",
|
|
25
|
+
"name",
|
|
26
|
+
"label",
|
|
27
|
+
"description",
|
|
28
|
+
"roles.name",
|
|
29
|
+
"roles.id",
|
|
30
|
+
"users.id",
|
|
31
|
+
"users.username",
|
|
32
|
+
]
|
|
33
|
+
show_columns = list_columns
|
|
34
|
+
edit_columns = ["name", "label", "description", "users", "roles"]
|
|
35
|
+
add_columns = edit_columns
|
|
36
|
+
search_columns = list_columns
|
|
37
|
+
|
|
38
|
+
add_model_schema = GroupPostSchema()
|
|
39
|
+
edit_model_schema = GroupPutSchema()
|
|
40
|
+
|
|
41
|
+
openapi_spec_component_schemas = (
|
|
42
|
+
GroupPostSchema,
|
|
43
|
+
GroupPutSchema,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@expose("/", methods=["POST"])
|
|
47
|
+
@protect()
|
|
48
|
+
@safe
|
|
49
|
+
@permission_name("post")
|
|
50
|
+
def post(self):
|
|
51
|
+
"""Create new group
|
|
52
|
+
---
|
|
53
|
+
post:
|
|
54
|
+
requestBody:
|
|
55
|
+
description: Model schema
|
|
56
|
+
required: true
|
|
57
|
+
content:
|
|
58
|
+
application/json:
|
|
59
|
+
schema:
|
|
60
|
+
$ref: '#/components/schemas/GroupPostSchema'
|
|
61
|
+
responses:
|
|
62
|
+
201:
|
|
63
|
+
description: Group created
|
|
64
|
+
content:
|
|
65
|
+
application/json:
|
|
66
|
+
schema:
|
|
67
|
+
type: object
|
|
68
|
+
properties:
|
|
69
|
+
result:
|
|
70
|
+
$ref: '#/components/schemas/GroupPostSchema'
|
|
71
|
+
400:
|
|
72
|
+
$ref: '#/components/responses/400'
|
|
73
|
+
401:
|
|
74
|
+
$ref: '#/components/responses/401'
|
|
75
|
+
422:
|
|
76
|
+
$ref: '#/components/responses/422'
|
|
77
|
+
500:
|
|
78
|
+
$ref: '#/components/responses/500'
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
item = self.add_model_schema.load(request.json)
|
|
82
|
+
model = Group()
|
|
83
|
+
roles = []
|
|
84
|
+
users = []
|
|
85
|
+
|
|
86
|
+
for key, value in item.items():
|
|
87
|
+
if key == "roles":
|
|
88
|
+
roles = self._fetch_entities(Role, value)
|
|
89
|
+
missing_role_ids = set(item["roles"]) - {r.id for r in roles}
|
|
90
|
+
if missing_role_ids:
|
|
91
|
+
return self.response_400(
|
|
92
|
+
message={
|
|
93
|
+
"roles": [
|
|
94
|
+
(
|
|
95
|
+
f"Role(s) with ID(s) {sorted(missing_role_ids)} "
|
|
96
|
+
"do not exist."
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
elif key == "users":
|
|
102
|
+
users = self._fetch_entities(User, value)
|
|
103
|
+
missing_user_ids = set(item["users"]) - {u.id for u in users}
|
|
104
|
+
if missing_user_ids:
|
|
105
|
+
return self.response_400(
|
|
106
|
+
message={
|
|
107
|
+
"users": [
|
|
108
|
+
(
|
|
109
|
+
f"User(s) with ID(s) {sorted(missing_user_ids)} "
|
|
110
|
+
"do not exist."
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
setattr(model, key, value)
|
|
117
|
+
|
|
118
|
+
model.roles = roles
|
|
119
|
+
model.users = users
|
|
120
|
+
|
|
121
|
+
self.pre_add(model)
|
|
122
|
+
self.datamodel.add(model)
|
|
123
|
+
|
|
124
|
+
return self.response(201, id=model.id)
|
|
125
|
+
|
|
126
|
+
except ValidationError as error:
|
|
127
|
+
return self.response_400(message=error.messages)
|
|
128
|
+
except IntegrityError as e:
|
|
129
|
+
return self.response_422(message=str(e.orig))
|
|
130
|
+
|
|
131
|
+
@expose("/<pk>", methods=["PUT"])
|
|
132
|
+
@protect()
|
|
133
|
+
@safe
|
|
134
|
+
@permission_name("put")
|
|
135
|
+
def put(self, pk):
|
|
136
|
+
"""Edit group
|
|
137
|
+
---
|
|
138
|
+
put:
|
|
139
|
+
parameters:
|
|
140
|
+
- in: path
|
|
141
|
+
schema:
|
|
142
|
+
type: integer
|
|
143
|
+
name: pk
|
|
144
|
+
requestBody:
|
|
145
|
+
description: Model schema
|
|
146
|
+
required: true
|
|
147
|
+
content:
|
|
148
|
+
application/json:
|
|
149
|
+
schema:
|
|
150
|
+
$ref: '#/components/schemas/GroupPutSchema'
|
|
151
|
+
responses:
|
|
152
|
+
200:
|
|
153
|
+
description: Group updated
|
|
154
|
+
content:
|
|
155
|
+
application/json:
|
|
156
|
+
schema:
|
|
157
|
+
type: object
|
|
158
|
+
properties:
|
|
159
|
+
result:
|
|
160
|
+
$ref: '#/components/schemas/GroupPutSchema'
|
|
161
|
+
400:
|
|
162
|
+
$ref: '#/components/responses/400'
|
|
163
|
+
401:
|
|
164
|
+
$ref: '#/components/responses/401'
|
|
165
|
+
404:
|
|
166
|
+
$ref: '#/components/responses/404'
|
|
167
|
+
422:
|
|
168
|
+
$ref: '#/components/responses/422'
|
|
169
|
+
500:
|
|
170
|
+
$ref: '#/components/responses/500'
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
item = self.edit_model_schema.load(request.json)
|
|
174
|
+
model = self.datamodel.get(pk, self._base_filters)
|
|
175
|
+
if not model:
|
|
176
|
+
return self.response_404()
|
|
177
|
+
|
|
178
|
+
roles = []
|
|
179
|
+
users = []
|
|
180
|
+
|
|
181
|
+
for key, value in item.items():
|
|
182
|
+
if key == "roles":
|
|
183
|
+
roles = self._fetch_entities(Role, value)
|
|
184
|
+
missing_role_ids = set(value) - {r.id for r in roles}
|
|
185
|
+
if missing_role_ids:
|
|
186
|
+
return self.response_400(
|
|
187
|
+
message={
|
|
188
|
+
"roles": [
|
|
189
|
+
(
|
|
190
|
+
f"Role(s) with ID(s) {sorted(missing_role_ids)} "
|
|
191
|
+
"do not exist."
|
|
192
|
+
)
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
elif key == "users":
|
|
197
|
+
users = self._fetch_entities(User, value)
|
|
198
|
+
missing_user_ids = set(value) - {u.id for u in users}
|
|
199
|
+
if missing_user_ids:
|
|
200
|
+
return self.response_400(
|
|
201
|
+
message={
|
|
202
|
+
"users": [
|
|
203
|
+
(
|
|
204
|
+
f"User(s) with ID(s) {sorted(missing_user_ids)} "
|
|
205
|
+
"do not exist."
|
|
206
|
+
)
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
setattr(model, key, value)
|
|
212
|
+
|
|
213
|
+
if "roles" in item.keys():
|
|
214
|
+
model.roles = roles
|
|
215
|
+
if "users" in item.keys():
|
|
216
|
+
model.users = users
|
|
217
|
+
self.pre_update(model)
|
|
218
|
+
self.datamodel.edit(model)
|
|
219
|
+
return self.response(
|
|
220
|
+
200,
|
|
221
|
+
**{API_RESULT_RES_KEY: self.edit_model_schema.dump(item, many=False)},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
except ValidationError as e:
|
|
225
|
+
return self.response_400(message=e.messages)
|
|
226
|
+
except IntegrityError as e:
|
|
227
|
+
return self.response_422(message=str(e.orig))
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from flask_appbuilder.security.sqla.models import Group
|
|
2
|
+
from marshmallow import fields, Schema
|
|
3
|
+
from marshmallow.validate import Length
|
|
4
|
+
|
|
5
|
+
name_description = "Group name"
|
|
6
|
+
label_description = "Group label"
|
|
7
|
+
description_description = "Group description"
|
|
8
|
+
roles_description = "Group roles"
|
|
9
|
+
users_description = "Group users"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GroupPostSchema(Schema):
|
|
13
|
+
model_cls = Group
|
|
14
|
+
|
|
15
|
+
name = fields.String(
|
|
16
|
+
required=True,
|
|
17
|
+
validate=[Length(1, 100)],
|
|
18
|
+
metadata={"description": name_description},
|
|
19
|
+
)
|
|
20
|
+
label = fields.String(
|
|
21
|
+
required=False,
|
|
22
|
+
allow_none=True,
|
|
23
|
+
validate=[Length(0, 150)],
|
|
24
|
+
metadata={"description": label_description},
|
|
25
|
+
)
|
|
26
|
+
description = fields.String(
|
|
27
|
+
required=False,
|
|
28
|
+
allow_none=True,
|
|
29
|
+
validate=[Length(0, 512)],
|
|
30
|
+
metadata={"description": description_description},
|
|
31
|
+
)
|
|
32
|
+
roles = fields.List(
|
|
33
|
+
fields.Integer,
|
|
34
|
+
required=False,
|
|
35
|
+
metadata={"description": roles_description},
|
|
36
|
+
)
|
|
37
|
+
users = fields.List(
|
|
38
|
+
fields.Integer,
|
|
39
|
+
required=False,
|
|
40
|
+
metadata={"description": users_description},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class GroupPutSchema(Schema):
|
|
45
|
+
model_cls = Group
|
|
46
|
+
|
|
47
|
+
name = fields.String(
|
|
48
|
+
required=False,
|
|
49
|
+
validate=[Length(1, 100)],
|
|
50
|
+
metadata={"description": name_description},
|
|
51
|
+
)
|
|
52
|
+
label = fields.String(
|
|
53
|
+
required=False,
|
|
54
|
+
allow_none=True,
|
|
55
|
+
validate=[Length(0, 150)],
|
|
56
|
+
metadata={"description": label_description},
|
|
57
|
+
)
|
|
58
|
+
description = fields.String(
|
|
59
|
+
required=False,
|
|
60
|
+
allow_none=True,
|
|
61
|
+
validate=[Length(0, 512)],
|
|
62
|
+
metadata={"description": description_description},
|
|
63
|
+
)
|
|
64
|
+
roles = fields.List(
|
|
65
|
+
fields.Integer,
|
|
66
|
+
required=False,
|
|
67
|
+
metadata={"description": roles_description},
|
|
68
|
+
)
|
|
69
|
+
users = fields.List(
|
|
70
|
+
fields.Integer,
|
|
71
|
+
required=False,
|
|
72
|
+
metadata={"description": users_description},
|
|
73
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import PermissionApi # noqa: F401
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from flask_appbuilder import ModelRestApi
|
|
2
|
+
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
|
3
|
+
from flask_appbuilder.security.sqla.models import Permission
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PermissionApi(ModelRestApi):
|
|
7
|
+
resource_name = "security/permissions"
|
|
8
|
+
openapi_spec_tag = "Security Permissions"
|
|
9
|
+
|
|
10
|
+
class_permission_name = "Permission"
|
|
11
|
+
datamodel = SQLAInterface(Permission)
|
|
12
|
+
allow_browser_login = True
|
|
13
|
+
include_route_methods = {"info", "get", "get_list"}
|
|
14
|
+
|
|
15
|
+
list_columns = ["id", "name"]
|
|
16
|
+
show_columns = list_columns
|
|
17
|
+
add_columns = ["name"]
|
|
18
|
+
edit_columns = add_columns
|
|
19
|
+
search_columns = list_columns
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import PermissionViewMenuApi # noqa: F401
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from flask_appbuilder import ModelRestApi
|
|
2
|
+
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
|
3
|
+
from flask_appbuilder.security.sqla.models import PermissionView
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PermissionViewMenuApi(ModelRestApi):
|
|
7
|
+
resource_name = "security/permissions-resources"
|
|
8
|
+
openapi_spec_tag = "Security Permissions on Resources (View Menus)"
|
|
9
|
+
class_permission_name = "PermissionViewMenu"
|
|
10
|
+
datamodel = SQLAInterface(PermissionView)
|
|
11
|
+
allow_browser_login = True
|
|
12
|
+
|
|
13
|
+
list_columns = ["id", "permission.name", "view_menu.name"]
|
|
14
|
+
show_columns = list_columns
|
|
15
|
+
add_columns = ["permission_id", "view_menu_id"]
|
|
16
|
+
edit_columns = add_columns
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .api import RoleApi # noqa: F401
|