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']