def test_init_accepts_list_of_attributes(self):
        # Arrange
        attributes = [
            SAMLAttribute(SAMLAttributeType.uid.name, [12345]),
            SAMLAttribute(SAMLAttributeType.eduPersonTargetedID.name, [12345]),
        ]

        # Act
        attribute_statement = SAMLAttributeStatement(attributes)

        # Assert
        assert True == (SAMLAttributeType.uid.name
                        in attribute_statement.attributes)
        assert (attributes[0].values == attribute_statement.attributes[
            SAMLAttributeType.uid.name].values)

        assert True == (SAMLAttributeType.eduPersonTargetedID.name
                        in attribute_statement.attributes)
        assert (attributes[1].values == attribute_statement.attributes[
            SAMLAttributeType.eduPersonTargetedID.name].values)
Beispiel #2
0
class TestSAMLSubjectParser(object):
    @parameterized.expand([
        (
            "name_id_and_attributes",
            SAMLNameIDFormat.TRANSIENT.value,
            fixtures.IDP_1_ENTITY_ID,
            fixtures.SP_ENTITY_ID,
            "12345",
            {
                SAMLAttributeType.eduPersonUniqueId.value: ["12345"]
            },
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.TRANSIENT.value,
                    fixtures.IDP_1_ENTITY_ID,
                    fixtures.SP_ENTITY_ID,
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(SAMLAttributeType.eduPersonUniqueId.name,
                                  ["12345"])
                ]),
            ),
        ),
        (
            "edu_person_targeted_id_as_name_id",
            None,
            None,
            None,
            None,
            {
                SAMLAttributeType.eduPersonTargetedID.value: [{
                    "NameID": {
                        "Format": SAMLNameIDFormat.PERSISTENT.value,
                        "NameQualifier": fixtures.IDP_1_ENTITY_ID,
                        "value": "12345",
                    }
                }]
            },
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    fixtures.IDP_1_ENTITY_ID,
                    None,
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(SAMLAttributeType.eduPersonTargetedID.name,
                                  ["12345"])
                ]),
            ),
        ),
        (
            "edu_person_targeted_id_as_name_id_and_other_attributes",
            None,
            None,
            None,
            None,
            {
                SAMLAttributeType.eduPersonTargetedID.value: [{
                    "NameID": {
                        "Format": SAMLNameIDFormat.PERSISTENT.value,
                        "NameQualifier": fixtures.IDP_1_ENTITY_ID,
                        "value": "12345",
                    }
                }],
                SAMLAttributeType.eduPersonPrincipalName.value: ["12345"],
            },
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    fixtures.IDP_1_ENTITY_ID,
                    None,
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(SAMLAttributeType.eduPersonTargetedID.name,
                                  ["12345"]),
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonPrincipalName.name,
                        ["12345"]),
                ]),
            ),
        ),
        (
            "edu_person_principal_name_as_name_id",
            None,
            None,
            None,
            None,
            {
                SAMLAttributeType.eduPersonPrincipalName.value: [{
                    "NameID": {
                        "Format": SAMLNameIDFormat.PERSISTENT.value,
                        "NameQualifier": fixtures.IDP_1_ENTITY_ID,
                        "value": "12345",
                    }
                }]
            },
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    fixtures.IDP_1_ENTITY_ID,
                    None,
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonPrincipalName.name,
                        ["12345"])
                ]),
            ),
        ),
    ])
    def test_parse(
        self,
        _,
        name_id_format,
        name_id_nq,
        name_id_spnq,
        name_id,
        attributes,
        expected_result,
    ):
        # Arrange
        parser = SAMLSubjectParser()
        auth = create_autospec(spec=OneLogin_Saml2_Auth)
        auth.get_nameid_format = MagicMock(return_value=name_id_format)
        auth.get_nameid_nq = MagicMock(return_value=name_id_nq)
        auth.get_nameid_spnq = MagicMock(return_value=name_id_spnq)
        auth.get_nameid = MagicMock(return_value=name_id)
        auth.get_attributes = MagicMock(return_value=attributes)
        auth.get_session_expiration = MagicMock(return_value=None)
        auth.get_last_assertion_not_on_or_after = MagicMock(return_value=None)

        # Act
        result = parser.parse(auth)

        # Arrange
        assert result == expected_result
class TestSAMLSubjectPatronIDExtractor(object):
    @parameterized.expand([
        ("subject_without_patron_id", SAMLSubject(None, None), None),
        (
            "subject_with_eduPersonTargetedID_attribute",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonTargetedID.name,
                        values=["2"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "3",
        ),
        (
            "subject_with_eduPersonUniqueId_attribute",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["2"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["3"]),
                ]),
            ),
            "2",
        ),
        (
            "subject_with_uid_attribute",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["2"])
                ]),
            ),
            "2",
        ),
        (
            "subject_with_name_id",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["2"],
                    )
                ]),
            ),
            "1",
        ),
        (
            "subject_with_switched_off_use_of_name_id",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["2"],
                    )
                ]),
            ),
            None,
            False,
        ),
        (
            "patron_id_attributes_matching_attributes_in_subject",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonTargetedID.name,
                        values=["2"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "4",
            False,
            [SAMLAttributeType.uid.name],
        ),
        (
            "patron_id_attributes_matching_second_saml_attribute",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonTargetedID.name,
                        values=[None],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "4",
            True,
            [
                SAMLAttributeType.eduPersonTargetedID.name,
                SAMLAttributeType.uid.name,
            ],
        ),
        (
            "patron_id_attributes_not_matching_attributes_in_subject",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonTargetedID.name,
                        values=["2"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            None,
            False,
            [SAMLAttributeType.givenName.name],
        ),
        (
            "patron_id_attributes_not_matching_attributes_in_subject_and_using_name_id_instead",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonTargetedID.name,
                        values=["2"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "1",
            True,
            [SAMLAttributeType.givenName.name],
        ),
        (
            "patron_id_regular_expression_matching_saml_subject",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "patron",
            False,
            [
                SAMLAttributeType.eduPersonPrincipalName.name,
                SAMLAttributeType.mail.name,
            ],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_ORG,
        ),
        (
            "patron_id_regular_expression_matching_second_saml_attribute",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "patron",
            False,
            [
                SAMLAttributeType.eduPersonUniqueId.name,
                SAMLAttributeType.eduPersonPrincipalName.name,
                SAMLAttributeType.mail.name,
            ],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_ORG,
        ),
        (
            "unicode_patron_id_regular_expression_matching_saml_subject",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["pą[email protected]"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "pątron",
            False,
            [
                SAMLAttributeType.eduPersonPrincipalName.name,
                SAMLAttributeType.mail.name,
            ],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_ORG,
        ),
        (
            "patron_id_regular_expression_not_matching_saml_subject",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "1"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            None,
            False,
            [
                SAMLAttributeType.eduPersonPrincipalName.name,
                SAMLAttributeType.mail.name,
            ],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_COM,
        ),
        (
            "patron_id_regular_expression_not_matching_saml_attributes_but_matching_name_id",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "",
                           "*****@*****.**"),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["3"],
                    ),
                    SAMLAttribute(name=SAMLAttributeType.uid.name,
                                  values=["4"]),
                ]),
            ),
            "patron",
            True,
            [
                SAMLAttributeType.eduPersonPrincipalName.name,
                SAMLAttributeType.mail.name,
            ],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_COM,
        ),
    ])
    def test(
        self,
        _,
        subject,
        expected_patron_id,
        use_name_id=True,
        patron_id_attributes=None,
        patron_id_regular_expression=None,
    ):
        """Make sure that SAMLSubjectUIDExtractor correctly extracts a unique patron ID from the SAML subject.

        :param _: Name of the test case
        :type _: str

        :param expected_patron_id: Expected patron ID
        :type expected_patron_id: str

        :param use_name_id: Boolean value indicating whether SAMLSubjectUIDExtractor
            is allowed to search for patron IDs in NameID
        :type use_name_id: bool

        :param patron_id_attributes: List of SAML attributes used by SAMLSubjectUIDExtractor to search for a patron ID
        :type patron_id_attributes: List[SAMLAttributeType]

        :param patron_id_regular_expression: Regular expression used to extract a patron ID from SAML attributes
        :type patron_id_regular_expression: str
        """
        # Arrange
        extractor = SAMLSubjectPatronIDExtractor(use_name_id,
                                                 patron_id_attributes,
                                                 patron_id_regular_expression)

        # Act
        patron_id = extractor.extract(subject)

        # Assert
        assert expected_patron_id == patron_id
Beispiel #4
0
class TestSAMLSubjectFilter(object):
    @parameterized.expand(
        [
            (
                "fails_in_the_case_of_syntax_error",
                'subject.attribute_statement.attributes["eduPersonEntitlement"].values[0 == "urn:mace:nyu.edu:entl:lib:eresources"',
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                name=SAMLAttributeType.eduPersonEntitlement.name,
                                values=["urn:mace:nyu.edu:entl:lib:eresources"],
                            )
                        ]
                    ),
                ),
                None,
                SAMLSubjectFilterError,
            ),
            (
                "fails_in_the_case_of_unknown_attribute",
                'subject.attribute_statement.attributes["mail"].values[0] == "urn:mace:nyu.edu:entl:lib:eresources"',
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                name=SAMLAttributeType.eduPersonEntitlement.name,
                                values=["urn:mace:nyu.edu:entl:lib:eresources"],
                            )
                        ]
                    ),
                ),
                None,
                SAMLSubjectFilterError,
            ),
            (
                "fails_when_subject_is_not_used",
                'attributes["eduPersonEntitlement"].values[0] == "urn:mace:nyu.edu:entl:lib:eresources"',
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                name=SAMLAttributeType.eduPersonEntitlement.name,
                                values=["urn:mace:nyu.edu:entl:lib:eresources"],
                            )
                        ]
                    ),
                ),
                None,
                SAMLSubjectFilterError,
            ),
            (
                "can_filter_when_attribute_has_one_value",
                '"urn:mace:nyu.edu:entl:lib:eresources" == subject.attribute_statement.attributes["eduPersonEntitlement"].values[0]',
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                name=SAMLAttributeType.eduPersonEntitlement.name,
                                values=["urn:mace:nyu.edu:entl:lib:eresources"],
                            )
                        ]
                    ),
                ),
                True,
            ),
            (
                "can_filter_when_attribute_has_multiple_values",
                '"urn:mace:nyu.edu:entl:lib:eresources" in subject.attribute_statement.attributes["eduPersonEntitlement"].values',
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                name=SAMLAttributeType.eduPersonEntitlement.name,
                                values=[
                                    "urn:mace:nyu.edu:entl:lib:eresources",
                                    "urn:mace:nyu.edu:entl:lib:books",
                                ],
                            )
                        ]
                    ),
                ),
                True,
            ),
        ]
    )
    def test_execute(
        self, _, expression, subject, expected_result, expected_exception=None
    ):
        # Arrange
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)

        # Act
        if expected_exception:
            with pytest.raises(expected_exception):
                subject_filter.execute(expression, subject)
        else:
            result = subject_filter.execute(expression, subject)

            # Assert
            assert expected_result == result

    @parameterized.expand(
        [
            (
                "fails_in_the_case_of_syntax_error",
                'subject.attribute_statement.attributes["eduPersonEntitlement"].values[0 == "urn:mace:nyu.edu:entl:lib:eresources"',
                SAMLSubjectFilterError,
            ),
            (
                "fails_when_subject_is_not_used",
                'attributes["eduPersonEntitlement"].values[0] == "urn:mace:nyu.edu:entl:lib:eresources"',
                SAMLSubjectFilterError,
            ),
            (
                "can_filter_by_attribute_oid",
                'subject.attribute_statement.attributes["urn:oid:1.3.6.1.4.1.5923.1.8"].values[0] == "urn:mace:nyu.edu:entl:lib:eresources"',
                None,
            ),
            (
                "can_filter_when_attribute_has_one_value",
                '"urn:mace:nyu.edu:entl:lib:eresources" == subject.attribute_statement.attributes["eduPersonEntitlement"].values[0]',
                None,
            ),
            (
                "can_filter_when_attribute_has_multiple_values",
                '"urn:mace:nyu.edu:entl:lib:eresources" in subject.attribute_statement.attributes["eduPersonEntitlement"].values',
                None,
            ),
        ]
    )
    def test_validate(self, _, expression, expected_exception):
        # Arrange
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)

        # Act
        if expected_exception:
            with pytest.raises(expected_exception):
                subject_filter.validate(expression)
        else:
            subject_filter.validate(expression)
Beispiel #5
0
class TestProQuestCredentialManager(DatabaseTest):
    def setup_method(self):
        super(TestProQuestCredentialManager, self).setup_method()

        self._data_source = DataSource.lookup(
            self._db, DataSource.PROQUEST, autocreate=True
        )

    def test_lookup_proquest_token_returns_none_if_token_missing(self):
        # Arrange
        credential_manager = ProQuestCredentialManager()
        patron = self._patron()

        # Act
        token = credential_manager.lookup_proquest_token(self._db, patron)

        # Assert
        assert None == token

    def test_lookup_proquest_token_returns_token(self):
        # Arrange
        credential_manager = ProQuestCredentialManager()
        patron = self._patron()
        expected_token = "12345"

        Credential.temporary_token_create(
            self._db,
            self._data_source,
            ProQuestCredentialType.PROQUEST_JWT_TOKEN.value,
            patron,
            datetime.timedelta(hours=1),
            expected_token,
        )

        # Act
        token = credential_manager.lookup_proquest_token(self._db, patron)

        # Assert
        assert True == isinstance(token, Credential)
        assert expected_token == token.credential

    def test_save_proquest_token_saves_token(self):
        # Arrange
        credential_manager = ProQuestCredentialManager()
        patron = self._patron()
        expected_token = "12345"

        # Act
        credential_manager.save_proquest_token(
            self._db, patron, datetime.timedelta(hours=1), expected_token
        )
        token = Credential.lookup_by_patron(
            self._db,
            self._data_source.name,
            ProQuestCredentialType.PROQUEST_JWT_TOKEN.value,
            patron,
        )

        # Assert
        assert expected_token == token.credential

    @parameterized.expand(
        [
            ("when_there_is_no_token", None, None),
            (
                "when_subject_does_not_contain_affiliation_id_attributes",
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [SAMLAttribute(SAMLAttributeType.mail.name, [fixtures.MAIL])]
                    ),
                ),
                None,
            ),
            (
                "when_subject_contains_affiliation_id_attribute",
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                SAMLAttributeType.eduPersonPrincipalName.name,
                                [fixtures.EDU_PERSON_PRINCIPAL_NAME],
                            ),
                        ]
                    ),
                ),
                fixtures.EDU_PERSON_PRINCIPAL_NAME,
            ),
            (
                "when_subject_contains_multiple_affiliation_id_attributes",
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                SAMLAttributeType.eduPersonPrincipalName.name,
                                ["12345", fixtures.EDU_PERSON_PRINCIPAL_NAME],
                            ),
                        ]
                    ),
                ),
                "12345",
            ),
            (
                "with_custom_affiliation_attributes",
                SAMLSubject(
                    None,
                    SAMLAttributeStatement(
                        [
                            SAMLAttribute(
                                SAMLAttributeType.mail.name,
                                ["*****@*****.**"],
                            ),
                        ]
                    ),
                ),
                "*****@*****.**",
                (SAMLAttributeType.mail.name,),
            ),
        ]
    )
    def test_lookup_patron_affiliation_id(
        self, _, subject, expected_affiliation_id, affiliation_attributes=None
    ):
        # Arrange
        credential_manager = ProQuestCredentialManager()
        patron = self._patron()

        if subject:
            expected_token = json.dumps(subject, cls=SAMLSubjectJSONEncoder)

            data_source = DataSource.lookup(
                self._db,
                BaseSAMLAuthenticationProvider.TOKEN_DATA_SOURCE_NAME,
                autocreate=True,
            )
            Credential.temporary_token_create(
                self._db,
                data_source,
                BaseSAMLAuthenticationProvider.TOKEN_TYPE,
                patron,
                datetime.timedelta(hours=1),
                expected_token,
            )

        # Act
        if affiliation_attributes:
            token = credential_manager.lookup_patron_affiliation_id(
                self._db, patron, affiliation_attributes
            )
        else:
            token = credential_manager.lookup_patron_affiliation_id(self._db, patron)

        # Assert
        assert expected_affiliation_id == token
class TestSAMLWebSSOAuthenticationProvider(ControllerTest):
    def setup_method(self, _db=None, set_up_circulation_manager=True):
        super(TestSAMLWebSSOAuthenticationProvider, self).setup_method()

        metadata_parser = SAMLMetadataParser()

        self._external_integration_association = create_autospec(
            spec=HasExternalIntegration)
        self._external_integration_association.external_integration = MagicMock(
            return_value=self._integration)

        self._configuration_storage = ConfigurationStorage(
            self._external_integration_association)
        self._configuration_factory = SAMLConfigurationFactory(metadata_parser)

    @parameterized.expand([
        (
            "identity_provider_with_display_name",
            [IDENTITY_PROVIDER_WITH_DISPLAY_NAME],
            {
                "type":
                SAMLWebSSOAuthenticationProvider.FLOW_TYPE,
                "description":
                SAMLWebSSOAuthenticationProvider.NAME,
                "links": [{
                    "rel":
                    "authenticate",
                    "href":
                    "http://localhost/default/saml_authenticate?provider=SAML+2.0+Web+SSO&idp_entity_id=http%3A%2F%2Fidp2.hilbertteam.net%2Fidp%2Fshibboleth",
                    "display_names": [
                        {
                            "value": fixtures.IDP_1_UI_INFO_EN_DISPLAY_NAME,
                            "language": "en",
                        },
                        {
                            "value": fixtures.IDP_1_UI_INFO_ES_DISPLAY_NAME,
                            "language": "es",
                        },
                    ],
                    "descriptions": [
                        {
                            "value": fixtures.IDP_1_UI_INFO_DESCRIPTION,
                            "language": "en",
                        },
                        {
                            "value": fixtures.IDP_1_UI_INFO_DESCRIPTION,
                            "language": "es",
                        },
                    ],
                    "information_urls": [
                        {
                            "value": fixtures.IDP_1_UI_INFO_INFORMATION_URL,
                            "language": "en",
                        },
                        {
                            "value": fixtures.IDP_1_UI_INFO_INFORMATION_URL,
                            "language": "es",
                        },
                    ],
                    "privacy_statement_urls": [
                        {
                            "value":
                            fixtures.IDP_1_UI_INFO_PRIVACY_STATEMENT_URL,
                            "language": "en",
                        },
                        {
                            "value":
                            fixtures.IDP_1_UI_INFO_PRIVACY_STATEMENT_URL,
                            "language": "es",
                        },
                    ],
                    "logo_urls": [
                        {
                            "value": fixtures.IDP_1_UI_INFO_LOGO_URL,
                            "language": "en",
                        },
                        {
                            "value": fixtures.IDP_1_UI_INFO_LOGO_URL,
                            "language": "es",
                        },
                    ],
                }],
            },
        ),
        (
            "identity_provider_with_organization_display_name",
            [IDENTITY_PROVIDER_WITH_ORGANIZATION_DISPLAY_NAME],
            {
                "type":
                SAMLWebSSOAuthenticationProvider.FLOW_TYPE,
                "description":
                SAMLWebSSOAuthenticationProvider.NAME,
                "links": [{
                    "rel":
                    "authenticate",
                    "href":
                    "http://localhost/default/saml_authenticate?provider=SAML+2.0+Web+SSO&idp_entity_id=http%3A%2F%2Fidp2.hilbertteam.net%2Fidp%2Fshibboleth",
                    "display_names": [
                        {
                            "value": fixtures.
                            IDP_1_ORGANIZATION_EN_ORGANIZATION_DISPLAY_NAME,
                            "language": "en",
                        },
                        {
                            "value": fixtures.
                            IDP_1_ORGANIZATION_ES_ORGANIZATION_DISPLAY_NAME,
                            "language": "es",
                        },
                    ],
                    "descriptions": [],
                    "information_urls": [],
                    "privacy_statement_urls": [],
                    "logo_urls": [],
                }],
            },
        ),
        (
            "identity_provider_without_display_names_and_default_template",
            [
                IDENTITY_PROVIDER_WITHOUT_DISPLAY_NAMES,
                IDENTITY_PROVIDER_WITHOUT_DISPLAY_NAMES,
            ],
            {
                "type":
                SAMLWebSSOAuthenticationProvider.FLOW_TYPE,
                "description":
                SAMLWebSSOAuthenticationProvider.NAME,
                "links": [
                    {
                        "rel":
                        "authenticate",
                        "href":
                        "http://localhost/default/saml_authenticate?provider=SAML+2.0+Web+SSO&idp_entity_id=http%3A%2F%2Fidp1.hilbertteam.net%2Fidp%2Fshibboleth",
                        "display_names": [{
                            "value":
                            SAMLConfiguration.
                            IDP_DISPLAY_NAME_DEFAULT_TEMPLATE.format(1),
                            "language":
                            "en",
                        }],
                        "descriptions": [],
                        "information_urls": [],
                        "privacy_statement_urls": [],
                        "logo_urls": [],
                    },
                    {
                        "rel":
                        "authenticate",
                        "href":
                        "http://localhost/default/saml_authenticate?provider=SAML+2.0+Web+SSO&idp_entity_id=http%3A%2F%2Fidp1.hilbertteam.net%2Fidp%2Fshibboleth",
                        "display_names": [{
                            "value":
                            SAMLConfiguration.
                            IDP_DISPLAY_NAME_DEFAULT_TEMPLATE.format(2),
                            "language":
                            "en",
                        }],
                        "descriptions": [],
                        "information_urls": [],
                        "privacy_statement_urls": [],
                        "logo_urls": [],
                    },
                ],
            },
        ),
    ])
    def test_authentication_document(self, _, identity_providers,
                                     expected_result):
        # Arrange
        configuration = create_autospec(spec=SAMLConfiguration)
        configuration.get_service_provider = MagicMock(
            return_value=SERVICE_PROVIDER)
        configuration.get_identity_providers = MagicMock(
            return_value=identity_providers)
        configuration.patron_id_use_name_id = "true"
        configuration.patron_id_attributes = []
        configuration.patron_id_regular_expression = None

        configuration_factory_create_context_manager = MagicMock()
        configuration_factory_create_context_manager.__enter__ = MagicMock(
            return_value=configuration)

        configuration_factory = create_autospec(spec=SAMLConfigurationFactory)
        configuration_factory.create = MagicMock(
            return_value=configuration_factory_create_context_manager)

        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        subject_parser = SAMLSubjectParser()
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)
        authentication_manager = SAMLAuthenticationManager(
            onelogin_configuration, subject_parser, subject_filter)

        authentication_manager_factory = create_autospec(
            spec=SAMLAuthenticationManagerFactory)
        authentication_manager_factory.create = MagicMock(
            return_value=authentication_manager)

        with patch("api.saml.provider.SAMLAuthenticationManagerFactory"
                   ) as authentication_manager_factory_constructor_mock, patch(
                       "api.saml.provider.SAMLConfigurationFactory"
                   ) as configuration_factory_constructor_mock:
            authentication_manager_factory_constructor_mock.return_value = (
                authentication_manager_factory)
            configuration_factory_constructor_mock.return_value = configuration_factory

            # Act
            provider = SAMLWebSSOAuthenticationProvider(
                self._default_library, self._integration)

            self.app.config["SERVER_NAME"] = "localhost"

            with self.app.test_request_context("/"):
                result = provider.authentication_flow_document(self._db)

            # Assert
            assert expected_result == result

    @parameterized.expand([
        ("empty_subject", None,
         SAML_INVALID_SUBJECT.detailed("Subject is empty")),
        (
            "subject_is_patron_data",
            PatronData(permanent_id=12345),
            PatronData(permanent_id=12345),
        ),
        (
            "subject_does_not_have_unique_id",
            SAMLSubject(None, None),
            SAML_INVALID_SUBJECT.detailed("Subject does not have a unique ID"),
        ),
        (
            "subject_has_unique_id",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
        ),
        (
            "subject_has_unique_name_id_but_use_of_name_id_is_switched_off_using_integer_literal",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "12345"),
                SAMLAttributeStatement([]),
            ),
            SAML_INVALID_SUBJECT.detailed("Subject does not have a unique ID"),
            0,
        ),
        (
            "subject_has_unique_name_id_but_use_of_name_id_is_switched_off_using_string_literal",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "12345"),
                SAMLAttributeStatement([]),
            ),
            SAML_INVALID_SUBJECT.detailed("Subject does not have a unique ID"),
            "false",
        ),
        (
            "subject_has_unique_name_id_and_use_of_name_id_is_switched_on_using_string_literal_true",
            SAMLSubject(
                SAMLNameID(SAMLNameIDFormat.UNSPECIFIED, "", "", "12345"),
                SAMLAttributeStatement([]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            "true",
        ),
        (
            "subject_has_unique_id_matching_the_regular_expression",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="firstname.lastname",
                authorization_identifier="firstname.lastname",
                external_type="A",
                complete=True,
            ),
            False,
            [SAMLAttributeType.eduPersonPrincipalName.name],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_ORG,
        ),
        (
            "subject_has_unique_id_not_matching_the_regular_expression",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonPrincipalName.name,
                        values=["*****@*****.**"],
                    )
                ]),
            ),
            SAML_INVALID_SUBJECT.detailed("Subject does not have a unique ID"),
            False,
            [SAMLAttributeType.eduPersonPrincipalName.name],
            fixtures.PATRON_ID_REGULAR_EXPRESSION_ORG,
        ),
    ])
    def test_remote_patron_lookup(
        self,
        _,
        subject,
        expected_result,
        patron_id_use_name_id=None,
        patron_id_attributes=None,
        patron_id_regular_expression=None,
    ):
        # Arrange
        with self._configuration_factory.create(
                self._configuration_storage, self._db,
                SAMLConfiguration) as configuration:
            if patron_id_use_name_id is not None:
                configuration.patron_id_use_name_id = patron_id_use_name_id
            if patron_id_attributes is not None:
                configuration.patron_id_attributes = json.dumps(
                    patron_id_attributes)
            if patron_id_regular_expression is not None:
                configuration.patron_id_regular_expression = (
                    patron_id_regular_expression)

        provider = SAMLWebSSOAuthenticationProvider(self._default_library,
                                                    self._integration)

        # Act
        result = provider.remote_patron_lookup(subject)

        # Assert
        if isinstance(result, ProblemDetail):
            assert result.response == expected_result.response
        else:
            assert result == expected_result

    @parameterized.expand([
        ("empty_subject", None,
         SAML_INVALID_SUBJECT.detailed("Subject is empty")),
        (
            "subject_does_not_have_unique_id",
            SAMLSubject(None, None),
            SAML_INVALID_SUBJECT.detailed("Subject does not have a unique ID"),
        ),
        (
            "subject_has_unique_id",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
        ),
        (
            "subject_has_unique_id_and_persistent_name_id",
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    "name-qualifier",
                    "sp-name-qualifier",
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            None,
        ),
        (
            "subject_has_unique_id_and_transient_name_id",
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.TRANSIENT.value,
                    "name-qualifier",
                    "sp-name-qualifier",
                    "12345",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            '{"attributes": {"eduPersonUniqueId": ["12345"]}}',
        ),
        (
            "subject_has_unique_id_and_custom_session_lifetime",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            None,
            datetime_utc(2020, 1, 1) + datetime.timedelta(days=42),
            42,
        ),
        (
            "subject_has_unique_id_and_empty_session_lifetime",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            None,
            None,
            "",
        ),
        (
            "subject_has_unique_id_and_non_default_expiration_timeout",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
                valid_till=datetime.timedelta(days=1),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
        ),
        (
            "subject_has_unique_id_non_default_expiration_timeout_and_custom_session_lifetime",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
                valid_till=datetime.timedelta(days=1),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            None,
            datetime_utc(2020, 1, 1) + datetime.timedelta(days=42),
            42,
        ),
        (
            "subject_has_unique_id_non_default_expiration_timeout_and_empty_session_lifetime",
            SAMLSubject(
                None,
                SAMLAttributeStatement([
                    SAMLAttribute(
                        name=SAMLAttributeType.eduPersonUniqueId.name,
                        values=["12345"],
                    )
                ]),
                valid_till=datetime.timedelta(days=1),
            ),
            PatronData(
                permanent_id="12345",
                authorization_identifier="12345",
                external_type="A",
                complete=True,
            ),
            None,
            None,
            "",
        ),
    ])
    @freeze_time("2020-01-01 00:00:00")
    def test_saml_callback(
        self,
        _,
        subject,
        expected_patron_data,
        expected_credential=None,
        expected_expiration_time=None,
        cm_session_lifetime=None,
    ):
        # This test makes sure that SAMLWebSSOAuthenticationProvider.saml_callback
        # correctly processes a SAML subject and returns right PatronData.

        # Arrange
        provider = SAMLWebSSOAuthenticationProvider(self._default_library,
                                                    self._integration)

        if expected_credential is None:
            expected_credential = json.dumps(subject,
                                             cls=SAMLSubjectJSONEncoder)

        if expected_expiration_time is None and subject is not None:
            expected_expiration_time = utc_now() + subject.valid_till

        if cm_session_lifetime is not None:
            with self._configuration_factory.create(
                    self._configuration_storage, self._db,
                    SAMLConfiguration) as configuration:
                configuration.session_lifetime = cm_session_lifetime

        # Act
        result = provider.saml_callback(self._db, subject)

        # Assert
        if isinstance(result, ProblemDetail):
            assert result.response == expected_patron_data.response
        else:
            credential, patron, patron_data = result

            assert expected_credential == credential.credential
            assert expected_patron_data.permanent_id == patron.external_identifier
            assert expected_patron_data == patron_data
            assert expected_expiration_time == credential.expires
Beispiel #7
0
class TestSAMLAuthenticationManager(ControllerTest):
    @parameterized.expand([
        (
            "with_unsigned_authentication_request",
            SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS,
            IDENTITY_PROVIDERS,
        ),
        (
            "with_signed_authentication_request",
            SERVICE_PROVIDER_WITH_SIGNED_REQUESTS,
            IDENTITY_PROVIDERS,
        ),
    ])
    def test_start_authentication(self, _, service_provider,
                                  identity_providers):
        configuration = create_autospec(spec=SAMLConfiguration)
        configuration.service_provider_debug_mode = MagicMock(
            return_value=False)
        configuration.service_provider_strict_mode = MagicMock(
            return_value=False)
        configuration.get_service_provider = MagicMock(
            return_value=service_provider)
        configuration.get_identity_providers = MagicMock(
            return_value=identity_providers)
        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        subject_parser = SAMLSubjectParser()
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)
        authentication_manager = SAMLAuthenticationManager(
            onelogin_configuration, subject_parser, subject_filter)

        with self.app.test_request_context("/"):
            result = authentication_manager.start_authentication(
                self._db, fixtures.IDP_1_ENTITY_ID, "")

            query_items = parse_qs(urlsplit(result).query)
            saml_request = query_items["SAMLRequest"][0]
            decoded_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(
                saml_request)

            validation_result = OneLogin_Saml2_XML.validate_xml(
                decoded_saml_request, "saml-schema-protocol-2.0.xsd", False)
            assert isinstance(validation_result,
                              OneLogin_Saml2_XML._element_class)

            saml_request_dom = fromstring(decoded_saml_request)

            acs_url = saml_request_dom.get("AssertionConsumerServiceURL")
            assert acs_url == SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.acs_service.url

            acs_binding = saml_request_dom.get("ProtocolBinding")
            assert (acs_binding == SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.
                    acs_service.binding.value)

            sso_url = saml_request_dom.get("Destination")
            assert sso_url == IDENTITY_PROVIDERS[0].sso_service.url

            name_id_policy_nodes = OneLogin_Saml2_XML.query(
                saml_request_dom, "./samlp:NameIDPolicy")

            assert name_id_policy_nodes is not None
            assert len(name_id_policy_nodes) == 1

            name_id_policy_node = name_id_policy_nodes[0]
            name_id_format = name_id_policy_node.get("Format")

            assert (name_id_format ==
                    SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.name_id_format)

    @parameterized.expand([
        (
            "with_name_id_and_attributes",
            SAML_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            None,
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.TRANSIENT.value,
                    "http://idp.hilbertteam.net/idp/shibboleth",
                    "http://opds.hilbertteam.net/metadata/",
                    "AAdzZWNyZXQxeAj5TZ2CQ6FkW//TigUE8kgDuJfVEw7mtnCAFq02hvot2hQzlCj5QqQOBRlsAs0dqp1oHoi/apPWmrC2G30BvrtXcDfZsCGQv9eTGSRDydTLVPEe+lfCc1yg3WlxTeiCbFazW6kcybVgUper",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(SAMLAttributeType.uid.name, ["student1"]),
                    SAMLAttribute(SAMLAttributeType.mail.name,
                                  ["*****@*****.**"]),
                    SAMLAttribute(SAMLAttributeType.surname.name, ["Ent"]),
                    SAMLAttribute(SAMLAttributeType.givenName.name, ["Stud"]),
                ]),
            ),
        ),
        (
            "with_name_id_attributes_and_filter_expression_returning_false",
            SAML_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            "subject.attribute_statement.attributes['uid'].values[0] != 'student1'",
            SAML_NO_ACCESS_ERROR,
        ),
        (
            "with_name_id_attributes_and_filter_expression_returning_true",
            SAML_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            "subject.attribute_statement.attributes['uid'].values[0] == 'student1'",
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.TRANSIENT.value,
                    "http://idp.hilbertteam.net/idp/shibboleth",
                    "http://opds.hilbertteam.net/metadata/",
                    "AAdzZWNyZXQxeAj5TZ2CQ6FkW//TigUE8kgDuJfVEw7mtnCAFq02hvot2hQzlCj5QqQOBRlsAs0dqp1oHoi/apPWmrC2G30BvrtXcDfZsCGQv9eTGSRDydTLVPEe+lfCc1yg3WlxTeiCbFazW6kcybVgUper",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(SAMLAttributeType.uid.name, ["student1"]),
                    SAMLAttribute(SAMLAttributeType.mail.name,
                                  ["*****@*****.**"]),
                    SAMLAttribute(SAMLAttributeType.surname.name, ["Ent"]),
                    SAMLAttribute(SAMLAttributeType.givenName.name, ["Stud"]),
                ]),
            ),
        ),
        (
            "with_name_id_inside_edu_person_targeted_id_attribute",
            SAML_COLUMBIA_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            None,
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    "https://shibboleth-dev.cc.columbia.edu/idp/shibboleth",
                    None,
                    "0Mi3izMnex9L0sMt9wRfwY0pqQ8=",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonScopedAffiliation.name,
                        ["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonTargetedID.name,
                        ["0Mi3izMnex9L0sMt9wRfwY0pqQ8="],
                    ),
                    SAMLAttribute(SAMLAttributeType.displayName.name,
                                  ["William Tester"]),
                ]),
            ),
            True,
        ),
        (
            "with_name_id_inside_edu_person_targeted_id_attribute_and_filter_expression_returning_false",
            SAML_COLUMBIA_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            "subject.attribute_statement.attributes['eduPersonScopedAffiliation'].values[0] != '*****@*****.**'",
            SAML_NO_ACCESS_ERROR,
            True,
        ),
        (
            "with_name_id_inside_edu_person_targeted_id_attribute_and_filter_expression_returning_true",
            SAML_COLUMBIA_RESPONSE,
            datetime_utc(2020, 6, 7, 23, 43, 0),
            "subject.attribute_statement.attributes['eduPersonScopedAffiliation'].values[0] == '*****@*****.**'",
            SAMLSubject(
                SAMLNameID(
                    SAMLNameIDFormat.PERSISTENT.value,
                    "https://shibboleth-dev.cc.columbia.edu/idp/shibboleth",
                    None,
                    "0Mi3izMnex9L0sMt9wRfwY0pqQ8=",
                ),
                SAMLAttributeStatement([
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonScopedAffiliation.name,
                        ["*****@*****.**"],
                    ),
                    SAMLAttribute(
                        SAMLAttributeType.eduPersonTargetedID.name,
                        ["0Mi3izMnex9L0sMt9wRfwY0pqQ8="],
                    ),
                    SAMLAttribute(SAMLAttributeType.displayName.name,
                                  ["William Tester"]),
                ]),
            ),
            True,
        ),
    ])
    def test_finish_authentication(
        self,
        _,
        saml_response,
        current_time,
        filter_expression,
        expected_value,
        mock_validation=False,
    ):
        # Arrange
        identity_provider_entity_id = "http://idp.hilbertteam.net/idp/shibboleth"
        service_provider_host_name = "opds.hilbertteam.net"

        identity_providers = [
            copy(identity_provider) for identity_provider in IDENTITY_PROVIDERS
        ]
        identity_providers[0].entity_id = identity_provider_entity_id

        if mock_validation:
            validate_mock = MagicMock(return_value=True)
        else:
            real_validate_sign = OneLogin_Saml2_Utils.validate_sign
            validate_mock = MagicMock(side_effect=lambda *args, **kwargs:
                                      real_validate_sign(*args, **kwargs))

        filter_expression_mock = PropertyMock(return_value=filter_expression)
        service_provider_debug_mode_mock = PropertyMock(return_value=False)
        service_provider_strict_mode = PropertyMock(return_value=False)

        configuration = create_autospec(spec=SAMLConfiguration)
        type(configuration).filter_expression = filter_expression_mock
        type(configuration
             ).service_provider_debug_mode = service_provider_debug_mode_mock
        type(configuration
             ).service_provider_strict_mode = service_provider_strict_mode
        configuration.get_service_provider = MagicMock(
            return_value=SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS)
        configuration.get_identity_providers = MagicMock(
            return_value=identity_providers)
        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        subject_parser = SAMLSubjectParser()
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)
        authentication_manager = SAMLAuthenticationManager(
            onelogin_configuration, subject_parser, subject_filter)
        saml_response = base64.b64encode(saml_response)

        # Act
        with freeze_time(current_time):
            with patch(
                    "onelogin.saml2.response.OneLogin_Saml2_Utils.validate_sign",
                    validate_mock,
            ):
                self.app.config["SERVER_NAME"] = service_provider_host_name

                with self.app.test_request_context(
                        "/SAML2/POST", data={"SAMLResponse": saml_response}):
                    result = authentication_manager.finish_authentication(
                        self._db, identity_provider_entity_id)

                    # Assert
                    assert expected_value == result