Coverage for /usr/lib/python3.10/site-packages/hyd/backend/user/models.py: 84%
43 statements
« prev ^ index » next coverage.py v7.0.3, created at 2023-02-05 02:26 +0000
« prev ^ index » next coverage.py v7.0.3, created at 2023-02-05 02:26 +0000
1import datetime as dt
2from typing import TypedDict
4from fastapi import status
5from sqlalchemy import Boolean, Column, Integer, LargeBinary, String
6from sqlalchemy.orm import Mapped, relationship
8from hyd.backend.db import EXTEND_EXISTING, DeclarativeMeta
9from hyd.backend.exc import HTTPException_NO_PERMISSION
10from hyd.backend.token.models import TokenEntry, TokenSchema
11from hyd.backend.util.const import MAX_LENGTH_STR_ID
12from hyd.backend.util.models import (
13 BASE_API_RESPONSE_SCHEMA,
14 DETAIL_STR,
15 NameStr,
16 PrimaryKey,
17 TimeStampMixin,
18)
20####################################################################################################
21#### SQLAlchemy table definitions
22####################################################################################################
25class UserEntry(DeclarativeMeta, TimeStampMixin):
26 __tablename__ = "user_table"
27 __table_args__ = {"extend_existing": EXTEND_EXISTING}
28 id: Mapped[PrimaryKey] = Column(Integer, primary_key=True)
29 username: Mapped[NameStr] = Column(String(length=MAX_LENGTH_STR_ID), unique=True)
30 hashed_password: Mapped[bytes] = Column(LargeBinary)
31 is_admin: Mapped[bool] = Column(Boolean)
32 is_disabled: Mapped[bool] = Column(Boolean, default=False)
34 token_entries: Mapped[list[TokenEntry]] = relationship(
35 "TokenEntry", back_populates="user_entry"
36 )
37 login_token_entries: Mapped[list[TokenEntry]] = relationship(
38 "TokenEntry",
39 primaryjoin=("and_(TokenEntry.user_id==UserEntry.id, TokenEntry.is_login_token==True)"),
40 viewonly=True,
41 )
43 _session_token_entry: TokenEntry | None = None
44 _session_permitted_scopes: list[str] | None = None
46 @property
47 def session_token_entry(self) -> TokenEntry:
48 return self._session_token_entry
50 def check_scope_permission(self, *, scope: str) -> bool: # TODO raise if None
51 """Check if the given scope is permitted for the given session."""
52 return scope in self._session_permitted_scopes
54 def check_token_project_permission(self, *, project_id: PrimaryKey) -> None:
55 token_project_id = self._session_token_entry.project_id
56 if token_project_id is None:
57 return
58 if token_project_id != project_id:
59 raise HTTPException_NO_PERMISSION
62####################################################################################################
63#### Response schema
64####################################################################################################
66# NOTE currently unused
67class UserResponseSchema(TypedDict):
68 id: PrimaryKey
69 username: NameStr
70 is_admin: bool
71 is_disabled: bool
72 created_at: dt.datetime
75####################################################################################################
76#### OpenAPI definitions
77####################################################################################################
80API_V1_LOGIN__POST = {
81 **BASE_API_RESPONSE_SCHEMA,
82 status.HTTP_200_OK: {"model": TokenSchema},
83 status.HTTP_400_BAD_REQUEST: DETAIL_STR,
84}
87API_V1_LOGOUT__PATCH = {
88 **BASE_API_RESPONSE_SCHEMA,
89 status.HTTP_200_OK: {"model": str},
90}
92API_V1_CHANGE_PASSWORD__PATCH = {
93 **BASE_API_RESPONSE_SCHEMA,
94 status.HTTP_200_OK: {"model": str},
95 status.HTTP_400_BAD_REQUEST: DETAIL_STR,
96}
99API_V1_GREET__GET = {
100 **BASE_API_RESPONSE_SCHEMA,
101 status.HTTP_200_OK: {"model": str},
102}