Skip to content

Commit

Permalink
Added credentials site creation API EDLY-2904 (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
Muhammad Haseeb authored May 5, 2021
1 parent edfb3e2 commit 51cf14c
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from rest_framework import permissions

from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.constants import EDLY_PANEL_WORKER_USER
from credentials.apps.edx_credentials_extensions.edly_credentials_app.utils import user_has_edx_organization_access

logger = logging.getLogger(__name__)
Expand All @@ -23,3 +24,16 @@ def has_permission(self, request, view):
return True

return False


class CanAccessSiteCreation(permissions.BasePermission):
"""
Checks if a user has the access to create and update methods for sites.
"""

def has_permission(self, request, view):
"""
Checks for user's permission for current site.
"""
logger.info('User {user} is trying to access site creation API'.format(user=request.user.username))
return request.user.is_staff or request.user.username == EDLY_PANEL_WORKER_USER
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Constants for Edly Credentials API.
"""
from django.utils.translation import ugettext as _

ERROR_MESSAGES = {
'CLIENT_SITES_SETUP_SUCCESS': _('Client sites setup successful.'),
'CLIENT_SITES_SETUP_FAILURE': _('Client sites setup failed.'),
}

CLIENT_SITE_SETUP_FIELDS = [
'lms_site',
'credentials_site',
'wordpress_site',
'edly_slug',
'platform_name',
'discovery_site',
'theme_dir_name',
'oauth_clients'
]

EDLY_PANEL_WORKER_USER = 'edly_panel_worker'
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from django.conf.urls import url
from rest_framework import routers

from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.views.edly_sites import EdlySiteViewSet
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.views.program_certificate_configuration import ProgramCertificateConfigurationViewSet


router = routers.SimpleRouter()
router.register(r'program-certificate-configuration', ProgramCertificateConfigurationViewSet, basename='program-certificate-configuration')

urlpatterns = router.urls
urlpatterns = [
url(r'^edly_sites/', EdlySiteViewSet.as_view(), name='edly_sites'),
]

urlpatterns += router.urls
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Views for Edly Site Creation API.
"""
from django.contrib.sites.models import Site
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from credentials.apps.core.models import SiteConfiguration
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.permissions import CanAccessSiteCreation
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.constants import ERROR_MESSAGES
from credentials.apps.edx_credentials_extensions.edly_credentials_app.helpers import (
get_credentials_site_configuration,
validate_site_configurations,
)


class EdlySiteViewSet(APIView):
"""
Creates credentials site and it's site configuration.
"""
permission_classes = [IsAuthenticated, CanAccessSiteCreation]

def post(self, request):
"""
POST /api/edly_api/v1/edly_sites.
"""
validations_messages = validate_site_configurations(request.data)
if len(validations_messages) > 0:
return Response(validations_messages, status=status.HTTP_400_BAD_REQUEST)

try:
self.process_client_sites_setup()
return Response(
{'success': ERROR_MESSAGES.get('CLIENT_SITES_SETUP_SUCCESS')},
status=status.HTTP_200_OK
)
except TypeError:
return Response(
{'error': ERROR_MESSAGES.get('CLIENT_SITES_SETUP_FAILURE')},
status=status.HTTP_400_BAD_REQUEST
)

def process_client_sites_setup(self):
"""
Process client sites setup and update configurations.
"""
edly_slug = self.request.data.get('edly_slug', '')
credentials_base = self.request.data.get('credentials_site', '')
theme_dir_name = self.request.data.get('theme_dir_name', 'openedx')
lms_url_root = '{protocol}://{lms_url_root}'.format(
protocol=self.request.data.get('protocol', 'https'),
lms_url_root=self.request.data.get('lms_site', '')
)
catalog_api_url = '{protocol}://{discovery_site}/api/v1/'.format(
protocol=self.request.data.get('protocol', 'https'),
discovery_site=self.request.data.get('discovery_site', '')
)
wordpress_site = '{protocol}://{wordpress_site}'.format(
protocol=self.request.data.get('protocol', 'https'),
wordpress_site=self.request.data.get('wordpress_site', '')
)
credentials_site, __ = Site.objects.update_or_create(domain=credentials_base, defaults=dict(name=credentials_base))
credentials_site_config, __ = SiteConfiguration.objects.update_or_create(
site=credentials_site,
defaults=dict(
edx_org_short_name=edly_slug,
platform_name=self.request.data.get('platform_name', ''),
company_name=self.request.data.get('platform_name', ''),
theme_name=theme_dir_name,
lms_url_root=lms_url_root,
catalog_api_url=catalog_api_url,
tos_url='{lms_url_root}/tos'.format(lms_url_root=lms_url_root),
privacy_policy_url='{lms_url_root}/privacy'.format(lms_url_root=lms_url_root),
homepage_url=wordpress_site,
certificate_help_url='{wordpress_site}/contact-us'.format(wordpress_site=wordpress_site),
edly_client_branding_and_django_settings=get_credentials_site_configuration(self.request.data),
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
"""
import json

from django.contrib.sites.models import Site
from django.urls import reverse

from rest_framework import status
from rest_framework.test import APITestCase

from credentials.apps.catalog.tests.factories import ProgramFactory
from credentials.apps.core.models import SiteConfiguration
from credentials.apps.core.tests.factories import UserFactory
from credentials.apps.core.tests.mixins import SiteMixin
from credentials.apps.credentials.tests.factories import ProgramCertificateFactory
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.constants import (
CLIENT_SITE_SETUP_FIELDS,
EDLY_PANEL_WORKER_USER,
)

JSON_CONTENT_TYPE = 'application/json'

Expand Down Expand Up @@ -99,3 +105,94 @@ def test_list_program_certificate_configuration(self):
actual_data = response.json()
actual_data = actual_data.get('results')[0]
assert str(program_certificate_configuration.program_uuid) == actual_data.get('program_uuid')


class EdlySiteViewSet(APITestCase):
"""
Unit tests for EdlySiteViewSet viewset.
"""

def setUp(self):
"""
Prepare environment for tests.
"""
super(EdlySiteViewSet, self).setUp()
self.admin_user = UserFactory(is_staff=True, username=EDLY_PANEL_WORKER_USER)
self.edly_sites_url = reverse('edly_api:edly_sites')
self.client.force_authenticate(self.admin_user)
self.request_data = dict(
lms_site='example.lms',
credentials_site='example.credentials',
wordpress_site='example.wordpress',
edly_slug='edx',
platform_name='Edly',
discovery_site='example.discovery',
theme_dir_name='openedx',
oauth_clients={
'credentials-sso': {
'id': 'credentials-sso-key',
'secret': 'credentials-sso-secret'
},
'credentials-backend': {
'id': 'credentials-backend-key',
'secret': 'credentials-backend-secret'
}
},
)

def test_without_authentication(self):
"""
Verify authentication is required when accessing the endpoint.
"""
self.client.logout()
response = self.client.post(self.edly_sites_url)
assert response.status_code == status.HTTP_401_UNAUTHORIZED

def test_without_permission(self):
"""
Verify panel permission is required when accessing the endpoint.
"""
user = UserFactory()
self.client.logout()
self.client.force_authenticate(user)
response = self.client.post(self.edly_sites_url)

assert response.status_code == status.HTTP_403_FORBIDDEN

def test_request_data_validation(self):
"""
Verify validation messages in response for missing required data.
"""
response = self.client.post(self.edly_sites_url, data={})

assert response.status_code == status.HTTP_400_BAD_REQUEST
assert set(response.json().keys()) == set(CLIENT_SITE_SETUP_FIELDS)

def test_client_setup(self):
"""
Verify successful client setup with correct data.
"""
response = self.client.post(self.edly_sites_url, data=self.request_data, format='json')

assert response.status_code == status.HTTP_200_OK
credentials_site = Site.objects.get(domain=self.request_data.get('credentials_site', ''))
assert credentials_site.siteconfiguration
assert credentials_site.siteconfiguration.edly_client_branding_and_django_settings

def test_client_setup_idempotent(self):
"""
Test that the values are only update not created on multiple API calls.
"""
response = self.client.post(self.edly_sites_url, data=self.request_data, format='json')

assert response.status_code == status.HTTP_200_OK
credentials_site = Site.objects.get(domain=self.request_data.get('credentials_site', ''))
assert credentials_site.siteconfiguration

sites_count = Site.objects.all().count()
site_configurations_count = SiteConfiguration.objects.all().count()
response = self.client.post(self.edly_sites_url, data=self.request_data, format='json')

assert response.status_code == status.HTTP_200_OK
assert Site.objects.all().count() == sites_count
assert SiteConfiguration.objects.all().count() == site_configurations_count
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from credentials.apps.edx_credentials_extensions.edly_credentials_app.api.v1.constants import CLIENT_SITE_SETUP_FIELDS


def validate_site_configurations(request_data):
"""
Identify missing required fields for client's site setup.
Arguments:
request_data (dict): Request data passed for site setup
Returns:
validation_messages (dict): Missing fields information
"""

validation_messages = {}

for field in CLIENT_SITE_SETUP_FIELDS:
if not request_data.get(field, None):
validation_messages[field] = '{0} is Missing'.format(field.replace('_', ' ').title())

return validation_messages


def get_credentials_site_configuration(request_data):
"""
Prepare Credentials Site Configurations for Client based on Request Data.
Arguments:
request_data (dict): Request data passed for site setup
Returns:
(dict): Credentials Site Configuration
"""
protocol = request_data.get('protocol', 'https')
lms_site = request_data.get('lms_site', '')
lms_site_with_protocol = '{protocol}://{lms_root_domain}'.format(
protocol=protocol,
lms_root_domain=lms_site,
)
oauth2_clients = request_data.get('oauth_clients', {})
credentials_sso_values = oauth2_clients.get('credentials-sso', {})
credentials_backend_values = oauth2_clients.get('credentials-backend', {})

return {
'DJANGO_SETTINGS_OVERRIDE': {
'SOCIAL_AUTH_EDX_OAUTH2_KEY': credentials_sso_values.get('id', ''),
'SOCIAL_AUTH_EDX_OAUTH2_SECRET': credentials_sso_values.get('secret', ''),
'SOCIAL_AUTH_EDX_OAUTH2_ISSUER': lms_site_with_protocol,
'SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT': lms_site_with_protocol,
'SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT': lms_site_with_protocol,
'SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL': '{lms_site_with_protocol}/logout'.format(
lms_site_with_protocol=lms_site_with_protocol
),
'BACKEND_SERVICE_EDX_OAUTH2_KEY': credentials_backend_values.get('id', ''),
'BACKEND_SERVICE_EDX_OAUTH2_SECRET': credentials_backend_values.get('secret', ''),
'BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL': '{lms_site_with_protocol}/oauth2'.format(
lms_site_with_protocol=lms_site_with_protocol
),
}
}
Loading

0 comments on commit 51cf14c

Please sign in to comment.