示例#1
0
    def test_get_service_provider_returns_correct_value(self):
        # Arrange
        service_provider_metadata = fixtures.CORRECT_XML_WITH_ONE_SP

        metadata_parser = SAMLMetadataParser()
        metadata_parser.parse = MagicMock(side_effect=metadata_parser.parse)

        configuration_storage = ConfigurationStorage(
            self._saml_integration_association)
        configuration_storage.load = MagicMock(
            side_effect=configuration_storage.load)

        saml_configuration_factory = SAMLConfigurationFactory(metadata_parser)

        with saml_configuration_factory.create(
                configuration_storage, self._db,
                SAMLConfiguration) as configuration:
            configuration.service_provider_xml_metadata = service_provider_metadata

            # Act
            service_provider = configuration.get_service_provider(self._db)

            # Assert
            assert True == isinstance(service_provider,
                                      SAMLServiceProviderMetadata)
            assert fixtures.SP_ENTITY_ID == service_provider.entity_id

            configuration_storage.load.assert_has_calls([
                call(self._db,
                     SAMLConfiguration.service_provider_xml_metadata.key),
                call(self._db,
                     SAMLConfiguration.service_provider_private_key.key),
            ])
            metadata_parser.parse.assert_called_once_with(
                service_provider_metadata)
示例#2
0
    def test_get_identity_providers_returns_non_federated_idps(self):
        # Arrange
        identity_providers_metadata = fixtures.CORRECT_XML_WITH_MULTIPLE_IDPS

        metadata_parser = SAMLMetadataParser()
        metadata_parser.parse = MagicMock(side_effect=metadata_parser.parse)

        configuration_storage = ConfigurationStorage(
            self._saml_integration_association)
        configuration_storage.load = MagicMock(
            side_effect=configuration_storage.load)

        saml_configuration_factory = SAMLConfigurationFactory(metadata_parser)

        with saml_configuration_factory.create(
                configuration_storage, self._db,
                SAMLConfiguration) as configuration:
            configuration.non_federated_identity_provider_xml_metadata = (
                identity_providers_metadata)

            # Act
            identity_providers = configuration.get_identity_providers(self._db)

            # Assert
            assert 2 == len(identity_providers)

            assert True == isinstance(identity_providers[0],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_1_ENTITY_ID == identity_providers[0].entity_id

            assert True == isinstance(identity_providers[1],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_2_ENTITY_ID == identity_providers[1].entity_id

            configuration_storage.load.assert_has_calls([
                call(
                    self._db,
                    SAMLConfiguration.
                    non_federated_identity_provider_xml_metadata.key,
                ),
                call(
                    self._db,
                    SAMLConfiguration.federated_identity_provider_entity_ids.
                    key,
                ),
            ])
            metadata_parser.parse.assert_called_once_with(
                identity_providers_metadata)
示例#3
0
    def __init__(self, library, integration, analytics=None):
        """Initializes a new instance of SAMLAuthenticationProvider class

        :param library: Patrons authenticated through this provider
            are associated with this Library. Don't store this object!
            It's associated with a scoped database session. Just pull
            normal Python objects out of it.
        :type library: Library

        :param integration: The ExternalIntegration that
            configures this AuthenticationProvider. Don't store this
            object! It's associated with a scoped database session. Just
            pull normal Python objects out of it.
        :type integration: ExternalIntegration
        """
        super(BaseSAMLAuthenticationProvider,
              self).__init__(library, integration, analytics)

        self._logger = logging.getLogger(__name__)
        self._configuration_storage = ConfigurationStorage(self)
        self._configuration_factory = SAMLConfigurationFactory(
            SAMLMetadataParser())
        self._authentication_manager_factory = SAMLAuthenticationManagerFactory(
        )

        db = Session.object_session(library)

        with self.get_configuration(db) as configuration:
            self._patron_id_use_name_id = ConfigurationMetadata.to_bool(
                configuration.patron_id_use_name_id)
            self._patron_id_attributes = configuration.patron_id_attributes
            self._patron_id_regular_expression = (
                configuration.patron_id_regular_expression)
示例#4
0
    def test_import_audiobook(self, importer, mock_get, datasource, db):
        """Ensure that ODL2Importer2 correctly processes and imports a feed with an audiobook."""
        license = self.get_data("license-audiobook.json")
        feed = self.get_data("feed-audiobook.json")
        mock_get.add(license)

        configuration_storage = ConfigurationStorage(importer)
        configuration_factory = ConfigurationFactory()

        with configuration_factory.create(
            configuration_storage, db, ODL2APIConfiguration
        ) as configuration:
            configuration.skipped_license_formats = json.dumps(["text/html"])

        imported_editions, pools, works, failures = importer.import_from_feed(feed)

        # Make sure we imported one edition and it is an audiobook
        assert isinstance(imported_editions, list)
        assert 1 == len(imported_editions)

        [edition] = imported_editions
        assert isinstance(edition, Edition)
        assert edition.primary_identifier.identifier == "9780792766919"
        assert edition.primary_identifier.type == "ISBN"
        assert EditionConstants.AUDIO_MEDIUM == edition.medium

        # Make sure that license pools have correct configuration
        assert isinstance(pools, list)
        assert 1 == len(pools)

        [license_pool] = pools
        assert not license_pool.open_access
        assert 1 == license_pool.licenses_owned
        assert 1 == license_pool.licenses_available

        assert 2 == len(license_pool.delivery_mechanisms)

        lcp_delivery_mechanism = (
            self._get_delivery_mechanism_by_drm_scheme_and_content_type(
                license_pool.delivery_mechanisms,
                MediaTypes.AUDIOBOOK_PACKAGE_LCP_MEDIA_TYPE,
                DeliveryMechanism.LCP_DRM,
            )
        )
        assert lcp_delivery_mechanism is not None

        feedbooks_delivery_mechanism = (
            self._get_delivery_mechanism_by_drm_scheme_and_content_type(
                license_pool.delivery_mechanisms,
                MediaTypes.AUDIOBOOK_MANIFEST_MEDIA_TYPE,
                DeliveryMechanism.FEEDBOOKS_AUDIOBOOK_DRM,
            )
        )
        assert feedbooks_delivery_mechanism is not None
    def setup_method(self, mock_search=True):
        super(TestLCPAPI, self).setup_method()

        self._lcp_collection = self._collection(
            protocol=ExternalIntegration.LCP)
        self._integration = self._lcp_collection.external_integration
        integration_association = create_autospec(spec=HasExternalIntegration)
        integration_association.external_integration = MagicMock(
            return_value=self._integration)
        self._configuration_storage = ConfigurationStorage(
            integration_association)
        self._configuration_factory = ConfigurationFactory()
示例#6
0
    def setup_method(self, mock_search=True):
        super(TestProQuestAPIClient, self).setup_method()

        self._proquest_collection = self._collection(
            protocol=ExternalIntegration.PROQUEST)
        self._integration = self._proquest_collection.external_integration
        integration_owner = create_autospec(spec=HasExternalIntegration)
        integration_owner.external_integration = MagicMock(
            return_value=self._integration)
        self._configuration_storage = ConfigurationStorage(integration_owner)
        self._configuration_factory = ConfigurationFactory()
        self._client = ProQuestAPIClient(self._configuration_storage,
                                         self._configuration_factory)
    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)
示例#8
0
    def _create_lcp_server(self):
        """Creates a new instance of LCPServer

        :return: New instance of LCPServer
        :rtype: LCPServer
        """
        configuration_storage = ConfigurationStorage(self)
        configuration_factory = ConfigurationFactory()
        hasher_factory = HasherFactory()
        credential_factory = LCPCredentialFactory()
        lcp_server = LCPServer(configuration_storage, configuration_factory,
                               hasher_factory, credential_factory)

        return lcp_server
示例#9
0
    def create(self, integration_association):
        """Create a new instance of ProQuestAPIClientFactory.

        :param integration_association: Association with an external integration
        :type integration_association: core.model.configuration.HasExternalIntegration

        :return: New instance of ProQuestAPIClient
        :rtype: ProQuestAPIClient
        """
        configuration_storage = ConfigurationStorage(integration_association)
        configuration_factory = ConfigurationFactory()
        client = ProQuestAPIClient(configuration_storage,
                                   configuration_factory)

        return client
示例#10
0
    def test_local_lcpencrypt(
        self,
        _,
        file_path,
        lcpencrypt_output,
        expected_result,
        expected_exception=None,
        create_file=True,
    ):
        # Arrange
        integration_owner = create_autospec(spec=HasExternalIntegration)
        integration_owner.external_integration = MagicMock(
            return_value=self._integration)
        configuration_storage = ConfigurationStorage(integration_owner)
        configuration_factory = ConfigurationFactory()
        encryptor = LCPEncryptor(configuration_storage, configuration_factory)
        identifier = Identifier(identifier=fixtures.BOOK_IDENTIFIER)

        with configuration_factory.create(
                configuration_storage, self._db,
                LCPEncryptionConfiguration) as configuration:
            configuration.lcpencrypt_location = (
                LCPEncryptionConfiguration.DEFAULT_LCPENCRYPT_LOCATION)

            with Patcher() as patcher:
                patcher.fs.create_file(
                    LCPEncryptionConfiguration.DEFAULT_LCPENCRYPT_LOCATION)

                if create_file:
                    patcher.fs.create_file(file_path)

                with patch("subprocess.check_output"
                           ) as subprocess_check_output_mock:
                    subprocess_check_output_mock.return_value = lcpencrypt_output

                    if expected_exception:
                        with pytest.raises(expected_exception.__class__
                                           ) as exception_metadata:
                            encryptor.encrypt(self._db, file_path,
                                              identifier.identifier)

                        # Assert
                        assert exception_metadata.value == expected_exception
                    else:
                        # Assert
                        result = encryptor.encrypt(self._db, file_path,
                                                   identifier.identifier)
                        assert result == expected_result
示例#11
0
    def setup(self, mock_search=True):
        super(TestLCPServer, self).setup()

        self._lcp_collection = self._collection(
            protocol=ExternalIntegration.LCP)
        self._integration = self._lcp_collection.external_integration
        integration_owner = create_autospec(spec=HasExternalIntegration)
        integration_owner.external_integration = MagicMock(
            return_value=self._integration)
        self._configuration_storage = ConfigurationStorage(integration_owner)
        self._configuration_factory = ConfigurationFactory()
        self._hasher_factory = HasherFactory()
        self._credential_factory = LCPCredentialFactory()
        self._lcp_server = LCPServer(self._configuration_storage,
                                     self._configuration_factory,
                                     self._hasher_factory,
                                     self._credential_factory)
示例#12
0
    def create(self, integration_association):
        """Creates a new instance of LCPServer

        :param integration_association: Association with an external integration
        :type integration_association: core.model.configuration.HasExternalIntegration

        :return: New instance of LCPServer
        :rtype: LCPServer
        """
        configuration_storage = ConfigurationStorage(integration_association)
        configuration_factory = ConfigurationFactory()
        hasher_factory = HasherFactory()
        credential_factory = LCPCredentialFactory()
        lcp_server = LCPServer(configuration_storage, configuration_factory,
                               hasher_factory, credential_factory)

        return lcp_server
示例#13
0
    def create(self, integration_owner):
        """
        Creates a new instance of SAMLAuthenticationManager class

        :param integration_owner: External integration owner
        :type integration_owner: api.saml.configuration.ExternalIntegrationOwner

        :return: SAML authentication manager
        :rtype: SAMLAuthenticationManager
        """
        configuration_storage = ConfigurationStorage(integration_owner)
        configuration = SAMLConfiguration(configuration_storage, SAMLMetadataParser())
        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        subject_parser = SAMLSubjectParser()
        authentication_manager = SAMLAuthenticationManager(onelogin_configuration, subject_parser)

        return authentication_manager
示例#14
0
    def test_configuration_metadata_correctly_recognize_bool_values(
            self, _, value, expected_result):
        """Ensure that ConfigurationMetadata.to_bool correctly translates different values into boolean (True/False).

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

        :param value: Configuration setting's value
        :type value: Any

        :param expected_result: Expected boolean result
        :type expected_result: bool
        """
        # Arrange
        external_integration = self._external_integration("test")

        external_integration_association = create_autospec(
            spec=HasExternalIntegration)
        external_integration_association.external_integration = MagicMock(
            return_value=external_integration)

        configuration_storage = ConfigurationStorage(
            external_integration_association)

        configuration = ConfigurationWithBooleanProperty(
            configuration_storage, self._db)

        # We set a new value using ConfigurationMetadata.__set__
        configuration.boolean_setting = value

        # Act
        # We read the existing value using ConfigurationMetadata.__get__
        result = ConfigurationMetadata.to_bool(configuration.boolean_setting)

        # Assert
        assert expected_result == result
示例#15
0
    def __init__(
        self,
        db,
        collection,
        parser=None,
        data_source_name=None,
        identifier_mapping=None,
        http_get=None,
        metadata_client=None,
        content_modifier=None,
        map_from_collection=None,
        mirrors=None,
    ):
        """Initialize a new instance of ProQuestOPDS2Importer class.

        :param db: Database session
        :type db: sqlalchemy.orm.session.Session

        :param collection: Circulation Manager's collection.
            LicensePools created by this OPDS2Import class will be associated with the given Collection.
            If this is None, no LicensePools will be created -- only Editions.
        :type collection: Collection

        :param parser: Feed parser
        :type parser: RWPMManifestParser

        :param data_source_name: Name of the source of this OPDS feed.
            All Editions created by this import will be associated with this DataSource.
            If there is no DataSource with this name, one will be created.
            NOTE: If `collection` is provided, its .data_source will take precedence over any value provided here.
            This is only for use when you are importing OPDS metadata without any particular Collection in mind.
        :type data_source_name: str

        :param identifier_mapping: Dictionary used for mapping external identifiers into a set of internal ones
        :type identifier_mapping: Dict

        :param metadata_client: A SimplifiedOPDSLookup object that is used to fill in missing metadata
        :type metadata_client: SimplifiedOPDSLookup

        :param content_modifier: A function that may modify-in-place representations (such as images and EPUB documents)
            as they come in from the network.
        :type content_modifier: Callable

        :param map_from_collection: Identifier mapping
        :type map_from_collection: Dict

        :param mirrors: A dictionary of different MirrorUploader objects for different purposes
        :type mirrors: Dict[MirrorUploader]
        """
        super(ProQuestOPDS2Importer, self).__init__(
            db,
            collection,
            parser if parser else RWPMManifestParser(OPDS2FeedParserFactory()),
            data_source_name,
            identifier_mapping,
            http_get,
            metadata_client,
            content_modifier,
            map_from_collection,
            mirrors,
        )

        self._logger = logging.getLogger(__name__)

        self._configuration_storage = ConfigurationStorage(self)
        self._configuration_factory = ConfigurationFactory()

        factory = ProQuestAPIClientFactory()
        self._api_client = factory.create(self)
        self._credential_manager = ProQuestCredentialManager()
示例#16
0
    def test_import(self, importer, mock_get, datasource, db):
        """Ensure that ODL2Importer2 correctly processes and imports the ODL feed encoded using OPDS 2.x.

        NOTE: `freeze_time` decorator is required to treat the licenses in the ODL feed as non-expired.
        """
        # Arrange
        moby_dick_license = LicenseInfoHelper(
            license=LicenseHelper(
                identifier="urn:uuid:f7847120-fc6f-11e3-8158-56847afe9799",
                concurrency=10,
                checkouts=30,
                expires="2016-04-25T12:25:21+02:00",
            ),
            left=30,
            available=10,
        )

        mock_get.add(moby_dick_license)
        feed = self.get_data("feed.json")

        configuration_storage = ConfigurationStorage(importer)
        configuration_factory = ConfigurationFactory()

        with configuration_factory.create(
            configuration_storage, db, ODL2APIConfiguration
        ) as configuration:
            configuration.skipped_license_formats = json.dumps(["text/html"])

        # Act
        imported_editions, pools, works, failures = importer.import_from_feed(feed)

        # Assert

        # 1. Make sure that there is a single edition only
        assert isinstance(imported_editions, list)
        assert 1 == len(imported_editions)

        [moby_dick_edition] = imported_editions
        assert isinstance(moby_dick_edition, Edition)
        assert moby_dick_edition.primary_identifier.identifier == "978-3-16-148410-0"
        assert moby_dick_edition.primary_identifier.type == "ISBN"

        assert u"Moby-Dick" == moby_dick_edition.title
        assert u"eng" == moby_dick_edition.language
        assert u"eng" == moby_dick_edition.language
        assert EditionConstants.BOOK_MEDIUM == moby_dick_edition.medium
        assert u"Herman Melville" == moby_dick_edition.author

        assert 1 == len(moby_dick_edition.author_contributors)
        [moby_dick_author] = moby_dick_edition.author_contributors
        assert isinstance(moby_dick_author, Contributor)
        assert u"Herman Melville" == moby_dick_author.display_name
        assert u"Melville, Herman" == moby_dick_author.sort_name

        assert 1 == len(moby_dick_author.contributions)
        [moby_dick_author_author_contribution] = moby_dick_author.contributions
        assert isinstance(moby_dick_author_author_contribution, Contribution)
        assert moby_dick_author == moby_dick_author_author_contribution.contributor
        assert moby_dick_edition == moby_dick_author_author_contribution.edition
        assert Contributor.AUTHOR_ROLE == moby_dick_author_author_contribution.role

        assert datasource == moby_dick_edition.data_source

        assert u"Test Publisher" == moby_dick_edition.publisher
        assert datetime.date(2015, 9, 29) == moby_dick_edition.published

        assert u"http://example.org/cover.jpg" == moby_dick_edition.cover_full_url
        assert (
            u"http://example.org/cover-small.jpg"
            == moby_dick_edition.cover_thumbnail_url
        )

        # 2. Make sure that license pools have correct configuration
        assert isinstance(pools, list)
        assert 1 == len(pools)

        [moby_dick_license_pool] = pools
        assert isinstance(moby_dick_license_pool, LicensePool)
        assert moby_dick_license_pool.identifier.identifier == "978-3-16-148410-0"
        assert moby_dick_license_pool.identifier.type == "ISBN"
        assert not moby_dick_license_pool.open_access
        assert 30 == moby_dick_license_pool.licenses_owned
        assert 10 == moby_dick_license_pool.licenses_available

        assert 2 == len(moby_dick_license_pool.delivery_mechanisms)

        moby_dick_epub_adobe_drm_delivery_mechanism = (
            self._get_delivery_mechanism_by_drm_scheme_and_content_type(
                moby_dick_license_pool.delivery_mechanisms,
                MediaTypes.EPUB_MEDIA_TYPE,
                DeliveryMechanism.ADOBE_DRM,
            )
        )
        assert moby_dick_epub_adobe_drm_delivery_mechanism is not None

        moby_dick_epub_lcp_drm_delivery_mechanism = (
            self._get_delivery_mechanism_by_drm_scheme_and_content_type(
                moby_dick_license_pool.delivery_mechanisms,
                MediaTypes.EPUB_MEDIA_TYPE,
                DeliveryMechanism.LCP_DRM,
            )
        )
        assert moby_dick_epub_lcp_drm_delivery_mechanism is not None

        assert 1 == len(moby_dick_license_pool.licenses)
        [moby_dick_license] = moby_dick_license_pool.licenses
        assert (
            "urn:uuid:f7847120-fc6f-11e3-8158-56847afe9799"
            == moby_dick_license.identifier
        )
        assert (
            "http://www.example.com/get{?id,checkout_id,expires,patron_id,passphrase,hint,hint_url,notification_url}"
            == moby_dick_license.checkout_url
        )
        assert "http://www.example.com/status/294024" == moby_dick_license.status_url
        assert (
            datetime.datetime(2016, 4, 25, 10, 25, 21, tzinfo=datetime.timezone.utc)
            == moby_dick_license.expires
        )
        assert 30 == moby_dick_license.checkouts_left
        assert 10 == moby_dick_license.checkouts_available

        # 3. Make sure that work objects contain all the required metadata
        assert isinstance(works, list)
        assert 1 == len(works)

        [moby_dick_work] = works
        assert isinstance(moby_dick_work, Work)
        assert moby_dick_edition == moby_dick_work.presentation_edition
        assert 1 == len(moby_dick_work.license_pools)
        assert moby_dick_license_pool == moby_dick_work.license_pools[0]

        # 4. Make sure that the failure is covered
        assert 1 == len(failures)
        huck_finn_failures = failures["9781234567897"]

        assert 1 == len(huck_finn_failures)
        [huck_finn_failure] = huck_finn_failures
        assert isinstance(huck_finn_failure, CoverageFailure)
        assert "9781234567897" == huck_finn_failure.obj.identifier

        huck_finn_semantic_error = (
            ODL_PUBLICATION_MUST_CONTAIN_EITHER_LICENSES_OR_OA_ACQUISITION_LINK_ERROR(
                node=ODLPublication(
                    metadata=PresentationMetadata(identifier="urn:isbn:9781234567897")
                ),
                node_property=None,
            )
        )
        assert str(huck_finn_semantic_error) == huck_finn_failure.exception
示例#17
0
    def test_get_identity_providers_returns_both_non_federated_and_federated_idps(
            self):
        # Arrange
        non_federated_identity_providers_metadata = (
            fixtures.CORRECT_XML_WITH_MULTIPLE_IDPS)

        federated_identity_provider_entity_ids = json.dumps(
            [fixtures.IDP_1_ENTITY_ID, fixtures.IDP_2_ENTITY_ID])

        metadata_parser = SAMLMetadataParser()
        metadata_parser.parse = MagicMock(side_effect=metadata_parser.parse)

        configuration_storage = ConfigurationStorage(
            self._saml_integration_association)
        configuration_storage.load = MagicMock(
            side_effect=configuration_storage.load)

        saml_configuration_factory = SAMLConfigurationFactory(metadata_parser)

        federation = SAMLFederation("Test federation", "http://localhost")
        federated_idp_1 = SAMLFederatedIdentityProvider(
            federation,
            fixtures.IDP_1_ENTITY_ID,
            fixtures.IDP_1_UI_INFO_EN_DISPLAY_NAME,
            fixtures.CORRECT_XML_WITH_IDP_1,
        )
        federated_idp_2 = SAMLFederatedIdentityProvider(
            federation,
            fixtures.IDP_2_ENTITY_ID,
            fixtures.IDP_2_UI_INFO_EN_DISPLAY_NAME,
            fixtures.CORRECT_XML_WITH_IDP_2,
        )

        self._db.add_all([federation, federated_idp_1, federated_idp_2])

        with saml_configuration_factory.create(
                configuration_storage, self._db,
                SAMLConfiguration) as configuration:
            configuration.non_federated_identity_provider_xml_metadata = (
                non_federated_identity_providers_metadata)
            configuration.federated_identity_provider_entity_ids = (
                federated_identity_provider_entity_ids)

            # Act
            identity_providers = configuration.get_identity_providers(self._db)

            # Assert
            assert 4 == len(identity_providers)
            assert True == isinstance(identity_providers[0],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_1_ENTITY_ID == identity_providers[0].entity_id

            assert True == isinstance(identity_providers[1],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_2_ENTITY_ID == identity_providers[1].entity_id

            assert True == isinstance(identity_providers[2],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_1_ENTITY_ID == identity_providers[2].entity_id

            assert True == isinstance(identity_providers[3],
                                      SAMLIdentityProviderMetadata)
            assert fixtures.IDP_2_ENTITY_ID == identity_providers[3].entity_id

            configuration_storage.load.assert_has_calls([
                call(
                    self._db,
                    SAMLConfiguration.
                    non_federated_identity_provider_xml_metadata.key,
                ),
                call(
                    self._db,
                    SAMLConfiguration.federated_identity_provider_entity_ids.
                    key,
                ),
            ])
            metadata_parser.parse.assert_has_calls([
                call(non_federated_identity_providers_metadata),
                call(federated_idp_1.xml_metadata),
                call(federated_idp_2.xml_metadata),
            ])