From 973b924c27c423b8cbca7d50c99ed54c0c87ab83 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Tue, 20 Aug 2024 06:48:14 -0400 Subject: [PATCH 01/17] Add unbounded sessions --- auth_backend/auth_method/method_mixins.py | 11 +++++++++-- auth_backend/auth_plugins/email.py | 7 ++++++- auth_backend/auth_plugins/github.py | 13 +++++++++++-- auth_backend/auth_plugins/google.py | 13 +++++++++++-- auth_backend/auth_plugins/keycloak.py | 13 +++++++++++-- auth_backend/auth_plugins/lkmsu.py | 13 +++++++++++-- auth_backend/auth_plugins/telegram.py | 13 +++++++++++-- auth_backend/auth_plugins/vk.py | 13 +++++++++++-- auth_backend/auth_plugins/yandex.py | 13 +++++++++++-- auth_backend/models/db.py | 1 + auth_backend/routes/user_session.py | 8 +++++++- auth_backend/schemas/models.py | 2 ++ auth_backend/utils/security.py | 7 ++++++- auth_backend/utils/user_session_control.py | 9 +++++++-- 14 files changed, 115 insertions(+), 21 deletions(-) diff --git a/auth_backend/auth_method/method_mixins.py b/auth_backend/auth_method/method_mixins.py index 72a2670f..ce42cf84 100644 --- a/auth_backend/auth_method/method_mixins.py +++ b/auth_backend/auth_method/method_mixins.py @@ -51,7 +51,14 @@ async def _login(*args, **kwargs) -> Session: @staticmethod async def _create_session( - user: User, scopes_list_names: list[TypeScope] | None, session_name: str | None = None, *, db_session: DbSession + user: User, + scopes_list_names: list[TypeScope] | None, + session_name: str | None = None, + is_unbounded: bool | None = None, + *, + db_session: DbSession, ) -> Session: """Создает сессию пользователя""" - return await create_session(user, scopes_list_names, db_session=db_session, session_name=session_name) + return await create_session( + user, scopes_list_names, db_session=db_session, session_name=session_name, is_unbounded=is_unbounded + ) diff --git a/auth_backend/auth_plugins/email.py b/auth_backend/auth_plugins/email.py index 330867dd..58c17a10 100644 --- a/auth_backend/auth_plugins/email.py +++ b/auth_backend/auth_plugins/email.py @@ -66,6 +66,7 @@ class EmailLogin(Base): password: Annotated[str, MinLen(1)] scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None email_validator = field_validator("email")(check_email) @@ -166,7 +167,11 @@ async def _login(cls, user_inp: EmailLogin, background_tasks: BackgroundTasks) - userdata, ) return await cls._create_session( - query.user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + query.user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @staticmethod diff --git a/auth_backend/auth_plugins/github.py b/auth_backend/auth_plugins/github.py index 1ef892a0..1ad24309 100644 --- a/auth_backend/auth_plugins/github.py +++ b/auth_backend/auth_plugins/github.py @@ -40,6 +40,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="GitHub JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -114,7 +115,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -169,7 +174,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/google.py b/auth_backend/auth_plugins/google.py index 90afb810..081520a8 100644 --- a/auth_backend/auth_plugins/google.py +++ b/auth_backend/auth_plugins/google.py @@ -49,6 +49,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Google JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -122,7 +123,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -161,7 +166,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/keycloak.py b/auth_backend/auth_plugins/keycloak.py index 1520f188..43f1a301 100644 --- a/auth_backend/auth_plugins/keycloak.py +++ b/auth_backend/auth_plugins/keycloak.py @@ -41,6 +41,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Keycloak JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -110,7 +111,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -160,7 +165,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/lkmsu.py b/auth_backend/auth_plugins/lkmsu.py index b9cac630..ef90707c 100644 --- a/auth_backend/auth_plugins/lkmsu.py +++ b/auth_backend/auth_plugins/lkmsu.py @@ -42,6 +42,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="LK MSU JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -109,7 +110,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -161,7 +166,11 @@ async def _login( userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/telegram.py b/auth_backend/auth_plugins/telegram.py index 11484f5b..2ea09981 100644 --- a/auth_backend/auth_plugins/telegram.py +++ b/auth_backend/auth_plugins/telegram.py @@ -45,6 +45,7 @@ class OauthResponseSchema(BaseModel): hash: str | None = None scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -88,7 +89,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -118,7 +123,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/vk.py b/auth_backend/auth_plugins/vk.py index ff771dad..e7d3668a 100644 --- a/auth_backend/auth_plugins/vk.py +++ b/auth_backend/auth_plugins/vk.py @@ -54,6 +54,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="VK JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -121,7 +122,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -170,7 +175,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/yandex.py b/auth_backend/auth_plugins/yandex.py index c1f8dbbe..b9e04fe4 100644 --- a/auth_backend/auth_plugins/yandex.py +++ b/auth_backend/auth_plugins/yandex.py @@ -41,6 +41,7 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Yandex JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None + is_unbounded: bool | None = None @classmethod async def _register( @@ -126,7 +127,11 @@ async def _register( ) await AuthPluginMeta.user_updated(new_user, old_user) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -174,7 +179,11 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun userdata, ) return await cls._create_session( - user, user_inp.scopes, db_session=db.session, session_name=user_inp.session_name + user, + user_inp.scopes, + db_session=db.session, + session_name=user_inp.session_name, + is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/models/db.py b/auth_backend/models/db.py index 8c31cee9..412554e5 100644 --- a/auth_backend/models/db.py +++ b/auth_backend/models/db.py @@ -158,6 +158,7 @@ class UserSession(BaseDbModel): user_id: Mapped[int] = mapped_column(Integer, sqlalchemy.ForeignKey("user.id")) expires: Mapped[datetime.datetime] = mapped_column(DateTime, default=session_expires_date) token: Mapped[str] = mapped_column(String, unique=True) + is_unbounded: Mapped[bool] = mapped_column(Boolean, default=True) last_activity: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) user: Mapped[User] = relationship( diff --git a/auth_backend/routes/user_session.py b/auth_backend/routes/user_session.py index 50e0a69d..359fd870 100644 --- a/auth_backend/routes/user_session.py +++ b/auth_backend/routes/user_session.py @@ -69,7 +69,11 @@ async def me( | UserIndirectGroups(indirect_groups=[group.id for group in session.user.indirect_groups]).model_dump() ) if "session_scopes" in info: - result = result | SessionScopes(session_scopes=session.scopes).model_dump() + result = result | ( + UserScopes(user_scopes=session.user.scopes).model_dump() + if session.is_unbounded + else SessionScopes(session_scopes=session.scopes).model_dump() + ) if "user_scopes" in info: result = result | UserScopes(user_scopes=session.user.scopes).model_dump() if "auth_methods" in info: @@ -98,6 +102,7 @@ async def create_session( new_session.expires, db_session=db.session, session_name=new_session.session_name, + is_unbounded=new_session.is_unbounded, ) @@ -146,6 +151,7 @@ async def get_sessions( id=session.id, last_activity=session.last_activity, session_name=session.session_name, + is_unbounded=session.is_unbounded, ) if "session_scopes" in info: result['session_scopes'] = [_scope.name for _scope in session.scopes] diff --git a/auth_backend/schemas/models.py b/auth_backend/schemas/models.py index 5621cd89..8fd7bd24 100644 --- a/auth_backend/schemas/models.py +++ b/auth_backend/schemas/models.py @@ -132,6 +132,7 @@ class Session(Base): expires: datetime | None = None id: int user_id: int + is_unbounded: bool | None = None session_scopes: list[Scope] | None = None last_activity: datetime @@ -140,6 +141,7 @@ class SessionPost(Base): session_name: str | None = None scopes: list[Scope] = [] expires: datetime | None = None + is_unbounded: bool | None = None @classmethod @field_validator("expires") diff --git a/auth_backend/utils/security.py b/auth_backend/utils/security.py index 4588a4b1..1119f33b 100644 --- a/auth_backend/utils/security.py +++ b/auth_backend/utils/security.py @@ -53,7 +53,12 @@ async def __call__( if user_session.expired: self._except() - session_scopes = set([scope.name.lower() for scope in user_session.scopes]) + session_scopes = set( + [ + scope.name.lower() + for scope in (user_session.user.scopes if user_session.is_unbounded else user_session.scopes) + ] + ) if self._SESSION_UPDATE_SCOPE in session_scopes: user_session.expires = session_expires_date() db.session.commit() diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index e45f5f18..916ab246 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -20,6 +20,7 @@ async def create_session( scopes_list_names: list[TypeScope] | None, expires: datetime | None = None, session_name: str | None = None, + is_unbounded: bool | None = None, *, db_session: DbSession, ) -> Session: @@ -33,10 +34,13 @@ async def create_session( user_id=user.id, token=random_string(length=settings.TOKEN_LENGTH), session_name=session_name ) user_session.expires = expires or user_session.expires + if is_unbounded is not None: + user_session.is_unbounded = is_unbounded db_session.add(user_session) db_session.flush() - for scope in scopes: - db_session.add(UserSessionScope(scope_id=scope.id, user_session_id=user_session.id)) + if not user_session.is_unbounded: + for scope in scopes: + db_session.add(UserSessionScope(scope_id=scope.id, user_session_id=user_session.id)) db_session.commit() return Session( session_name=session_name, @@ -44,6 +48,7 @@ async def create_session( token=user_session.token, id=user_session.id, expires=user_session.expires, + is_unbounded=user_session.is_unbounded, session_scopes=[_scope.name for _scope in user_session.scopes], last_activity=user_session.last_activity, ) From 6895ed9d95d6b09ca952b92f93caed7fdd75ab38 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Tue, 20 Aug 2024 07:10:44 -0400 Subject: [PATCH 02/17] Add unbounded sessions --- auth_backend/routes/user_session.py | 2 +- ...6dffd8e42152_193_add_unbounded_sessions.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py diff --git a/auth_backend/routes/user_session.py b/auth_backend/routes/user_session.py index 359fd870..dda75998 100644 --- a/auth_backend/routes/user_session.py +++ b/auth_backend/routes/user_session.py @@ -70,7 +70,7 @@ async def me( ) if "session_scopes" in info: result = result | ( - UserScopes(user_scopes=session.user.scopes).model_dump() + SessionScopes(session_scopes=session.user.scopes).model_dump() if session.is_unbounded else SessionScopes(session_scopes=session.scopes).model_dump() ) diff --git a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py new file mode 100644 index 00000000..cc4ff8e2 --- /dev/null +++ b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py @@ -0,0 +1,37 @@ +"""193 Add unbounded sessions + +Revision ID: 6dffd8e42152 +Revises: 2d29fc132e89 +Create Date: 2024-08-19 19:27:25.867548 + +""" + +import datetime + +import sqlalchemy as sa +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '6dffd8e42152' +down_revision = '2d29fc132e89' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('user_session', sa.Column('is_unbounded', sa.Boolean(), nullable=True)) + conn = op.get_bind() + session_list = conn.execute(sa.text("SELECT token, expires FROM user_session")).fetchall() + for session in session_list: + if session[1] <= datetime.datetime.utcnow(): + conn.execute(sa.text(f"UPDATE user_session SET is_unbounded='false' WHERE token='{session[0]}'")) + else: + conn.execute(sa.text(f"UPDATE user_session SET is_unbounded='true' WHERE token='{session[0]}'")) + op.alter_column('user_session', 'is_unbounded', nullable=False) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('user_session', 'is_unbounded') + # ### end Alembic commands ### From 614a7e7f0ce9c849c39212c54bec7ae0b441b834 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Tue, 27 Aug 2024 09:30:28 -0400 Subject: [PATCH 03/17] Fix session scopes --- auth_backend/routes/user_session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/auth_backend/routes/user_session.py b/auth_backend/routes/user_session.py index dda75998..37927f73 100644 --- a/auth_backend/routes/user_session.py +++ b/auth_backend/routes/user_session.py @@ -154,7 +154,9 @@ async def get_sessions( is_unbounded=session.is_unbounded, ) if "session_scopes" in info: - result['session_scopes'] = [_scope.name for _scope in session.scopes] + result['session_scopes'] = [ + _scope.name for _scope in (session.user.scopes if session.is_unbounded else session.scopes) + ] if "token" in info: result['token'] = session.token[-4:] if "expires" in info: From 3ba3638175675c1b850257a641ea13b3e643b733 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Mon, 2 Sep 2024 11:08:49 -0400 Subject: [PATCH 04/17] Fixes for unbounded sessions --- auth_backend/routes/user.py | 2 + .../dcb89e72d446_session_security_scopes.py | 10 ++--- tests/conftest.py | 41 +++++++------------ tests/test_routes/test_group_scopes.py | 4 +- tests/test_routes/test_user_groups.py | 5 +++ 5 files changed, 28 insertions(+), 34 deletions(-) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 5214c7e7..4ada8519 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -164,3 +164,5 @@ async def delete_user( User.delete(user_id, session=db.session) await AuthPluginMeta.user_updated(None, old_user) logger.info(f'{user=} deleted') + db.session.commit() + diff --git a/migrations/versions/dcb89e72d446_session_security_scopes.py b/migrations/versions/dcb89e72d446_session_security_scopes.py index 01c585f4..8b698ee3 100644 --- a/migrations/versions/dcb89e72d446_session_security_scopes.py +++ b/migrations/versions/dcb89e72d446_session_security_scopes.py @@ -9,7 +9,7 @@ from alembic import op from sqlalchemy.orm import Session -from auth_backend.models.db import DynamicOption, Group, Scope, User, UserSession +from auth_backend.models.db import DynamicOption, Group, Scope, User, UserSession, UserSessionScope # revision identifiers, used by Alembic. @@ -41,10 +41,10 @@ def upgrade(): root_group.scopes.update([scope1, scope2]) user_group.scopes.update([scope1, scope2]) session.flush() - user_sessions = UserSession.query(session=session).all() - for user_session in user_sessions: - user_session.scopes.extend((scope1, scope2)) - session.flush() + sessions_id = session.query(UserSession.id).all() + for session_id in sessions_id: + UserSessionScope.create(user_session_id=session_id.id, scope_id=scope1.id, is_deleted=False, session=session) + UserSessionScope.create(user_session_id=session_id.id, scope_id=scope2.id, is_deleted=False, session=session) session.commit() diff --git a/tests/conftest.py b/tests/conftest.py index 68319eba..a6e2af39 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -115,7 +115,9 @@ def parent_id(client, dbsession): body = {"name": f"group{time}", "parent_id": None, "scopes": []} response = client.post(url="/group", json=body) yield response.json()["id"] - dbsession.query(Group).get(response.json()["id"]) + group: Group = Group.get(response.json()["id"], session=dbsession) + group.users.clear() + group.delete(id=response.json()["id"], session=dbsession) dbsession.commit() @@ -135,7 +137,7 @@ def _group(client: TestClient): for row in _ids: group: Group = Group.get(row, session=dbsession) group.users.clear() - group.delete(session=dbsession) + group.delete(id=row, session=dbsession) dbsession.commit() @@ -153,31 +155,13 @@ def _user(client): yield _user - for row in dbsession.query(UserGroup).all(): - dbsession.delete(row) - dbsession.flush() - - dbsession.query(GroupScope).delete() - dbsession.flush() - - dbsession.query(Scope).delete() - dbsession.flush() - - dbsession.query(Group).delete() - dbsession.flush() - - dbsession.query(AuthMethod).delete() - dbsession.flush() - - dbsession.query(UserSession).delete() - dbsession.flush() - - dbsession.query(User).delete() + for user in _users: + dbsession.delete(user) dbsession.commit() @pytest.fixture() -def user_scopes(dbsession, user): +def user_scopes(dbsession, user, group, client): user_id, body, response = user["user_id"], user["body"], user["login_json"] scopes_names = [ "auth.scope.create", @@ -201,14 +185,17 @@ def user_scopes(dbsession, user): token_ = random_string() dbsession.add(user_session := UserSession(user_id=user_id, token=token_)) dbsession.commit() - user_scopes = [] + group_scopes = [] + group_id = group(client) for i in scopes: - dbsession.add(user_scope1 := UserSessionScope(scope_id=i.id, user_session_id=user_session.id)) - user_scopes.append(user_scope1) + dbsession.add(group_scope := GroupScope(scope_id=i.id, group_id=group_id)) + group_scopes.append(group_scope) + dbsession.add(user_group := UserGroup(user_id=user_id, group_id=group_id)) dbsession.flush() dbsession.commit() yield token_, user - for i in user_scopes: + dbsession.delete(user_group) + for i in group_scopes: dbsession.delete(i) dbsession.flush() for i in scopes: diff --git a/tests/test_routes/test_group_scopes.py b/tests/test_routes/test_group_scopes.py index 1ce55b81..bbd620a7 100644 --- a/tests/test_routes/test_group_scopes.py +++ b/tests/test_routes/test_group_scopes.py @@ -2,7 +2,6 @@ from auth_backend.models.db import Group, GroupScope, Scope, UserGroup - def test_scopes_groups(client_auth, dbsession, user_scopes): token = user_scopes[0] scope1 = dbsession.query(Scope).filter(Scope.name == "auth.group.create").one() @@ -10,7 +9,8 @@ def test_scopes_groups(client_auth, dbsession, user_scopes): time1 = datetime.datetime.utcnow() body = {"name": f"group{time1}", "parent_id": None, "scopes": []} headers = {"Authorization": token} - _group1 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] + resp = client_auth.post(url="/group", json=body, headers=headers).json() + _group1 = resp["id"] time2 = datetime.datetime.utcnow() body = {"name": f"group{time2}", "parent_id": _group1, "scopes": []} _group2 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] diff --git a/tests/test_routes/test_user_groups.py b/tests/test_routes/test_user_groups.py index db7f87ed..b0905bb7 100644 --- a/tests/test_routes/test_user_groups.py +++ b/tests/test_routes/test_user_groups.py @@ -65,6 +65,8 @@ def test_get_user_list(client, dbsession, user_factory): assert us1 in gr.users assert us2 in gr.users assert us3 in gr.users + dbsession.delete(gr) + dbsession.commit() def test_del_user_from_group(client, dbsession, user_factory): @@ -99,3 +101,6 @@ def test_del_user_from_group(client, dbsession, user_factory): assert us1 in gr.users assert us2 not in gr.users assert us3 in gr.users + gr.users.clear() + gr.delete(id=group, session=dbsession) + dbsession.commit() From 8835c6910bb31642c3d3dc1f742edb30f038feb0 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Mon, 2 Sep 2024 21:06:03 -0400 Subject: [PATCH 05/17] Add tests for unbounded sessions --- auth_backend/routes/user.py | 1 - .../dcb89e72d446_session_security_scopes.py | 4 +- tests/conftest.py | 31 +++++++------ tests/test_routes/test_group_scopes.py | 4 +- tests/test_routes/test_login.py | 46 ++++++++++++++++++- tests/test_routes/test_user_sessions.py | 27 ++++++++++- 6 files changed, 91 insertions(+), 22 deletions(-) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 4ada8519..538910f5 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -165,4 +165,3 @@ async def delete_user( await AuthPluginMeta.user_updated(None, old_user) logger.info(f'{user=} deleted') db.session.commit() - diff --git a/migrations/versions/dcb89e72d446_session_security_scopes.py b/migrations/versions/dcb89e72d446_session_security_scopes.py index 8b698ee3..7eff670b 100644 --- a/migrations/versions/dcb89e72d446_session_security_scopes.py +++ b/migrations/versions/dcb89e72d446_session_security_scopes.py @@ -43,8 +43,8 @@ def upgrade(): session.flush() sessions_id = session.query(UserSession.id).all() for session_id in sessions_id: - UserSessionScope.create(user_session_id=session_id.id, scope_id=scope1.id, is_deleted=False, session=session) - UserSessionScope.create(user_session_id=session_id.id, scope_id=scope2.id, is_deleted=False, session=session) + UserSessionScope.create(user_session_id=session_id.id, scope_id=scope1.id, is_deleted=False, session=session) + UserSessionScope.create(user_session_id=session_id.id, scope_id=scope2.id, is_deleted=False, session=session) session.commit() diff --git a/tests/conftest.py b/tests/conftest.py index a6e2af39..20965d8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,13 +4,13 @@ import pytest from fastapi.testclient import TestClient -from sqlalchemy import create_engine +from sqlalchemy import create_engine, func from sqlalchemy.orm import sessionmaker from starlette import status from auth_backend.auth_plugins import YandexAuth from auth_backend.models import AuthMethod, User -from auth_backend.models.db import AuthMethod, Group, GroupScope, Scope, User, UserGroup, UserSession, UserSessionScope +from auth_backend.models.db import AuthMethod, Group, Scope, User, UserSession, UserSessionScope from auth_backend.routes.base import app from auth_backend.settings import get_settings from auth_backend.utils.string import random_string @@ -78,7 +78,7 @@ def user_id(client_auth: TestClient, dbsession): def user(client_auth: TestClient, dbsession): url = "/email/login" time = datetime.datetime.utcnow() - body = {"email": f"user{time}@example.com", "password": "string", "scopes": []} + body = {"email": f"user{time}@example.com", "password": "string", "scopes": [], "is_unbounded": False} response = client_auth.post("/email/registration", json=body) db_user: AuthMethod = ( dbsession.query(AuthMethod).filter(AuthMethod.value == body['email'], AuthMethod.param == 'email').one() @@ -161,7 +161,7 @@ def _user(client): @pytest.fixture() -def user_scopes(dbsession, user, group, client): +def user_scopes(dbsession, user): user_id, body, response = user["user_id"], user["body"], user["login_json"] scopes_names = [ "auth.scope.create", @@ -179,28 +179,29 @@ def user_scopes(dbsession, user, group, client): "auth.session.update", ] scopes = [] + created_scopes = [] for i in scopes_names: - dbsession.add(scope1 := Scope(name=i, creator_id=user_id)) + scope1 = Scope.query(session=dbsession).filter(func.lower(Scope.name) == i.lower()).one_or_none() + if scope1 is None: + dbsession.add(scope1 := Scope(name=i, creator_id=user_id)) + created_scopes.append(scope1) scopes.append(scope1) token_ = random_string() - dbsession.add(user_session := UserSession(user_id=user_id, token=token_)) + dbsession.add(user_session := UserSession(user_id=user_id, token=token_, is_unbounded=False)) dbsession.commit() - group_scopes = [] - group_id = group(client) + user_scopes = [] for i in scopes: - dbsession.add(group_scope := GroupScope(scope_id=i.id, group_id=group_id)) - group_scopes.append(group_scope) - dbsession.add(user_group := UserGroup(user_id=user_id, group_id=group_id)) + dbsession.add(user_scope1 := UserSessionScope(scope_id=i.id, user_session_id=user_session.id)) + user_scopes.append(user_scope1) dbsession.flush() dbsession.commit() yield token_, user - dbsession.delete(user_group) - for i in group_scopes: + for i in user_scopes: dbsession.delete(i) dbsession.flush() - for i in scopes: + for i in created_scopes: dbsession.delete(i) - dbsession.delete(user_session) + dbsession.flush() dbsession.commit() diff --git a/tests/test_routes/test_group_scopes.py b/tests/test_routes/test_group_scopes.py index bbd620a7..1ce55b81 100644 --- a/tests/test_routes/test_group_scopes.py +++ b/tests/test_routes/test_group_scopes.py @@ -2,6 +2,7 @@ from auth_backend.models.db import Group, GroupScope, Scope, UserGroup + def test_scopes_groups(client_auth, dbsession, user_scopes): token = user_scopes[0] scope1 = dbsession.query(Scope).filter(Scope.name == "auth.group.create").one() @@ -9,8 +10,7 @@ def test_scopes_groups(client_auth, dbsession, user_scopes): time1 = datetime.datetime.utcnow() body = {"name": f"group{time1}", "parent_id": None, "scopes": []} headers = {"Authorization": token} - resp = client_auth.post(url="/group", json=body, headers=headers).json() - _group1 = resp["id"] + _group1 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] time2 = datetime.datetime.utcnow() body = {"name": f"group{time2}", "parent_id": _group1, "scopes": []} _group2 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] diff --git a/tests/test_routes/test_login.py b/tests/test_routes/test_login.py index 0599c6db..111e8242 100644 --- a/tests/test_routes/test_login.py +++ b/tests/test_routes/test_login.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import Session from starlette import status -from auth_backend.models.db import AuthMethod, Group, User, UserGroup, UserSession +from auth_backend.models.db import AuthMethod, Group, GroupScope, Scope, User, UserGroup, UserSession url = "/email/login" @@ -134,3 +134,47 @@ def test_check_me_groups(client_auth: TestClient, user_scopes, dbsession): dbsession.query(Group).filter(Group.id == _group2).delete() dbsession.query(Group).filter(Group.id == _group1).delete() dbsession.commit() + + +def test_check_unbounded_session(client_auth: TestClient, user_scopes, dbsession): + token_, user = user_scopes + body_user = user["body"] + body_user["is_unbounded"] = True + scope1 = dbsession.query(Scope).filter(Scope.name == "auth.group.create").one() + time1 = datetime.datetime.utcnow() + body = {"name": f"group{time1}", "parent_id": None, "scopes": []} + headers = {"Authorization": token_} + _group1 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] + client_auth.patch(f"/user/{user['user_id']}", json={"groups": [_group1]}, headers={"Authorization": token_}) + response = client_auth.post("/email/login", json=body_user) + assert response.status_code == status.HTTP_200_OK + token = response.json()["token"] + response = client_auth.get( + "/me", headers={"Authorization": token}, params={"info": ["session_scopes", "user_scopes"]} + ) + assert response.json()["session_scopes"] == response.json()["user_scopes"] + assert scope1.id not in [row["id"] for row in response.json()["session_scopes"]] + client_auth.patch(f"/group/{_group1}", json={"scopes": [scope1.id]}, headers=headers) + response = client_auth.get("/me", headers={"Authorization": token}, params={"info": ["session_scopes"]}) + assert scope1.id in [row["id"] for row in response.json()["session_scopes"]] + dbsession.query(GroupScope).filter(GroupScope.group_id == _group1).delete() + dbsession.query(UserGroup).filter(UserGroup.group_id == _group1).delete() + dbsession.query(Group).filter(Group.id == _group1).delete() + dbsession.commit() + + +def test_check_unbounded_session_scopes(client_auth: TestClient, user_scopes, dbsession): + token_, user = user_scopes + body_user = user["body"] + body_user["is_unbounded"] = True + scope1 = dbsession.query(Scope).filter(Scope.name == "auth.session.create").one() + scope2 = dbsession.query(Scope).filter(Scope.name == "auth.session.update").one() + response = client_auth.post("/email/login", json=body_user | {"scopes": [scope1.name]}) + assert response.status_code == status.HTTP_200_OK + token = response.json()["token"] + response = client_auth.get( + "/me", headers={"Authorization": token}, params={"info": ["session_scopes", "user_scopes"]} + ) + assert response.json()["session_scopes"] == response.json()["user_scopes"] + assert scope1.id in [row["id"] for row in response.json()["session_scopes"]] + assert scope2.id in [row["id"] for row in response.json()["session_scopes"]] diff --git a/tests/test_routes/test_user_sessions.py b/tests/test_routes/test_user_sessions.py index 049dd9bc..8fc934cb 100644 --- a/tests/test_routes/test_user_sessions.py +++ b/tests/test_routes/test_user_sessions.py @@ -177,7 +177,7 @@ def test_patch_session(client_auth: TestClient, dbsession: Session, user_scopes) token = user_scopes[0] header = {"Authorization": token} params = {"info": ["session_scopes", "token", "expires"]} - payload = {"session_name": "test_session"} + payload = {"session_name": "test_session", "is_unbounded": False} new_session1 = client_auth.post("/session", headers=header, json=payload) assert new_session1.status_code == status.HTTP_200_OK assert new_session1.json()['session_name'] == payload['session_name'] @@ -193,3 +193,28 @@ def test_patch_session(client_auth: TestClient, dbsession: Session, user_scopes) for session in get_patch_session2.json(): if session['id'] == new_session1.json()['id']: assert session["session_scopes"] == [] + + +def test_create_unbounded_session(client_auth: TestClient, user_scopes, dbsession): + token_, user = user_scopes + scope1 = dbsession.query(Scope).filter(Scope.name == "auth.group.create").one() + time1 = datetime.utcnow() + body = {"name": f"group{time1}", "parent_id": None, "scopes": []} + headers = {"Authorization": token_} + _group1 = client_auth.post(url="/group", json=body, headers=headers).json()["id"] + client_auth.patch(f"/user/{user['user_id']}", json={"groups": [_group1]}, headers={"Authorization": token_}) + response = client_auth.post("/session", json={"is_unbounded": True}, headers=headers) + assert response.status_code == status.HTTP_200_OK + token = response.json()["token"] + response = client_auth.get( + "/me", headers={"Authorization": token}, params={"info": ["session_scopes", "user_scopes"]} + ) + assert response.json()["session_scopes"] == response.json()["user_scopes"] + assert scope1.id not in [row["id"] for row in response.json()["session_scopes"]] + client_auth.patch(f"/group/{_group1}", json={"scopes": [scope1.id]}, headers=headers) + response = client_auth.get("/me", headers={"Authorization": token}, params={"info": ["session_scopes"]}) + assert scope1.id in [row["id"] for row in response.json()["session_scopes"]] + dbsession.query(GroupScope).filter(GroupScope.group_id == _group1).delete() + dbsession.query(UserGroup).filter(UserGroup.group_id == _group1).delete() + dbsession.query(Group).filter(Group.id == _group1).delete() + dbsession.commit() From 6a2d871baf462eb027e4821117101e9cf21cd1b0 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Mon, 2 Sep 2024 22:30:52 -0400 Subject: [PATCH 06/17] Fix tests --- tests/conftest.py | 4 +++- tests/test_routes/test_email_message_delay.py | 11 +++++++++++ tests/test_routes/test_groups.py | 4 ++-- tests/test_routes/test_registration.py | 13 ++++++++++++- tests/test_routes/test_user.py | 5 ++++- tests/test_routes/test_user_groups.py | 14 ++++++++++++++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 20965d8f..f5562266 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from auth_backend.auth_plugins import YandexAuth from auth_backend.models import AuthMethod, User -from auth_backend.models.db import AuthMethod, Group, Scope, User, UserSession, UserSessionScope +from auth_backend.models.db import AuthMethod, Group, Scope, User, UserGroup, UserSession, UserSessionScope from auth_backend.routes.base import app from auth_backend.settings import get_settings from auth_backend.utils.string import random_string @@ -103,6 +103,8 @@ def user(client_auth: TestClient, dbsession): for row in session: dbsession.delete(row) dbsession.commit() + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == db_user.user_id).all(): + dbsession.delete(row) for row in dbsession.query(AuthMethod).filter(AuthMethod.user_id == db_user.user_id).all(): dbsession.delete(row) dbsession.delete(dbsession.query(User).filter(User.id == db_user.user_id).one()) diff --git a/tests/test_routes/test_email_message_delay.py b/tests/test_routes/test_email_message_delay.py index 64d84920..ebc9b9de 100644 --- a/tests/test_routes/test_email_message_delay.py +++ b/tests/test_routes/test_email_message_delay.py @@ -2,6 +2,7 @@ from sqlalchemy.orm import Session from starlette import status +from auth_backend.models.db import AuthMethod, User from auth_backend.settings import get_settings @@ -25,3 +26,13 @@ def test_message_delay(client_auth_email_delay: TestClient, dbsession: Session): assert delay_response.status_code == status.HTTP_429_TOO_MANY_REQUESTS settings_.IP_DELAY_TIME_IN_MINUTES = ip_delay settings_.EMAIL_DELAY_TIME_IN_MINUTES = email_delay + auth_method = ( + dbsession.query(AuthMethod) + .filter(AuthMethod.param == "email", AuthMethod.value == "test-user@profcomff.com") + .one() + ) + for row in dbsession.query(AuthMethod).filter(AuthMethod.user_id == auth_method.user_id).all(): + dbsession.delete(row) + dbsession.flush() + dbsession.delete(dbsession.query(User).filter(User.id == auth_method.user_id).one()) + dbsession.commit() diff --git a/tests/test_routes/test_groups.py b/tests/test_routes/test_groups.py index 9263bf62..c5f23fb6 100644 --- a/tests/test_routes/test_groups.py +++ b/tests/test_routes/test_groups.py @@ -35,8 +35,8 @@ def test_create(client, dbsession): assert parent.parent_id == response_parent.json()["parent_id"] assert parent.name == response_parent.json()["name"] - Group.delete(response.json()["id"], session=dbsession) - Group.delete(response_parent.json()["id"], session=dbsession) + for row in dbsession.query(Group).get(group.id), dbsession.query(Group).get(parent.id): + dbsession.delete(row) dbsession.commit() diff --git a/tests/test_routes/test_registration.py b/tests/test_routes/test_registration.py index 5eb74b63..a8d76708 100644 --- a/tests/test_routes/test_registration.py +++ b/tests/test_routes/test_registration.py @@ -12,7 +12,7 @@ url = "/email/registration" -def test_invalid_email(client_auth: TestClient): +def test_invalid_email(client_auth: TestClient, dbsession: Session): body1 = {"email": f"notEmailForSure", "password": "string"} body2 = {"email": f"EmailForSure{datetime.datetime.utcnow()}@mail.gtg", "password": ""} body3 = { @@ -38,6 +38,17 @@ def test_invalid_email(client_auth: TestClient): response = client_auth.post(url, json=body6) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + ids = [] + for email in [body3["email"], body4["email"], body5["email"]]: + ids.append( + dbsession.query(AuthMethod).filter(AuthMethod.param == "email", AuthMethod.value == email).one().user_id + ) + for user_id in ids: + for row in dbsession.query(AuthMethod).filter(AuthMethod.user_id == user_id).all(): + dbsession.delete(row) + dbsession.delete(dbsession.query(User).filter(User.id == user_id).one()) + dbsession.commit() + def test_main_scenario(client_auth: TestClient, dbsession: Session): time = datetime.datetime.utcnow() diff --git a/tests/test_routes/test_user.py b/tests/test_routes/test_user.py index 21650a3c..c70fd972 100644 --- a/tests/test_routes/test_user.py +++ b/tests/test_routes/test_user.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import Session from auth_backend.models import AuthMethod, User -from auth_backend.models.db import Group +from auth_backend.models.db import Group, UserGroup def test_user_email(client: TestClient, dbsession: Session, user_factory): @@ -19,7 +19,10 @@ def test_user_email(client: TestClient, dbsession: Session, user_factory): resp = client.patch(f"/user/{user1}", json={"groups": [group]}) assert resp.status_code == 200 assert "email" not in resp.json().keys() + dbsession.delete(email_user) + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == user1).all(): + dbsession.delete(row) gr = Group.get(group, session=dbsession) dbsession.delete(gr) dbsession.commit() diff --git a/tests/test_routes/test_user_groups.py b/tests/test_routes/test_user_groups.py index b0905bb7..249b885b 100644 --- a/tests/test_routes/test_user_groups.py +++ b/tests/test_routes/test_user_groups.py @@ -25,6 +25,9 @@ def test_add_user(client: TestClient, dbsession: Session, user_factory): user = User.get(usergroup.user_id, session=dbsession) assert user in gr.users assert gr in user.groups + + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == user1).all(): + dbsession.delete(row) dbsession.delete(gr) dbsession.commit() @@ -65,6 +68,11 @@ def test_get_user_list(client, dbsession, user_factory): assert us1 in gr.users assert us2 in gr.users assert us3 in gr.users + + for user_id in [user1, user2, user3]: + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == user_id).all(): + dbsession.delete(row) + dbsession.commit() dbsession.delete(gr) dbsession.commit() @@ -104,3 +112,9 @@ def test_del_user_from_group(client, dbsession, user_factory): gr.users.clear() gr.delete(id=group, session=dbsession) dbsession.commit() + + for user_id in [user1, user2, user3]: + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == user_id).all(): + dbsession.delete(row) + dbsession.delete(gr) + dbsession.commit() From 4560805140e2f3ad81d18e540dee09c3e3bda02d Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Thu, 12 Sep 2024 12:59:58 -0400 Subject: [PATCH 07/17] Temp Pydantic fix --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index db19b3a6..ee60901e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ fastapi fastapi-sqlalchemy psycopg2-binary -pydantic +pydantic==2.8.2 uvicorn alembic SQLAlchemy From 6b4a3cf7a09cf2b72d19e4b52057cd8245d158b1 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Thu, 17 Oct 2024 21:48:23 -0400 Subject: [PATCH 08/17] Test fix --- tests/test_routes/test_user.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_routes/test_user.py b/tests/test_routes/test_user.py index a8492fc4..7ec45daf 100644 --- a/tests/test_routes/test_user.py +++ b/tests/test_routes/test_user.py @@ -41,8 +41,9 @@ def test_delete_user(client: TestClient, dbsession: Session, user_factory): assert resp.status_code == 200 user = dbsession.query(User).filter(User.id == user1).one_or_none() assert user.is_deleted - dbsession.delete(email_user) dbsession.query(GroupScope).filter(GroupScope.group_id == group).delete() - dbsession.query(UserGroup).filter(UserGroup.group_id == group).delete() + for row in dbsession.query(UserGroup).filter(UserGroup.user_id == user1).all(): + dbsession.delete(row) dbsession.query(Group).filter(Group.id == group).delete() + dbsession.delete(email_user) dbsession.commit() From 440adb4941c18937ffb6c366d0e8e4270ecbaa89 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Wed, 6 Nov 2024 08:28:29 -0500 Subject: [PATCH 09/17] Fix unbounded for login --- auth_backend/auth_method/method_mixins.py | 3 +-- auth_backend/auth_plugins/email.py | 2 -- auth_backend/auth_plugins/github.py | 3 --- auth_backend/auth_plugins/google.py | 3 --- auth_backend/auth_plugins/keycloak.py | 3 --- auth_backend/auth_plugins/lkmsu.py | 3 --- auth_backend/auth_plugins/telegram.py | 3 --- auth_backend/auth_plugins/vk.py | 3 --- auth_backend/auth_plugins/yandex.py | 3 --- tests/conftest.py | 2 +- tests/test_routes/test_group_scopes.py | 33 +++-------------------- 11 files changed, 6 insertions(+), 55 deletions(-) diff --git a/auth_backend/auth_method/method_mixins.py b/auth_backend/auth_method/method_mixins.py index ce42cf84..7b026c33 100644 --- a/auth_backend/auth_method/method_mixins.py +++ b/auth_backend/auth_method/method_mixins.py @@ -54,11 +54,10 @@ async def _create_session( user: User, scopes_list_names: list[TypeScope] | None, session_name: str | None = None, - is_unbounded: bool | None = None, *, db_session: DbSession, ) -> Session: """Создает сессию пользователя""" return await create_session( - user, scopes_list_names, db_session=db_session, session_name=session_name, is_unbounded=is_unbounded + user, scopes_list_names, db_session=db_session, session_name=session_name, is_unbounded=True ) diff --git a/auth_backend/auth_plugins/email.py b/auth_backend/auth_plugins/email.py index 75565b9a..267d7ff9 100644 --- a/auth_backend/auth_plugins/email.py +++ b/auth_backend/auth_plugins/email.py @@ -66,7 +66,6 @@ class EmailLogin(Base): password: Annotated[str, MinLen(1)] scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None email_validator = field_validator("email")(check_email) @@ -171,7 +170,6 @@ async def _login(cls, user_inp: EmailLogin, background_tasks: BackgroundTasks) - user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @staticmethod diff --git a/auth_backend/auth_plugins/github.py b/auth_backend/auth_plugins/github.py index 1ad24309..126168a5 100644 --- a/auth_backend/auth_plugins/github.py +++ b/auth_backend/auth_plugins/github.py @@ -40,7 +40,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="GitHub JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -119,7 +118,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -178,7 +176,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/google.py b/auth_backend/auth_plugins/google.py index 081520a8..686eaa3d 100644 --- a/auth_backend/auth_plugins/google.py +++ b/auth_backend/auth_plugins/google.py @@ -49,7 +49,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Google JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -127,7 +126,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -170,7 +168,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/keycloak.py b/auth_backend/auth_plugins/keycloak.py index f438bee8..6759f8c9 100644 --- a/auth_backend/auth_plugins/keycloak.py +++ b/auth_backend/auth_plugins/keycloak.py @@ -41,7 +41,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Keycloak JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -118,7 +117,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -179,7 +177,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/lkmsu.py b/auth_backend/auth_plugins/lkmsu.py index ef90707c..c39277d3 100644 --- a/auth_backend/auth_plugins/lkmsu.py +++ b/auth_backend/auth_plugins/lkmsu.py @@ -42,7 +42,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="LK MSU JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -114,7 +113,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -170,7 +168,6 @@ async def _login( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/telegram.py b/auth_backend/auth_plugins/telegram.py index d0f0b98c..79a691c5 100644 --- a/auth_backend/auth_plugins/telegram.py +++ b/auth_backend/auth_plugins/telegram.py @@ -45,7 +45,6 @@ class OauthResponseSchema(BaseModel): hash: str | None = None scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -93,7 +92,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -127,7 +125,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/vk.py b/auth_backend/auth_plugins/vk.py index 4231a1c5..7bb2bdd8 100644 --- a/auth_backend/auth_plugins/vk.py +++ b/auth_backend/auth_plugins/vk.py @@ -54,7 +54,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="VK JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -126,7 +125,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -179,7 +177,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/auth_backend/auth_plugins/yandex.py b/auth_backend/auth_plugins/yandex.py index c618665d..96f1239f 100644 --- a/auth_backend/auth_plugins/yandex.py +++ b/auth_backend/auth_plugins/yandex.py @@ -41,7 +41,6 @@ class OauthResponseSchema(BaseModel): id_token: str | None = Field(default=None, help="Yandex JWT token identifier") scopes: list[Scope] | None = None session_name: str | None = None - is_unbounded: bool | None = None @classmethod async def _register( @@ -131,7 +130,6 @@ async def _register( user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod @@ -183,7 +181,6 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun user_inp.scopes, db_session=db.session, session_name=user_inp.session_name, - is_unbounded=user_inp.is_unbounded, ) @classmethod diff --git a/tests/conftest.py b/tests/conftest.py index f5562266..1f42b388 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,7 +78,7 @@ def user_id(client_auth: TestClient, dbsession): def user(client_auth: TestClient, dbsession): url = "/email/login" time = datetime.datetime.utcnow() - body = {"email": f"user{time}@example.com", "password": "string", "scopes": [], "is_unbounded": False} + body = {"email": f"user{time}@example.com", "password": "string", "scopes": []} response = client_auth.post("/email/registration", json=body) db_user: AuthMethod = ( dbsession.query(AuthMethod).filter(AuthMethod.value == body['email'], AuthMethod.param == 'email').one() diff --git a/tests/test_routes/test_group_scopes.py b/tests/test_routes/test_group_scopes.py index 1ce55b81..d79d3841 100644 --- a/tests/test_routes/test_group_scopes.py +++ b/tests/test_routes/test_group_scopes.py @@ -85,28 +85,19 @@ def test_scopes_user_session(client_auth, dbsession, user_scopes): assert response.status_code == 200 response = client_auth.patch(f"/user/{user_id}", json={"groups": [_group3]}, headers=headers) assert response.status_code == 200 - response = client_auth.post("/email/login", json=body_user | {"scopes": [scope1.name]}) + response = client_auth.post("/email/login", json=body_user) assert response.status_code == 200 token = response.json()["token"] - response = client_auth.post("/email/login", json=body_user | {"scopes": [scope2.name + "s"]}) - assert response.status_code == 404 response = client_auth.get("/me", headers={"Authorization": token}, params={"info": ["session_scopes"]}) assert response.status_code == 200 assert scope1.id in [row["id"] for row in response.json()["session_scopes"]] - response = client_auth.get("/me", headers={"Authorization": login["token"]}, params={"info": ["session_scopes"]}) - assert response.status_code == 200 - assert scope2.id not in [row["id"] for row in response.json()["session_scopes"]] response = client_auth.patch(f"/group/{_group3}", json={"scopes": [scope1.id, scope2.id]}, headers=headers) assert response.status_code == 200 - response = client_auth.post("/email/login", json=body_user | {"scopes": [scope1.name, scope2.name]}) + response = client_auth.post("/email/login", json=body_user) assert response.status_code == 200 token1 = response.json()["token"] - response = client_auth.post("/email/login", json=body_user | {"scopes": [scope2.name]}) - assert response.status_code == 200 - token2 = response.json()["token"] - response = client_auth.post("/email/login", json=body_user | {"scopes": [scope1.name]}) + response = client_auth.post("/email/login", json=body_user) assert response.status_code == 200 - token3 = response.json()["token"] response = client_auth.get( "/me", headers={"Authorization": token1}, params={"info": ["session_scopes", "user_scopes"]} ) @@ -116,27 +107,11 @@ def test_scopes_user_session(client_auth, dbsession, user_scopes): assert scope2.id in [row["id"] for row in response.json()["user_scopes"]] assert scope1.id in [row["id"] for row in response.json()["user_scopes"]] response = client_auth.get( - "/me", headers={"Authorization": token2}, params={"info": ["session_scopes", "user_scopes"]} + "/me", headers={"Authorization": login["token"]}, params={"info": ["session_scopes", "user_scopes"]} ) assert response.status_code == 200 assert scope2.id in [row["id"] for row in response.json()["session_scopes"]] - assert scope1.id not in [row["id"] for row in response.json()["session_scopes"]] - assert scope2.id in [row["id"] for row in response.json()["user_scopes"]] - assert scope1.id in [row["id"] for row in response.json()["user_scopes"]] - response = client_auth.get( - "/me", headers={"Authorization": token3}, params={"info": ["session_scopes", "user_scopes"]} - ) - assert response.status_code == 200 assert scope1.id in [row["id"] for row in response.json()["session_scopes"]] - assert scope2.id not in [row["id"] for row in response.json()["session_scopes"]] - assert scope2.id in [row["id"] for row in response.json()["user_scopes"]] - assert scope1.id in [row["id"] for row in response.json()["user_scopes"]] - response = client_auth.get( - "/me", headers={"Authorization": login["token"]}, params={"info": ["session_scopes", "user_scopes"]} - ) - assert response.status_code == 200 - assert scope2.id not in [row["id"] for row in response.json()["session_scopes"]] - assert scope1.id not in [row["id"] for row in response.json()["session_scopes"]] assert scope2.id in [row["id"] for row in response.json()["user_scopes"]] assert scope1.id in [row["id"] for row in response.json()["user_scopes"]] dbsession.query(GroupScope).filter(GroupScope.group_id == _group1).delete() From 198fbf171799d8cd24dd5fb67fce88209491036b Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Wed, 6 Nov 2024 13:39:12 -0500 Subject: [PATCH 10/17] Minor style fix --- auth_backend/routes/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 8f59e57e..8974355f 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -156,6 +156,7 @@ async def delete_user( logger.debug(f'User id={current_user.id} triggered delete_user') old_user = {"user_id": current_user.id} user: User = User.get(user_id, session=db.session) + for method in user._auth_methods: if method.is_deleted: continue From e287390af7df8090c258b6be74c86fecd283ebdf Mon Sep 17 00:00:00 2001 From: Timur Enikeev <163281083+DaymasS@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:12:10 -0500 Subject: [PATCH 11/17] Fix get name and group from lk.msu (#219) (#222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Изменения Починен парсер имён пользователей из ЛК МГУ и парсер групп для студентов магистратуры ## Детали реализации Теперь поиск группы происходит с конца. Парсится первая встреченная с конца группа физфака ## Check-List - [x] Вы проверили свой код перед отправкой запроса? - [x] Вы написали тесты к реализованным функциям? - [x] Вы не забыли применить форматирование `black` и `isort` для _Back-End_ или `Prettier` для _Front-End_? ## Изменения ## Детали реализации ## Check-List - [ ] Вы проверили свой код перед отправкой запроса? - [ ] Вы написали тесты к реализованным функциям? - [ ] Вы не забыли применить форматирование `black` и `isort` для _Back-End_ или `Prettier` для _Front-End_? --- auth_backend/auth_plugins/lkmsu.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/auth_backend/auth_plugins/lkmsu.py b/auth_backend/auth_plugins/lkmsu.py index c39277d3..812588d7 100644 --- a/auth_backend/auth_plugins/lkmsu.py +++ b/auth_backend/auth_plugins/lkmsu.py @@ -186,12 +186,12 @@ async def _auth_url(cls): def get_student(cls, data: dict[str, Any]) -> list[dict[str | Any]]: student: dict[str, Any] = data.get("student", {}) first_name, last_name, middle_name = '', '', '' - if 'first_name' in data.keys() and data['first_name'] is not None: - first_name = data['first_name'] - if 'last_name' in data.keys() and data['last_name'] is not None: - last_name = data['last_name'] - if 'middle_name' in data.keys() and data['middle_name'] is not None: - middle_name = data['middle_name'] + if 'first_name' in student.keys() and student['first_name'] is not None: + first_name = student['first_name'] + if 'last_name' in student.keys() and student['last_name'] is not None: + last_name = student['last_name'] + if 'middle_name' in student.keys() and student['middle_name'] is not None: + middle_name = student['middle_name'] full_name = concantenate_strings([first_name, last_name, middle_name]) if not full_name: full_name = None @@ -203,14 +203,8 @@ def get_student(cls, data: dict[str, Any]) -> list[dict[str | Any]]: @classmethod def get_entrants(cls, data: dict[str, Any]) -> list[dict[str, Any]]: student: dict[str, Any] = data.get("student", {}) - faculties_names = [] - for entrant in student.get('entrants'): - faculties_names.append(entrant.get('faculty', {}).get("name")) - for entrant in student.get('entrants'): - if ( - cls.settings.LKMSU_FACULTY_NAME in faculties_names - and entrant.get('faculty', {}).get("name") != cls.settings.LKMSU_FACULTY_NAME - ): + for entrant in reversed(student.get('entrants', [])): + if entrant.get('faculty', {}).get("name") != cls.settings.LKMSU_FACULTY_NAME: continue if not (group := entrant.get("groups")): group = [{}] @@ -237,8 +231,6 @@ def get_entrants(cls, data: dict[str, Any]) -> list[dict[str, Any]]: "value": group[0].get("name"), }, ] - if cls.settings.LKMSU_FACULTY_NAME not in faculties_names: - break return items @classmethod From 45cb2175bf4728d44c70151373484e89ebc0766c Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Sat, 23 Nov 2024 00:55:50 -0500 Subject: [PATCH 12/17] Minor style fix --- auth_backend/utils/user_session_control.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index 916ab246..6cfc14db 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -34,8 +34,7 @@ async def create_session( user_id=user.id, token=random_string(length=settings.TOKEN_LENGTH), session_name=session_name ) user_session.expires = expires or user_session.expires - if is_unbounded is not None: - user_session.is_unbounded = is_unbounded + user_session.is_unbounded = is_unbounded or user_session.is_unbounded db_session.add(user_session) db_session.flush() if not user_session.is_unbounded: From 6f1fbd2c0ae809d272be616727d3f95ca6738483 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Sat, 23 Nov 2024 01:08:01 -0500 Subject: [PATCH 13/17] Fix unbounded bug --- auth_backend/utils/user_session_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index 6cfc14db..36109a03 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -34,7 +34,7 @@ async def create_session( user_id=user.id, token=random_string(length=settings.TOKEN_LENGTH), session_name=session_name ) user_session.expires = expires or user_session.expires - user_session.is_unbounded = is_unbounded or user_session.is_unbounded + user_session.is_unbounded = user_session.is_unbounded if is_unbounded is None else is_unbounded db_session.add(user_session) db_session.flush() if not user_session.is_unbounded: From 29baeb7d55bd8774c7e76c085b52be6a78e94148 Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Mon, 25 Nov 2024 02:42:17 -0500 Subject: [PATCH 14/17] Fix migrations --- ...6dffd8e42152_193_add_unbounded_sessions.py | 12 +--- .../dcb89e72d446_session_security_scopes.py | 69 +++++++++++-------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py index cc4ff8e2..d2940e86 100644 --- a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py +++ b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py @@ -6,8 +6,6 @@ """ -import datetime - import sqlalchemy as sa from alembic import op @@ -21,17 +19,9 @@ def upgrade(): op.add_column('user_session', sa.Column('is_unbounded', sa.Boolean(), nullable=True)) - conn = op.get_bind() - session_list = conn.execute(sa.text("SELECT token, expires FROM user_session")).fetchall() - for session in session_list: - if session[1] <= datetime.datetime.utcnow(): - conn.execute(sa.text(f"UPDATE user_session SET is_unbounded='false' WHERE token='{session[0]}'")) - else: - conn.execute(sa.text(f"UPDATE user_session SET is_unbounded='true' WHERE token='{session[0]}'")) + op.execute("UPDATE user_session SET is_unbounded='false'") op.alter_column('user_session', 'is_unbounded', nullable=False) def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### op.drop_column('user_session', 'is_unbounded') - # ### end Alembic commands ### diff --git a/migrations/versions/dcb89e72d446_session_security_scopes.py b/migrations/versions/dcb89e72d446_session_security_scopes.py index 7eff670b..033b8bea 100644 --- a/migrations/versions/dcb89e72d446_session_security_scopes.py +++ b/migrations/versions/dcb89e72d446_session_security_scopes.py @@ -7,9 +7,7 @@ """ from alembic import op -from sqlalchemy.orm import Session - -from auth_backend.models.db import DynamicOption, Group, Scope, User, UserSession, UserSessionScope +from sqlalchemy.sql import text # revision identifiers, used by Alembic. @@ -21,31 +19,46 @@ def upgrade(): conn = op.get_bind() - session = Session(conn) - - root_group_id: DynamicOption = session.query(DynamicOption).filter(DynamicOption.name == "root_group_id").one() - users_group_id: DynamicOption = session.query(DynamicOption).filter(DynamicOption.name == "users_group_id").one() - - root_group: Group = Group.get(root_group_id.value_integer, session=session) - user_group: Group = Group.get(users_group_id.value_integer, session=session) - try: - user = root_group.users[0] - except IndexError: - user = User.create(session=session) - user.groups.append(root_group) - - scope1 = Scope(creator_id=user.id, name="auth.session.create", comment="Create user session") - scope2 = Scope(creator_id=user.id, name="auth.session.update", comment="Update user session") - session.add_all((scope1, scope2)) - session.flush() - root_group.scopes.update([scope1, scope2]) - user_group.scopes.update([scope1, scope2]) - session.flush() - sessions_id = session.query(UserSession.id).all() - for session_id in sessions_id: - UserSessionScope.create(user_session_id=session_id.id, scope_id=scope1.id, is_deleted=False, session=session) - UserSessionScope.create(user_session_id=session_id.id, scope_id=scope2.id, is_deleted=False, session=session) - session.commit() + + query: str = 'SELECT value_integer FROM dynamic_option WHERE name=:option_name' + root_group_id: int = conn.execute(text(query).bindparams(option_name="root_group_id")).scalar() + users_group_id: int = conn.execute(text(query).bindparams(option_name="users_group_id")).scalar() + + query = 'SELECT user_id FROM user_group WHERE group_id=:group_id' + root_user_id = conn.execute(text(query).bindparams(group_id=root_group_id)).scalar() + if root_user_id is None: + query = ( + 'INSERT INTO "user" (is_deleted, create_ts, update_ts) VALUES (false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)' + ) + conn.execute(text(query)) + + query = 'INSERT INTO "user_group" VALUES (:user_id, :group_id, false)' + root_user_id = conn.execute(text('SELECT id FROM "user" ORDER BY id DESC')).scalar() + conn.execute(text(query).bindparams(user_id=root_user_id, group_id=root_group_id)) + + query = 'INSERT INTO "scope" VALUES (:creator_id, :name, :comment, false)' + conn.execute( + text(query).bindparams(creator_id=root_user_id, name="auth.session.create", comment="Create user session") + ) + conn.execute( + text(query).bindparams(creator_id=root_user_id, name="auth.session.update", comment="Update user session") + ) + + query = 'SELECT id FROM scope WHERE name=:name' + scope1_id = conn.execute(text(query).bindparams(name="auth.session.create")).scalar() + scope2_id = conn.execute(text(query).bindparams(name="auth.session.update")).scalar() + + query = 'INSERT INTO "group_scope" VALUES (:group_id, :scope_id, false)' + conn.execute(text(query).bindparams(group_id=root_group_id, scope_id=scope1_id)) + conn.execute(text(query).bindparams(group_id=root_group_id, scope_id=scope2_id)) + conn.execute(text(query).bindparams(group_id=users_group_id, scope_id=scope1_id)) + conn.execute(text(query).bindparams(group_id=users_group_id, scope_id=scope2_id)) + + session_ids = conn.execute(text('SELECT id FROM user_session')).all() + query = 'INSERT INTO "user_session_scope" VALUES (:user_session_id, :scope_id, false)' + for session_id in session_ids: + conn.execute(text(query).bindparams(user_session_id=session_id[0], scope_id=scope1_id)) + conn.execute(text(query).bindparams(user_session_id=session_id[0], scope_id=scope2_id)) def downgrade(): From 711c048b34ae43d3c82a7667887692e2e73bed3a Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Mon, 25 Nov 2024 10:08:58 -0500 Subject: [PATCH 15/17] Default unbounded to false --- auth_backend/models/db.py | 2 +- auth_backend/utils/user_session_control.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/auth_backend/models/db.py b/auth_backend/models/db.py index 412554e5..05693772 100644 --- a/auth_backend/models/db.py +++ b/auth_backend/models/db.py @@ -158,7 +158,7 @@ class UserSession(BaseDbModel): user_id: Mapped[int] = mapped_column(Integer, sqlalchemy.ForeignKey("user.id")) expires: Mapped[datetime.datetime] = mapped_column(DateTime, default=session_expires_date) token: Mapped[str] = mapped_column(String, unique=True) - is_unbounded: Mapped[bool] = mapped_column(Boolean, default=True) + is_unbounded: Mapped[bool] = mapped_column(Boolean, default=False) last_activity: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) create_ts: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) user: Mapped[User] = relationship( diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index 36109a03..11389315 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -20,7 +20,7 @@ async def create_session( scopes_list_names: list[TypeScope] | None, expires: datetime | None = None, session_name: str | None = None, - is_unbounded: bool | None = None, + is_unbounded: bool = False, *, db_session: DbSession, ) -> Session: @@ -34,7 +34,7 @@ async def create_session( user_id=user.id, token=random_string(length=settings.TOKEN_LENGTH), session_name=session_name ) user_session.expires = expires or user_session.expires - user_session.is_unbounded = user_session.is_unbounded if is_unbounded is None else is_unbounded + user_session.is_unbounded = is_unbounded db_session.add(user_session) db_session.flush() if not user_session.is_unbounded: From 30f8447fb1c4d3dab1623e40632b65aee6774a6e Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Sat, 7 Dec 2024 13:01:56 -0500 Subject: [PATCH 16/17] merge migrations --- ...1a7f2276d4_merge_unbounded_and_verified.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py diff --git a/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py b/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py new file mode 100644 index 00000000..444c6b63 --- /dev/null +++ b/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py @@ -0,0 +1,24 @@ +"""merge unbounded and verified + +Revision ID: ed1a7f2276d4 +Revises: 5d71a2a2405d, 6dffd8e42152 +Create Date: 2024-12-07 12:58:57.981808 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ed1a7f2276d4' +down_revision = ('5d71a2a2405d', '6dffd8e42152') +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass From 0303cefdaf3cbb20f53551ddf36856b0f05cc50d Mon Sep 17 00:00:00 2001 From: Timur Enikeev Date: Sat, 7 Dec 2024 13:02:23 -0500 Subject: [PATCH 17/17] merge migrations lint --- .../versions/ed1a7f2276d4_merge_unbounded_and_verified.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py b/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py index 444c6b63..aafb82ea 100644 --- a/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py +++ b/migrations/versions/ed1a7f2276d4_merge_unbounded_and_verified.py @@ -5,9 +5,6 @@ Create Date: 2024-12-07 12:58:57.981808 """ -from alembic import op -import sqlalchemy as sa - # revision identifiers, used by Alembic. revision = 'ed1a7f2276d4'