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:07 UTC
[airavata-django-portal] 02/04: Merge branch 'airavata-3383' into
develop
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']