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:
依瑪貓 2023-04-03 19:36:26 +08:00
parent 9263ae0274
commit f7efacad75
3 changed files with 27 additions and 4 deletions

View File

@ -21,7 +21,7 @@ This module should not import any other module from the application.
"""
import typing as t
from flask import abort, Blueprint
from flask import abort, Blueprint, Response
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.
"""
if not rule():
if get_current_user() is None:
response: Response | None = _unauthorized_func()
if response is not None:
return response
abort(403)
return view(*args, **kwargs)
@ -66,6 +70,9 @@ data."""
__can_admin_func: t.Callable[[], bool] = lambda: True
"""The callback that returns whether the current user can administrate the
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:
@ -111,10 +118,12 @@ def init_app(bp: Blueprint, user_utils: UserUtilityInterface) -> None:
:param user_utils: The user utilities.
: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_edit_func = user_utils.can_edit
__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_edit, "accounting_can_edit")
bp.add_app_template_global(user_utils.can_admin, "accounting_can_admin")

View File

@ -23,7 +23,7 @@ import typing as t
from abc import ABC, abstractmethod
import sqlalchemy as sa
from flask import g
from flask import g, Response
from flask_sqlalchemy.model import Model
T = t.TypeVar("T", bound=Model)
@ -59,6 +59,17 @@ class UserUtilityInterface(t.Generic[T], ABC):
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
@abstractmethod
def cls(self) -> t.Type[T]:

View File

@ -22,7 +22,7 @@ import typing as t
from secrets import token_urlsafe
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_babel_js import BabelJS
from flask_sqlalchemy import SQLAlchemy
@ -82,6 +82,9 @@ def create_app(is_testing: bool = False) -> Flask:
return auth.current_user() is not None \
and auth.current_user().username == "admin"
def unauthorized(self) -> Response:
return redirect("/login")
@property
def cls(self) -> t.Type[auth.User]:
return auth.User