You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/06/14 20:09:06 UTC

[airavata-django-portal] 01/04: AIRAVATA-3383 settings_local.py download

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 8cce97c2b08bc672c9939491e95b82766e9e46f4
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jun 14 15:58:51 2021 -0400

    AIRAVATA-3383 settings_local.py download
---
 django_airavata/apps/admin/apps.py                 |   8 ++
 .../components/developers/DevelopersContainer.vue  |  18 +++
 .../static/django_airavata_admin/src/router.js     |   8 +-
 django_airavata/apps/admin/urls.py                 |   2 +
 django_airavata/apps/admin/views.py                |   6 +
 .../settings_local.py.template                     | 137 +++++++++++++++++++++
 django_airavata/apps/auth/urls.py                  |   2 +
 django_airavata/apps/auth/views.py                 |  99 ++++++++++++++-
 8 files changed, 277 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/admin/apps.py b/django_airavata/apps/admin/apps.py
index 5501733..82876b9 100644
--- a/django_airavata/apps/admin/apps.py
+++ b/django_airavata/apps/admin/apps.py
@@ -65,4 +65,12 @@ class AdminConfig(AiravataAppConfig):
             'enabled': lambda req: (req.is_gateway_admin or
                                     req.is_read_only_gateway_admin)
         },
+        {
+            'label': 'Developer Console',
+            'icon': 'fa fa-code',
+            'url': 'django_airavata_admin:developers',
+            'active_prefixes': ['developers'],
+            'enabled': lambda req: (req.is_gateway_admin or
+                                    req.is_read_only_gateway_admin)
+        },
     ]
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/developers/DevelopersContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/developers/DevelopersContainer.vue
new file mode 100644
index 0000000..674676f
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/developers/DevelopersContainer.vue
@@ -0,0 +1,18 @@
+<template>
+  <div>
+    <div class="row">
+      <div class="col">
+        <h1 class="h4 mb-4">Developer Console</h1>
+      </div>
+    </div>
+    <b-card header="Download a settings_local.py file for local development">
+      <b-link href="/auth/settings-local">
+        <i class="fas fa-download"></i>
+        Download
+      </b-link>
+    </b-card>
+  </div>
+</template>
+<script>
+export default {};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
index 6d803b5..de0c0d4 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/router.js
@@ -6,8 +6,9 @@ import ApplicationModuleEditor from "./components/applications/ApplicationModule
 import ApplicationsDashboard from "./components/dashboards/ApplicationsDashboard.vue";
 import ComputePreference from "./components/admin/group_resource_preferences/ComputePreference";
 import ComputeResourcePreferenceDashboard from "./components/dashboards/ComputeResourcePreferenceDashboard";
-import ExperimentStatisticsContainer from "./components/statistics/ExperimentStatisticsContainer";
 import CredentialStoreDashboard from "./components/dashboards/CredentialStoreDashboard";
+import DevelopersContainer from "./components/developers//DevelopersContainer.vue";
+import ExperimentStatisticsContainer from "./components/statistics/ExperimentStatisticsContainer";
 import GatewayResourceProfileEditorContainer from "./components/gatewayprofile/GatewayResourceProfileEditorContainer.vue";
 import GroupComputeResourcePreference from "./components/admin/group_resource_preferences/GroupComputeResourcePreference";
 import IdentityServiceUserManagementContainer from "./components/users/IdentityServiceUserManagementContainer.vue";
@@ -149,6 +150,11 @@ const routes = [
     component: ExperimentStatisticsContainer,
     name: "experiment-statistics",
   },
+  {
+    path: "/developers",
+    component: DevelopersContainer,
+    name: "developers",
+  }
 ];
 export default new VueRouter({
   mode: "history",
diff --git a/django_airavata/apps/admin/urls.py b/django_airavata/apps/admin/urls.py
index 2c5d481..9404e64 100644
--- a/django_airavata/apps/admin/urls.py
+++ b/django_airavata/apps/admin/urls.py
@@ -1,4 +1,5 @@
 from django.conf.urls import url
+from django.urls import path
 
 from . import views
 
@@ -15,4 +16,5 @@ urlpatterns = [
         name='gateway_resource_profile'),
     url(r'^notices/', views.notices, name='notices'),
     url(r'^users/', views.users, name='users'),
+    path('developers/', views.developers, name='developers'),
 ]
diff --git a/django_airavata/apps/admin/views.py b/django_airavata/apps/admin/views.py
index 59ed496..caca63a 100644
--- a/django_airavata/apps/admin/views.py
+++ b/django_airavata/apps/admin/views.py
@@ -57,3 +57,9 @@ def users(request):
 def experiment_statistics(request):
     request.active_nav_item = 'experiment-statistics'
     return render(request, 'admin/admin_base.html')
+
+
+@login_required
+def developers(request):
+    request.active_nav_item = 'developers'
+    return render(request, 'admin/admin_base.html')
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/settings_local.py.template b/django_airavata/apps/auth/templates/django_airavata_auth/settings_local.py.template
new file mode 100644
index 0000000..cdda4ce
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/settings_local.py.template
@@ -0,0 +1,137 @@
+"""
+Override default Django settings for a particular instance.
+
+Copy this file to settings_local.py and modify as appropriate. This file will
+be imported into settings.py last of all so settings in this file override any
+defaults specified in settings.py.
+"""
+
+{% autoescape off %}
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Django - general settings
+# Uncomment and specify for production deployments
+# DEBUG = False
+# STATIC_ROOT = "/var/www/path/to/sitename/static/"
+# ALLOWED_HOSTS = ['production.hostname']
+
+# Django - database settings
+# MySQL - to use MySQL, uncomment and specify the settings
+# DATABASES = {
+#     'default': {
+#         'ENGINE': 'django.db.backends.mysql',
+#         'NAME': '...',
+#         'HOST': '...',
+#         'USER': '...',
+#         'PASSWORD': '...',
+#         'OPTIONS': {
+#             'init_command': 'SET default_storage_engine=INNODB,collation_connection=utf8_bin',
+#         }
+# }
+
+# Django - Email settings
+# Uncomment and specify the following for sending emails (default email backend
+# just prints to the console)
+# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+# EMAIL_HOST = '...'
+# EMAIL_PORT = '...'
+# EMAIL_HOST_USER = '...'
+# EMAIL_HOST_PASSWORD = '...'
+# EMAIL_USE_TLS = True
+# ADMINS receive error emails
+# ADMINS = [('Admin Name', 'admin@example.com')]
+# Optional: PORTAL_ADMINS receive administrative emails, like when a new user is created
+# This can be set to a different value than ADMINS so that the PORTAL_ADMINS
+# don't receive error emails. Defaults to the same value as ADMINS.
+# PORTAL_ADMINS = ADMINS
+# SERVER_EMAIL = 'portal@example.com'
+
+# Keycloak Configuration
+KEYCLOAK_CLIENT_ID = '{{ keycloak_client_id }}'
+KEYCLOAK_CLIENT_SECRET = '{{ keycloak_client_secret }}'
+KEYCLOAK_AUTHORIZE_URL = '{{ KEYCLOAK_AUTHORIZE_URL }}'
+KEYCLOAK_TOKEN_URL = '{{ KEYCLOAK_TOKEN_URL }}'
+KEYCLOAK_USERINFO_URL = '{{ KEYCLOAK_USERINFO_URL }}'
+KEYCLOAK_LOGOUT_URL = '{{ KEYCLOAK_LOGOUT_URL }}'
+# Optional: specify if using self-signed certificate or certificate from unrecognized CA
+#KEYCLOAK_CA_CERTFILE = os.path.join(BASE_DIR, "django_airavata", "resources", "incommon_rsa_server_ca.pem")
+KEYCLOAK_VERIFY_SSL = True
+
+AUTHENTICATION_OPTIONS = {{ AUTHENTICATION_OPTIONS }}
+
+# Airavata API Configuration
+GATEWAY_ID = '{{ GATEWAY_ID }}'
+AIRAVATA_API_HOST = '{{ AIRAVATA_API_HOST }}'
+AIRAVATA_API_PORT = {{ AIRAVATA_API_PORT }}
+AIRAVATA_API_SECURE = {{ AIRAVATA_API_SECURE }}
+GATEWAY_DATA_STORE_RESOURCE_ID = '{{ GATEWAY_DATA_STORE_RESOURCE_ID }}'
+GATEWAY_DATA_STORE_DIR = '/tmp'
+GATEWAY_DATA_STORE_REMOTE_API = '{{ GATEWAY_DATA_STORE_REMOTE_API }}'
+# FILE_UPLOAD_TEMP_DIR = '/tmp'
+
+# Profile Service Configuration
+PROFILE_SERVICE_HOST = '{{ PROFILE_SERVICE_HOST }}'
+PROFILE_SERVICE_PORT = {{ PROFILE_SERVICE_PORT }}
+PROFILE_SERVICE_SECURE = {{ PROFILE_SERVICE_SECURE }}
+
+# Portal settings
+PORTAL_TITLE = '{{ PORTAL_TITLE }}'
+
+# Tus upload - uncomment the following to enable tus uploads
+# Override and set to a valid tus endpoint
+# TUS_ENDPOINT = "https://tus.domainname.org/files/"
+# Override and set to the directory where tus uploads will be stored.
+# TUS_DATA_DIR = "/path/to/tus-temp-dir"
+
+# Legacy (PGA) Portal link - uncomment to provide a link to the legacy portal
+# PGA_URL = '...'
+
+# Google Analytics Tracking ID ("UA-XXXXXXXX-X"). If this setting is set, then
+# Google Analytics tracking will be added to all pages.
+# GOOGLE_ANALYTICS_TRACKING_ID = '...'
+
+# Logging configuration. Uncomment following to override default log configuration
+# LOGGING = {
+#     'version': 1,
+#     'disable_existing_loggers': False,
+#     'filters': {
+#         'require_debug_false': {
+#             '()': 'django.utils.log.RequireDebugFalse',
+#         },
+#         'require_debug_true': {
+#             '()': 'django.utils.log.RequireDebugTrue',
+#         },
+#     },
+#     'formatters': {
+#         'verbose': {
+#             'format': '[%(asctime)s %(name)s:%(lineno)d %(levelname)s] %(message)s'
+#         },
+#     },
+#     'handlers': {
+#         'console': {
+#             'class': 'logging.StreamHandler',
+#             'formatter': 'verbose'
+#         },
+#         'mail_admins': {
+#             'filters': ['require_debug_false'],
+#             'level': 'ERROR',
+#             'class': 'django.utils.log.AdminEmailHandler',
+#             'include_html': True,
+#         }
+#     },
+#     'loggers': {
+#         'django_airavata': {
+#             'handlers': ['console', 'mail_admins'],
+#             'level': 'DEBUG' if DEBUG else 'INFO'
+#         },
+#         'root': {
+#             'handlers': ['console', 'mail_admins'],
+#             'level': 'WARNING'
+#         }
+#     },
+# }
+
+{% endautoescape %}
diff --git a/django_airavata/apps/auth/urls.py b/django_airavata/apps/auth/urls.py
index b5d874d..25454ad 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -1,5 +1,6 @@
 
 from django.conf.urls import url
+from django.urls import path
 
 from . import views
 
@@ -28,4 +29,5 @@ urlpatterns = [
         views.login_desktop_success, name="login_desktop_success"),
     url(r'^refreshed-token-desktop$', views.refreshed_token_desktop,
         name="refreshed_token_desktop"),
+    path('settings-local/', views.download_settings_local, name="download_settings_local"),
 ]
diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index 0fe55b0..f005a67 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -1,16 +1,20 @@
+import io
 import logging
 import time
 from datetime import datetime, timedelta, timezone
 from urllib.parse import quote, urlencode
 
+import requests
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth import authenticate, login, logout
-from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.auth.decorators import login_required
+from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.forms import ValidationError
-from django.http import HttpResponseBadRequest, JsonResponse
+from django.http import FileResponse, HttpResponseBadRequest, JsonResponse
 from django.shortcuts import redirect, render, resolve_url
 from django.template import Context
+from django.template.loader import render_to_string
 from django.urls import reverse
 from django.views.decorators.debug import sensitive_variables
 from requests_oauthlib import OAuth2Session
@@ -502,3 +506,94 @@ def _create_login_desktop_failed_response(request, idp_alias=None):
         params['username'] = request.POST['username']
     return redirect(reverse('django_airavata_auth:login_desktop') +
                     "?" + urlencode(params))
+
+
+@login_required
+def download_settings_local(request):
+
+    if not request.is_gateway_admin or not request.is_read_only_gateway_admin:
+        raise PermissionDenied()
+
+    if settings.DEBUG:
+        raise Exception("Downloading a settings_local.py file isn't allowed in DEBUG mode.")
+
+    development_client_id = f"local-django-{request.user.username}"
+    access_token = utils.get_service_account_authz_token().accessToken
+    clients_endpoint = get_clients_endpoint()
+    development_client = get_client(access_token, clients_endpoint, development_client_id)
+    if development_client is None:
+        development_client_endpoint = create_client(access_token, clients_endpoint, development_client_id)
+    else:
+        development_client_endpoint = get_client_endpoint(development_client)
+    development_client_secret = get_client_secret(access_token, development_client_endpoint)
+
+    context = {}
+    context['AUTHENTICATION_OPTIONS'] = settings.AUTHENTICATION_OPTIONS
+    context['keycloak_client_id'] = development_client_id
+    context['keycloak_client_secret'] = development_client_secret
+    context['KEYCLOAK_AUTHORIZE_URL'] = settings.KEYCLOAK_AUTHORIZE_URL
+    context['KEYCLOAK_TOKEN_URL'] = settings.KEYCLOAK_TOKEN_URL
+    context['KEYCLOAK_USERINFO_URL'] = settings.KEYCLOAK_USERINFO_URL
+    context['KEYCLOAK_LOGOUT_URL'] = settings.KEYCLOAK_LOGOUT_URL
+    context['GATEWAY_ID'] = settings.GATEWAY_ID
+    context['AIRAVATA_API_HOST'] = settings.AIRAVATA_API_HOST
+    context['AIRAVATA_API_PORT'] = settings.AIRAVATA_API_PORT
+    context['AIRAVATA_API_SECURE'] = settings.AIRAVATA_API_SECURE
+    context['GATEWAY_DATA_STORE_RESOURCE_ID'] = settings.GATEWAY_DATA_STORE_RESOURCE_ID
+    if hasattr(settings, 'GATEWAY_DATA_STORE_REMOTE_API'):
+        context['GATEWAY_DATA_STORE_REMOTE_API'] = settings.GATEWAY_DATA_STORE_REMOTE_API
+    else:
+        context['GATEWAY_DATA_STORE_REMOTE_API'] = request.build_absolute_uri("/api")
+    context['PROFILE_SERVICE_HOST'] = settings.PROFILE_SERVICE_HOST
+    context['PROFILE_SERVICE_PORT'] = settings.PROFILE_SERVICE_PORT
+    context['PROFILE_SERVICE_SECURE'] = settings.PROFILE_SERVICE_SECURE
+    context['PORTAL_TITLE'] = settings.PORTAL_TITLE
+    settings_local_str = render_to_string("django_airavata_auth/settings_local.py.template", context)
+    settings_local_bytesio = io.BytesIO(settings_local_str.encode())
+    return FileResponse(settings_local_bytesio, as_attachment=True, filename="settings_local.py")
+
+
+def get_client(access_token, clients_endpoint, client_id):
+    headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json'}
+    r = requests.get(clients_endpoint, {'clientId': client_id}, headers=headers)
+    r.raise_for_status()
+    clients = r.json()
+    if len(clients) == 0:
+        return None
+    else:
+        return clients[0]
+
+
+def get_clients_endpoint():
+    realm = settings.GATEWAY_ID
+    clients_endpoint = f"https://iamdev.scigap.org/auth/admin/realms/{realm}/clients"
+    return clients_endpoint
+
+
+def get_client_endpoint(client):
+    return f"{get_clients_endpoint()}/{client['id']}"
+
+
+def create_client(access_token, clients_endpoint, client_id):
+    client = {
+        'clientId': client_id,
+        "redirectUris": [
+            "http://localhost:8000/",
+            "http://localhost:8000/auth/callback*",
+            "http://127.0.0.1:8000/",
+            "http://127.0.0.1:8000/auth/callback*"
+        ],
+        "directAccessGrantsEnabled": True
+    }
+    headers = {'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json'}
+    r = requests.post(clients_endpoint, json=client, headers=headers)
+    r.raise_for_status()
+    return r.headers['Location']
+
+
+def get_client_secret(access_token, client_endpoint):
+
+    headers = {'Authorization': f'Bearer {access_token}'}
+    r = requests.get(client_endpoint + "/client-secret", headers=headers)
+    r.raise_for_status()
+    return r.json()['value']