Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] SA2.0, model/data-access edits, unit testing #17662

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8e59493
Initial data access testing infrastructure
jdavcs Feb 28, 2024
f4d34e5
Add first library data access tests
jdavcs Feb 28, 2024
e9b20fd
Add first user data access tests
jdavcs Feb 28, 2024
2bcb5a4
Start replacing test_galaxy_mapping w/data access tests
jdavcs Feb 28, 2024
6676b01
security.get_npns_roles: test + factor out
jdavcs Feb 28, 2024
7062119
security.get_private_user_role: test + factor out
jdavcs Feb 28, 2024
f71ec6f
webapps.galaxy.services.user.get_users_for_index: test + factor out
jdavcs Feb 28, 2024
3b4bbca
Move user data access method from webapps to managers
jdavcs Feb 28, 2024
cc382f2
More test-galaxy-mapping conversions
jdavcs Feb 28, 2024
6430a11
Convert another test
jdavcs Mar 1, 2024
79741f6
Convert test_ratings
jdavcs Mar 4, 2024
1e343bb
Refactor model fixtures
jdavcs Mar 4, 2024
c645b3a
Drop test
jdavcs Mar 7, 2024
9a0bf39
Drop test
jdavcs Mar 7, 2024
3e147fc
Convert test_history_contents
jdavcs Mar 7, 2024
07d4a42
Convert test_current_galaxy_sesssion
jdavcs Mar 7, 2024
5831f4e
Convert hid tests
jdavcs Mar 7, 2024
a5fc071
Convert test_get_display_name
jdavcs Mar 7, 2024
4884363
Drop test_tags
jdavcs Mar 7, 2024
43486e3
Drop incomplete test
jdavcs Mar 7, 2024
713ce55
Drop test_basic
jdavcs Mar 7, 2024
873deec
Convert test_metadata_spec
jdavcs Mar 7, 2024
f4aef30
Drop unused fixture arg
jdavcs Mar 7, 2024
e44d866
Convert + improve test_job/task_metrics
jdavcs Mar 7, 2024
847fef4
Drop test_tasks
jdavcs Mar 7, 2024
af7da08
Add test_history_update
jdavcs Mar 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/galaxy/managers/hdcas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import logging
from typing import Dict

from sqlalchemy import select

from galaxy import model
from galaxy.managers import (
annotatable,
Expand Down Expand Up @@ -334,3 +336,12 @@ def serialize_job_state_summary(self, item, key, **context):
def serialize_elements_datatypes(self, item, key, **context):
extensions_set = item.dataset_dbkeys_and_extensions_summary[1]
return list(extensions_set)


def get_hdca_by_name(session, name):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to belong in managers to me because it doesn't use trans or app. Also this code is only used in tests as far as I can grep - should just be moved to galaxy.model.unittest_utils.model_testing_utils or a new file like galaxy.model.unittest_utils.helpers. I think there is something jarring about seeing code like this mixed with app logic - I was immediately scared we were accessing data this way from the app.

stmt = (
select(model.HistoryDatasetCollectionAssociation)
.where(model.HistoryDatasetCollectionAssociation.name == name)
.limit(1)
)
return session.scalars(stmt).first()
34 changes: 34 additions & 0 deletions lib/galaxy/managers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from sqlalchemy import (
and_,
exc,
false,
func,
or_,
select,
true,
)
Expand Down Expand Up @@ -893,3 +895,35 @@ def get_user_by_email(session, email: str, model_class=User, case_sensitive=True
def get_user_by_username(session, username: str, model_class=User):
stmt = select(model_class).filter(model_class.username == username).limit(1)
return session.scalars(stmt).first()


def get_users_for_index(
session,
deleted: bool,
f_email: Optional[str] = None,
f_name: Optional[str] = None,
f_any: Optional[str] = None,
is_admin: bool = False,
expose_user_email: bool = False,
expose_user_name: bool = False,
):
stmt = select(User)
if f_email and (is_admin or expose_user_email):
stmt = stmt.where(User.email.like(f"%{f_email}%"))
if f_name and (is_admin or expose_user_name):
stmt = stmt.where(User.username.like(f"%{f_name}%"))
if f_any:
if is_admin:
stmt = stmt.where(or_(User.email.like(f"%{f_any}%"), User.username.like(f"%{f_any}%")))
else:
if expose_user_email and expose_user_name:
stmt = stmt.where(or_(User.email.like(f"%{f_any}%"), User.username.like(f"%{f_any}%")))
elif expose_user_email:
stmt = stmt.where(User.email.like(f"%{f_any}%"))
elif expose_user_name:
stmt = stmt.where(User.username.like(f"%{f_any}%"))
if deleted:
stmt = stmt.where(User.deleted == true())
else:
stmt = stmt.where(User.deleted == false())
return session.scalars(stmt).all()
61 changes: 30 additions & 31 deletions lib/galaxy/model/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,11 @@ def sort_by_attr(self, seq, attr):
intermed.sort()
return [_[-1] for _ in intermed]

def _get_npns_roles(self, trans):
"""
non-private, non-sharing roles
"""
stmt = (
select(Role)
.where(and_(Role.deleted == false(), Role.type != Role.types.PRIVATE, Role.type != Role.types.SHARING))
.order_by(Role.name)
)
return trans.sa_session.scalars(stmt)

def get_all_roles(self, trans, cntrller):
admin_controller = cntrller in ["library_admin"]
roles = set()
if not trans.user:
return self._get_npns_roles(trans)
return get_npns_roles(trans.sa_session)
if admin_controller:
# The library is public and the user is an admin, so all roles are legitimate
stmt = select(Role).where(Role.deleted == false()).order_by(Role.name)
Expand All @@ -108,7 +97,7 @@ def get_all_roles(self, trans, cntrller):
for role in self.get_sharing_roles(trans.user):
roles.add(role)
# Add all remaining non-private, non-sharing roles
for role in self._get_npns_roles(trans):
for role in get_npns_roles(trans.sa_session):
roles.add(role)
return self.sort_by_attr(list(roles), "name")

Expand Down Expand Up @@ -189,7 +178,7 @@ def get_valid_roles(self, trans, item, query=None, page=None, page_limit=None, i
for role in self.get_sharing_roles(trans.user):
roles.append(role)
# Add all remaining non-private, non-sharing roles
for role in self._get_npns_roles(trans):
for role in get_npns_roles(trans.sa_session):
roles.append(role)
# User will see all the roles derived from the access roles on the item
else:
Expand Down Expand Up @@ -765,23 +754,9 @@ def create_private_user_role(self, user):
return self.get_private_user_role(user)

def get_private_user_role(self, user, auto_create=False):
stmt = (
select(Role)
.where(
and_(
UserRoleAssociation.user_id == user.id,
Role.id == UserRoleAssociation.role_id,
Role.type == Role.types.PRIVATE,
)
)
.distinct()
)
role = self.sa_session.execute(stmt).scalar_one_or_none()
if not role:
if auto_create:
return self.create_private_user_role(user)
else:
return None
role = get_private_user_role(user, self.sa_session)
if not role and auto_create:
role = self.create_private_user_role(user)
return role

def get_role(self, name, type=None):
Expand Down Expand Up @@ -1695,3 +1670,27 @@ def _walk_action_roles(permissions, query_action):
yield action, roles
elif action == query_action.action and roles:
yield action, roles


def get_npns_roles(session):
"""
non-private, non-sharing roles
"""
stmt = (
select(Role)
.where(and_(Role.deleted == false(), Role.type != Role.types.PRIVATE, Role.type != Role.types.SHARING))
.order_by(Role.name)
)
return session.scalars(stmt)


def get_private_user_role(user, session):
stmt = select(Role).where(
and_(
UserRoleAssociation.user_id == user.id,
Role.id == UserRoleAssociation.role_id,
Role.type == Role.types.PRIVATE,
)
)
.distinct()
return session.execute(stmt).scalar_one_or_none()
47 changes: 16 additions & 31 deletions lib/galaxy/webapps/galaxy/services/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@
Union,
)

from sqlalchemy import (
false,
or_,
select,
true,
)

import galaxy.managers.base as managers_base
from galaxy import (
exceptions as glx_exceptions,
Expand All @@ -22,6 +15,7 @@
ProvidesUserContext,
)
from galaxy.managers.users import (
get_users_for_index,
UserDeserializer,
UserManager,
UserSerializer,
Expand Down Expand Up @@ -205,31 +199,11 @@ def get_index(
f_name: Optional[str],
f_any: Optional[str],
) -> List[Union[UserModel, LimitedUserModel]]:
rval = []
stmt = select(User)

if f_email and (trans.user_is_admin or trans.app.config.expose_user_email):
stmt = stmt.filter(User.email.like(f"%{f_email}%"))

if f_name and (trans.user_is_admin or trans.app.config.expose_user_name):
stmt = stmt.filter(User.username.like(f"%{f_name}%"))

if f_any:
if trans.user_is_admin:
stmt = stmt.filter(or_(User.email.like(f"%{f_any}%"), User.username.like(f"%{f_any}%")))
else:
if trans.app.config.expose_user_email and trans.app.config.expose_user_name:
stmt = stmt.filter(or_(User.email.like(f"%{f_any}%"), User.username.like(f"%{f_any}%")))
elif trans.app.config.expose_user_email:
stmt = stmt.filter(User.email.like(f"%{f_any}%"))
elif trans.app.config.expose_user_name:
stmt = stmt.filter(User.username.like(f"%{f_any}%"))

# check for early return conditions
if deleted:
# only admins can see deleted users
if not trans.user_is_admin:
# only admins can see deleted users
return []
stmt = stmt.filter(User.deleted == true())
else:
# special case: user can see only their own user
# special case2: if the galaxy admin has specified that other user email/names are
Expand All @@ -244,8 +218,19 @@ def get_index(
return [item]
else:
return []
stmt = stmt.filter(User.deleted == false())
for user in trans.sa_session.scalars(stmt).all():

users = get_users_for_index(
trans.sa_session,
deleted,
f_email,
f_name,
f_any,
trans.user_is_admin,
trans.app.config.expose_user_email,
trans.app.config.expose_user_name,
)
rval = []
for user in users:
item = user.to_dict()
# If NOT configured to expose_email, do not expose email UNLESS the user is self, or
# the user is an admin
Expand Down
20 changes: 20 additions & 0 deletions test/unit/data/data_access/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from collections import namedtuple

PRIVATE_OBJECT_STORE_ID = "my_private_data"

MockTransaction = namedtuple("MockTransaction", "user")


class MockObjectStore:

def is_private(self, object):
if object.object_store_id == PRIVATE_OBJECT_STORE_ID:
return True
else:
return False


def verify_items(items1, length, items2=None):
assert len(items1) == length
if items2:
assert set(items2) == set(items1)
Loading
Loading