diff --git a/atst/domain/authnid/__init__.py b/atst/domain/authnid/__init__.py index 25c04a5d..b644020e 100644 --- a/atst/domain/authnid/__init__.py +++ b/atst/domain/authnid/__init__.py @@ -47,8 +47,6 @@ class AuthenticationContext: def _crl_check(self): try: self.crl_cache.crl_check(self.cert) - except CRLInvalidException as exc: - raise UnauthenticatedError("CRL expired. " + str(exc)) except CRLRevocationException as exc: raise UnauthenticatedError("CRL check failed. " + str(exc)) diff --git a/atst/domain/authnid/crl/__init__.py b/atst/domain/authnid/crl/__init__.py index 82dc4896..26956f09 100644 --- a/atst/domain/authnid/crl/__init__.py +++ b/atst/domain/authnid/crl/__init__.py @@ -2,9 +2,13 @@ import sys import os import re import hashlib +from flask import current_app as app from datetime import datetime from OpenSSL import crypto, SSL +# error codes from OpenSSL: https://github.com/openssl/openssl/blob/2c75f03b39de2fa7d006bc0f0d7c58235a54d9bb/include/openssl/x509_vfy.h#L111 +CRL_EXPIRED_ERROR_CODE = 12 + def get_common_name(x509_name_object): for comp in x509_name_object.get_components(): @@ -176,10 +180,11 @@ class CRLCache(CRLInterface): return True except crypto.X509StoreContextError as err: - if ( - err.args[0][2] == "CRL has expired" - ): # there has to be a better way than this - raise CRLInvalidException("CRL expired. Args: {}".format(err.args)) + if err.args[0][0] == CRL_EXPIRED_ERROR_CODE: + if app.config.get("CRL_FAIL_OPEN"): + return True + else: + raise CRLInvalidException("CRL expired. Args: {}".format(err.args)) raise CRLRevocationException( "Certificate revoked or errored. Error: {}. Args: {}".format( type(err), err.args diff --git a/atst/routes/errors.py b/atst/routes/errors.py index 279d6c8f..d00c7f0a 100644 --- a/atst/routes/errors.py +++ b/atst/routes/errors.py @@ -8,6 +8,7 @@ from atst.domain.invitations import ( ExpiredError as InvitationExpiredError, WrongUserError as InvitationWrongUserError, ) +from atst.domain.authnid.crl import CRLInvalidException from atst.domain.portfolios import PortfolioError from atst.utils.flash import formatted_flash as flash @@ -32,6 +33,11 @@ def make_error_pages(app): def not_found(e): return handle_error(e) + @app.errorhandler(CRLInvalidException) + # pylint: disable=unused-variable + def missing_crl(e): + return handle_error(e, message="Error Code 008", code=401) + @app.errorhandler(exceptions.UnauthenticatedError) # pylint: disable=unused-variable def unauthorized(e): diff --git a/tests/domain/authnid/test_authentication_context.py b/tests/domain/authnid/test_authentication_context.py index 820f7028..f83cea64 100644 --- a/tests/domain/authnid/test_authentication_context.py +++ b/tests/domain/authnid/test_authentication_context.py @@ -56,12 +56,9 @@ def test_expired_crl_check_fails(): auth_context = AuthenticationContext( MockCRLCache(valid=False, expired=True), "SUCCESS", DOD_SDN, CERT ) - with pytest.raises(UnauthenticatedError) as excinfo: + with pytest.raises(CRLInvalidException) as excinfo: assert auth_context.authenticate() - (message,) = excinfo.value.args - assert "CRL expired" in message - def test_bad_sdn(): auth_context = AuthenticationContext(MockCRLCache(), "SUCCESS", "abc123", CERT) diff --git a/tests/test_auth.py b/tests/test_auth.py index 30f8c47b..6c1f9cf2 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -6,6 +6,7 @@ from .mocks import DOD_SDN_INFO, DOD_SDN, FIXTURE_EMAIL_ADDRESS from atst.domain.users import Users from atst.domain.roles import Roles from atst.domain.exceptions import NotFoundError +from atst.domain.authnid.crl import CRLInvalidException from atst.domain.auth import UNPROTECTED_ROUTES from .factories import UserFactory @@ -211,3 +212,15 @@ def test_redirected_on_login(client, monkeypatch): target_route = url_for("users.user") response = _login(client, next=target_route) assert target_route in response.headers.get("Location") + + +def test_error_on_invalid_crl(client, monkeypatch): + def _raise_crl_error(*args): + raise CRLInvalidException() + + monkeypatch.setattr( + "atst.domain.authnid.AuthenticationContext.authenticate", _raise_crl_error + ) + response = _login(client) + assert response.status_code == 401 + assert "Error Code 008" in response.data.decode()