Skip to content

Commit

Permalink
unicef security
Browse files Browse the repository at this point in the history
	modified:   frontend_cluster/src_ts/endpoints.ts
  • Loading branch information
domdinicola committed Jun 3, 2022
1 parent fd7de0b commit 528c4a4
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 227 deletions.
13 changes: 7 additions & 6 deletions django_api/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ isort = "*"
[packages]
aiohttp = "<=3.8.1"
azure-common = "<=1.1.28"
azure-storage-blob = "<=12.11"
azure-storage-blob = "<=12.12"
azure-storage-common = "<=2.1"
Babel = "<=2.10.1"
boto3 = "<1.22" # issue locking
carto = "<=1.11.3"
cryptography = "<3.4" # issue with image
django = "<4.0"
Expand All @@ -25,7 +24,7 @@ django-celery-beat = "<=2.2.1"
django-celery-email = "<=3.0"
django-celery-results = "<=2.3.1"
django-cors-headers = "<=3.12"
django-cron = "<=0.5.1"
django-cron = "<=0.6"
django-environ = "<=0.8.1"
django-extensions = "<=3.1.5"
django-filter = "<=21.1"
Expand All @@ -38,24 +37,26 @@ django-rest-swagger = "<=2.2"
django-storages = "<=1.12.3"
djangorestframework = "<=3.13.1"
djangorestframework-gis = "<=1.0"
djangorestframework-simplejwt = "<=5.1"
djangorestframework-simplejwt = "<=5.2"
drfpasswordless = "<=1.5.8"
greenlet = "<=1.1.2"
newrelic = "*"
openpyxl = "<=3.0.9"
psycopg2-binary = "<=2.9.3"
pycountry = "<=20.7.3"
pycountry = "<=22.3.5"
python-json-logger = "<=2.0.2"
python-social-auth = "<=0.3.6"
pyrestcli = "<=0.6.12"
requests = "<=2.27.1"
sentry-sdk = "<=1.5.12"
social-auth-app-django = "<=5.0"
social-auth-core = "<=5.0"
unicef-djangolib = "<=0.6"
unicef-notification = "<=1.1"
unicef-security = "<=1.1"
unicef-locations = "<=4.0.1"
uWSGI = "<=2.0.20"
weasyprint = "<=54.3"
weasyprint = "<=55"

[requires]
python_version = "3.9"
232 changes: 119 additions & 113 deletions django_api/Pipfile.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions django_api/etools_prp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from etools_prp.config.celery import app as celery_app
from etools_prp.config.celery import app as celery_app # noqa

NAME = 'etools_prp'
VERSION = '9.13'

__all__ = ['celery_app', ]
84 changes: 1 addition & 83 deletions django_api/etools_prp/apps/core/mixins.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
from datetime import datetime, timedelta
from urllib.parse import quote

from django.conf import settings
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect
from django.utils.deconstruct import deconstructible

from social_core.backends.azuread_b2c import AzureADB2COAuth2
from social_core.pipeline import social_auth, user as social_core_user
from social_django.middleware import SocialAuthExceptionMiddleware
from storages.backends.azure_storage import AzureStorage
from storages.utils import setting


def social_details(backend, details, response, *args, **kwargs):
r = social_auth.social_details(backend, details, response, *args, **kwargs)

user = kwargs.get('user', None)
if user:
# here we are preventing messing up between current us and social user
return HttpResponseRedirect(f"/unauthorized/?eu={user.email}&msgc=alreadyauthenticated")

r['details']['idp'] = response.get('idp')

if not r['details'].get('email'):
if not response.get('email'):
r['details']['email'] = response["signInNames.emailAddress"]
else:
r['details']['email'] = response.get('email')

email = r['details'].get('email')
if isinstance(email, str):
r['details']['email'] = email.lower().strip()
return r


def get_username(strategy, details, backend, user=None, *args, **kwargs):
"""allow to login only existing/already created users """
username = details.get('email')

try:
Expand All @@ -48,63 +23,6 @@ def get_username(strategy, details, backend, user=None, *args, **kwargs):
return {'username': details.get('email')}


def user_details(strategy, details, backend, user=None, *args, **kwargs):
# # This is where we update the user
# # see what the property to map by is here
# updates_available = False

if user:
# user_groups = [group.name for group in user.groups.all()]
# business_area_code = details.get("business_area_code", 'defaultBA1235')

# Update username with email and unusable password
user.username = user.email
user.first_name = details['first_name']
user.last_name = details['last_name']
user.set_unusable_password()
user.save()

return social_core_user.user_details(strategy, details, backend, user, *args, **kwargs)


class CustomAzureADBBCOAuth2(AzureADB2COAuth2):
BASE_URL = 'https://{tenant_id}.b2clogin.com/{tenant_id}.onmicrosoft.com'

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.redirect_uri = settings.FRONTEND_HOST + '/social/complete/azuread-b2c-oauth2/'


class CustomSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware):

def get_redirect_uri(self, request, exception):
error = request.GET.get('error', None)

# This is what we should expect:
# ['AADB2C90118: The user has forgotten their password.\r\n
# Correlation ID: 7e8c3cf9-2fa7-47c7-8924-a1ea91137ba9\r\n
# Timestamp: 2018-11-13 11:37:56Z\r\n']
error_description = request.GET.get('error_description', None)
if error == "access_denied" and error_description is not None:
if 'AADB2C90118' in error_description:
auth_class = CustomAzureADBBCOAuth2()
redirect_home = auth_class.get_redirect_uri()
redirect_url = auth_class.base_url + '/oauth2/v2.0/' + \
'authorize?p=' + settings.SOCIAL_PASSWORD_RESET_POLICY + \
'&client_id=' + settings.KEY + \
'&nonce=defaultNonce&redirect_uri=' + redirect_home + \
'&scope=openid+email&response_type=code'
return redirect_url

# TODO: In case of password reset the state can't be verified figure out a way to log the user in after reset
if error is None:
return "/landing"

strategy = getattr(request, 'social_strategy', None)
redirect_url = strategy.setting('LOGIN_ERROR_URL') + "?msgc=loginerror"
return redirect_url


@deconstructible
class EToolsAzureStorage(AzureStorage):
account_name = setting("AZURE_ACCOUNT_NAME")
Expand Down
9 changes: 0 additions & 9 deletions django_api/etools_prp/apps/core/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import importlib

from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import HttpResponseRedirect
Expand Down Expand Up @@ -274,11 +273,3 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['unicef_user'] = self.request.user.is_authenticated and self.request.user.email.endswith('@unicef.org')
return context


# TODO import from unicef-security
class SocialLogoutView(RedirectView):

def get_redirect_url(self, *args, **kwargs):
return f'https://{settings.TENANT_B2C_URL}/{settings.TENANT_ID}.onmicrosoft.com/{settings.POLICY}/oauth2/' \
f'v2.0/logout?post_logout_redirect_uri={settings.FRONTEND_HOST}{settings.LOGOUT_URL}'
26 changes: 16 additions & 10 deletions django_api/etools_prp/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration

import etools_prp

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
env = environ.Env()

Expand All @@ -36,10 +38,10 @@
os.path.join(BASE_DIR, 'static'),
]

DOMAIN_NAME = env('DOMAIN_NAME', default='127.0.0.1:8081') # 'www.partnerreportingportal.org'
DOMAIN_NAME = env('DOMAIN_NAME', default='localhost:8081') # 'www.partnerreportingportal.org'
WWW_ROOT = 'http://%s/' % DOMAIN_NAME
ALLOWED_HOSTS = env('ALLOWED_HOSTS', default='localhost').split(",")

HOST = DOMAIN_NAME

FRONTEND_HOST = env(
'PRP_FRONTEND_HOST',
Expand Down Expand Up @@ -100,9 +102,9 @@
'leaflet',
'django_cron',
'social_django',

'unicef_djangolib',
'unicef_locations',

'unicef_security',
'etools_prp.apps.account',
'etools_prp.apps.cluster',
'etools_prp.apps.core',
Expand All @@ -122,7 +124,7 @@
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'etools_prp.apps.core.mixins.CustomSocialAuthExceptionMiddleware',
'unicef_security.middleware.UNICEFSocialAuthExceptionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
Expand Down Expand Up @@ -423,8 +425,9 @@
SOCIAL_PASSWORD_RESET_POLICY = env('AZURE_B2C_PASS_RESET_POLICY', default="B2C_1_PasswordResetPolicy")
POLICY = env('AZURE_B2C_POLICY_NAME', default="b2c_1A_UNICEF_PARTNERS_signup_signin")

TENANT_ID = env('AZURE_B2C_TENANT', default='unicefpartners')
TENANT_B2C_URL = f'{TENANT_ID}.b2clogin.com'
TENANT_NAME = env('TENANT_NAME', default='unicefpartners')
TENANT_ID = f'{TENANT_NAME}.onmicrosoft.com'
TENANT_B2C_URL = f'{TENANT_NAME}.b2clogin.com'


SCOPE = ['openid', 'email']
Expand All @@ -443,7 +446,7 @@

SOCIAL_AUTH_PIPELINE = (
# 'social_core.pipeline.social_auth.social_details',
'etools_prp.apps.core.mixins.social_details',
'unicef_security.pipeline.social_details',
'social_core.pipeline.social_auth.social_uid',
# allows based on emails being listed in 'WHITELISTED_EMAILS' or 'WHITELISTED_DOMAINS'
'social_core.pipeline.social_auth.auth_allowed',
Expand All @@ -455,7 +458,7 @@
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
# 'social_core.pipeline.user.user_details',
'etools_prp.apps.core.mixins.user_details',
'unicef_security.pipeline.user_details',
)


Expand Down Expand Up @@ -517,7 +520,7 @@
})

AUTHENTICATION_BACKENDS = (
'etools_prp.apps.core.mixins.CustomAzureADBBCOAuth2',
'unicef_security.backends.UNICEFAzureADB2COAuth2',
'django.contrib.auth.backends.ModelBackend',
)

Expand All @@ -543,3 +546,6 @@
DOCS_URL = 'api/docs/'

UNICEF_LOCATIONS_MODEL = 'core.Location'

PROJECT_NAME = etools_prp.NAME
PROJECT_VERSION = etools_prp.VERSION
4 changes: 2 additions & 2 deletions django_api/etools_prp/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from rest_framework_swagger.views import get_swagger_view

from etools_prp.apps.core.views import HomeView, RedirectAppView, SocialLogoutView, UnauthorizedView
from etools_prp.apps.core.views import HomeView, RedirectAppView, UnauthorizedView

schema_view = get_swagger_view(title='eTools PRP API')

Expand All @@ -41,7 +41,7 @@
re_path(r'^api/id-management/', include('etools_prp.apps.id_management.urls')),

# Social auth urls
re_path(r'^social/unicef-logout/', SocialLogoutView.as_view()),
re_path(r'^security/', include('unicef_security.urls')),
re_path(r'^social/', include('social_django.urls', namespace='social')),
re_path(r'^unauthorized/$', UnauthorizedView.as_view(), name="unauthorized"),
]
Expand Down
2 changes: 1 addition & 1 deletion frontend_cluster/src_ts/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Endpoints = {
},

login() {
return '/social/login/azuread-b2c-oauth2/';
return '/social/login/unicef-azuread-b2c-oauth2/';
},

config() {
Expand Down
2 changes: 1 addition & 1 deletion frontend_ip/src_ts/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const Endpoints = {
},

login() {
return '/social/login/azuread-b2c-oauth2/';
return '/social/login/unicef-azuread-b2c-oauth2/';
},

interventions() {
Expand Down

0 comments on commit 528c4a4

Please sign in to comment.