From cd9c32312512e5e66395d75bb776332876589048 Mon Sep 17 00:00:00 2001 From: andrew-propelauth <89474256+andrew-propelauth@users.noreply.github.com> Date: Sun, 31 Mar 2024 23:02:43 -0700 Subject: [PATCH] Update flask version and expose new user methods (#19) * Add new methods to the LoggedInUser delegate * Switch to g so we support flask 3 --- propelauth_flask/__init__.py | 10 +--- propelauth_flask/auth_decorator.py | 96 +++++++++++++++++++----------- propelauth_flask/user.py | 50 +++++++++++++++- requirements.txt | 2 +- setup.py | 4 +- 5 files changed, 115 insertions(+), 47 deletions(-) diff --git a/propelauth_flask/__init__.py b/propelauth_flask/__init__.py index 33600a7..b422339 100644 --- a/propelauth_flask/__init__.py +++ b/propelauth_flask/__init__.py @@ -1,6 +1,6 @@ from collections import namedtuple -from flask import _request_ctx_stack +from flask import g from propelauth_py import TokenVerificationMetadata, init_base_auth from werkzeug.local import LocalProxy @@ -14,14 +14,10 @@ ) """Returns the current user. Must be used with one of require_user, optional_user, or require_org_member""" -current_user = LocalProxy( - lambda: getattr(_request_ctx_stack.top, "propelauth_current_user", None) -) +current_user = LocalProxy(lambda: g.propelauth_current_user) """Returns the current org. Must be used with require_org_member""" -current_org = LocalProxy( - lambda: getattr(_request_ctx_stack.top, "propelauth_current_org", None) -) +current_org = LocalProxy(lambda: g.propelauth_current_org) Auth = namedtuple( "Auth", diff --git a/propelauth_flask/auth_decorator.py b/propelauth_flask/auth_decorator.py index 4e1c81c..15d1727 100644 --- a/propelauth_flask/auth_decorator.py +++ b/propelauth_flask/auth_decorator.py @@ -1,13 +1,15 @@ import functools -from flask import _request_ctx_stack, request, abort, Response +from flask import g, request, abort, Response from propelauth_py import UnauthorizedException from propelauth_py.errors import ForbiddenException from propelauth_flask.user import LoggedOutUser, LoggedInUser -def _get_user_credential_decorator(validate_access_token_and_get_user, require_user, debug_mode): +def _get_user_credential_decorator( + validate_access_token_and_get_user, require_user, debug_mode +): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): @@ -15,10 +17,10 @@ def wrapper(*args, **kwargs): authorization_header = request.headers.get("Authorization") user = validate_access_token_and_get_user(authorization_header) - _request_ctx_stack.top.propelauth_current_user = LoggedInUser(user) + g.propelauth_current_user = LoggedInUser(user) except UnauthorizedException as e: - _request_ctx_stack.top.propelauth_current_user = LoggedOutUser() + g.propelauth_current_user = LoggedOutUser() _return_401_if_user_required(e, require_user, debug_mode) return func(*args, **kwargs) @@ -36,10 +38,12 @@ def wrapper(*args, **kwargs): try: authorization_header = request.headers.get("Authorization") required_org_id = req_to_org_id(request) - user_and_org = validate_access_token_and_get_user_with_org(authorization_header, required_org_id) + user_and_org = validate_access_token_and_get_user_with_org( + authorization_header, required_org_id + ) - _request_ctx_stack.top.propelauth_current_user = user_and_org.user - _request_ctx_stack.top.propelauth_current_org = user_and_org.org_member_info + g.propelauth_current_user = user_and_org.user + g.propelauth_current_org = user_and_org.org_member_info except UnauthorizedException as e: _return_401_if_user_required(e, True, debug_mode) @@ -56,20 +60,26 @@ def wrapper(*args, **kwargs): return decorator_that_takes_arguments -def _require_org_member_with_minimum_role_decorator(validate_access_token_and_get_user_with_org_by_minimum_role, - debug_mode): - def decorator_that_takes_arguments(minimum_required_role, req_to_org_id=_default_req_to_org_id): +def _require_org_member_with_minimum_role_decorator( + validate_access_token_and_get_user_with_org_by_minimum_role, debug_mode +): + def decorator_that_takes_arguments( + minimum_required_role, req_to_org_id=_default_req_to_org_id + ): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: authorization_header = request.headers.get("Authorization") required_org_id = req_to_org_id(request) - user_and_org = validate_access_token_and_get_user_with_org_by_minimum_role( - authorization_header, required_org_id, minimum_required_role) + user_and_org = ( + validate_access_token_and_get_user_with_org_by_minimum_role( + authorization_header, required_org_id, minimum_required_role + ) + ) - _request_ctx_stack.top.propelauth_current_user = user_and_org.user - _request_ctx_stack.top.propelauth_current_org = user_and_org.org_member_info + g.propelauth_current_user = user_and_org.user + g.propelauth_current_org = user_and_org.org_member_info except UnauthorizedException as e: _return_401_if_user_required(e, True, debug_mode) @@ -86,8 +96,9 @@ def wrapper(*args, **kwargs): return decorator_that_takes_arguments -def _require_org_member_with_exact_role_decorator(validate_access_token_and_get_user_with_org_by_exact_role, - debug_mode): +def _require_org_member_with_exact_role_decorator( + validate_access_token_and_get_user_with_org_by_exact_role, debug_mode +): def decorator_that_takes_arguments(role, req_to_org_id=_default_req_to_org_id): def decorator(func): @functools.wraps(func) @@ -95,11 +106,14 @@ def wrapper(*args, **kwargs): try: authorization_header = request.headers.get("Authorization") required_org_id = req_to_org_id(request) - user_and_org = validate_access_token_and_get_user_with_org_by_exact_role( - authorization_header, required_org_id, role) + user_and_org = ( + validate_access_token_and_get_user_with_org_by_exact_role( + authorization_header, required_org_id, role + ) + ) - _request_ctx_stack.top.propelauth_current_user = user_and_org.user - _request_ctx_stack.top.propelauth_current_org = user_and_org.org_member_info + g.propelauth_current_user = user_and_org.user + g.propelauth_current_org = user_and_org.org_member_info except UnauthorizedException as e: _return_401_if_user_required(e, True, debug_mode) @@ -116,20 +130,26 @@ def wrapper(*args, **kwargs): return decorator_that_takes_arguments -def _require_org_member_with_permission_decorator(validate_access_token_and_get_user_with_org_by_permission, - debug_mode): - def decorator_that_takes_arguments(permission, req_to_org_id=_default_req_to_org_id): +def _require_org_member_with_permission_decorator( + validate_access_token_and_get_user_with_org_by_permission, debug_mode +): + def decorator_that_takes_arguments( + permission, req_to_org_id=_default_req_to_org_id + ): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: authorization_header = request.headers.get("Authorization") required_org_id = req_to_org_id(request) - user_and_org = validate_access_token_and_get_user_with_org_by_permission( - authorization_header, required_org_id, permission) + user_and_org = ( + validate_access_token_and_get_user_with_org_by_permission( + authorization_header, required_org_id, permission + ) + ) - _request_ctx_stack.top.propelauth_current_user = user_and_org.user - _request_ctx_stack.top.propelauth_current_org = user_and_org.org_member_info + g.propelauth_current_user = user_and_org.user + g.propelauth_current_org = user_and_org.org_member_info except UnauthorizedException as e: _return_401_if_user_required(e, True, debug_mode) @@ -146,20 +166,26 @@ def wrapper(*args, **kwargs): return decorator_that_takes_arguments -def _require_org_member_with_all_permissions_decorator(validate_access_token_and_get_user_with_org_by_all_permissions, - debug_mode): - def decorator_that_takes_arguments(permissions, req_to_org_id=_default_req_to_org_id): +def _require_org_member_with_all_permissions_decorator( + validate_access_token_and_get_user_with_org_by_all_permissions, debug_mode +): + def decorator_that_takes_arguments( + permissions, req_to_org_id=_default_req_to_org_id + ): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: authorization_header = request.headers.get("Authorization") required_org_id = req_to_org_id(request) - user_and_org = validate_access_token_and_get_user_with_org_by_all_permissions( - authorization_header, required_org_id, permissions) + user_and_org = ( + validate_access_token_and_get_user_with_org_by_all_permissions( + authorization_header, required_org_id, permissions + ) + ) - _request_ctx_stack.top.propelauth_current_user = user_and_org.user - _request_ctx_stack.top.propelauth_current_org = user_and_org.org_member_info + g.propelauth_current_user = user_and_org.user + g.propelauth_current_org = user_and_org.org_member_info except UnauthorizedException as e: _return_401_if_user_required(e, True, debug_mode) @@ -191,4 +217,4 @@ def _return_exception(e, status, debug_mode): def _default_req_to_org_id(req): - return req.view_args.get('org_id') + return req.view_args.get("org_id") diff --git a/propelauth_flask/user.py b/propelauth_flask/user.py index d690873..9645394 100644 --- a/propelauth_flask/user.py +++ b/propelauth_flask/user.py @@ -3,6 +3,9 @@ class LoggedInUser: def __init__(self, user: User): + self.user = user + + # We only need the user, but these are here for backwards compatibility self.user_id = user.user_id self.org_id_to_org_member_info = user.org_id_to_org_member_info self.legacy_user_id = user.legacy_user_id @@ -10,10 +13,53 @@ def __init__(self, user: User): def exists(self): return True + def is_impersonated(self): + """Returns true if the user is impersonated""" + return self.user.is_impersonated() + + def get_active_org(self): + """Returns the active org member info, if the user has an active org.""" + return self.user.get_active_org() + + def get_active_org_id(self): + """Returns the active org id, if the user has an active org.""" + return self.user.get_active_org_id() + + def get_org(self, org_id): + """Returns the org member info for the org_id, if the user is in the org.""" + return self.user.get_org(org_id) + + def get_org_by_name(self, org_name): + """Returns the org member info for the org_name, if the user is in the org.""" + return self.user.get_org_by_name(org_name) + + def get_user_property(self, property_name): + """Returns the user property value, if it exists.""" + return self.user.get_user_property(property_name) + + def get_orgs(self): + """Returns the orgs the user is in.""" + return self.user.get_orgs() + + def is_role_in_org(self, org_id, role): + """Returns true if the user is the role in the org.""" + return self.user.is_role_in_org(org_id, role) + + def is_at_least_role_in_org(self, org_id, role): + """Returns true if the user is at least the role in the org.""" + return self.user.is_at_least_role_in_org(org_id, role) + + def has_permission_in_org(self, org_id, permission): + """Returns true if the user has the permission in the org.""" + return self.user.has_permission_in_org(org_id, permission) + + def has_all_permissions_in_org(self, org_id, permissions): + """Returns true if the user has all the permissions in the org.""" + return self.user.has_all_permissions_in_org(org_id, permissions) + def __eq__(self, other): if isinstance(other, LoggedInUser): - return self.user_id == other.user_id and self.org_id_to_org_member_info == other.org_id_to_org_member_info \ - and self.legacy_user_id == other.legacy_user_id + return self.user == other.user return False diff --git a/requirements.txt b/requirements.txt index 4191fd7..cee4dac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -flask<3 +flask<4 propelauth-py pytest requests-mock diff --git a/setup.py b/setup.py index 268ea44..750b638 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name="propelauth-flask", - version="2.1.11", + version="2.1.12", description="A library for managing authentication in Flask", long_description=README, long_description_content_type="text/markdown", @@ -20,7 +20,7 @@ author="PropelAuth", author_email="support@propelauth.com", license="MIT", - install_requires=["flask<3", "propelauth-py==3.1.12", "requests"], + install_requires=["flask<4", "propelauth-py==3.1.12", "requests"], setup_requires=pytest_runner, tests_require=["pytest==4.4.1"], test_suite="tests",