From 64977defe1d60c2fd019a8935790abee546ebc6a Mon Sep 17 00:00:00 2001 From: Stefano Alberto Russo <stefano.russo@gmail.com> Date: Fri, 5 Nov 2021 16:38:21 +0100 Subject: [PATCH] Added the power user status and restricted options for standard users when adding containers and running taks. Fixed bug in proxy auth. --- .../migrations/0010_profile_is_power_user.py | 18 ++++++ .../webapp/code/rosetta/core_app/models.py | 3 +- .../core_app/templates/add_container.html | 7 +++ .../core_app/templates/create_task.html | 55 ++++++++++++------- .../webapp/code/rosetta/core_app/views.py | 30 ++++++++-- 5 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 services/webapp/code/rosetta/core_app/migrations/0010_profile_is_power_user.py diff --git a/services/webapp/code/rosetta/core_app/migrations/0010_profile_is_power_user.py b/services/webapp/code/rosetta/core_app/migrations/0010_profile_is_power_user.py new file mode 100644 index 0000000..f8d9f43 --- /dev/null +++ b/services/webapp/code/rosetta/core_app/migrations/0010_profile_is_power_user.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2021-11-05 11:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_app', '0009_auto_20211103_2256'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='is_power_user', + field=models.BooleanField(default=False, verbose_name='Power user status'), + ), + ] diff --git a/services/webapp/code/rosetta/core_app/models.py b/services/webapp/code/rosetta/core_app/models.py index 700281d..d4cea0d 100644 --- a/services/webapp/code/rosetta/core_app/models.py +++ b/services/webapp/code/rosetta/core_app/models.py @@ -45,6 +45,7 @@ class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) timezone = models.CharField('User Timezone', max_length=36, default='UTC') authtoken = models.CharField('User auth token', max_length=36, blank=True, null=True) + is_power_user = models.BooleanField('Power user status', default=False) def save(self, *args, **kwargs): @@ -116,7 +117,7 @@ class Container(models.Model): @ property def color(self): - string_int_hash = hash_string_to_int(self.registry + self.image + self.tag) + string_int_hash = hash_string_to_int(self.image + self.tag + self.registry) color_map_index = string_int_hash % len(color_map) return color_map[color_map_index] diff --git a/services/webapp/code/rosetta/core_app/templates/add_container.html b/services/webapp/code/rosetta/core_app/templates/add_container.html index ebbb51b..300d486 100644 --- a/services/webapp/code/rosetta/core_app/templates/add_container.html +++ b/services/webapp/code/rosetta/core_app/templates/add_container.html @@ -72,7 +72,14 @@ <tr> <td><b>Interface protocol</b></td> <td> + {% if request.user.profile.is_power_user %} <input type="text" value="http" name="container_interface_protocol" size="5" /> + {% else %} + <select name="container_interface_protocol" > + <option value="http" selected>http</option> + <option value="https" selected>https</option> + </select> + {% endif %} </td> </tr> </table> diff --git a/services/webapp/code/rosetta/core_app/templates/create_task.html b/services/webapp/code/rosetta/core_app/templates/create_task.html index b2912bc..9ff63a0 100644 --- a/services/webapp/code/rosetta/core_app/templates/create_task.html +++ b/services/webapp/code/rosetta/core_app/templates/create_task.html @@ -70,7 +70,7 @@ <div style="width:300px; background:#ffffff; margin-left:10px; margin-right:10px; margin-top:10px; margin-bottom:0px"> <h4>Details and confirm</h4> - <div style="width:640px; float:left; border: #e0e0e0 solid 0px; margin:10px; ; margin-bottom:15px"> + <div style="width:620px; float:left; border: #e0e0e0 solid 0px; margin-top:10px; ; margin-bottom:15px"> <form action="/create_task/" method="POST"> @@ -81,7 +81,7 @@ <input type="hidden" name="task_container_uuid" value="{{ data.task_container.uuid }}" /> <input type="hidden" name="task_computing_uuid" value="{{ data.task_computing.uuid }}" /> - <table class="dashboard" style="max-width:700px; margin-bottom:20px"> + <table class="dashboard" style="max-width:640px; margin-bottom:20px"> <tr><td colspan="2"></td></tr> <tr> @@ -92,12 +92,18 @@ <tr> <td valign="top"><b>Task password</b></td> <td> - - <input type="text" name="task_auth_password" placeholder="{{data.task_auth_token}}" size="37" style="-webkit-text-security: disc;" /><br> + <input type="hidden" name="task_auth_token" value="{{data.task_auth_token}}"> - <!-- <font size=-1>A one-time task password. By default set to a randomly generated token, and automatically handled by Rosetta. Can be also set to a custom one, which in this case will be required to be entered manually when connecting to the task (as it will not be stored by Rosetta). Six characters minimum.</font> --> - <font size=-1>A one-time task password. By default set to a randomly generated token, and automatically handled by Rosetta. Six chars min.</font> + {% if request.user.profile.is_power_user %} + <input type="text" name="task_auth_password" placeholder="{{data.task_auth_token}}" size="37" style="-webkit-text-security: disc;" /><br> + <font size=-1> + A one-time task password. By default set to a randomly generated token, and usually automatically handled by Rosetta. Six chars min. + </font> + {% else %} + <input type="text" name="task_auth_password_disabled" value="{{data.task_auth_token}}" size="37" readonly/><br> + <font size=-1>A randomly generated token to be used as task password. Usually automatically handled by Rosetta when loggin-in to the task.</font> + {% endif %} </td> </tr> @@ -112,24 +118,35 @@ </tr> {% endif %} --> - + + {% if request.user.profile.is_power_user %} <tr> <td><b>Access method</b></td><td> - <select name="access_method" > - <option value="direct_tunnel" selected>Direct tunnel</option> - <option value="https_proxy">HTTPS proxy</option> - <option value="None">None</option> - </select> + <select name="access_method" > + <option value="auto" selected>Auto</option> + <option value="direct_tunnel">Direct tunnel</option> + <option value="https_proxy">HTTPS proxy</option> + <option value="None">None</option> + </select> </td> </tr> + {% else %} + <tr> + <td><b>Access method</b></td><td> + <select name="access_method" > + <option value="auto" selected>Auto</option> + </select> + </td> + </tr> + {% endif %} <tr> <td><b>Container runtime</b></td><td> - <select name="run_using" > - <option value="default" selected>Default</option> - <!-- <option value="docker" selected>default</option> --> - <!-- <option value="singularity">Singularity</option> --> - </select> + <select name="run_using" > + <option value="default" selected>Default</option> + <!-- <option value="docker" selected>default</option> --> + <!-- <option value="singularity">Singularity</option> --> + </select> </td> </tr> @@ -165,8 +182,8 @@ </p></div> {% endif %} --> - <table><tr><td style="border: 1px solid lightgray;" > - I understand that files saved or modified in this container, if not explicitly saved to a persistent storage, will be LOST upon task completition. + <table style="max-width:620px;"><tr><td style="border: 1px solid lightgray;" > + I understand that files saved or modified in the task, if not explicitly saved to a persistent storage, will be LOST upon task completition. </td><td style="border: 1px solid lightgray;" > <input class="form-check-input" type="checkbox" value="" id="invalidCheck" required> </td></table> diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py index 972a72b..298c86d 100644 --- a/services/webapp/code/rosetta/core_app/views.py +++ b/services/webapp/code/rosetta/core_app/views.py @@ -510,6 +510,8 @@ def create_task(request): # Add auth task_auth_password = request.POST.get('task_auth_password', None) + if task_auth_password and not request.user.profile.is_power_user: + raise ErrorMessage('Sorry, only power users can set a custom task password.') task_auth_token = request.POST.get('task_auth_token', None) if task_auth_password: if task_auth_password != task_auth_token: # Just an extra check probably not much useful @@ -527,14 +529,23 @@ def create_task(request): # Task access method access_method = request.POST.get('access_method', None) - if access_method == 'direct_tunnel': + if access_method and access_method != 'auto' and not request.user.profile.is_power_user: + raise ErrorMessage('Sorry, only power users can set a task access method other than \'auto\'.') + if access_method == 'auto': + if task.container.interface_protocol in ['http','https']: + task.requires_proxy = True + task.requires_proxy_auth = True + else: + task.requires_proxy = False + task.requires_proxy_auth = False + elif access_method == 'direct_tunnel': task.requires_proxy = False task.requires_proxy_auth = False elif access_method == 'https_proxy': task.requires_proxy = True task.requires_proxy_auth = True else: - raise ErrorMessage('Unknow access method "{}"'.format(access_method)) + raise ErrorMessage('Unknown access method "{}"'.format(access_method)) # Computing options # TODO: This is hardcoded thinking about Slurm and Singularity computing_cpus = request.POST.get('computing_cpus', None) @@ -578,6 +589,10 @@ def create_task(request): # ..and re-raise raise + # Ensure proxy conf directory exists + if not os.path.exists('/shared/etc_apache2_sites_enabled'): + os.makedirs('/shared/etc_apache2_sites_enabled') + # Add here proxy auth file as we have the password if task.requires_proxy_auth: out = os_shell('ssh -o StrictHostKeyChecking=no proxy "cd /shared/etc_apache2_sites_enabled/ && htpasswd -bc {}.htpasswd {} {}"'.format(task.uuid, task.user.email, task.password), capture=True) @@ -759,16 +774,21 @@ def add_container(request): container_interface_port = request.POST.get('container_interface_port', None) if container_interface_port: try: - int(container_interface_port) + container_interface_port = int(container_interface_port) except: raise ErrorMessage('Invalid container port "{}"') + else: + container_interface_port = None # Container interface protocol - container_interface_protocol = request.POST.get('container_interface_protocol') + container_interface_protocol = request.POST.get('container_interface_protocol', None) + + if container_interface_protocol and not container_interface_protocol in ['http','https']: + raise ErrorMessage('Sorry, only power users can add custom software containers with interface protocols other than \'http\' or \'https\'.') # Container interface transport container_interface_transport = request.POST.get('container_interface_transport') - logger.critical('Creating with desc={}, transp={}'.format(container_description, container_interface_transport)) + # Capabilities container_supports_custom_interface_port = request.POST.get('container_supports_custom_interface_port', None) if container_supports_custom_interface_port and container_supports_custom_interface_port == 'True': -- GitLab