changeset 8326:f734d107296e

auth: for default permissions, use existing explicit query result values instead of following dot references in ORM result objects There has been reports of spurious crashes on resolving references like .repository from Permissions: File ".../kallithea/lib/auth.py", line 678, in __wrapper if self.check_permissions(user): File ".../kallithea/lib/auth.py", line 718, in check_permissions return user.has_repository_permission_level(repo_name, self.required_perm) File ".../kallithea/lib/auth.py", line 450, in has_repository_permission_level actual_perm = self.permissions['repositories'].get(repo_name) File ".../kallithea/lib/vcs/utils/lazy.py", line 41, in __get__ value = self._func(obj) File ".../kallithea/lib/auth.py", line 442, in permissions return self.__get_perms(user=self, cache=False) File ".../kallithea/lib/auth.py", line 498, in __get_perms return compute(user_id, user_is_admin) File ".../kallithea/lib/auth.py", line 190, in _cached_perms_data r_k = perm.UserRepoToPerm.repository.repo_name File ".../sqlalchemy/orm/attributes.py", line 285, in __get__ return self.impl.get(instance_state(instance), dict_) File ".../sqlalchemy/orm/attributes.py", line 721, in get value = self.callable_(state, passive) File ".../sqlalchemy/orm/strategies.py", line 710, in _load_for_state % (orm_util.state_str(state), self.key) sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <UserRepoToPerm at ...> is not bound to a Session; lazy load operation of attribute 'repository' cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3) Permissions are cached between requests: SA result records are stored in in beaker.cache.sql_cache_short and resued in following requests after the initial session as been removed. References in Permission objects would usually give lazy lookup ... but not outside the original session, where we would get an error like this. Permissions are indeed implemented/used incorrectly. That might explain a part of the problem. Even if not fully explaining or fixing this problem, it is still worth fixing: Permissions are fetched from the database using Session().query with multiple class/table names (joined together in way that happens to match the references specified in the table definitions) - including Repository. The results are thus "structs" with selected objects. If repositories always were retrieved using this selected repository, everything would be fine. In some places, this was what we did. But in some places, the code happened to do what was more intuitive: just use .repository and rely on "lazy" resolving. SA was not aware that this one already was present in the result struct, and would try to fetch it again. Best case, that could be inefficient. Worst case, it would fail as we see here. Fix this by only querying from one table but use the "joinedload" option to also fetch other referenced tables in the same select. (This might inefficiently return the main record multiple times ... but that was already the case with the previous approach.) This change is thus doing multiple things with circular dependencies that can't be split up in minor parts without taking detours: The existing repository join like: .join((Repository, UserGroupRepoToPerm.repository_id == Repository.repo_id)) is thus replaced by: .options(joinedload(UserGroupRepoToPerm.repository)) Since we only are doing Session.query() on one table, the results will be of that type instead of "structs" with multiple objects. If only querying for UserRepoToPerm this means: - perm.UserRepoToPerm.repository becomes perm.repository - perm.Permission.permission_name looked at the explicitly queried Permission in the result struct - instead it should look in the the dereferenced repository as perm.permission.permission_name
author Mads Kiilerich <mads@kiilerich.com>
date Mon, 30 Mar 2020 12:07:06 +0200
parents da39f9548758
children 8c57eda0823e
files kallithea/lib/auth.py kallithea/model/db.py
diffstat 2 files changed, 41 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/auth.py	Mon Mar 30 15:44:47 2020 +0200
+++ b/kallithea/lib/auth.py	Mon Mar 30 12:07:06 2020 +0200
@@ -44,8 +44,8 @@
 from kallithea.lib.utils import conditional_cache, get_repo_group_slug, get_repo_slug, get_user_group_slug
 from kallithea.lib.utils2 import ascii_bytes, ascii_str, safe_bytes
 from kallithea.lib.vcs.utils.lazy import LazyProperty
-from kallithea.model.db import (Permission, RepoGroup, Repository, User, UserApiKeys, UserGroup, UserGroupMember, UserGroupRepoGroupToPerm, UserGroupRepoToPerm,
-                                UserGroupToPerm, UserGroupUserGroupToPerm, UserIpMap, UserToPerm)
+from kallithea.model.db import (Permission, User, UserApiKeys, UserGroup, UserGroupMember, UserGroupRepoGroupToPerm, UserGroupRepoToPerm, UserGroupToPerm,
+                                UserGroupUserGroupToPerm, UserIpMap, UserToPerm)
 from kallithea.model.meta import Session
 from kallithea.model.user import UserModel
 
@@ -156,19 +156,19 @@
 
         # repositories
         for perm in default_repo_perms:
-            r_k = perm.UserRepoToPerm.repository.repo_name
+            r_k = perm.repository.repo_name
             p = 'repository.admin'
             permissions[RK][r_k] = p
 
         # repository groups
         for perm in default_repo_groups_perms:
-            rg_k = perm.UserRepoGroupToPerm.group.group_name
+            rg_k = perm.group.group_name
             p = 'group.admin'
             permissions[GK][rg_k] = p
 
         # user groups
         for perm in default_user_group_perms:
-            u_k = perm.UserUserGroupToPerm.user_group.users_group_name
+            u_k = perm.user_group.users_group_name
             p = 'usergroup.admin'
             permissions[UK][u_k] = p
         return permissions
@@ -187,27 +187,27 @@
 
     # defaults for repositories, taken from default user
     for perm in default_repo_perms:
-        r_k = perm.UserRepoToPerm.repository.repo_name
-        if perm.Repository.owner_id == user_id:
+        r_k = perm.repository.repo_name
+        if perm.repository.owner_id == user_id:
             p = 'repository.admin'
-        elif perm.Repository.private:
+        elif perm.repository.private:
             p = 'repository.none'
         else:
-            p = perm.Permission.permission_name
+            p = perm.permission.permission_name
         permissions[RK][r_k] = p
 
     # defaults for repository groups taken from default user permission
     # on given group
     for perm in default_repo_groups_perms:
-        rg_k = perm.UserRepoGroupToPerm.group.group_name
-        p = perm.Permission.permission_name
+        rg_k = perm.group.group_name
+        p = perm.permission.permission_name
         permissions[GK][rg_k] = p
 
     # defaults for user groups taken from default user permission
     # on given user group
     for perm in default_user_group_perms:
-        u_k = perm.UserUserGroupToPerm.user_group.users_group_name
-        p = perm.Permission.permission_name
+        u_k = perm.user_group.users_group_name
+        p = perm.permission.permission_name
         permissions[UK][u_k] = p
 
     #======================================================================
@@ -261,30 +261,28 @@
 
     # user group for repositories permissions
     user_repo_perms_from_users_groups = \
-     Session().query(UserGroupRepoToPerm, Permission, Repository,) \
-        .join((Repository, UserGroupRepoToPerm.repository_id ==
-               Repository.repo_id)) \
-        .join((Permission, UserGroupRepoToPerm.permission_id ==
-               Permission.permission_id)) \
+     Session().query(UserGroupRepoToPerm) \
         .join((UserGroup, UserGroupRepoToPerm.users_group_id ==
                UserGroup.users_group_id)) \
         .filter(UserGroup.users_group_active == True) \
         .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
                UserGroupMember.users_group_id)) \
         .filter(UserGroupMember.user_id == user_id) \
+        .options(joinedload(UserGroupRepoToPerm.repository)) \
+        .options(joinedload(UserGroupRepoToPerm.permission)) \
         .all()
 
     for perm in user_repo_perms_from_users_groups:
         bump_permission(RK,
-            perm.UserGroupRepoToPerm.repository.repo_name,
-            perm.Permission.permission_name)
+            perm.repository.repo_name,
+            perm.permission.permission_name)
 
     # user permissions for repositories
     user_repo_perms = Permission.get_default_perms(user_id)
     for perm in user_repo_perms:
         bump_permission(RK,
-            perm.UserRepoToPerm.repository.repo_name,
-            perm.Permission.permission_name)
+            perm.repository.repo_name,
+            perm.permission.permission_name)
 
     #======================================================================
     # !! PERMISSIONS FOR REPOSITORY GROUPS !!
@@ -295,59 +293,56 @@
     #======================================================================
     # user group for repo groups permissions
     user_repo_group_perms_from_users_groups = \
-     Session().query(UserGroupRepoGroupToPerm, Permission, RepoGroup) \
-     .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)) \
-     .join((Permission, UserGroupRepoGroupToPerm.permission_id
-            == Permission.permission_id)) \
+     Session().query(UserGroupRepoGroupToPerm) \
      .join((UserGroup, UserGroupRepoGroupToPerm.users_group_id ==
             UserGroup.users_group_id)) \
      .filter(UserGroup.users_group_active == True) \
      .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
             == UserGroupMember.users_group_id)) \
      .filter(UserGroupMember.user_id == user_id) \
+     .options(joinedload(UserGroupRepoGroupToPerm.permission)) \
      .all()
 
     for perm in user_repo_group_perms_from_users_groups:
         bump_permission(GK,
-            perm.UserGroupRepoGroupToPerm.group.group_name,
-            perm.Permission.permission_name)
+            perm.group.group_name,
+            perm.permission.permission_name)
 
     # user explicit permissions for repository groups
     user_repo_groups_perms = Permission.get_default_group_perms(user_id)
     for perm in user_repo_groups_perms:
         bump_permission(GK,
-            perm.UserRepoGroupToPerm.group.group_name,
-            perm.Permission.permission_name)
+            perm.group.group_name,
+            perm.permission.permission_name)
 
     #======================================================================
     # !! PERMISSIONS FOR USER GROUPS !!
     #======================================================================
     # user group for user group permissions
     user_group_user_groups_perms = \
-     Session().query(UserGroupUserGroupToPerm, Permission, UserGroup) \
+     Session().query(UserGroupUserGroupToPerm) \
      .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
             == UserGroup.users_group_id)) \
-     .join((Permission, UserGroupUserGroupToPerm.permission_id
-            == Permission.permission_id)) \
      .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
             == UserGroupMember.users_group_id)) \
      .filter(UserGroupMember.user_id == user_id) \
      .join((UserGroup, UserGroupMember.users_group_id ==
             UserGroup.users_group_id), aliased=True, from_joinpoint=True) \
      .filter(UserGroup.users_group_active == True) \
+     .options(joinedload(UserGroupUserGroupToPerm.permission)) \
      .all()
 
     for perm in user_group_user_groups_perms:
         bump_permission(UK,
-            perm.UserGroupUserGroupToPerm.target_user_group.users_group_name,
-            perm.Permission.permission_name)
+            perm.target_user_group.users_group_name,
+            perm.permission.permission_name)
 
     # user explicit permission for user groups
     user_user_groups_perms = Permission.get_default_user_group_perms(user_id)
     for perm in user_user_groups_perms:
         bump_permission(UK,
-            perm.UserUserGroupToPerm.user_group.users_group_name,
-            perm.Permission.permission_name)
+            perm.user_group.users_group_name,
+            perm.permission.permission_name)
 
     return permissions
 
--- a/kallithea/model/db.py	Mon Mar 30 15:44:47 2020 +0200
+++ b/kallithea/model/db.py	Mon Mar 30 12:07:06 2020 +0200
@@ -1743,27 +1743,27 @@
 
     @classmethod
     def get_default_perms(cls, default_user_id):
-        q = Session().query(UserRepoToPerm, Repository, cls) \
-         .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id)) \
-         .join((cls, UserRepoToPerm.permission_id == cls.permission_id)) \
+        q = Session().query(UserRepoToPerm) \
+         .options(joinedload(UserRepoToPerm.repository)) \
+         .options(joinedload(UserRepoToPerm.permission)) \
          .filter(UserRepoToPerm.user_id == default_user_id)
 
         return q.all()
 
     @classmethod
     def get_default_group_perms(cls, default_user_id):
-        q = Session().query(UserRepoGroupToPerm, RepoGroup, cls) \
-         .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id)) \
-         .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id)) \
+        q = Session().query(UserRepoGroupToPerm) \
+         .options(joinedload(UserRepoGroupToPerm.group)) \
+         .options(joinedload(UserRepoGroupToPerm.permission)) \
          .filter(UserRepoGroupToPerm.user_id == default_user_id)
 
         return q.all()
 
     @classmethod
     def get_default_user_group_perms(cls, default_user_id):
-        q = Session().query(UserUserGroupToPerm, UserGroup, cls) \
-         .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id)) \
-         .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id)) \
+        q = Session().query(UserUserGroupToPerm) \
+         .options(joinedload(UserUserGroupToPerm.user_group)) \
+         .options(joinedload(UserUserGroupToPerm.permission)) \
          .filter(UserUserGroupToPerm.user_id == default_user_id)
 
         return q.all()