Skip to content

Commit

Permalink
Merge pull request #584 from uktrade/release/yotoc
Browse files Browse the repository at this point in the history
Release Yotoc
  • Loading branch information
reupen authored Oct 27, 2017
2 parents b1379f1 + 7645d35 commit ace1cd7
Show file tree
Hide file tree
Showing 122 changed files with 2,920 additions and 2,473 deletions.
6 changes: 0 additions & 6 deletions config/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from datahub.leads import urls as leads_urls
from datahub.omis import urls as omis_urls
from datahub.search import urls as search_urls
from datahub.v2.urls import urlpatterns as v2_urlpatterns


# API V1
Expand All @@ -22,11 +21,6 @@
v1_urls = router_v1.urls


# API V2

v2_urls = v2_urlpatterns


# API V3

v3_urls = [
Expand Down
1 change: 1 addition & 0 deletions config/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@
}
DATAHUB_SECRET = env('DATAHUB_SECRET')
CDMS_AUTH_URL = env('CDMS_AUTH_URL')
CDMS_AUTH_TIMEOUT = env.int('CDMS_AUTH_TIMEOUT', 10)
CDMS_TEXT_MAX_LENGTH = 4000
CHAR_FIELD_MAX_LENGTH = 255
HEROKU = False
Expand Down
1 change: 0 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

urlpatterns = [
url(r'^', include((api_urls.v1_urls, 'api'), namespace='api-v1')), # V1 has actually no version in the URL
url(r'^v2/', include((api_urls.v2_urls, 'api'), namespace='api-v2')),
url(r'^v3/', include((api_urls.v3_urls, 'api'), namespace='api-v3')),
] + unversioned_urls

Expand Down
30 changes: 30 additions & 0 deletions datahub/company/migrations/0013_update_marketing_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-25 10:14
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('company', '0012_add_adviser_contact_email'),
]

operations = [
migrations.RenameField(
model_name='contact',
old_name='contactable_by_dit_partners',
new_name='contactable_by_uk_dit_partners',
),
migrations.AddField(
model_name='contact',
name='accepts_dit_email_marketing',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='contact',
name='contactable_by_overseas_dit_partners',
field=models.BooleanField(default=False),
),
]
8 changes: 7 additions & 1 deletion datahub/company/models/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ class Contact(ArchivableModel, BaseModel):
telephone_alternative = models.CharField(max_length=MAX_LENGTH, blank=True, null=True)
email_alternative = models.EmailField(null=True, blank=True)
notes = models.TextField(null=True, blank=True)

# Marketing preferences
contactable_by_dit = models.BooleanField(default=False)
contactable_by_dit_partners = models.BooleanField(default=False)
contactable_by_uk_dit_partners = models.BooleanField(default=False)
contactable_by_overseas_dit_partners = models.BooleanField(default=False)
accepts_dit_email_marketing = models.BooleanField(default=False)

# Contact mode preferences
contactable_by_email = models.BooleanField(default=True)
contactable_by_phone = models.BooleanField(default=True)

Expand Down
4 changes: 3 additions & 1 deletion datahub/company/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ class Meta:
'email_alternative',
'notes',
'contactable_by_dit',
'contactable_by_dit_partners',
'contactable_by_uk_dit_partners',
'contactable_by_overseas_dit_partners',
'accepts_dit_email_marketing',
'contactable_by_email',
'contactable_by_phone',
'archived',
Expand Down
32 changes: 23 additions & 9 deletions datahub/company/test/test_contact_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ def test_with_manual_address(self):
},
'address_postcode': 'SW1A1AA',
'notes': 'lorem ipsum',
'contactable_by_dit': False,
'contactable_by_dit_partners': False,
'contactable_by_dit': True,
'contactable_by_uk_dit_partners': True,
'contactable_by_overseas_dit_partners': True,
'accepts_dit_email_marketing': True,
'contactable_by_email': True,
'contactable_by_phone': True
}, format='json')
Expand Down Expand Up @@ -94,8 +96,10 @@ def test_with_manual_address(self):
},
'address_postcode': 'SW1A1AA',
'notes': 'lorem ipsum',
'contactable_by_dit': False,
'contactable_by_dit_partners': False,
'contactable_by_dit': True,
'contactable_by_uk_dit_partners': True,
'contactable_by_overseas_dit_partners': True,
'accepts_dit_email_marketing': True,
'contactable_by_email': True,
'contactable_by_phone': True,
'archived': False,
Expand Down Expand Up @@ -163,7 +167,9 @@ def test_defaults(self):
assert not response_data['address_postcode']
assert not response_data['notes']
assert not response_data['contactable_by_dit']
assert not response_data['contactable_by_dit_partners']
assert not response_data['contactable_by_uk_dit_partners']
assert not response_data['contactable_by_overseas_dit_partners']
assert not response_data['accepts_dit_email_marketing']
assert response_data['contactable_by_email']
assert response_data['contactable_by_phone']

Expand Down Expand Up @@ -313,7 +319,9 @@ def test_patch(self):
address_postcode='SW1A1AA',
notes='lorem ipsum',
contactable_by_dit=False,
contactable_by_dit_partners=False,
contactable_by_uk_dit_partners=False,
contactable_by_overseas_dit_partners=False,
accepts_dit_email_marketing=False,
contactable_by_email=True,
contactable_by_phone=True
)
Expand Down Expand Up @@ -362,7 +370,9 @@ def test_patch(self):
'address_postcode': 'SW1A1AA',
'notes': 'lorem ipsum',
'contactable_by_dit': False,
'contactable_by_dit_partners': False,
'contactable_by_uk_dit_partners': False,
'contactable_by_overseas_dit_partners': False,
'accepts_dit_email_marketing': False,
'contactable_by_email': True,
'contactable_by_phone': True,
'archived': False,
Expand Down Expand Up @@ -456,7 +466,9 @@ def test_view(self):
address_postcode='SW1A1AA',
notes='lorem ipsum',
contactable_by_dit=False,
contactable_by_dit_partners=False,
contactable_by_uk_dit_partners=False,
contactable_by_overseas_dit_partners=False,
accepts_dit_email_marketing=False,
contactable_by_email=True,
contactable_by_phone=True
)
Expand Down Expand Up @@ -501,7 +513,9 @@ def test_view(self):
'address_postcode': 'SW1A1AA',
'notes': 'lorem ipsum',
'contactable_by_dit': False,
'contactable_by_dit_partners': False,
'contactable_by_uk_dit_partners': False,
'contactable_by_overseas_dit_partners': False,
'accepts_dit_email_marketing': False,
'contactable_by_email': True,
'contactable_by_phone': True,
'archived': False,
Expand Down
20 changes: 14 additions & 6 deletions datahub/core/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import logging

import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from pyquery import PyQuery

logger = logging.getLogger(__name__)


class CDMSInvalidCredentialsError(RuntimeError):
"""Thrown when CDMS credentials are invalid."""
Expand All @@ -12,16 +16,20 @@ class CDMSInvalidCredentialsError(RuntimeError):
class CDMSUserBackend(ModelBackend):
"""Model backend that authenticates against CDMS and checks for whitelisting."""

def validate_cdms_credentials(self, username, password):
def validate_cdms_credentials(self, user, username, password):
"""Authenticate CDMS user/adviser using cdms login page."""
try:
return self._cdms_login(
url=settings.CDMS_AUTH_URL,
username=username,
password=password,
) is True # No errors in the process assume success
except requests.RequestException:
return None # Communication with CDMS failed
except requests.RequestException as exc:
logging.exception('Connection error when communicating with CDMS auth server')
if user.has_usable_password():
# Indicate that the cached password should be used if there is one
return None
raise
except (CDMSInvalidCredentialsError, AssertionError):
return False # Invalid credentials

Expand All @@ -38,7 +46,7 @@ def authenticate(self, request, username=None, password=None, **kwargs):
user_model().set_password(password)
else:
if self.user_can_authenticate(user):
auth_result = self.validate_cdms_credentials(username, password)
auth_result = self.validate_cdms_credentials(user, username, password)
if auth_result is True:
# user authenticated via CDMS
# cache passwd hash for backup auth
Expand Down Expand Up @@ -84,7 +92,7 @@ def _cdms_login(self, url, username, password, user_agent=None):
session.headers.update({'User-Agent': user_agent})
# 1. get login page
# url = '{}/?whr={}'.format(CDMS_BASE_URL, CDMS_ADFS_URL)
resp = session.get(url)
resp = session.get(url, timeout=settings.CDMS_AUTH_TIMEOUT)
assert resp.ok

html_parser = PyQuery(resp.text)
Expand Down Expand Up @@ -130,7 +138,7 @@ def _submit_form(session, source, url=None, params=None):
data.update(params or {})

url = url or form_action
resp = session.post(url, data)
resp = session.post(url, data, timeout=settings.CDMS_AUTH_TIMEOUT)

if not resp.ok:
raise CDMSInvalidCredentialsError()
Expand Down
6 changes: 0 additions & 6 deletions datahub/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,12 +446,6 @@ class Team(Enum):
crm = Constant('crm', 'a7f924aa-9698-e211-a939-e4115bead28a')


class ServiceDeliveryStatus(Enum):
"""Service delivery status."""

offered = Constant('Offered', '45329c18-6095-e211-a939-e4115bead28a')


class HeadquarterType(Enum):
"""Headquarter type."""

Expand Down
73 changes: 69 additions & 4 deletions datahub/core/test/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.timezone import now
from oauth2_provider.models import Application
Expand Down Expand Up @@ -57,7 +58,17 @@ def get_cdms_user():
return get_or_create_user(
email='cdms@user.com',
last_name='Useri',
first_name='CDMS'
first_name='CDMS',
)


def get_cdms_user_with_password():
"""Shortcut to create cdms user."""
return get_or_create_user(
email='cdms@user.com',
last_name='Useri',
first_name='CDMS',
password='password',
)


Expand Down Expand Up @@ -100,10 +111,10 @@ def test_invalid_cdms_credentials(auth_mock, settings, live_server):

@pytest.mark.liveserver
@mock.patch('datahub.core.auth.CDMSUserBackend._cdms_login')
def test_cdms_returns_500(mocked_login, live_server):
def test_cdms_returns_500_with_hash(mocked_login, live_server):
"""Test login when CDMS is not available."""
mocked_login.side_effect = requests.RequestException
cdms_user = get_cdms_user()
cdms_user = get_cdms_user_with_password()
application, _ = Application.objects.get_or_create(
user=cdms_user,
client_type=Application.CLIENT_CONFIDENTIAL,
Expand All @@ -117,14 +128,67 @@ def test_cdms_returns_500(mocked_login, live_server):
data={
'grant_type': 'password',
'username': cdms_user.email,
'password': cdms_user.password
'password': 'password'
},
auth=auth
)
assert response.status_code == status.HTTP_200_OK
assert '"token_type": "Bearer"' in response.text


@pytest.mark.liveserver
@mock.patch('datahub.core.auth.CDMSUserBackend._cdms_login')
def test_cdms_returns_500_with_hash_wrong_password(mocked_login, live_server):
"""Test login when CDMS is not available."""
mocked_login.side_effect = requests.RequestException
cdms_user = get_cdms_user_with_password()
application, _ = Application.objects.get_or_create(
user=cdms_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_PASSWORD,
name='Test auth client'
)
url = live_server + reverse('token')
auth = requests.auth.HTTPBasicAuth(application.client_id, application.client_secret)
response = requests.post(
url,
data={
'grant_type': 'password',
'username': cdms_user.email,
'password': 'wrong_password'
},
auth=auth
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert 'Invalid credentials given' in response.text


@pytest.mark.liveserver
@mock.patch('datahub.core.auth.CDMSUserBackend._cdms_login')
def test_cdms_returns_500_no_hash(mocked_login, live_server):
"""Test login when CDMS is not available."""
mocked_login.side_effect = requests.RequestException
cdms_user = get_cdms_user()
application, _ = Application.objects.get_or_create(
user=cdms_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_PASSWORD,
name='Test auth client'
)
url = live_server + reverse('token')
auth = requests.auth.HTTPBasicAuth(application.client_id, application.client_secret)
response = requests.post(
url,
data={
'grant_type': 'password',
'username': cdms_user.email,
'password': cdms_user.password
},
auth=auth
)
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR


@pytest.mark.liveserver
@mock.patch('datahub.core.auth.CDMSUserBackend.validate_cdms_credentials')
def test_valid_cdms_credentials(auth_mock, live_server):
Expand Down Expand Up @@ -331,6 +395,7 @@ def test_submit_form():
test2='test2_val',
injected='param',
),
timeout=settings.CDMS_AUTH_TIMEOUT,
)


Expand Down
Loading

0 comments on commit ace1cd7

Please sign in to comment.