Update flask-security to 3.4.5
Sophie Brun
3 years ago
100 | 100 | verify_and_update_password, |
101 | 101 | ) |
102 | 102 | |
103 | __version__ = "3.4.2" | |
103 | __version__ = "3.4.5" |
120 | 120 | @with_appcontext |
121 | 121 | @commit |
122 | 122 | def roles_add(user, role): |
123 | """Add user to role.""" | |
123 | """Add role to user.""" | |
124 | 124 | user, role = _datastore._prepare_role_modify_args(user, role) |
125 | 125 | if user is None: |
126 | 126 | raise click.UsageError("Cannot find user.") |
128 | 128 | raise click.UsageError("Cannot find role.") |
129 | 129 | if _datastore.add_role_to_user(user, role): |
130 | 130 | click.secho( |
131 | 'Role "{0}" added to user "{1}" ' "successfully.".format(role, user), | |
131 | 'Role "{0}" added to user "{1}" ' "successfully.".format(role.name, user), | |
132 | 132 | fg="green", |
133 | 133 | ) |
134 | 134 | else: |
141 | 141 | @with_appcontext |
142 | 142 | @commit |
143 | 143 | def roles_remove(user, role): |
144 | """Remove user from role.""" | |
144 | """Remove role from user.""" | |
145 | 145 | user, role = _datastore._prepare_role_modify_args(user, role) |
146 | 146 | if user is None: |
147 | 147 | raise click.UsageError("Cannot find user.") |
149 | 149 | raise click.UsageError("Cannot find role.") |
150 | 150 | if _datastore.remove_role_from_user(user, role): |
151 | 151 | click.secho( |
152 | 'Role "{0}" removed from user "{1}" ' "successfully.".format(role, user), | |
152 | 'Role "{0}" removed from user "{1}" ' | |
153 | "successfully.".format(role.name, user), | |
153 | 154 | fg="green", |
154 | 155 | ) |
155 | 156 | else: |
164 | 165 | """Activate a user.""" |
165 | 166 | user_obj = _datastore.get_user(user) |
166 | 167 | if user_obj is None: |
167 | raise click.UsageError("ERROR: User not found.") | |
168 | raise click.UsageError("User not found.") | |
168 | 169 | if _datastore.activate_user(user_obj): |
169 | 170 | click.secho('User "{0}" has been activated.'.format(user), fg="green") |
170 | 171 | else: |
179 | 180 | """Deactivate a user.""" |
180 | 181 | user_obj = _datastore.get_user(user) |
181 | 182 | if user_obj is None: |
182 | raise click.UsageError("ERROR: User not found.") | |
183 | raise click.UsageError("User not found.") | |
183 | 184 | if _datastore.deactivate_user(user_obj): |
184 | 185 | click.secho('User "{0}" has been deactivated.'.format(user), fg="green") |
185 | 186 | else: |
186 | 187 | click.secho('User "{0}" was already deactivated.'.format(user), fg="yellow") |
188 | ||
189 | ||
190 | @users.command( | |
191 | "reset_access", | |
192 | help="Reset all authentication credentials for user." | |
193 | " This includes session, auth token, two-factor" | |
194 | " and unified sign in secrets. ", | |
195 | ) | |
196 | @click.argument("user") | |
197 | @with_appcontext | |
198 | @commit | |
199 | def users_reset_access(user): | |
200 | """ Reset all authentication tokens etc.""" | |
201 | user_obj = _datastore.get_user(user) | |
202 | if user_obj is None: | |
203 | raise click.UsageError("User not found.") | |
204 | _datastore.reset_user_access(user_obj) | |
205 | click.secho( | |
206 | 'User "{user}" authentication credentials have been reset.'.format(user=user), | |
207 | fg="green", | |
208 | ) |
677 | 677 | Caller must commit to DB. |
678 | 678 | |
679 | 679 | .. versionadded:: 3.3.0 |
680 | ||
681 | .. deprecated:: 3.4.4 | |
682 | Use :meth:`.UserDatastore.remove_permissions_from_role` | |
680 | 683 | """ |
681 | 684 | if hasattr(self, "permissions"): |
682 | 685 | current_perms = self.get_permissions() |
687 | 690 | else: |
688 | 691 | perms = {permissions} |
689 | 692 | self.permissions = ",".join(current_perms.union(perms)) |
690 | else: | |
693 | else: # pragma: no cover | |
691 | 694 | raise NotImplementedError("Role model doesn't have permissions") |
692 | 695 | |
693 | 696 | def remove_permissions(self, permissions): |
699 | 702 | Caller must commit to DB. |
700 | 703 | |
701 | 704 | .. versionadded:: 3.3.0 |
705 | ||
706 | .. deprecated:: 3.4.4 | |
707 | Use :meth:`.UserDatastore.remove_permissions_from_role` | |
702 | 708 | """ |
703 | 709 | if hasattr(self, "permissions"): |
704 | 710 | current_perms = self.get_permissions() |
709 | 715 | else: |
710 | 716 | perms = {permissions} |
711 | 717 | self.permissions = ",".join(current_perms.difference(perms)) |
712 | else: | |
718 | else: # pragma: no cover | |
713 | 719 | raise NotImplementedError("Role model doesn't have permissions") |
714 | 720 | |
715 | 721 | |
790 | 796 | |
791 | 797 | """ |
792 | 798 | for role in self.roles: |
793 | if hasattr(role, "permissions"): | |
794 | if permission in role.get_permissions(): | |
795 | return True | |
799 | if permission in role.get_permissions(): | |
800 | return True | |
796 | 801 | return False |
797 | 802 | |
798 | 803 | def get_security_payload(self): |
199 | 199 | rv = True |
200 | 200 | user.roles.remove(role) |
201 | 201 | self.put(user) |
202 | return rv | |
203 | ||
204 | def add_permissions_to_role(self, role, permissions): | |
205 | """Add one or more permissions to role. | |
206 | ||
207 | :param role: The role to modify. Can be a Role object or | |
208 | string role name | |
209 | :param permissions: a set, list, or single string. | |
210 | :return: True if permissions added, False if role doesn't exist. | |
211 | ||
212 | Caller must commit to DB. | |
213 | ||
214 | .. versionadded:: 3.4.4 | |
215 | """ | |
216 | ||
217 | rv = False | |
218 | user, role = self._prepare_role_modify_args(None, role) | |
219 | if role: | |
220 | rv = True | |
221 | role.add_permissions(permissions) | |
222 | self.put(role) | |
223 | return rv | |
224 | ||
225 | def remove_permissions_from_role(self, role, permissions): | |
226 | """Remove one or more permissions from a role. | |
227 | ||
228 | :param role: The role to modify. Can be a Role object or | |
229 | string role name | |
230 | :param permissions: a set, list, or single string. | |
231 | :return: True if permissions removed, False if role doesn't exist. | |
232 | ||
233 | Caller must commit to DB. | |
234 | ||
235 | .. versionadded:: 3.4.4 | |
236 | """ | |
237 | ||
238 | rv = False | |
239 | user, role = self._prepare_role_modify_args(None, role) | |
240 | if role: | |
241 | rv = True | |
242 | role.remove_permissions(permissions) | |
243 | self.put(role) | |
202 | 244 | return rv |
203 | 245 | |
204 | 246 | def toggle_active(self, user): |
62 | 62 | def default_unauthn_handler(mechanisms, headers=None): |
63 | 63 | """ Default callback for failures to authenticate |
64 | 64 | |
65 | If caller wants JSON - return 401 | |
65 | If caller wants JSON - return 401. | |
66 | If caller wants BasicAuth - return 401 (the WWW-Authenticate header is set). | |
66 | 67 | Otherwise - assume caller is html and redirect if possible to a login view. |
67 | 68 | We let Flask-Login handle this. |
68 | 69 | |
69 | 70 | """ |
71 | headers = headers or {} | |
70 | 72 | msg = get_message("UNAUTHENTICATED")[0] |
71 | 73 | |
72 | 74 | if config_value("BACKWARDS_COMPAT_UNAUTHN"): |
73 | 75 | return _get_unauthenticated_response(headers=headers) |
74 | 76 | if _security._want_json(request): |
75 | # Ignore headers since today, the only thing in there might be WWW-Authenticate | |
76 | # and we never want to send that in a JSON response (browsers will intercept | |
77 | # that and pop up their own login form). | |
77 | # Remove WWW-Authenticate from headers for JSON responses since | |
78 | # browsers will intercept that and pop up their own login form. | |
79 | headers.pop("WWW-Authenticate", None) | |
78 | 80 | payload = json_error_response(errors=msg) |
79 | return _security._render_json(payload, 401, None, None) | |
81 | return _security._render_json(payload, 401, headers, None) | |
82 | ||
83 | # Basic-Auth is often used to provide a browser based login form and then the | |
84 | # browser will always add the BasicAuth credentials. For that to work we need to | |
85 | # return 401 and not redirect to our login view. | |
86 | if "WWW-Authenticate" in headers: | |
87 | return Response(msg, 401, headers) | |
80 | 88 | return _security.login_manager.unauthorized() |
81 | 89 | |
82 | 90 | |
211 | 219 | |
212 | 220 | :param realm: optional realm name |
213 | 221 | |
222 | If authentication fails, then a 401 with the 'WWW-Authenticate' header set will be | |
223 | returned. | |
224 | ||
214 | 225 | Once authenticated, if so configured, CSRF protection will be tested. |
215 | 226 | """ |
216 | 227 | |
268 | 279 | return 'Dashboard' |
269 | 280 | |
270 | 281 | :param auth_methods: Specified mechanisms (token, basic, session). If not specified |
271 | then all current available mechanisms will be tried. | |
282 | then all current available mechanisms (except "basic") will be tried. | |
272 | 283 | :kwparam within: Add 'freshness' check to authentication. Is either an int |
273 | 284 | specifying # of minutes, or a callable that returns a timedelta. For timedeltas, |
274 | 285 | timedelta.total_seconds() is used for the calculations: |
296 | 307 | On authentication failure `.Security.unauthorized_callback` (deprecated) |
297 | 308 | or :meth:`.Security.unauthn_handler` will be called. |
298 | 309 | |
310 | .. note:: | |
311 | If "basic" is specified in addition to other methods, then if authentication | |
312 | fails, a 401 with the "WWW-Authenticate" header will be returned - rather than | |
313 | being redirected to the login view. | |
314 | ||
299 | 315 | .. versionchanged:: 3.3.0 |
300 | 316 | If ``auth_methods`` isn't specified, then all will be tried. Authentication |
301 | 317 | mechanisms will always be tried in order of ``token``, ``session``, ``basic`` |
303 | 319 | |
304 | 320 | .. versionchanged:: 3.4.0 |
305 | 321 | Added ``within`` and ``grace`` parameters to enforce a freshness check. |
322 | ||
323 | .. versionchanged:: 3.4.4 | |
324 | If ``auth_methods`` isn't specified try all mechanisms EXCEPT ``basic``. | |
306 | 325 | |
307 | 326 | """ |
308 | 327 | |
313 | 332 | } |
314 | 333 | mechanisms_order = ["token", "session", "basic"] |
315 | 334 | if not auth_methods: |
316 | auth_methods = {"basic", "session", "token"} | |
335 | auth_methods = {"session", "token"} | |
317 | 336 | else: |
318 | 337 | auth_methods = [am for am in auth_methods] |
319 | 338 |
169 | 169 | msg = user.tf_send_security_token( |
170 | 170 | method=user.tf_primary_method, |
171 | 171 | totp_secret=user.tf_totp_secret, |
172 | phone_number=user.tf_phone_number, | |
172 | phone_number=getattr(user, "tf_phone_number", None), | |
173 | 173 | ) |
174 | 174 | if msg: |
175 | 175 | # send code didn't work |
343 | 343 | msg = user.us_send_security_token( |
344 | 344 | method, |
345 | 345 | totp_secret=totp_secrets[method], |
346 | phone_number=user.us_phone_number, | |
346 | phone_number=getattr(user, "us_phone_number", None), | |
347 | 347 | send_magic_link=True, |
348 | 348 | ) |
349 | 349 | code_sent = True |
141 | 141 | """ |
142 | 142 | |
143 | 143 | if current_user.is_authenticated and request.method == "POST": |
144 | # Just redirect current_user to POST_LOGIN_VIEW (or next). | |
144 | # Just redirect current_user to POST_LOGIN_VIEW. | |
145 | 145 | # While its tempting to try to logout the current user and login the |
146 | 146 | # new requested user - that simply doesn't work with CSRF. |
147 | 147 | |
148 | # While this is close to anonymous_user_required - it differs in that | |
149 | # it uses get_post_login_redirect which correctly handles 'next'. | |
150 | # TODO: consider changing anonymous_user_required to also call | |
151 | # get_post_login_redirect - not sure why it never has? | |
148 | # This does NOT use get_post_login_redirect() so that it doesn't look at | |
149 | # 'next' - which can cause infinite redirect loops | |
150 | # (see test_common::test_authenticated_loop) | |
152 | 151 | if _security._want_json(request): |
153 | 152 | payload = json_error_response( |
154 | 153 | errors=get_message("ANONYMOUS_USER_REQUIRED")[0] |
155 | 154 | ) |
156 | 155 | return _security._render_json(payload, 400, None, None) |
157 | 156 | else: |
158 | return redirect(get_post_login_redirect()) | |
157 | return redirect(get_url(_security.post_login_view)) | |
159 | 158 | |
160 | 159 | form_class = _security.login_form |
161 | 160 | |
181 | 180 | login_user(form.user, remember=remember_me, authn_via=["password"]) |
182 | 181 | after_this_request(_commit) |
183 | 182 | |
184 | if not _security._want_json(request): | |
185 | return redirect(get_post_login_redirect()) | |
183 | if _security._want_json(request): | |
184 | return base_render_json(form, include_auth_token=True) | |
185 | return redirect(get_post_login_redirect()) | |
186 | 186 | |
187 | 187 | if _security._want_json(request): |
188 | 188 | if current_user.is_authenticated: |
189 | 189 | form.user = current_user |
190 | return base_render_json(form, include_auth_token=True) | |
190 | return base_render_json(form) | |
191 | 191 | |
192 | 192 | if current_user.is_authenticated: |
193 | # Basically a no-op if authenticated - just perform the same | |
194 | # post-login redirect as if user just logged in. | |
195 | return redirect(get_post_login_redirect()) | |
193 | return redirect(get_url(_security.post_login_view)) | |
196 | 194 | else: |
197 | 195 | return _security.render_template( |
198 | 196 | config_value("LOGIN_USER_TEMPLATE"), login_user_form=form, **_ctx("login") |
624 | 622 | if form.validate_on_submit(): |
625 | 623 | after_this_request(_commit) |
626 | 624 | change_user_password(current_user._get_current_object(), form.new_password.data) |
627 | if not _security._want_json(request): | |
628 | do_flash(*get_message("PASSWORD_CHANGE")) | |
629 | return redirect( | |
630 | get_url(_security.post_change_view) | |
631 | or get_url(_security.post_login_view) | |
632 | ) | |
625 | if _security._want_json(request): | |
626 | form.user = current_user | |
627 | return base_render_json(form, include_auth_token=True) | |
628 | ||
629 | do_flash(*get_message("PASSWORD_CHANGE")) | |
630 | return redirect( | |
631 | get_url(_security.post_change_view) or get_url(_security.post_login_view) | |
632 | ) | |
633 | 633 | |
634 | 634 | if _security._want_json(request): |
635 | 635 | form.user = current_user |
636 | return base_render_json(form, include_auth_token=True) | |
636 | return base_render_json(form) | |
637 | 637 | |
638 | 638 | return _security.render_template( |
639 | 639 | config_value("CHANGE_PASSWORD_TEMPLATE"), |
733 | 733 | msg = user.tf_send_security_token( |
734 | 734 | method=pm, |
735 | 735 | totp_secret=session["tf_totp_secret"], |
736 | phone_number=user.tf_phone_number, | |
736 | phone_number=getattr(user, "tf_phone_number", None), | |
737 | 737 | ) |
738 | 738 | if msg: |
739 | 739 | # send code didn't work |
920 | 920 | msg = form.user.tf_send_security_token( |
921 | 921 | method="email", |
922 | 922 | totp_secret=form.user.tf_totp_secret, |
923 | phone_number=form.user.tf_phone_number, | |
923 | phone_number=getattr(form.user, "tf_phone_number", None), | |
924 | 924 | ) |
925 | 925 | if msg: |
926 | 926 | rproblem = "" |