Skip to content

Commit

Permalink
Merge pull request #205 from EBI-Metagenomics/hotfix/permissions-issu…
Browse files Browse the repository at this point in the history
…es-api

Fix for private/public analysis issue.
  • Loading branch information
mberacochea committed Apr 29, 2021
2 parents 5eb12ef + 37538d4 commit 5f24d0e
Show file tree
Hide file tree
Showing 3 changed files with 577 additions and 104 deletions.
153 changes: 119 additions & 34 deletions emgapi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,23 @@ def __init__(self, **kwargs):
class BaseQuerySet(models.QuerySet):
"""Auth mechanism to filter private models
"""
# TODO: the QuerySet should not have to handle the request
# if should recieve the username
# move the requests bits to the filters and serializers as needed

def available(self, request=None):

"""
Filter data based on the status or other properties.
Status table:
1 draft
2 private
3 cancelled
4 public
5 suppressed
6 killed
7 temporary_suppressed
8 temporary_killed
"""
_query_filters = {
'StudyQuerySet': {
'all': [Q(is_public=1), ],
Expand All @@ -74,18 +88,11 @@ def available(self, request=None):
Q(status_id=4),
],
},
'AnalysisJobQuerySet': {
'all': [
# TMP: IS_PUBLIC = 5 is suppressed
~Q(sample__is_public=5),
Q(run__status_id=4) | Q(assembly__status_id=4),
Q(analysis_status_id=3) | Q(analysis_status_id=6)
],
},
'AnalysisJobDownloadQuerySet': {
'all': [
# TMP: IS_PUBLIC = 5 is suppressed
~Q(job__sample__is_public=5),
Q(job__study__is_public=1),
Q(job__run__status_id=4) | Q(job__assembly__status_id=4),
Q(job__analysis_status_id=3) | Q(job__analysis_status_id=6)
],
Expand All @@ -108,33 +115,29 @@ def available(self, request=None):
[Q(samples__studies__submission_account_id=_username,
status_id=2) |
Q(status_id=4)]
_query_filters['AnalysisJobQuerySet']['authenticated'] = \
[Q(study__submission_account_id=_username,
run__status_id=2) |
Q(study__submission_account_id=_username,
assembly__status_id=2) |
Q(run__status_id=4) | Q(assembly__status_id=4)]
_query_filters['AnalysisJobDownloadQuerySet']['authenticated'] = \
[Q(job__study__submission_account_id=_username,
job__run__status_id=2) |
Q(job__study__submission_account_id=_username,
job__assembly__status_id=2) |
Q(job__run__status_id=4) | Q(job__assembly__status_id=4)]

filters = _query_filters.get(self.__class__.__name__)

if filters:
return self._apply_filters(filters, request)
return self

def _apply_filters(self, filters, request):
"""Apply the QS filters for "all" and "authenticated" users
"""
q = list()
try:
_instance = _query_filters[self.__class__.__name__]
if isinstance(self, self.__class__):
if request is not None and request.user.is_authenticated():
if not request.user.is_superuser:
q.extend(_instance['authenticated'])
else:
q.extend(_instance['all'])
return self.distinct().filter(*q)
except KeyError:
pass
# TODO: should return nothing
return self
if request is not None and request.user.is_authenticated():
if not request.user.is_superuser:
q.extend(filters['authenticated'])
else:
q.extend(filters['all'])
return self.distinct().filter(*q)


class PipelineTool(models.Model):
Expand Down Expand Up @@ -171,12 +174,13 @@ def multiple_pk(self):

class PipelineManager(models.Manager):
def get_queryset(self):
return BaseQuerySet(self.model, using=self._db)
return super().get_queryset()


class PipelineAnnotatedManager(models.Manager):
def get_queryset(self):
return BaseQuerySet(self.model, using=self._db) \
return super() \
.get_queryset() \
.annotate(
analyses_count=Count(
'analyses', filter=(
Expand Down Expand Up @@ -240,6 +244,26 @@ class Meta:


class AnalysisStatus(models.Model):
"""
Current values:
1 scheduled
2 running
3 completed
4 failed
5 suppressed
6 QC not passed
7 Unable to process
8 unknown
"""
SCHEDULED = 1
RUNNING = 2
COMPLETED = 3
FAILED = 4
SUPPRESSED = 5
QC_NOT_PASSED = 6
UNABLE_TO_PROCESS = 7
UNKNOWN = 8

analysis_status_id = models.AutoField(
db_column='ANALYSIS_STATUS_ID', primary_key=True)
analysis_status = models.CharField(
Expand Down Expand Up @@ -506,7 +530,7 @@ class AnalysisJobDownloadQuerySet(BaseQuerySet):

class AnalysisJobDownloadManager(models.Manager):
def get_queryset(self):
return BaseQuerySet(self.model, using=self._db).\
return AnalysisJobDownloadQuerySet(self.model, using=self._db).\
select_related(
'job',
'pipeline',
Expand Down Expand Up @@ -840,6 +864,11 @@ def available(self, request, prefetch=False):


class Sample(models.Model):
# is_public possible values
PUBLIC = 1
PRIVATE = 0
SUPPRESSED = 5 # TODO: this should not be in is_public

sample_id = models.AutoField(
db_column='SAMPLE_ID', primary_key=True)
accession = models.CharField(
Expand Down Expand Up @@ -1006,6 +1035,19 @@ def __str__(self):


class Status(models.Model):
"""Status
Current values as constants to make
the code easier to read
"""
DRAFT = 1
PRIVATE = 2
CANCELLED = 3
PUBLIC = 4
SUPRESSED = 5
KILLED = 6
TEMPORARY_SUPPRESSED = 7
TEMPORARY_KILLED = 8

status_id = models.SmallIntegerField(
db_column='STATUS_ID', primary_key=True)
status = models.CharField(
Expand All @@ -1019,9 +1061,13 @@ def __str__(self):
return self.status


class RunQuerySet(BaseQuerySet):
pass


class RunManager(models.Manager):
def get_queryset(self):
return BaseQuerySet(self.model, using=self._db) \
return RunQuerySet(self.model, using=self._db) \
.select_related(
'status_id',
'sample',
Expand Down Expand Up @@ -1158,11 +1204,46 @@ def __str__(self):
return 'Assembly:{} - Sample:{}'.format(self.assembly, self.sample)


class AnalysisJobQuerySet(BaseQuerySet):
def available(self, request=None):
"""Override BaseQuerySet with the AnalysisJob auth rules
Use cases
- all | has access to public analyses
- authenticated | has access to public and private analyses they own
Filtered out analyses for SUPPRESSED samples
"""
query_filters = {
"all": [
Q(study__is_public=1),
~Q(sample__is_public=Sample.SUPPRESSED),
Q(run__status_id=Status.PUBLIC) | Q(assembly__status_id=Status.PUBLIC),
Q(analysis_status_id=AnalysisStatus.COMPLETED)
| Q(analysis_status_id=AnalysisStatus.QC_NOT_PASSED),
],
}

if request is not None and request.user.is_authenticated():
username = request.user.username
query_filters["authenticated"] = [
~Q(sample__is_public=Sample.SUPPRESSED),
Q(study__submission_account_id=username, run__status_id=Status.PRIVATE)
| Q(
study__submission_account_id=username,
assembly__status_id=Status.PRIVATE,
)
| Q(run__status_id=Status.PUBLIC)
| Q(assembly__status_id=Status.PUBLIC)
]

return self._apply_filters(query_filters, request)


class AnalysisJobManager(models.Manager):
def get_queryset(self):
_qs = AnalysisJobAnn.objects.all() \
.select_related('var')
return BaseQuerySet(self.model, using=self._db) \
return AnalysisJobQuerySet(self.model, using=self._db) \
.select_related(
'pipeline',
'analysis_status',
Expand Down Expand Up @@ -1601,10 +1682,14 @@ class Meta:
db_table = 'GENOME_GEOGRAPHIC_RANGE'


class ReleaseManagerQuerySet(BaseQuerySet):
pass


class ReleaseManager(models.Manager):

def get_queryset(self):
qs = BaseQuerySet(self.model, using=self._db)
qs = ReleaseManagerQuerySet(self.model, using=self._db)
return qs.annotate(genome_count=Count('genomes'))

def available(self, request):
Expand Down
Loading

0 comments on commit 5f24d0e

Please sign in to comment.