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:05 UTC
[airavata-django-portal] branch develop updated (9ec9b00 -> 3b022ea)
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.
from 9ec9b00 Optimize docker image size: exclude node_modules
new 8cce97c AIRAVATA-3383 settings_local.py download
new 27dc31b Merge branch 'airavata-3383' into develop
new f08c213 AIRAVATA-3833 Bug fixes
new 3b022ea Merge branch 'airavata-3383' into develop
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
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} | 67 ++++++---------
django_airavata/apps/auth/urls.py | 2 +
django_airavata/apps/auth/views.py | 99 +++++++++++++++++++++-
8 files changed, 165 insertions(+), 45 deletions(-)
create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/developers/DevelopersContainer.vue
copy django_airavata/{settings_local.py.sample => apps/auth/templates/django_airavata_auth/settings_local.py.template} (74%)
[airavata-django-portal] 04/04: Merge branch 'airavata-3383' into
develop
Posted by ma...@apache.org.
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 3b022ea407ed75873515930f12ac31d6b31260cc
Merge: 27dc31b f08c213
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jun 14 16:08:29 2021 -0400
Merge branch 'airavata-3383' into develop
django_airavata/apps/auth/views.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --cc django_airavata/apps/auth/views.py
index 33684b5,1ef0259..fb0d295
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@@ -520,91 -509,9 +520,91 @@@ def _create_login_desktop_failed_respon
@login_required
+def access_token_redirect(request):
+ redirect_uri = request.GET['redirect_uri']
+ config = next(filter(lambda d: d.get('URI') == redirect_uri,
+ settings.ACCESS_TOKEN_REDIRECT_ALLOWED_URIS), None)
+ if config is None:
+ logger.warning(f"redirect_uri value '{redirect_uri}' is not configured "
+ "in ACCESS_TOKEN_REDIRECT_ALLOWED_URIS setting")
+ return HttpResponseForbidden("Invalid redirect_uri value")
+ return redirect(redirect_uri + f"{'&' if '?' in redirect_uri else '?'}{config.get('PARAM_NAME', 'access_token')}="
+ f"{quote(request.authz_token.accessToken)}")
+
+
+def user_profile(request):
+ return render(request, "django_airavata_auth/base.html", {
+ 'bundle_name': "user-profile"
+ })
+
+
+class IsUserOrReadOnlyForAdmins(permissions.BasePermission):
+ def has_permission(self, request, view):
+ return request.user.is_authenticated
+
+ def has_object_permission(self, request, view, obj):
+ if (request.method in permissions.SAFE_METHODS and
+ request.is_gateway_admin):
+ return True
+ return obj == request.user
+
+
+# TODO: disable deleting and creating?
+class UserViewSet(viewsets.ModelViewSet):
+ serializer_class = serializers.UserSerializer
+ queryset = get_user_model().objects.all()
+ permission_classes = [IsUserOrReadOnlyForAdmins]
+
+ def get_queryset(self):
+ user = self.request.user
+ if user.is_superuser:
+ return get_user_model().objects.all()
+ else:
+ return get_user_model().objects.get(pk=user.pk)
+
+ @action(detail=False)
+ def current(self, request):
+ return redirect(reverse('django_airavata_auth:user-detail', kwargs={'pk': request.user.id}))
+
+ @action(methods=['post'], detail=True)
+ def resend_email_verification(self, request, pk=None):
+ pending_email_change = models.PendingEmailChange.objects.get(user=request.user, verified=False)
+ if pending_email_change is not None:
+ serializer = serializers.UserSerializer()
+ serializer._send_email_verification_link(request, pending_email_change)
+ return JsonResponse({})
+
+ @action(methods=['post'], detail=True)
+ @atomic
+ def verify_email_change(self, request, pk=None):
+ user = self.get_object()
+ code = request.data['code']
+
+ try:
+ pending_email_change = models.PendingEmailChange.objects.get(user=user, verification_code=code)
+ except models.PendingEmailChange.DoesNotExist:
+ raise Exception('Verification code is invalid. Please try again.')
+ pending_email_change.verified = True
+ pending_email_change.save()
+ user.email = pending_email_change.email_address
+ user.save()
+ user.refresh_from_db()
+
+ try:
+ user_profile_client = request.profile_service['user_profile']
+ airavata_user_profile = user_profile_client.getUserProfileById(
+ request.authz_token, user.username, settings.GATEWAY_ID)
+ airavata_user_profile.emails = [pending_email_change.email_address]
+ user_profile_client.updateUserProfile(request.authz_token, airavata_user_profile)
+ except Exception as e:
+ raise Exception(f"Failed to update Airavata User Profile with new email address: {e}") from e
+ serializer = self.get_serializer(user)
+ return Response(serializer.data)
+
+
def download_settings_local(request):
- if not request.is_gateway_admin or not request.is_read_only_gateway_admin:
+ if not (request.is_gateway_admin or request.is_read_only_gateway_admin):
raise PermissionDenied()
if settings.DEBUG:
[airavata-django-portal] 03/04: AIRAVATA-3833 Bug fixes
Posted by ma...@apache.org.
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 f08c213c42eec55a20d2895e5d30d653b68b13d3
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jun 14 16:08:22 2021 -0400
AIRAVATA-3833 Bug fixes
---
django_airavata/apps/auth/views.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index f005a67..1ef0259 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -2,7 +2,7 @@ import io
import logging
import time
from datetime import datetime, timedelta, timezone
-from urllib.parse import quote, urlencode
+from urllib.parse import quote, urlencode, urlparse
import requests
from django.conf import settings
@@ -511,7 +511,7 @@ def _create_login_desktop_failed_response(request, idp_alias=None):
@login_required
def download_settings_local(request):
- if not request.is_gateway_admin or not request.is_read_only_gateway_admin:
+ if not (request.is_gateway_admin or request.is_read_only_gateway_admin):
raise PermissionDenied()
if settings.DEBUG:
@@ -566,7 +566,8 @@ def get_client(access_token, clients_endpoint, client_id):
def get_clients_endpoint():
realm = settings.GATEWAY_ID
- clients_endpoint = f"https://iamdev.scigap.org/auth/admin/realms/{realm}/clients"
+ parse_result = urlparse(settings.KEYCLOAK_AUTHORIZE_URL)
+ clients_endpoint = f"{parse_result.scheme}://{parse_result.netloc}/auth/admin/realms/{realm}/clients"
return clients_endpoint
[airavata-django-portal] 02/04: Merge branch 'airavata-3383' into
develop
Posted by ma...@apache.org.
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 27dc31b8f5b307289e3d1fd4a4adbe329825fbf9
Merge: 9ec9b00 8cce97c
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jun 14 16:02:28 2021 -0400
Merge branch 'airavata-3383' into develop
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 | 96 ++++++++++++++-
8 files changed, 275 insertions(+), 2 deletions(-)
diff --cc django_airavata/apps/auth/urls.py
index ba343f4,25454ad..45936ba
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@@ -1,6 -1,6 +1,7 @@@
-from django.conf.urls import url
+from django.conf.urls import include, url
+ from django.urls import path
+from rest_framework import routers
from . import views
@@@ -32,6 -29,5 +33,7 @@@ urlpatterns =
views.login_desktop_success, name="login_desktop_success"),
url(r'^refreshed-token-desktop$', views.refreshed_token_desktop,
name="refreshed_token_desktop"),
+ url(r'^access-token-redirect$', views.access_token_redirect, name="access_token_redirect"),
+ url(r'^user-profile/', views.user_profile, name="user_profile"),
+ path('settings-local/', views.download_settings_local, name="download_settings_local"),
]
diff --cc django_airavata/apps/auth/views.py
index 914b632,f005a67..33684b5
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@@ -3,20 -4,17 +4,23 @@@ import tim
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.contrib.auth import authenticate, get_user_model, login, logout
from django.contrib.auth.decorators import login_required
- from django.core.exceptions import ObjectDoesNotExist
+ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
+from django.db.transaction import atomic
from django.forms import ValidationError
-from django.http import FileResponse, HttpResponseBadRequest, JsonResponse
+from django.http import (
++ FileResponse,
+ HttpResponseBadRequest,
+ HttpResponseForbidden,
+ 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
@@@ -516,83 -509,91 +520,173 @@@ def _create_login_desktop_failed_respon
@login_required
+def access_token_redirect(request):
+ redirect_uri = request.GET['redirect_uri']
+ config = next(filter(lambda d: d.get('URI') == redirect_uri,
+ settings.ACCESS_TOKEN_REDIRECT_ALLOWED_URIS), None)
+ if config is None:
+ logger.warning(f"redirect_uri value '{redirect_uri}' is not configured "
+ "in ACCESS_TOKEN_REDIRECT_ALLOWED_URIS setting")
+ return HttpResponseForbidden("Invalid redirect_uri value")
+ return redirect(redirect_uri + f"{'&' if '?' in redirect_uri else '?'}{config.get('PARAM_NAME', 'access_token')}="
+ f"{quote(request.authz_token.accessToken)}")
+
+
+def user_profile(request):
+ return render(request, "django_airavata_auth/base.html", {
+ 'bundle_name': "user-profile"
+ })
+
+
+class IsUserOrReadOnlyForAdmins(permissions.BasePermission):
+ def has_permission(self, request, view):
+ return request.user.is_authenticated
+
+ def has_object_permission(self, request, view, obj):
+ if (request.method in permissions.SAFE_METHODS and
+ request.is_gateway_admin):
+ return True
+ return obj == request.user
+
+
+# TODO: disable deleting and creating?
+class UserViewSet(viewsets.ModelViewSet):
+ serializer_class = serializers.UserSerializer
+ queryset = get_user_model().objects.all()
+ permission_classes = [IsUserOrReadOnlyForAdmins]
+
+ def get_queryset(self):
+ user = self.request.user
+ if user.is_superuser:
+ return get_user_model().objects.all()
+ else:
+ return get_user_model().objects.get(pk=user.pk)
+
+ @action(detail=False)
+ def current(self, request):
+ return redirect(reverse('django_airavata_auth:user-detail', kwargs={'pk': request.user.id}))
+
+ @action(methods=['post'], detail=True)
+ def resend_email_verification(self, request, pk=None):
+ pending_email_change = models.PendingEmailChange.objects.get(user=request.user, verified=False)
+ if pending_email_change is not None:
+ serializer = serializers.UserSerializer()
+ serializer._send_email_verification_link(request, pending_email_change)
+ return JsonResponse({})
+
+ @action(methods=['post'], detail=True)
+ @atomic
+ def verify_email_change(self, request, pk=None):
+ user = self.get_object()
+ code = request.data['code']
+
+ try:
+ pending_email_change = models.PendingEmailChange.objects.get(user=user, verification_code=code)
+ except models.PendingEmailChange.DoesNotExist:
+ raise Exception('Verification code is invalid. Please try again.')
+ pending_email_change.verified = True
+ pending_email_change.save()
+ user.email = pending_email_change.email_address
+ user.save()
+ user.refresh_from_db()
+
+ try:
+ user_profile_client = request.profile_service['user_profile']
+ airavata_user_profile = user_profile_client.getUserProfileById(
+ request.authz_token, user.username, settings.GATEWAY_ID)
+ airavata_user_profile.emails = [pending_email_change.email_address]
+ user_profile_client.updateUserProfile(request.authz_token, airavata_user_profile)
+ except Exception as e:
+ raise Exception(f"Failed to update Airavata User Profile with new email address: {e}") from e
+ serializer = self.get_serializer(user)
+ return Response(serializer.data)
++
++
+ 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']
[airavata-django-portal] 01/04: AIRAVATA-3383 settings_local.py
download
Posted by ma...@apache.org.
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']