Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added SDN compliance api and data model
- Loading branch information
1 parent
08d7574
commit 5a37736
Showing
44 changed files
with
6,417 additions
and
353 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""Compliance pipeline actions""" | ||
import logging | ||
|
||
from django.conf import settings | ||
from django.core import mail | ||
from social_core.exceptions import AuthException | ||
|
||
from authentication.exceptions import ( | ||
UserExportBlockedException, | ||
UserTryAgainLaterException, | ||
) | ||
from compliance import api | ||
|
||
|
||
log = logging.getLogger() | ||
|
||
|
||
def verify_exports_compliance( | ||
strategy, backend, user=None, **kwargs | ||
): # pylint: disable=unused-argument | ||
""" | ||
Verify that the user is allowed by exports compliance | ||
Args: | ||
strategy (social_django.strategy.DjangoStrategy): the strategy used to authenticate | ||
backend (social_core.backends.base.BaseAuth): the backend being used to authenticate | ||
user (User): the current user | ||
""" | ||
if not api.is_exports_verification_enabled(): | ||
log.warning("Export compliance checks are disabled") | ||
return {} | ||
|
||
# skip this step if the user is active or they have an existing export inquiry logged | ||
if user.is_active and user.exports_inquiries.exists(): | ||
return {} | ||
|
||
try: | ||
export_inquiry = api.verify_user_with_exports(user) | ||
except Exception as exc: # pylint: disable=broad-except | ||
# hard failure to request the exports API, log an error but don't let the user proceed | ||
log.exception("Unable to verify exports compliance") | ||
raise UserTryAgainLaterException(backend) from exc | ||
|
||
if export_inquiry is None: | ||
raise UserTryAgainLaterException(backend) | ||
elif export_inquiry.is_denied: | ||
log.info( | ||
"User with email '%s' was denied due to exports violation, for reason_code=%s, info_code=%s", | ||
user.email, | ||
export_inquiry.reason_code, | ||
export_inquiry.info_code, | ||
) | ||
try: | ||
with mail.get_connection(settings.NOTIFICATION_EMAIL_BACKEND) as connection: | ||
mail.send_mail( | ||
f"Exports Compliance: denied {user.email}", | ||
f"User with email '{user.email}' was denied due to exports violation, for reason_code={export_inquiry.reason_code}, info_code={export_inquiry.info_code}", | ||
settings.MAILGUN_FROM_EMAIL, | ||
[settings.EMAIL_SUPPORT], | ||
connection=connection, | ||
) | ||
except Exception: # pylint: disable=broad-except | ||
log.exception( | ||
"Exception sending email to support regarding export compliance check failure" | ||
) | ||
raise UserExportBlockedException(backend) | ||
elif export_inquiry.is_unknown: | ||
raise AuthException("Unable to authenticate, please contact support") | ||
|
||
return {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
"""Compliance pipeline tests""" | ||
import pytest | ||
from social_core.exceptions import AuthException | ||
|
||
from authentication.exceptions import ( | ||
UserExportBlockedException, | ||
UserTryAgainLaterException, | ||
) | ||
from authentication.pipeline import compliance | ||
from compliance.factories import ExportsInquiryLogFactory | ||
|
||
|
||
pytestmark = pytest.mark.django_db | ||
|
||
|
||
def test_verify_exports_compliance_disabled(mocker): | ||
"""Assert that nothing is done when the api is disabled""" | ||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.is_exports_verification_enabled.return_value = False | ||
|
||
assert compliance.verify_exports_compliance(None, None) == {} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"is_active, inquiry_exists, should_verify", | ||
[ | ||
[True, True, False], | ||
[True, False, True], | ||
[False, True, True], | ||
[False, False, True], | ||
], | ||
) | ||
def test_verify_exports_compliance_user_active( | ||
mailoutbox, mocker, user, is_active, inquiry_exists, should_verify | ||
): # pylint: disable=too-many-arguments | ||
"""Assert that the user is verified only if they already haven't been""" | ||
user.is_active = is_active | ||
if inquiry_exists: | ||
ExportsInquiryLogFactory.create(user=user) | ||
|
||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.verify_user_with_exports.return_value = mocker.Mock( | ||
is_denied=False, is_unknown=False | ||
) | ||
|
||
assert compliance.verify_exports_compliance(None, None, user=user) == {} | ||
|
||
if should_verify: | ||
mock_api.verify_user_with_exports.assert_called_once_with(user) | ||
assert len(mailoutbox) == 0 | ||
|
||
|
||
def test_verify_exports_compliance_no_record(mocker, user): | ||
"""Assert that an error to try again later is raised if no ExportsInquiryLog is created""" | ||
|
||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.verify_user_with_exports.return_value = None | ||
|
||
with pytest.raises(UserTryAgainLaterException): | ||
compliance.verify_exports_compliance(None, None, user=user) | ||
|
||
mock_api.verify_user_with_exports.assert_called_once_with(user) | ||
|
||
|
||
def test_verify_exports_compliance_api_raises_exception(mocker, user): | ||
"""Assert that an error to try again later is raised if the export api raises an exception""" | ||
|
||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.verify_user_with_exports.side_effect = Exception("error") | ||
|
||
with pytest.raises(UserTryAgainLaterException): | ||
compliance.verify_exports_compliance(None, None, user=user) | ||
|
||
mock_api.verify_user_with_exports.assert_called_once_with(user) | ||
|
||
|
||
@pytest.mark.parametrize("email_fails", [True, False]) | ||
def test_verify_exports_compliance_denied(mailoutbox, mocker, user, email_fails): | ||
"""Assert that a UserExportBlockedException is raised if the inquiry result is denied""" | ||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.verify_user_with_exports.return_value = mocker.Mock( | ||
is_denied=True, is_unknown=False, reason_code=100, info_code="123" | ||
) | ||
|
||
if email_fails: | ||
# a mail sending error should not obscurve the true error | ||
mocker.patch( | ||
"authentication.pipeline.compliance.mail.send_mail", | ||
side_effect=Exception("mail error"), | ||
) | ||
|
||
with pytest.raises(UserExportBlockedException): | ||
compliance.verify_exports_compliance(None, None, user=user) | ||
|
||
mock_api.verify_user_with_exports.assert_called_once_with(user) | ||
assert len(mailoutbox) == (0 if email_fails else 1) | ||
|
||
|
||
def test_verify_exports_compliance_unknown(mailoutbox, mocker, user): | ||
"""Assert that a UserExportBlockedException is raised if the inquiry result is unknown""" | ||
mock_api = mocker.patch("authentication.pipeline.compliance.api") | ||
mock_api.verify_user_with_exports.return_value = mocker.Mock( | ||
is_denied=False, is_unknown=True | ||
) | ||
|
||
with pytest.raises(AuthException): | ||
compliance.verify_exports_compliance(None, None, user=user) | ||
|
||
mock_api.verify_user_with_exports.assert_called_once_with(user) | ||
assert len(mailoutbox) == 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.