Skip to content

Commit

Permalink
Merge pull request #2130 from unicef/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
domdinicola authored Feb 6, 2019
2 parents c0eab56 + 122fb8b commit 5325758
Show file tree
Hide file tree
Showing 242 changed files with 10,762 additions and 6,637 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
PGUSER: postgres
POSTGRES_DB: circle_test
POSTGRES_PASSWORD: postgres
- image: redis
steps:
- checkout
- restore_cache:
Expand Down
18 changes: 0 additions & 18 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,9 @@ ignore =
; PyFlakes errors
; F405 name may be undefined, or defined from star imports: module
F405,
; pycodestyle / PEP8
; E121 continuation line under-indented for hanging indent
E121,
; E123 closing bracket does not match indentation of opening bracket’s line
E123,
; E126 continuation line over-indented for hanging indent
E126,
; E226 missing whitespace around arithmetic operator
E226,
; E241/E242 multiple spaces after ‘,’ tab after ‘,’
E24,
; E501 maximum line length
E501,
; E704 (*) multiple statements on one line (def)
E704,
; W503 line break before binary operator
W503,
; W504 line break after binary operator
W504
; H405 multi line docstring summary not separated with an empty line
H405,

exclude =
*/migrations,
Expand Down
14 changes: 0 additions & 14 deletions .pyup.yml

This file was deleted.

2 changes: 1 addition & 1 deletion src/etools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION = __version__ = '6.6.1'
VERSION = __version__ = '6.7'
NAME = 'eTools'
117 changes: 116 additions & 1 deletion src/etools/applications/EquiTrack/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,135 @@

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group

import jwt
from rest_framework.authentication import BasicAuthentication, SessionAuthentication, TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.utils import jwt_payload_handler
from social_core.pipeline import social_auth
from social_core.pipeline import user as social_core_user
from social_core.backends.azuread_b2c import AzureADB2COAuth2
from social_django.middleware import SocialAuthExceptionMiddleware

from etools.applications.EquiTrack.utils import set_country
from etools.applications.users.models import Country
from etools.libraries.tenant_support.utils import set_country

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
logger = logging.getLogger(__name__)


def social_details(backend, details, response, *args, **kwargs):
r = social_auth.social_details(backend, details, response, *args, **kwargs)
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()
return r


def get_username(strategy, details, backend, user=None, *args, **kwargs):
return {'username': details.get('email')}


def create_user(strategy, details, backend, user=None, *args, **kwargs):
""" Overwrite social_account.user.create_user to only create new users if they're UNICEF"""

if user:
return {'is_new': False}

fields = dict((name, kwargs.get(name, details.get(name)))
for name in backend.setting('USER_FIELDS', social_core_user.USER_FIELDS))
if not fields:
return

response = kwargs.get('response')
if response:
email = response.get('email') or response.get("signInNames.emailAddress")
if not email.endswith("unicef.org"):
return
return {
'is_new': True,
'user': strategy.create_user(**fields)
}


def user_details(strategy, details, user=None, *args, **kwargs):
# This is where we update the user
# see what the property to map by is here
if user:
user_groups = [group.name for group in user.groups.all()]
business_area_code = details.get("business_area_code", 'defaultBA1235')

try:
country = Country.objects.get(business_area_code=business_area_code)
except Country.DoesNotExist:
country = Country.objects.get(name='UAT')

if details.get("idp") == "UNICEF Azure AD" and "UNICEF User" not in user_groups:
user.groups.add(Group.objects.get(name='UNICEF User'))
user.is_staff = True
user.save()

if not user.profile.country:
user.profile.country = country
user.profile.save()
elif not user.profile.country_override:
# make sure that we update the workspace based business area
if business_area_code != user.profile.country.business_area_code:
user.profile.country = country
user.profile.save()

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


class CustomAzureADBBCOAuth2(AzureADB2COAuth2):

def __init__(self, *args, **kwargs):
super(CustomAzureADBBCOAuth2, self).__init__(*args, **kwargs)
self.redirect_uri = settings.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 = 'https://login.microsoftonline.com/' + \
settings.TENANT_ID + \
"/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 "/login"

strategy = getattr(request, 'social_strategy', None)
return strategy.setting('LOGIN_ERROR_URL')


class DRFBasicAuthMixin(BasicAuthentication):
def authenticate(self, request):
super_return = super().authenticate(request)
Expand Down
52 changes: 0 additions & 52 deletions src/etools/applications/EquiTrack/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ def check_config(app_configs, **kwargs):
)
)

configdir = settings.SAML_CONFIG['attribute_map_dir']
if not os.path.isdir(configdir):
errors.append(
Error(
"SAML configuration directory not found. ('attribute_map_dir')",
hint='check your settings.SAML_CONFIG',
obj=None,
id='etools.E003',
)
)
# check templates
for entry in settings.TEMPLATES:
for path in entry['DIRS']:
Expand All @@ -58,45 +48,3 @@ def check_config(app_configs, **kwargs):
)
)
return errors


@register('etools', deploy=True)
def check_config_deploy(app_configs, **kwargs):
from django.conf import settings
errors = []
cfg = settings.SAML_CONFIG

for i, filename in enumerate(cfg['metadata']['local']):
if not os.path.exists(filename):
errors.append(
Error(
f"SAML configuration error.'{filename}' does not exists",
hint='check your settings.SAML_CONFIG',
obj=None,
id=f'etools.E001{i}',
)
)

for i, filename in enumerate([cfg['key_file'], cfg['cert_file']]):
if not os.path.exists(filename):
errors.append(
Error(
f"SAML configuration error.'{filename}' does not exists",
hint='check your settings.SAML_CONFIG',
obj=None,
id=f'etools.E002{i}',
)
)

for i, entry in enumerate(cfg['encryption_keypairs']):
for k, v in entry.items():
if not os.path.exists(v):
errors.append(
Error(
f"Error in SAML configuration in entry [encryption_keypairs][{k}]. '{v}' does not exists ",
hint='check your settings.SAML_CONFIG',
obj=None,
id=f'etools.E003{i}',
)
)
return errors
10 changes: 4 additions & 6 deletions src/etools/applications/EquiTrack/middleware.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import logging

from django.conf import settings
Expand All @@ -10,14 +9,14 @@
from django_tenants.middleware import TenantMiddleware
from django_tenants.utils import get_public_schema_name

from etools.applications.EquiTrack.utils import set_country
from etools.libraries.tenant_support.utils import set_country

logger = logging.getLogger(__name__)

ANONYMOUS_ALLOWED_URL_FRAGMENTS = [
'api',
'social',
'login',
'saml',
'accounts',
'monitoring',
]
Expand Down Expand Up @@ -59,9 +58,8 @@ def process_request(self, request):
if request.user.is_superuser and not request.user.profile.country:
return None

if not request.user.is_superuser and \
(not request.user.profile.country or
request.user.profile.country.business_area_code in settings.INACTIVE_BUSINESS_AREAS):
if not request.user.is_superuser and (
not request.user.profile.country or request.user.profile.country.business_area_code in settings.INACTIVE_BUSINESS_AREAS):
return HttpResponseRedirect("/workspace_inactive/")

try:
Expand Down
7 changes: 0 additions & 7 deletions src/etools/applications/EquiTrack/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
from django.db.models import Aggregate
from django_tenants.models import DomainMixin


class DSum(Aggregate):
function = 'SUM'
template = '%(function)s(DISTINCT %(expressions)s)'
name = 'Sum'


class Domain(DomainMixin):
""" Tenant Domain Model"""
34 changes: 0 additions & 34 deletions src/etools/applications/EquiTrack/serializers.py

This file was deleted.

4 changes: 2 additions & 2 deletions src/etools/applications/EquiTrack/tests/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ def _should_check_constraints(self, connection):

@classmethod
def _load_fixtures(cls):
'''
"""
Load fixtures for current connection (shared/public or tenant)
This works the same as the code in TestCase.setUpClass(), but is
broken out here so we can call it twice, once for the public schema
and once for the tenant schema.
'''
"""
if cls.fixtures:
for db_name in cls._databases_names(include_mirrors=False):
try:
Expand Down
Loading

0 comments on commit 5325758

Please sign in to comment.