Added the unauthorized method to the UserUtilityInterface interface, so that when the user has not logged in, the permission decorator can ask the user to log in instead of failing with HTTP 403 Forbidden.
This commit is contained in:
parent
9263ae0274
commit
f7efacad75
@ -21,7 +21,7 @@ This module should not import any other module from the application.
|
|||||||
"""
|
"""
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from flask import abort, Blueprint
|
from flask import abort, Blueprint, Response
|
||||||
|
|
||||||
from accounting.utils.user import get_current_user, UserUtilityInterface
|
from accounting.utils.user import get_current_user, UserUtilityInterface
|
||||||
|
|
||||||
@ -49,6 +49,10 @@ def has_permission(rule: t.Callable[[], bool]) -> t.Callable:
|
|||||||
:raise Forbidden: When the user is denied.
|
:raise Forbidden: When the user is denied.
|
||||||
"""
|
"""
|
||||||
if not rule():
|
if not rule():
|
||||||
|
if get_current_user() is None:
|
||||||
|
response: Response | None = _unauthorized_func()
|
||||||
|
if response is not None:
|
||||||
|
return response
|
||||||
abort(403)
|
abort(403)
|
||||||
return view(*args, **kwargs)
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
@ -66,6 +70,9 @@ data."""
|
|||||||
__can_admin_func: t.Callable[[], bool] = lambda: True
|
__can_admin_func: t.Callable[[], bool] = lambda: True
|
||||||
"""The callback that returns whether the current user can administrate the
|
"""The callback that returns whether the current user can administrate the
|
||||||
accounting settings."""
|
accounting settings."""
|
||||||
|
_unauthorized_func: t.Callable[[], Response | None] \
|
||||||
|
= lambda: Response(status=403)
|
||||||
|
"""The callback that returns the response to require the user to log in."""
|
||||||
|
|
||||||
|
|
||||||
def can_view() -> bool:
|
def can_view() -> bool:
|
||||||
@ -111,10 +118,12 @@ def init_app(bp: Blueprint, user_utils: UserUtilityInterface) -> None:
|
|||||||
:param user_utils: The user utilities.
|
:param user_utils: The user utilities.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
global __can_view_func, __can_edit_func, __can_admin_func
|
global __can_view_func, __can_edit_func, __can_admin_func, \
|
||||||
|
_unauthorized_func
|
||||||
__can_view_func = user_utils.can_view
|
__can_view_func = user_utils.can_view
|
||||||
__can_edit_func = user_utils.can_edit
|
__can_edit_func = user_utils.can_edit
|
||||||
__can_admin_func = user_utils.can_admin
|
__can_admin_func = user_utils.can_admin
|
||||||
|
_unauthorized_func = user_utils.unauthorized
|
||||||
bp.add_app_template_global(user_utils.can_view, "accounting_can_view")
|
bp.add_app_template_global(user_utils.can_view, "accounting_can_view")
|
||||||
bp.add_app_template_global(user_utils.can_edit, "accounting_can_edit")
|
bp.add_app_template_global(user_utils.can_edit, "accounting_can_edit")
|
||||||
bp.add_app_template_global(user_utils.can_admin, "accounting_can_admin")
|
bp.add_app_template_global(user_utils.can_admin, "accounting_can_admin")
|
||||||
|
@ -23,7 +23,7 @@ import typing as t
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from flask import g
|
from flask import g, Response
|
||||||
from flask_sqlalchemy.model import Model
|
from flask_sqlalchemy.model import Model
|
||||||
|
|
||||||
T = t.TypeVar("T", bound=Model)
|
T = t.TypeVar("T", bound=Model)
|
||||||
@ -59,6 +59,17 @@ class UserUtilityInterface(t.Generic[T], ABC):
|
|||||||
accounting settings, or False otherwise.
|
accounting settings, or False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def unauthorized(self) -> Response | None:
|
||||||
|
"""Returns the response to require the user to log in.
|
||||||
|
|
||||||
|
This may be a redirection to the login page, or an HTTP 401
|
||||||
|
Unauthorized response for HTTP Authentication. If this returns None,
|
||||||
|
an HTTP 403 Forbidden response is return to the user.
|
||||||
|
|
||||||
|
:return: The response to require the user to log in.
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cls(self) -> t.Type[T]:
|
def cls(self) -> t.Type[T]:
|
||||||
|
@ -22,7 +22,7 @@ import typing as t
|
|||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from flask import Flask, Blueprint, render_template
|
from flask import Flask, Blueprint, render_template, redirect, Response
|
||||||
from flask.cli import with_appcontext
|
from flask.cli import with_appcontext
|
||||||
from flask_babel_js import BabelJS
|
from flask_babel_js import BabelJS
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
@ -82,6 +82,9 @@ def create_app(is_testing: bool = False) -> Flask:
|
|||||||
return auth.current_user() is not None \
|
return auth.current_user() is not None \
|
||||||
and auth.current_user().username == "admin"
|
and auth.current_user().username == "admin"
|
||||||
|
|
||||||
|
def unauthorized(self) -> Response:
|
||||||
|
return redirect("/login")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cls(self) -> t.Type[auth.User]:
|
def cls(self) -> t.Type[auth.User]:
|
||||||
return auth.User
|
return auth.User
|
||||||
|
Loading…
Reference in New Issue
Block a user