From 113cd4681d6085c98c01eea1312f984315bebeb1 Mon Sep 17 00:00:00 2001
From: Philip Abbet <philip.abbet@idiap.ch>
Date: Fri, 5 May 2017 10:59:09 +0200
Subject: [PATCH] [experiments, webapi] Bugfix: No more possible to create an
 experiment using an inaccessible  environment

---
 beat/web/attestations/tests.py |   1 +
 beat/web/experiments/api.py    |   0
 beat/web/experiments/models.py |  37 +++++--
 beat/web/experiments/tests.py  | 171 +++++++++++++++++++++++++++++++++
 beat/web/reports/tests.py      |   1 +
 5 files changed, 200 insertions(+), 10 deletions(-)
 mode change 100644 => 100755 beat/web/attestations/tests.py
 mode change 100644 => 100755 beat/web/experiments/api.py
 mode change 100644 => 100755 beat/web/experiments/models.py
 mode change 100644 => 100755 beat/web/experiments/tests.py
 mode change 100644 => 100755 beat/web/reports/tests.py

diff --git a/beat/web/attestations/tests.py b/beat/web/attestations/tests.py
old mode 100644
new mode 100755
index d2ea57a1c..baea5e956
--- a/beat/web/attestations/tests.py
+++ b/beat/web/attestations/tests.py
@@ -220,6 +220,7 @@ class AttestationsAPIBase(BaseTestCase):
         # Create an environment and queue
         environment = Environment(name='env1', version='1.0')
         environment.save()
+        environment.share()
 
         queue = Queue(name='queue1', memory_limit=1024, time_limit=60, cores_per_slot=1, max_slots_per_user=10)
         queue.save()
diff --git a/beat/web/experiments/api.py b/beat/web/experiments/api.py
old mode 100644
new mode 100755
diff --git a/beat/web/experiments/models.py b/beat/web/experiments/models.py
old mode 100644
new mode 100755
index 2b7e9c398..392382b9f
--- a/beat/web/experiments/models.py
+++ b/beat/web/experiments/models.py
@@ -74,37 +74,52 @@ logger = logging.getLogger(__name__)
 #----------------------------------------------------------
 
 
-def validate_environments(experiment):
+def validate_environments(experiment, user=None):
     """Validates the environments throughout the experiment"""
 
-    def _valid(queue, environment):
+    def _valid(environment):
+        q = Environment.objects.for_user(user, True) if user is not None else Environment.objects
+
+        return bool(q.filter(
+            name=environment['name'],
+            version=environment['version'],
+        ))
+
+    def _valid_combination(queue, environment):
         return bool(Queue.objects.filter(name=queue,
             environments__name=environment['name'],
-            environments__version=environment['version']))
+            environments__version=environment['version']
+        ))
 
     errors = []
 
     default_q = experiment.data['globals']['queue']
     default_env = experiment.data['globals']['environment']
-    if not _valid(default_q, default_env):
+    if not _valid(default_env):
+        errors.append("The environment '%s (%s)' in the global experiment declaration does not exist" % (default_env['name'], default_env['version']))
+    elif not _valid_combination(default_q, default_env):
         errors.append("The combination of queue '%s' with environment '%s (%s)' in the global experiment declaration does not exist" % (default_q, default_env['name'], default_env['version']))
 
     for name, config in experiment.blocks.items():
         q = config.get('queue', default_q)
         env = config.get('environment', default_env)
-        if not _valid(q, env):
+        if not _valid(env):
+            errors.append("The environment '%s (%s)' for block '%s' does not exist" % (env['name'], env['version'], name))
+        elif not _valid_combination(q, env):
             errors.append("The combination of queue '%s' with environment '%s (%s)' for block '%s' does not exist" % (q, env['name'], env['version'], name))
 
     for name, config in experiment.analyzers.items():
         q = config.get('queue', default_q)
         env = config.get('environment', default_env)
-        if not _valid(q, env):
+        if not _valid(env):
+            errors.append("The environment '%s (%s)' for analyzer '%s' does not exist" % (env['name'], env['version'], name))
+        elif not _valid_combination(q, env):
             errors.append("The combination of queue '%s' with environment '%s (%s)' for analyzer '%s' does not exist" % (q, env['name'], env['version'], name))
 
     return errors
 
 
-def validate_experiment(experiment_info, toolchain_info):
+def validate_experiment(experiment_info, toolchain_info, user=None):
     """Makes sure the experiment can be run"""
 
     xp = beat.core.experiment.Experiment(settings.PREFIX,
@@ -112,7 +127,7 @@ def validate_experiment(experiment_info, toolchain_info):
 
     if not xp.valid: return xp, xp.errors
 
-    return xp, xp.errors + validate_environments(xp)
+    return xp, xp.errors + validate_environments(xp, user)
 
 
 #----------------------------------------------------------
@@ -180,6 +195,8 @@ class ExperimentManager(ContributionManager):
         # Save the experiment (will run the validation)
         try:
             experiment.save()
+        except SyntaxError, e:
+            return (None, None, e.message)
         except Exception:
             import traceback
             return (None, None, traceback.format_exc())
@@ -387,7 +404,7 @@ class Experiment(Shareable):
 
         if content_modified:
             # validates the experiment
-            xp, errors = validate_experiment(declaration, self.toolchain.declaration)
+            xp, errors = validate_experiment(declaration, self.toolchain.declaration, self.author)
             if errors:
                 message = "The experiment isn't valid, due to the " \
                         "following errors:\n  * %s"
@@ -607,7 +624,7 @@ class Experiment(Shareable):
         return (self.reports.count() == 0) and not self.has_attestation() and super(Experiment, self).deletable()
 
     def core(self):
-        return validate_experiment(self.declaration, self.toolchain.declaration)[0]
+        return validate_experiment(self.declaration, self.toolchain.declaration, self.author)[0]
 
     def job_splits(self, status=None):
         from ..backend.models import JobSplit
diff --git a/beat/web/experiments/tests.py b/beat/web/experiments/tests.py
old mode 100644
new mode 100755
index 23107adf2..10408a6ab
--- a/beat/web/experiments/tests.py
+++ b/beat/web/experiments/tests.py
@@ -28,6 +28,7 @@
 import os
 import simplejson as json
 import shutil
+import copy
 from datetime import datetime
 
 from django.conf import settings
@@ -193,11 +194,16 @@ class ExperimentTestBase(BaseTestCase):
         # Create an environment and queue
         environment = Environment(name='env1', version='1.0')
         environment.save()
+        environment.share()
+
+        environment2 = Environment(name='private_env', version='1.0')
+        environment2.save()
 
         queue = Queue(name='queue1', memory_limit=1024, time_limit=60, cores_per_slot=1, max_slots_per_user=10)
         queue.save()
 
         queue.environments.add(environment)
+        queue.environments.add(environment2)
 
 
         DataFormat.objects.create_dataformat(
@@ -509,6 +515,171 @@ class ExperimentCreationAPI(ExperimentTestBase):
         self.checkResponse(response, 400, content_type='application/json')
 
 
+    def test_bad_request_with_unknown_global_environment_name(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['globals']['environment']['name'] = 'unknown'
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unknown_global_environment_version(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['globals']['environment']['version'] = 'unknown'
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unknown_algorithm_environment_name(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['blocks']['addition1']['environment'] = dict(
+            name='unknown',
+            version='1',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unknown_algorithm_environment_version(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['blocks']['addition1']['environment'] = dict(
+            name='env1',
+            version='unknown',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unknown_analyzer_environment_name(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['analyzers']['analysis']['environment'] = dict(
+            name='unknown',
+            version='1',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unknown_analyzer_environment_version(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['analyzers']['analysis']['environment'] = dict(
+            name='env1',
+            version='unknown',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unusable_global_environment(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['globals']['environment'] = dict(
+            name='private_env',
+            version='1.0'
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unusable_algorithm_environment(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['blocks']['addition1']['environment'] = dict(
+            name='private_env',
+            version='1.0',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
+    def test_bad_request_with_unusable_analyzer_environment(self):
+        self.client.login(username='johndoe', password='1234')
+
+        declaration = copy.deepcopy(ExperimentTestBase.DECLARATION1)
+
+        declaration['analyzers']['analysis']['environment'] = dict(
+            name='private_env',
+            version='1.0',
+        )
+
+        response = self.client.post(self.url,
+            json.dumps({
+                'toolchain': 'johndoe/toolchain1/1',
+                'declaration': declaration,
+            }), content_type='application/json')
+
+        self.checkResponse(response, 400, content_type='application/json')
+
+
     def test_valid_experiment(self):
         self.client.login(username='johndoe', password='1234')
 
diff --git a/beat/web/reports/tests.py b/beat/web/reports/tests.py
old mode 100644
new mode 100755
index e9edac72e..bd1fc642b
--- a/beat/web/reports/tests.py
+++ b/beat/web/reports/tests.py
@@ -498,6 +498,7 @@ class ReportTestCase(APITestCase):
         # Create an environment and queue
         environment = Environment(name='env1', version='1.0')
         environment.save()
+        environment.share()
 
         queue = Queue(name='queue1', memory_limit=1024, time_limit=60, cores_per_slot=1, max_slots_per_user=10)
         queue.save()
-- 
GitLab