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.
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.1.dist-info → flask_appbuilder-5.0.2.dist-info}/METADATA +36 -76
  135. flask_appbuilder-5.0.2.dist-info/RECORD +240 -0
  136. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/WHEEL +1 -1
  137. flask_appbuilder-5.0.2.dist-info/entry_points.txt +2 -0
  138. Flask_AppBuilder-3.2.1.dist-info/RECORD +0 -270
  139. Flask_AppBuilder-3.2.1.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.1.dist-info → flask_appbuilder-5.0.2.dist-info}/LICENSE +0 -0
  228. {Flask_AppBuilder-3.2.1.dist-info → flask_appbuilder-5.0.2.dist-info}/top_level.txt +0 -0
@@ -1,419 +0,0 @@
1
- import logging
2
- import os
3
- import unittest
4
-
5
- from flask import Flask
6
- from flask_appbuilder import AppBuilder, SQLA
7
- from flask_appbuilder.const import AUTH_OAUTH
8
- import jinja2
9
-
10
- logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
11
- logging.getLogger().setLevel(logging.DEBUG)
12
- log = logging.getLogger(__name__)
13
-
14
-
15
- class OAuthRegistrationRoleTestCase(unittest.TestCase):
16
- def setUp(self):
17
- # start Flask
18
- self.app = Flask(__name__)
19
- self.app.jinja_env.undefined = jinja2.StrictUndefined
20
- self.app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
21
- "SQLALCHEMY_DATABASE_URI"
22
- )
23
- self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
24
- self.app.config["AUTH_TYPE"] = AUTH_OAUTH
25
- self.app.config[
26
- "OAUTH_PROVIDERS"
27
- ] = [] # can be empty, because we dont use the external providers in tests
28
-
29
- # start Database
30
- self.db = SQLA(self.app)
31
-
32
- def tearDown(self):
33
- # stop Flask
34
- self.app = None
35
-
36
- # stop Flask-AppBuilder
37
- self.appbuilder = None
38
-
39
- # stop Database
40
- self.db.session.remove()
41
- self.db.drop_all()
42
- self.db = None
43
-
44
- # ----------------
45
- # Userinfo Objects
46
- # ----------------
47
- userinfo_alice = {
48
- "username": "alice",
49
- "first_name": "Alice",
50
- "last_name": "Doe",
51
- "email": "alice@example.com",
52
- "role_keys": ["GROUP_1", "GROUP_2"],
53
- }
54
-
55
- # ----------------
56
- # Unit Tests
57
- # ----------------
58
- def test__inactive_user(self):
59
- """
60
- OAUTH: test login flow for - inactive user
61
- """
62
- self.appbuilder = AppBuilder(self.app, self.db.session)
63
- sm = self.appbuilder.sm
64
-
65
- # validate - no users are registered
66
- self.assertEqual(sm.get_all_users(), [])
67
-
68
- # register a user
69
- new_user = sm.add_user(
70
- username="alice",
71
- first_name="Alice",
72
- last_name="Doe",
73
- email="alice@example.com",
74
- role=[],
75
- )
76
-
77
- # validate - user was registered
78
- self.assertEqual(len(sm.get_all_users()), 1)
79
-
80
- # set user inactive
81
- new_user.active = False
82
-
83
- # attempt login
84
- user = sm.auth_user_oauth(self.userinfo_alice)
85
-
86
- # validate - user was not allowed to log in
87
- self.assertIsNone(user)
88
-
89
- def test__missing_username(self):
90
- """
91
- OAUTH: test login flow for - missing credentials
92
- """
93
- self.appbuilder = AppBuilder(self.app, self.db.session)
94
- sm = self.appbuilder.sm
95
-
96
- # validate - no users are registered
97
- self.assertEqual(sm.get_all_users(), [])
98
-
99
- # create userinfo with missing info
100
- userinfo_missing = self.userinfo_alice.copy()
101
- userinfo_missing["username"] = ""
102
-
103
- # attempt login
104
- user = sm.auth_user_oauth(userinfo_missing)
105
-
106
- # validate - login failure (missing username)
107
- self.assertIsNone(user)
108
-
109
- # validate - no users were created
110
- self.assertEqual(sm.get_all_users(), [])
111
-
112
- def test__unregistered(self):
113
- """
114
- OAUTH: test login flow for - unregistered user
115
- """
116
- self.app.config["AUTH_USER_REGISTRATION"] = True
117
- self.app.config["AUTH_USER_REGISTRATION_ROLE"] = "Public"
118
- self.appbuilder = AppBuilder(self.app, self.db.session)
119
- sm = self.appbuilder.sm
120
-
121
- # validate - no users are registered
122
- self.assertEqual(sm.get_all_users(), [])
123
-
124
- # attempt login
125
- user = sm.auth_user_oauth(self.userinfo_alice)
126
-
127
- # validate - user was allowed to log in
128
- self.assertIsInstance(user, sm.user_model)
129
-
130
- # validate - user was registered
131
- self.assertEqual(len(sm.get_all_users()), 1)
132
-
133
- # validate - user was given the AUTH_USER_REGISTRATION_ROLE role
134
- self.assertEqual(user.roles, [sm.find_role("Public")])
135
-
136
- # validate - user was given the correct attributes
137
- self.assertEqual(user.first_name, "Alice")
138
- self.assertEqual(user.last_name, "Doe")
139
- self.assertEqual(user.email, "alice@example.com")
140
-
141
- def test__unregistered__no_self_register(self):
142
- """
143
- OAUTH: test login flow for - unregistered user - no self-registration
144
- """
145
- self.app.config["AUTH_USER_REGISTRATION"] = False
146
- self.appbuilder = AppBuilder(self.app, self.db.session)
147
- sm = self.appbuilder.sm
148
-
149
- # validate - no users are registered
150
- self.assertEqual(sm.get_all_users(), [])
151
-
152
- # attempt login
153
- user = sm.auth_user_oauth(self.userinfo_alice)
154
-
155
- # validate - user was not allowed to log in
156
- self.assertIsNone(user)
157
-
158
- # validate - no users were registered
159
- self.assertEqual(sm.get_all_users(), [])
160
-
161
- def test__unregistered__single_role(self):
162
- """
163
- OAUTH: test login flow for - unregistered user
164
- - single role mapping
165
- """
166
- self.app.config["AUTH_ROLES_MAPPING"] = {
167
- "GROUP_1": ["Admin"],
168
- "GROUP_2": ["User"],
169
- }
170
- self.app.config["AUTH_USER_REGISTRATION"] = True
171
- self.app.config["AUTH_USER_REGISTRATION_ROLE"] = "Public"
172
- self.appbuilder = AppBuilder(self.app, self.db.session)
173
- sm = self.appbuilder.sm
174
-
175
- # add User role
176
- sm.add_role("User")
177
-
178
- # validate - no users are registered
179
- self.assertEqual(sm.get_all_users(), [])
180
-
181
- # attempt login
182
- user = sm.auth_user_oauth(self.userinfo_alice)
183
-
184
- # validate - user was allowed to log in
185
- self.assertIsInstance(user, sm.user_model)
186
-
187
- # validate - user was registered
188
- self.assertEqual(len(sm.get_all_users()), 1)
189
-
190
- # validate - user was given the correct roles
191
- self.assertListEqual(
192
- user.roles,
193
- [sm.find_role("Admin"), sm.find_role("Public"), sm.find_role("User")],
194
- )
195
-
196
- # validate - user was given the correct attributes (read from LDAP)
197
- self.assertEqual(user.first_name, "Alice")
198
- self.assertEqual(user.last_name, "Doe")
199
- self.assertEqual(user.email, "alice@example.com")
200
-
201
- def test__unregistered__multi_role(self):
202
- """
203
- OAUTH: test login flow for - unregistered user - multi role mapping
204
- """
205
- self.app.config["AUTH_ROLES_MAPPING"] = {"GROUP_1": ["Admin", "User"]}
206
- self.app.config["AUTH_USER_REGISTRATION"] = True
207
- self.app.config["AUTH_USER_REGISTRATION_ROLE"] = "Public"
208
- self.appbuilder = AppBuilder(self.app, self.db.session)
209
- sm = self.appbuilder.sm
210
-
211
- # add User role
212
- sm.add_role("User")
213
-
214
- # validate - no users are registered
215
- self.assertEqual(sm.get_all_users(), [])
216
-
217
- # attempt login
218
- user = sm.auth_user_oauth(self.userinfo_alice)
219
-
220
- # validate - user was allowed to log in
221
- self.assertIsInstance(user, sm.user_model)
222
-
223
- # validate - user was registered
224
- self.assertEqual(len(sm.get_all_users()), 1)
225
-
226
- # validate - user was given the correct roles
227
- self.assertListEqual(
228
- user.roles,
229
- [sm.find_role("Admin"), sm.find_role("Public"), sm.find_role("User")],
230
- )
231
-
232
- # validate - user was given the correct attributes (read from LDAP)
233
- self.assertEqual(user.first_name, "Alice")
234
- self.assertEqual(user.last_name, "Doe")
235
- self.assertEqual(user.email, "alice@example.com")
236
-
237
- def test__unregistered__jmespath_role(self):
238
- """
239
- OAUTH: test login flow for - unregistered user - jmespath registration role
240
- """
241
- self.app.config["AUTH_USER_REGISTRATION"] = True
242
- self.app.config[
243
- "AUTH_USER_REGISTRATION_ROLE_JMESPATH"
244
- ] = "contains(['alice'], username) && 'User' || 'Public'"
245
- self.appbuilder = AppBuilder(self.app, self.db.session)
246
- sm = self.appbuilder.sm
247
-
248
- # add User role
249
- sm.add_role("User")
250
-
251
- # validate - no users are registered
252
- self.assertEqual(sm.get_all_users(), [])
253
-
254
- # attempt login
255
- user = sm.auth_user_oauth(self.userinfo_alice)
256
-
257
- # validate - user was allowed to log in
258
- self.assertIsInstance(user, sm.user_model)
259
-
260
- # validate - user was registered
261
- self.assertEqual(len(sm.get_all_users()), 1)
262
-
263
- # validate - user was given the correct roles
264
- self.assertListEqual(user.roles, [sm.find_role("User")])
265
-
266
- # validate - user was given the correct attributes (read from LDAP)
267
- self.assertEqual(user.first_name, "Alice")
268
- self.assertEqual(user.last_name, "Doe")
269
- self.assertEqual(user.email, "alice@example.com")
270
-
271
- def test__registered__multi_role__no_role_sync(self):
272
- """
273
- OAUTH: test login flow for - registered user - multi role mapping - no login role-sync
274
- """ # noqa
275
- self.app.config["AUTH_ROLES_MAPPING"] = {"GROUP_1": ["Admin", "User"]}
276
- self.app.config["AUTH_ROLES_SYNC_AT_LOGIN"] = False
277
- self.appbuilder = AppBuilder(self.app, self.db.session)
278
- sm = self.appbuilder.sm
279
-
280
- # add User role
281
- sm.add_role("User")
282
-
283
- # validate - no users are registered
284
- self.assertEqual(sm.get_all_users(), [])
285
-
286
- # register a user
287
- new_user = sm.add_user( # noqa
288
- username="alice",
289
- first_name="Alice",
290
- last_name="Doe",
291
- email="alice@example.com",
292
- role=[],
293
- )
294
-
295
- # validate - user was registered
296
- self.assertEqual(len(sm.get_all_users()), 1)
297
-
298
- # attempt login
299
- user = sm.auth_user_oauth(self.userinfo_alice)
300
-
301
- # validate - user was allowed to log in
302
- self.assertIsInstance(user, sm.user_model)
303
-
304
- # validate - user was given no roles
305
- self.assertListEqual(user.roles, [])
306
-
307
- def test__registered__multi_role__with_role_sync(self):
308
- """
309
- OAUTH: test login flow for - registered user - multi role mapping - with login role-sync
310
- """ # noqa
311
- self.app.config["AUTH_ROLES_MAPPING"] = {"GROUP_1": ["Admin", "User"]}
312
- self.app.config["AUTH_ROLES_SYNC_AT_LOGIN"] = True
313
- self.appbuilder = AppBuilder(self.app, self.db.session)
314
- sm = self.appbuilder.sm
315
-
316
- # add User role
317
- sm.add_role("User")
318
-
319
- # validate - no users are registered
320
- self.assertEqual(sm.get_all_users(), [])
321
-
322
- # register a user
323
- new_user = sm.add_user( # noqa
324
- username="alice",
325
- first_name="Alice",
326
- last_name="Doe",
327
- email="alice@example.com",
328
- role=[],
329
- )
330
-
331
- # validate - user was registered
332
- self.assertEqual(len(sm.get_all_users()), 1)
333
-
334
- # attempt login
335
- user = sm.auth_user_oauth(self.userinfo_alice)
336
-
337
- # validate - user was allowed to log in
338
- self.assertIsInstance(user, sm.user_model)
339
-
340
- # validate - user was given the correct roles
341
- self.assertListEqual(user.roles, [sm.find_role("Admin"), sm.find_role("User")])
342
-
343
- def test__registered__jmespath_role__no_role_sync(self):
344
- """
345
- OAUTH: test login flow for - registered user - jmespath registration role - no login role-sync
346
- """ # noqa
347
- self.app.config["AUTH_ROLES_SYNC_AT_LOGIN"] = False
348
- self.app.config["AUTH_USER_REGISTRATION"] = True
349
- self.app.config[
350
- "AUTH_USER_REGISTRATION_ROLE_JMESPATH"
351
- ] = "contains(['alice'], username) && 'User' || 'Public'"
352
- self.appbuilder = AppBuilder(self.app, self.db.session)
353
- sm = self.appbuilder.sm
354
-
355
- # add User role
356
- sm.add_role("User")
357
-
358
- # validate - no users are registered
359
- self.assertEqual(sm.get_all_users(), [])
360
-
361
- # register a user
362
- new_user = sm.add_user( # noqa
363
- username="alice",
364
- first_name="Alice",
365
- last_name="Doe",
366
- email="alice@example.com",
367
- role=[],
368
- )
369
-
370
- # validate - user was registered
371
- self.assertEqual(len(sm.get_all_users()), 1)
372
-
373
- # attempt login
374
- user = sm.auth_user_oauth(self.userinfo_alice)
375
-
376
- # validate - user was allowed to log in
377
- self.assertIsInstance(user, sm.user_model)
378
-
379
- # validate - user was given no roles
380
- self.assertListEqual(user.roles, [])
381
-
382
- def test__registered__jmespath_role__with_role_sync(self):
383
- """
384
- OAUTH: test login flow for - registered user - jmespath registration role - with login role-sync
385
- """ # noqa
386
- self.app.config["AUTH_ROLES_SYNC_AT_LOGIN"] = True
387
- self.app.config["AUTH_USER_REGISTRATION"] = True
388
- self.app.config[
389
- "AUTH_USER_REGISTRATION_ROLE_JMESPATH"
390
- ] = "contains(['alice'], username) && 'User' || 'Public'"
391
- self.appbuilder = AppBuilder(self.app, self.db.session)
392
- sm = self.appbuilder.sm
393
-
394
- # add User role
395
- sm.add_role("User")
396
-
397
- # validate - no users are registered
398
- self.assertEqual(sm.get_all_users(), [])
399
-
400
- # register a user
401
- new_user = sm.add_user( # noqa
402
- username="alice",
403
- first_name="Alice",
404
- last_name="Doe",
405
- email="alice@example.com",
406
- role=[],
407
- )
408
-
409
- # validate - user was registered
410
- self.assertEqual(len(sm.get_all_users()), 1)
411
-
412
- # attempt login
413
- user = sm.auth_user_oauth(self.userinfo_alice)
414
-
415
- # validate - user was allowed to log in
416
- self.assertIsInstance(user, sm.user_model)
417
-
418
- # validate - user was given the correct roles
419
- self.assertListEqual(user.roles, [sm.find_role("User")])
@@ -1,135 +0,0 @@
1
- import logging
2
- import unittest
3
-
4
- from flask import Flask
5
- from flask_appbuilder import AppBuilder, SQLA
6
- import jinja2
7
- import ldap
8
- from mockldap import MockLdap
9
-
10
-
11
- logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
12
- logging.getLogger().setLevel(logging.DEBUG)
13
- log = logging.getLogger(__name__)
14
-
15
-
16
- class LDAPSearchTestCase(unittest.TestCase):
17
-
18
- top = ("o=test", {"o": ["test"]})
19
- example = ("ou=example,o=test", {"ou": ["example"]})
20
- manager = (
21
- "cn=manager,ou=example,o=test",
22
- {"cn": ["manager"], "userPassword": ["ldaptest"]},
23
- )
24
- alice = (
25
- "cn=alice,ou=example,o=test",
26
- {
27
- "cn": ["alice"],
28
- "memberOf": ["cn=group,ou=groups,o=test"],
29
- "userPassword": ["alicepw"],
30
- },
31
- )
32
- group = (
33
- "cn=group,ou=groups,o=test",
34
- {"cn": ["group"], "member": ["cn=alice,ou=example,o=test"]},
35
- )
36
-
37
- directory = dict([top, example, group, manager, alice])
38
-
39
- @classmethod
40
- def setUpClass(cls):
41
- # We only need to create the MockLdap instance once. The content we
42
- # pass in will be used for all LDAP connections.
43
- cls.mockldap = MockLdap(cls.directory)
44
-
45
- @classmethod
46
- def tearDownClass(cls):
47
- del cls.mockldap
48
-
49
- def setUp(self):
50
-
51
- self.mockldap.start()
52
- self.ldapobj = self.mockldap["ldap://localhost/"]
53
-
54
- self.app = Flask(__name__)
55
- self.app.jinja_env.undefined = jinja2.StrictUndefined
56
- self.db = SQLA(self.app)
57
-
58
- self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
59
- self.app.config["AUTH_LDAP_UID_FIELD"] = "cn"
60
- self.app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"] = False
61
- self.app.config["AUTH_LDAP_USE_TLS"] = False
62
- self.app.config["AUTH_LDAP_SERVER"] = "ldap://localhost/"
63
- self.app.config["AUTH_LDAP_SEARCH"] = "ou=example,o=test"
64
- self.app.config["AUTH_LDAP_APPEND_DOMAIN"] = False
65
- self.app.config["AUTH_LDAP_FIRSTNAME_FIELD"] = None
66
- self.app.config["AUTH_LDAP_LASTNAME_FIELD"] = None
67
- self.app.config["AUTH_LDAP_EMAIL_FIELD"] = None
68
-
69
- def tearDown(self):
70
- self.mockldap.stop()
71
- del self.ldapobj
72
- log.debug("TEAR DOWN")
73
-
74
- self.appbuilder = None
75
- self.app = None
76
- self.db = None
77
-
78
- def test_ldapsearch(self):
79
- con = ldap.initialize("ldap://localhost/")
80
- con.simple_bind_s("cn=manager,ou=example,o=test", "ldaptest")
81
-
82
- self.app.config["AUTH_LDAP_SEARCH_FILTER"] = ""
83
- self.appbuilder = AppBuilder(self.app, self.db.session)
84
-
85
- initialize_call = ("initialize", ("ldap://localhost/",), {})
86
- simple_bind_s_call = (
87
- "simple_bind_s",
88
- ("cn=manager,ou=example,o=test", "ldaptest"),
89
- {},
90
- )
91
- search_s_call = (
92
- "search_s",
93
- ("ou=example,o=test", 2, "(cn=alice)", [None, None, None]),
94
- {},
95
- )
96
-
97
- user = self.appbuilder.sm._search_ldap(ldap, con, "alice")
98
- self.assertEqual(
99
- self.ldapobj.methods_called(with_args=True),
100
- [initialize_call, simple_bind_s_call, search_s_call],
101
- )
102
- self.assertEqual(user[0][0], self.alice[0])
103
-
104
- def test_ldapsearchfilter(self):
105
- con = ldap.initialize("ldap://localhost/")
106
- con.simple_bind_s("cn=manager,ou=example,o=test", "ldaptest")
107
-
108
- self.app.config[
109
- "AUTH_LDAP_SEARCH_FILTER"
110
- ] = "(memberOf=cn=group,ou=groups,o=test)"
111
- self.appbuilder = AppBuilder(self.app, self.db.session)
112
-
113
- initialize_call = ("initialize", ("ldap://localhost/",), {})
114
- simple_bind_s_call = (
115
- "simple_bind_s",
116
- ("cn=manager,ou=example,o=test", "ldaptest"),
117
- {},
118
- )
119
- search_s_call = (
120
- "search_s",
121
- (
122
- "ou=example,o=test",
123
- 2,
124
- "(&(memberOf=cn=group,ou=groups,o=test)(cn=alice))",
125
- [None, None, None],
126
- ),
127
- {},
128
- )
129
-
130
- user = self.appbuilder.sm._search_ldap(ldap, con, "alice")
131
- self.assertEqual(
132
- self.ldapobj.methods_called(with_args=True),
133
- [initialize_call, simple_bind_s_call, search_s_call],
134
- )
135
- self.assertEqual(user[0][0], self.alice[0])
@@ -1,59 +0,0 @@
1
- import logging
2
- import unittest
3
-
4
- from flask import Flask
5
- from flask_appbuilder import AppBuilder, SQLA
6
-
7
-
8
- logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
9
- logging.getLogger().setLevel(logging.DEBUG)
10
- log = logging.getLogger(__name__)
11
-
12
-
13
- class OAuthRegistrationRoleTestCase(unittest.TestCase):
14
- def setUp(self):
15
- self.app = Flask(__name__)
16
- self.app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
17
- self.db = SQLA(self.app)
18
-
19
- def tearDown(self):
20
- self.appbuilder = None
21
- self.app = None
22
- self.db = None
23
-
24
- def test_self_registration_not_enabled(self):
25
- self.app.config["AUTH_USER_REGISTRATION"] = False
26
- self.appbuilder = AppBuilder(self.app, self.db.session)
27
-
28
- result = self.appbuilder.sm.auth_user_oauth(userinfo={"username": "testuser"})
29
-
30
- self.assertIsNone(result)
31
- self.assertEqual(len(self.appbuilder.sm.get_all_users()), 0)
32
-
33
- def test_register_and_attach_static_role(self):
34
- self.app.config["AUTH_USER_REGISTRATION"] = True
35
- self.app.config["AUTH_USER_REGISTRATION_ROLE"] = "Public"
36
- self.appbuilder = AppBuilder(self.app, self.db.session)
37
-
38
- user = self.appbuilder.sm.auth_user_oauth(userinfo={"username": "testuser"})
39
-
40
- self.assertEqual(user.roles[0].name, "Public")
41
-
42
- def test_register_and_attach_dynamic_role(self):
43
- self.app.config["AUTH_USER_REGISTRATION"] = True
44
- self.app.config[
45
- "AUTH_USER_REGISTRATION_ROLE_JMESPATH"
46
- ] = "contains(['alice', 'celine'], username) && 'Admin' || 'Public'"
47
- self.appbuilder = AppBuilder(self.app, self.db.session)
48
-
49
- # Role for admin
50
- user = self.appbuilder.sm.auth_user_oauth(
51
- userinfo={"username": "alice", "email": "alice@example.com"}
52
- )
53
- self.assertEqual(user.roles[0].name, "Admin")
54
-
55
- # Role for non-admin
56
- user = self.appbuilder.sm.auth_user_oauth(
57
- userinfo={"username": "bob", "email": "bob@example.com"}
58
- )
59
- self.assertEqual(user.roles[0].name, "Public")
Binary file
@@ -1,90 +0,0 @@
1
- import json
2
- import unittest
3
-
4
- from flask_appbuilder.const import (
5
- API_SECURITY_PASSWORD_KEY,
6
- API_SECURITY_PROVIDER_KEY,
7
- API_SECURITY_USERNAME_KEY,
8
- API_SECURITY_VERSION,
9
- )
10
-
11
-
12
- class FABTestCase(unittest.TestCase):
13
- @staticmethod
14
- def auth_client_get(client, token, uri):
15
- return client.get(uri, headers={"Authorization": "Bearer {}".format(token)})
16
-
17
- @staticmethod
18
- def auth_client_delete(client, token, uri):
19
- return client.delete(uri, headers={"Authorization": "Bearer {}".format(token)})
20
-
21
- @staticmethod
22
- def auth_client_put(client, token, uri, json):
23
- return client.put(
24
- uri, json=json, headers={"Authorization": "Bearer {}".format(token)}
25
- )
26
-
27
- @staticmethod
28
- def auth_client_post(client, token, uri, json):
29
- return client.post(
30
- uri, json=json, headers={"Authorization": "Bearer {}".format(token)}
31
- )
32
-
33
- @staticmethod
34
- def _login(client, username, password):
35
- """
36
- Login help method
37
- :param client: Flask test client
38
- :param username: username
39
- :param password: password
40
- :return: Flask client response class
41
- """
42
- return client.post(
43
- "api/{}/security/login".format(API_SECURITY_VERSION),
44
- data=json.dumps(
45
- {
46
- API_SECURITY_USERNAME_KEY: username,
47
- API_SECURITY_PASSWORD_KEY: password,
48
- API_SECURITY_PROVIDER_KEY: "db",
49
- }
50
- ),
51
- content_type="application/json",
52
- )
53
-
54
- def login(self, client, username, password):
55
- # Login with default admin
56
- rv = self._login(client, username, password)
57
- try:
58
- return json.loads(rv.data.decode("utf-8")).get("access_token")
59
- except Exception:
60
- return rv
61
-
62
- def browser_login(self, client, username, password):
63
- # Login with default admin
64
- return client.post(
65
- "/login/",
66
- data=dict(username=username, password=password),
67
- follow_redirects=True,
68
- )
69
-
70
- @staticmethod
71
- def browser_logout(client):
72
- return client.get("/logout/")
73
-
74
- def create_admin_user(self, appbuilder, username, password):
75
- self.create_user(appbuilder, username, password, "Admin")
76
-
77
- @staticmethod
78
- def create_user(
79
- appbuilder,
80
- username,
81
- password,
82
- role_name,
83
- first_name="admin",
84
- last_name="user",
85
- email="admin@fab.org",
86
- ):
87
- role_admin = appbuilder.sm.find_role(role_name)
88
- appbuilder.sm.add_user(
89
- username, first_name, last_name, email, role_admin, password
90
- )