diff options
Diffstat (limited to 'src/liblaas')
-rw-r--r-- | src/liblaas/__init__.py | 8 | ||||
-rw-r--r-- | src/liblaas/endpoints.py | 254 | ||||
-rw-r--r-- | src/liblaas/tests.py | 65 | ||||
-rw-r--r-- | src/liblaas/urls.py | 24 | ||||
-rw-r--r-- | src/liblaas/utils.py | 63 | ||||
-rw-r--r-- | src/liblaas/views.py | 205 |
6 files changed, 619 insertions, 0 deletions
diff --git a/src/liblaas/__init__.py b/src/liblaas/__init__.py new file mode 100644 index 0000000..46e0bd7 --- /dev/null +++ b/src/liblaas/__init__.py @@ -0,0 +1,8 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +##############################################################################
\ No newline at end of file diff --git a/src/liblaas/endpoints.py b/src/liblaas/endpoints.py new file mode 100644 index 0000000..64e5126 --- /dev/null +++ b/src/liblaas/endpoints.py @@ -0,0 +1,254 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# HTTP Requests from the user will need to be processed here first, before the appropriate liblaas endpoint is called + +from account.models import UserProfile +from liblaas.views import * +from liblaas.utils import validate_collaborators +from django.http import JsonResponse, HttpResponse +from django.contrib.auth.models import User +from booking.models import Booking +from account.models import Lab +from django.utils import timezone +from datetime import timedelta + +def request_list_flavors(request) -> HttpResponse: + data = json.loads(request.body.decode('utf-8')) + if not "project" in data: + return HttpResponse(status=422) + + if not request.user.is_authenticated: + return HttpResponse(status=401) + + response = flavor_list_flavors(data["project"]) + return JsonResponse(status=200, data={"flavors_list": response}) + + +def request_list_template(request) -> HttpResponse: + if not request.user.is_authenticated: + return HttpResponse(status=401) + + uid = UserProfile.objects.get(user=request.user) + uid = uid.ipa_username + + response = template_list_templates(uid) + return JsonResponse(status=200, data={"templates_list": response}) + +def request_create_template(request) -> HttpResponse: + data = json.loads(request.body.decode('utf-8')) + if not "template_blob" in data: + return HttpResponse(status=422) + + if not request.user.is_authenticated: + return HttpResponse(status=401) + + response = template_make_template(data["template_blob"]) + return JsonResponse(status=200, data={"uuid": response}) + +def request_create_booking(request) -> HttpResponse: + # Validate request + data = json.loads(request.body.decode('utf-8')) + if not "booking_blob" in data: + return HttpResponse(status=422) + + if not request.user.is_authenticated: + return HttpResponse(status=401) + + data = data["booking_blob"] + + # Validate and build collaborators list + valid = validate_collaborators(data["allowed_users"]) + if (not valid["valid"]): + return JsonResponse( + data={"message": valid["message"], "error": True}, + status=200, + safe=False + ) + ipa_users = [] + ipa_users.append(UserProfile.objects.get(user=request.user).ipa_username) + for user in data["allowed_users"]: + collab_profile = UserProfile.objects.get(user=User.objects.get(username=user)) + ipa_users.append(collab_profile.ipa_username) + + # Reformat post data + bookingBlob = { + "template_id": data["template_id"], + "allowed_users": ipa_users, + "global_cifile": data["global_cifile"], + "metadata": { + "booking_id": None, # fill in after creating django object + "owner": UserProfile.objects.get(user=request.user).ipa_username, + "lab": "UNH_IOL", + "purpose": data["metadata"]["purpose"], + "project": data["metadata"]["project"], + "length": int(data["metadata"]["length"]) + }, + "origin": "anuket" if os.environ.get("TEMPLATE_OVERRIDE_DIR") == 'laas' else "lfedge" # todo - refactor + } + + # Create booking in dashboard + booking = Booking.objects.create( + purpose=bookingBlob["metadata"]["purpose"], + project=bookingBlob["metadata"]['project'], + lab=Lab.objects.get(name='UNH_IOL'), + owner=request.user, + start=timezone.now(), + end=timezone.now() + timedelta(days=int(bookingBlob["metadata"]['length'])), + ) + + for c in list(data["allowed_users"]): + booking.collaborators.add(User.objects.get(username=c)) + + # Create booking in liblaas + bookingBlob["metadata"]["booking_id"] = str(booking.id) + liblaas_response = booking_create_booking(bookingBlob) + if liblaas_response.status_code != 200: + print("received non success from liblaas, deleting booking from dashboard") + booking.delete() + return JsonResponse( + data={}, + status=500, + safe=False + ) + + aggregateId = liblaas_response + booking.aggregateId = aggregateId + booking.save() + + print(f"Created new booking with id {booking.id} and aggregate {aggregateId}") + + return JsonResponse( + data = {"bookingId": booking.id}, + status=200, + safe=False + ) + +def request_migrate_new(request) -> HttpResponse: + user = request.user + profile = UserProfile.objects.get(user=user) + + data = json.loads(request.body.decode('utf-8')) + fn = data["firstname"].strip() + ln = data["lastname"].strip() + company = data["company"].strip() + + if (fn == "" or ln == "" or company == ""): + return JsonResponse( + data = {"message": "Please fill out all fields."}, + status=406 + ) + + user_blob = { + "uid": str(user), # For this case, the username is not taken, so the dashboard username will be used + "givenname": str(fn), + "sn": str(ln), + "ou": str(company), + "mail": str(profile.email_addr), + 'random': True + } + + vpn_username = user_create_user(user_blob) + + if (vpn_username): + profile.ipa_username = vpn_username + profile.save() + return HttpResponse(status=200) + + return JsonResponse( + data = {"message": "Unable to create account. Please try again."}, + status=406 + ) + + + +def request_migrate_conflict(request) -> HttpResponse: + user = request.user + profile = UserProfile.objects.get(user=user) + + data = json.loads(request.body.decode('utf-8')) + fn = data["firstname"].strip() + ln = data["lastname"].strip() + company = data["company"].strip() + username = data["username"].strip() + + if (fn == "" or ln == "" or company == "" or username == ""): + return JsonResponse( + data = {"message": "Please fill out all fields."}, + status=406 + ) + + # Check if username is taken by querying for that user + existing_user = user_get_user(username) + if (existing_user): + return JsonResponse( + data = {"message": "Username is already taken. Please try again."}, + status=406 + ) + + # Create and link account + user_blob = { + "uid": str(username), + "givenname": str(fn), + "sn": str(ln), + "ou": str(company), + "mail": str(profile.email_addr), + 'random': True + } + + vpn_username = user_create_user(user_blob) + + if (vpn_username): + profile.ipa_username = vpn_username + profile.save() + return HttpResponse(status=200) + + return JsonResponse( + data = {"message": "Unable to create account. Please try again."}, + status=406 + ) + +def request_set_company(request): + data = json.loads(request.body.decode('utf-8')) + if not "data" in data: + return HttpResponse(status=422) + + if not request.user.is_authenticated: + return HttpResponse(status=401) + + up = UserProfile.objects.get(user=request.user) + success = user_set_company(up.ipa_username, data["data"].strip()) + + if (success): + return HttpResponse(status=200) + + return JsonResponse( + data = {"message": "Unable to update details. Please check your input and try again."}, + status=406 + ) + +def request_set_ssh(request): + data = json.loads(request.body.decode('utf-8')) + if not "data" in data: + return HttpResponse(status=422) + + if not request.user.is_authenticated: + return HttpResponse(status=401) + up = UserProfile.objects.get(user=request.user) + success = user_set_ssh(up.ipa_username, data["data"]) + + if (success): + return HttpResponse(status=200) + + # API does not provide a way to verify keys were successfully added, so it is unlikely that this case will ever be hit + return JsonResponse( + data = {"message": "Unable to add SSH key. Please verify that the key is valid and try again."}, + status=406 + ) + diff --git a/src/liblaas/tests.py b/src/liblaas/tests.py new file mode 100644 index 0000000..5ec7f15 --- /dev/null +++ b/src/liblaas/tests.py @@ -0,0 +1,65 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from django.test import TestCase +from liblaas.views import liblaas_docs, user_get_user + +class LibLaaSTests(TestCase): + def test_can_reach_liblaas(self): + response = liblaas_docs() + self.assertEqual(response.status_code, 200) + +class BookingTests(TestCase): + def test_create_booking_succeeds_with_valid_blob(self): + pass + + def test_create_booking_fails_with_invalid_blob(self): + pass + + def test_booking_status_succeeds_on_valid_booking_id(self): + pass + + def test_end_booking_succeeds_on_valid_agg_id(self): + pass + +class FlavorTests(TestCase): + def test_list_flavors_succeeds(self): + pass + + def test_get_flavor_by_id_succeeds(self): + pass + + def test_list_hosts_succeeds(self): + pass + +class TemplateTests(TestCase): + def test_list_templates_succeeds(self): + pass + + def test_make_template_succeeds(self): + pass + + def test_delete_template_succeeds(self): + pass + +class UserTests(TestCase): + def test_get_user_succeeds(self): + pass + + def test_create_user_succeeds(self): + pass + + def test_set_ssh_succeeds(self): + pass + + def test_set_company_succeeds(self): + pass + + def test_set_email_succeeds(self): + pass diff --git a/src/liblaas/urls.py b/src/liblaas/urls.py new file mode 100644 index 0000000..6deb9cf --- /dev/null +++ b/src/liblaas/urls.py @@ -0,0 +1,24 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from django.conf.urls import url + +from liblaas.endpoints import * + +app_name = 'liblaas' +urlpatterns = [ + url(r'^flavor/$', request_list_flavors, name='flavor'), + url(r'^template/$', request_list_template, name='template'), + url(r'^template/create/$', request_create_template, name='template_create'), + url(r'^booking/create/$', request_create_booking, name='booking_create'), + url(r'^migrate/new/$', request_migrate_new, name='migrate_new'), + url(r'^migrate/conflict/$', request_migrate_conflict, name='migrate_conflict'), + url(r'^ipa/ssh/$', request_set_ssh, name='set_ssh'), + url(r'^ipa/company/$', request_set_company, name='set_company'), +]
\ No newline at end of file diff --git a/src/liblaas/utils.py b/src/liblaas/utils.py new file mode 100644 index 0000000..c3b66a7 --- /dev/null +++ b/src/liblaas/utils.py @@ -0,0 +1,63 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from django.contrib.auth.models import User +from account.models import UserProfile +from liblaas.views import user_get_user + +def validate_collaborators(collab_list: list[str]) -> dict: + result = {"message": "n/a", "valid": False} + + for user in collab_list: + collab_profile = UserProfile.objects.get(user=User.objects.get(username=user)) + ipa_username = collab_profile.ipa_username + if ipa_username == None: + result["message"] = f"{str(collab_profile)} has not linked their IPA account yet. Please ask them to log into the LaaS dashboard, or remove them from the booking to continue." + return result + + ipa_account = user_get_user(ipa_username) + print(ipa_account) + + if not "ou" in ipa_account or ipa_account["ou"] == "": + result["message"] = f"{str(collab_profile)} has not set their company yet. Please ask them to log into the LaaS dashboard, go to the settings page and add it. Otherwise, remove them from the booking to continue." + return result + + if not "ipasshpubkey" in ipa_account: + result["message"] = f"{str(collab_profile)} has not added an SSH public key yet. Please ask them to log into the LaaS dashboard, go to the settings page and add it. Otherwise, remove them from the booking to continue." + return result + + result["valid"] = True + + return result + +# Returns whether the user has linked their ipa account. If not, determines how it needs to be linked. +def get_ipa_status(dashboard_user: User) -> str: + profile = UserProfile.objects.get(user=dashboard_user) + if profile == None: + return "n/a" + + # Already linked, no need to continue + if profile.ipa_username != None: + return "n/a" + + # Basic info + dashboard_username = str(dashboard_user) + dashboard_email = profile.email_addr + ipa_user = user_get_user(dashboard_username) + + # New user case + if ipa_user == None: + return "new" + + # Link case + if ipa_user["mail"] == dashboard_email: + return "link" + + # Conflict case + return "conflict" diff --git a/src/liblaas/views.py b/src/liblaas/views.py new file mode 100644 index 0000000..5edc727 --- /dev/null +++ b/src/liblaas/views.py @@ -0,0 +1,205 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Unauthenticated requests to liblaas. If a call makes it to here, it is assumed to be authenticated + +import os +import requests +import json + +base = os.environ.get("LIBLAAS_BASE_URL") +post_headers = {'Content-Type': 'application/json'} + +def liblaas_docs(): + endpoint = f'docs' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +### BOOKING + +# DELETE +def booking_end_booking(agg_id: str) -> requests.Response: + endpoint = f'booking/{agg_id}/end' + url = f'{base}{endpoint}' + try: + response = requests.delete(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# GET +def booking_booking_status(agg_id: str) -> requests.Response: + endpoint = f'booking/{agg_id}/status' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# POST +def booking_create_booking(booking_blob: dict) -> requests.Response: + endpoint = f'booking/create' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(booking_blob), headers=post_headers) + return response.json() + except: + print(f"Error at {url}") + return None + +### FLAVOR + +# GET +def flavor_list_flavors(project: str) -> requests.Response: + endpoint = f'flavor' #todo - add project to url once liblaas supports it + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# GET +def flavor_get_flavor_by_id(flavor_id: str) -> requests.Response: + endpoint = f'flavor/name/{flavor_id}/' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# GET +def flavor_list_hosts(project: str) -> requests.Response: + endpoint = f'flavor/hosts/{project}' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +### TEMPLATE + +# GET +def template_list_templates(uid: str) -> requests.Response: + endpoint = f'template/list/{uid}' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# DELETE +def template_delete_template(template_id: str) -> requests.Response: + endpoint = f'template/{template_id}' + url = f'{base}{endpoint}' + try: + response = requests.delete(url) + return response.json() + except: + print(f"Error at {url}") + return None + +#POST +def template_make_template(template_blob: dict) -> requests.Response: + endpoint = f'template/create' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(template_blob), headers=post_headers) + return response.json() + except: + print(f"Error at {url}") + return None + +### USER + +# GET +def user_get_user(uid: str) -> requests.Response: + endpoint = f'user/{uid}' + url = f'{base}{endpoint}' + try: + response = requests.get(url) + return response.json() + except: + print(f"Error at {url}") + return None + +# POST +def user_create_user(user_blob: dict) -> requests.Response: + endpoint = f'user/create' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(user_blob), headers=post_headers) + # LibLaaS is not returning anything here regardless, so we need some way of determining if it was successful + # return response.json() + + if response.status_code == 200: + return user_blob["uid"] + + return response.json() + except: + print(f"Error at {url}") + return None + +# POST +def user_set_ssh(uid: str, keys: list) -> requests.Response: + endpoint = f'user/{uid}/ssh' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(clean_ssh_keys(keys)), headers=post_headers) + # return response.json() + return response.status_code == 200 + except: + print(f"Error at {url}") + return None + +# POST +def user_set_company(uid: str, company: str): + endpoint = f'user/{uid}/company' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(company), headers=post_headers) + # return response.json() + return response.status_code == 200 + except: + print(f"Error at {url}") + return None + +# POST +def user_set_email(uid: str, email: str): + endpoint = f'user/{uid}/email' + url = f'{base}{endpoint}' + try: + response = requests.post(url, data=json.dumps(email), headers=post_headers) + # return response.json() + return response.status_code == 200 + except: + print(f"Error at {url}") + return None + +# utils +def clean_ssh_keys(ssh_key_list): + cleaned = [] + for key in ssh_key_list: + cleaned.append(key.strip()) + return cleaned
\ No newline at end of file |