Example #1
0
    def setup(self):
        super(TestVendorIDModel, self).setup()

        # This library is going to act as the Vendor ID server.
        self.vendor_id_library = self._default_library
        # This library can create Short Client Tokens that the Vendor
        # ID server will recognize.
        self.short_client_token_library = self._library(
            short_name="shortclienttoken"
        )

        # Initialize the Adobe-specific ExternalIntegrations for both
        # libraries.
        self.initialize_adobe(
            self.vendor_id_library, [self.short_client_token_library]
        )
        
        # Set up a simple authentication provider that validates
        # one specific patron.
        integration = self._external_integration(self._str)
        provider = SimpleAuthenticationProvider
        integration.setting(provider.TEST_IDENTIFIER).value = "validpatron"
        integration.setting(provider.TEST_PASSWORD).value = "password"
        self.authenticator = SimpleAuthenticationProvider(
            self._default_library, integration
        )
        
        self.model = AdobeVendorIDModel(
            self._default_library, self.authenticator, self.TEST_NODE_VALUE)
        self.data_source = DataSource.lookup(self._db, DataSource.ADOBE)

        self.bob_patron = self.authenticator.authenticated_patron(
            self._db, dict(username="******", password="******"))
Example #2
0
    def test_migrate_adobe_id_success(self):
        from api.opds import CirculationManagerAnnotator
        patron = self._patron()

        # This patron has a Credential containing their Adobe ID
        data_source = DataSource.lookup(self._db, DataSource.ADOBE)
        adobe_id = Credential(
            patron=patron, data_source=data_source,
            type=AdobeVendorIDModel.VENDOR_ID_UUID_TOKEN_TYPE,
            credential="My Adobe ID"
        )

        # Run the migration.
        new_credential, delegated_identifier = self.authdata.migrate_adobe_id(patron)
        
        # The patron now has _two_ Credentials -- the old one
        # containing the Adobe ID, and a new one.
        eq_(set([new_credential, adobe_id]), set(patron.credentials))

        # The new credential contains an anonymized patron identifier
        # used solely to connect the patron to their Adobe ID.
        eq_(AuthdataUtility.ADOBE_ACCOUNT_ID_PATRON_IDENTIFIER,
            new_credential.type)

        # We can use that identifier to look up a DelegatedPatronIdentifier
        # 
        def explode():
            # This method won't be called because the
            # DelegatedPatronIdentifier already exists.
            raise Exception()
        identifier, is_new = DelegatedPatronIdentifier.get_one_or_create(
            self._db, self.authdata.library_uri, new_credential.credential,
            DelegatedPatronIdentifier.ADOBE_ACCOUNT_ID, explode
        )
        eq_(delegated_identifier, identifier)
        eq_(False, is_new)
        eq_("My Adobe ID", identifier.delegated_identifier)

        # An integration-level test:
        # AdobeVendorIDModel.to_delegated_patron_identifier_uuid works
        # now.
        model = AdobeVendorIDModel(self._default_library, None, None)
        uuid, label = model.to_delegated_patron_identifier_uuid(
            self.authdata.library_uri, new_credential.credential
        )
        eq_("My Adobe ID", uuid)
        eq_('Delegated account ID My Adobe ID', label)
        
        # If we run the migration again, nothing new happens.
        new_credential_2, delegated_identifier_2 = self.authdata.migrate_adobe_id(patron)
        eq_(new_credential, new_credential_2)
        eq_(delegated_identifier, delegated_identifier_2)
        eq_(2, len(patron.credentials))
        uuid, label = model.to_delegated_patron_identifier_uuid(
            self.authdata.library_uri, new_credential.credential
        )
        eq_("My Adobe ID", uuid)
        eq_('Delegated account ID My Adobe ID', label)
Example #3
0
 def setup(self):
     super(TestVendorIDModel, self).setup()
     self.authenticator = DummyMilleniumPatronAPI()
     self.model = AdobeVendorIDModel(self._db, self.authenticator,
                                     self.TEST_NODE_VALUE)
     self.data_source = DataSource.lookup(self._db, DataSource.ADOBE)
     # Normally this test patron doesn't have an authorization identifier.
     # Let's make sure there is one so it'll show up as the label.
     self.bob_patron = self.authenticator.authenticated_patron(
         self._db, dict(username="******", password="******"))
     self.bob_patron.authorization_identifier = "5"
Example #4
0
class TestVendorIDModel(VendorIDTest):

    TEST_NODE_VALUE = 114740953091845
    
    credentials = dict(username="******", password="******")
    
    def setup(self):
        super(TestVendorIDModel, self).setup()

        # This library is going to act as the Vendor ID server.
        self.vendor_id_library = self._default_library
        # This library can create Short Client Tokens that the Vendor
        # ID server will recognize.
        self.short_client_token_library = self._library(
            short_name="shortclienttoken"
        )

        # Initialize the Adobe-specific ExternalIntegrations for both
        # libraries.
        self.initialize_adobe(
            self.vendor_id_library, [self.short_client_token_library]
        )
        
        # Set up a simple authentication provider that validates
        # one specific patron.
        integration = self._external_integration(self._str)
        provider = SimpleAuthenticationProvider
        integration.setting(provider.TEST_IDENTIFIER).value = "validpatron"
        integration.setting(provider.TEST_PASSWORD).value = "password"
        self.authenticator = SimpleAuthenticationProvider(
            self._default_library, integration
        )
        
        self.model = AdobeVendorIDModel(
            self._default_library, self.authenticator, self.TEST_NODE_VALUE)
        self.data_source = DataSource.lookup(self._db, DataSource.ADOBE)

        self.bob_patron = self.authenticator.authenticated_patron(
            self._db, dict(username="******", password="******"))
        
    def test_uuid(self):
        u = self.model.uuid()
        # All UUIDs need to start with a 0 and end with the same node
        # value.
        assert u.startswith('urn:uuid:0')
        assert u.endswith('685b35c00f05')

    def test_uuid_and_label_respects_existing_id(self):
        uuid, label = self.model.uuid_and_label(self.bob_patron)
        uuid2, label2 = self.model.uuid_and_label(self.bob_patron)
        eq_(uuid, uuid2)
        eq_(label, label2)

    def test_uuid_and_label_creates_delegatedpatronid_from_credential(self):
       
        # This patron once used the old system to create an Adobe
        # account ID which was stored in a Credential. For whatever
        # reason, the migration script did not give them a
        # DelegatedPatronIdentifier.
        adobe = self.data_source
        def set_value(credential):
            credential.credential = "A dummy value"
        old_style_credential = Credential.lookup(
            self._db, adobe, self.model.VENDOR_ID_UUID_TOKEN_TYPE,
            self.bob_patron, set_value, True
        )

        # Now uuid_and_label works.
        uuid, label = self.model.uuid_and_label(self.bob_patron)
        eq_("A dummy value", uuid)
        eq_("Delegated account ID A dummy value", label)

        # There is now an anonymized identifier associated with Bob's
        # patron account.
        internal = DataSource.lookup(self._db, DataSource.INTERNAL_PROCESSING)
        bob_anonymized_identifier = Credential.lookup(
            self._db, internal,
            AuthdataUtility.ADOBE_ACCOUNT_ID_PATRON_IDENTIFIER,
            self.bob_patron, None
        )

        # That anonymized identifier is associated with a
        # DelegatedPatronIdentifier whose delegated_identifier is
        # taken from the old-style Credential.
        [bob_delegated_patron_identifier] = self._db.query(
            DelegatedPatronIdentifier).filter(
                DelegatedPatronIdentifier.patron_identifier
                ==bob_anonymized_identifier.credential
            ).all()
        eq_("A dummy value",
            bob_delegated_patron_identifier.delegated_identifier)

        # If the DelegatedPatronIdentifier and the Credential
        # have different values, the DelegatedPatronIdentifier wins.
        old_style_credential.credential = "A different value."
        uuid, label = self.model.uuid_and_label(self.bob_patron)
        eq_("A dummy value", uuid)
        
        # We can even delete the old-style Credential, and
        # uuid_and_label will still give the value that was stored in
        # it.
        self._db.delete(old_style_credential)
        self._db.commit()
        uuid, label = self.model.uuid_and_label(self.bob_patron)
        eq_("A dummy value", uuid)

        
    def test_create_authdata(self):
        credential = self.model.create_authdata(self.bob_patron)

        # There's now a persistent token associated with Bob's
        # patron account, and that's the token returned by create_authdata()
        bob_authdata = Credential.lookup(
            self._db, self.data_source, self.model.AUTHDATA_TOKEN_TYPE,
            self.bob_patron, None)
        eq_(credential.credential, bob_authdata.credential)      
        
    def test_to_delegated_patron_identifier_uuid(self):
        
        foreign_uri = "http://your-library/"
        foreign_identifier = "foreign ID"

        # Pass in nothing and you get nothing.
        eq_((None, None),
            self.model.to_delegated_patron_identifier_uuid(foreign_uri, None)
        )
        eq_((None, None),
            self.model.to_delegated_patron_identifier_uuid(
                None, foreign_identifier
            )
        )

        # Pass in a URI and identifier and you get a UUID and a label.
        uuid, label = self.model.to_delegated_patron_identifier_uuid(
            foreign_uri, foreign_identifier
        )

        # We can't test a specific value for the UUID but we can test the label.
        eq_("Delegated account ID " + uuid, label)

        # And we can verify that a DelegatedPatronIdentifier was
        # created for the URI+identifier, and that it contains the
        # UUID.
        [dpi] = self._db.query(DelegatedPatronIdentifier).filter(
            DelegatedPatronIdentifier.library_uri==foreign_uri).filter(
            DelegatedPatronIdentifier.patron_identifier==foreign_identifier
        ).all()
        eq_(uuid, dpi.delegated_identifier)

    def test_authdata_lookup_delegated_patron_identifier_success(self):
        """Test that one library can perform an authdata lookup on a JWT
        generated by a different library.
        """
        # Here's a library that's not a Vendor ID server, but which
        # can generate a JWT for one of its patrons.
        sct_library = self.short_client_token_library
        utility = AuthdataUtility.from_config(sct_library)
        vendor_id, jwt = utility.encode("Foreign patron")

        # Here's an AuthdataUtility for the library that _is_
        # a Vendor ID server.
        vendor_id_utility = AuthdataUtility.from_config(self.vendor_id_library)

        # The Vendor ID library knows the secret it shares with the
        # other library -- initialize_adobe() took care of that.
        sct_library_uri = sct_library.setting(Configuration.WEBSITE_URL).value
        eq_("%s token secret" % sct_library.short_name,
            vendor_id_utility.secrets_by_library_uri[sct_library_uri]
        )

        # Because this library shares the other library's secret,
        # it can decode a JWT issued by the other library, and
        # issue an Adobe ID (UUID).
        uuid, label = self.model.authdata_lookup(jwt)

        # We get the same result if we smuggle the JWT into
        # a username/password lookup as the username.
        uuid2, label2 = self.model.standard_lookup(dict(username=jwt))
        eq_(uuid2, uuid)
        eq_(label2, label)
            
        # The UUID corresponds to a DelegatedPatronIdentifier,
        # associated with the foreign library and the patron
        # identifier that library encoded in its JWT.
        [dpi] = self._db.query(DelegatedPatronIdentifier).filter(
            DelegatedPatronIdentifier.library_uri==sct_library_uri).filter(
                DelegatedPatronIdentifier.patron_identifier=="Foreign patron"
            ).all()
        eq_(uuid, dpi.delegated_identifier)
        eq_("Delegated account ID %s" % uuid, label)

    def test_short_client_token_lookup_delegated_patron_identifier_success(self):
        """Test that one library can perform an authdata lookup on a short
        client token generated by a different library.
        """
        # Here's a library that's not a Vendor ID server, but which can
        # generate a Short Client Token for one of its patrons.
        sct_library = self.short_client_token_library
        utility = AuthdataUtility.from_config(sct_library)
        vendor_id, short_client_token = utility.encode_short_client_token(
            "Foreign patron"
        )

        # Here's an AuthdataUtility for the library that _is_
        # a Vendor ID server.
        vendor_id_utility = AuthdataUtility.from_config(self.vendor_id_library)

        # The Vendor ID library knows the secret it shares with the
        # other library -- initialize_adobe() took care of that.
        sct_library_url = sct_library.setting(Configuration.WEBSITE_URL).value
        eq_("%s token secret" % sct_library.short_name,
            vendor_id_utility.secrets_by_library_uri[sct_library_url]
        )
        
        # Because the Vendor ID library shares the Short Client Token
        # library's secret, it can decode a short client token issued
        # by that library, and issue an Adobe ID (UUID).
        token, signature = short_client_token.rsplit("|", 1)
        uuid, label = self.model.short_client_token_lookup(
            token, signature
        )
        
        # The UUID corresponds to a DelegatedPatronIdentifier,
        # associated with the foreign library and the patron
        # identifier that library encoded in its JWT.
        [dpi] = self._db.query(DelegatedPatronIdentifier).filter(
            DelegatedPatronIdentifier.library_uri==sct_library_url).filter(
                DelegatedPatronIdentifier.patron_identifier=="Foreign patron"
            ).all()
        eq_(uuid, dpi.delegated_identifier)
        eq_("Delegated account ID %s" % uuid, label)

        # We get the same UUID and label by passing the token and
        # signature to standard_lookup as username and password.
        # (That's because standard_lookup calls short_client_token_lookup
        # behind the scenes.)
        credentials = dict(username=token, password=signature)
        new_uuid, new_label = self.model.standard_lookup(credentials)
        eq_(new_uuid, uuid)
        eq_(new_label, label)
        
    def test_short_client_token_lookup_delegated_patron_identifier_failure(self):
        uuid, label = self.model.short_client_token_lookup(
            "bad token", "bad signature"
        )
        eq_(None, uuid)
        eq_(None, label)
        
    def test_username_password_lookup_success(self):
        urn, label = self.model.standard_lookup(self.credentials)

        # There is now an anonymized identifier associated with Bob's
        # patron account.
        internal = DataSource.lookup(self._db, DataSource.INTERNAL_PROCESSING)
        bob_anonymized_identifier = Credential.lookup(
            self._db, internal,
            AuthdataUtility.ADOBE_ACCOUNT_ID_PATRON_IDENTIFIER,
            self.bob_patron, None
        )

        # That anonymized identifier is associated with a
        # DelegatedPatronIdentifier whose delegated_identifier is a
        # UUID.
        [bob_delegated_patron_identifier] = self._db.query(
            DelegatedPatronIdentifier).filter(
                DelegatedPatronIdentifier.patron_identifier
                ==bob_anonymized_identifier.credential
            ).all()

        eq_("Delegated account ID %s" % urn, label)
        eq_(urn, bob_delegated_patron_identifier.delegated_identifier)
        assert urn.startswith("urn:uuid:0")
        assert urn.endswith('685b35c00f05')

    def test_authdata_token_credential_lookup_success(self):
        
        # Create an authdata token Credential for Bob.
        now = datetime.datetime.utcnow()
        token, ignore = Credential.persistent_token_create(
            self._db, self.data_source, self.model.AUTHDATA_TOKEN_TYPE,
            self.bob_patron
        )

        # The token is persistent.
        eq_(None, token.expires)

        # Use that token to perform a lookup of Bob's Adobe Vendor ID
        # UUID.
        urn, label = self.model.authdata_lookup(token.credential)

        # There is now an anonymized identifier associated with Bob's
        # patron account.
        internal = DataSource.lookup(self._db, DataSource.INTERNAL_PROCESSING)
        bob_anonymized_identifier = Credential.lookup(
            self._db, internal,
            AuthdataUtility.ADOBE_ACCOUNT_ID_PATRON_IDENTIFIER,
            self.bob_patron, None
        )

        # That anonymized identifier is associated with a
        # DelegatedPatronIdentifier whose delegated_identifier is a
        # UUID.
        [bob_delegated_patron_identifier] = self._db.query(
            DelegatedPatronIdentifier).filter(
                DelegatedPatronIdentifier.patron_identifier
                ==bob_anonymized_identifier.credential
            ).all()

        # That UUID is the one returned by authdata_lookup.
        eq_(urn, bob_delegated_patron_identifier.delegated_identifier)

    def test_smuggled_authdata_credential_success(self):
        # Bob's client has created a persistent token to authenticate him.
        now = datetime.datetime.utcnow()
        token, ignore = Credential.persistent_token_create(
            self._db, self.data_source, self.model.AUTHDATA_TOKEN_TYPE,
            self.bob_patron
        )

        # But Bob's client can't trigger the operation that will cause
        # Adobe to authenticate him via that token, so it passes in
        # the token credential as the 'username' and leaves the
        # password blank.
        urn, label = self.model.standard_lookup(
            dict(username=token.credential)
        )

        # There is now an anonymized identifier associated with Bob's
        # patron account.
        internal = DataSource.lookup(self._db, DataSource.INTERNAL_PROCESSING)
        bob_anonymized_identifier = Credential.lookup(
            self._db, internal,
            AuthdataUtility.ADOBE_ACCOUNT_ID_PATRON_IDENTIFIER,
            self.bob_patron, None
        )

        # That anonymized identifier is associated with a
        # DelegatedPatronIdentifier whose delegated_identifier is a
        # UUID.
        [bob_delegated_patron_identifier] = self._db.query(
            DelegatedPatronIdentifier).filter(
                DelegatedPatronIdentifier.patron_identifier
                ==bob_anonymized_identifier.credential
            ).all()

        # That UUID is the one returned by standard_lookup.
        eq_(urn, bob_delegated_patron_identifier.delegated_identifier)

        # A future attempt to authenticate with the token will succeed.
        urn, label = self.model.standard_lookup(
            dict(username=token.credential)
        )
        eq_(urn, bob_delegated_patron_identifier.delegated_identifier)

    def test_authdata_lookup_failure_no_token(self):
        urn, label = self.model.authdata_lookup("nosuchauthdata")
        eq_(None, urn)
        eq_(None, label)

    def test_authdata_lookup_failure_wrong_token(self):
        # Bob has an authdata token.
        token, ignore = Credential.persistent_token_create(
            self._db, self.data_source, self.model.AUTHDATA_TOKEN_TYPE,
            self.bob_patron
        )

        # But we look up a different token and get nothing.
        urn, label = self.model.authdata_lookup("nosuchauthdata")
        eq_(None, urn)
        eq_(None, label)

    def test_urn_to_label_success(self):
        urn, label = self.model.standard_lookup(self.credentials)
        label2 = self.model.urn_to_label(urn)
        eq_(label, label2)
        eq_("Delegated account ID %s" % urn, label)
Example #5
0
class TestVendorIDModel(DatabaseTest):

    TEST_NODE_VALUE = 114740953091845

    def setup(self):
        super(TestVendorIDModel, self).setup()
        self.authenticator = DummyMilleniumPatronAPI()
        self.model = AdobeVendorIDModel(self._db, self.authenticator,
                                        self.TEST_NODE_VALUE)
        self.data_source = DataSource.lookup(self._db, DataSource.ADOBE)
        # Normally this test patron doesn't have an authorization identifier.
        # Let's make sure there is one so it'll show up as the label.
        self.bob_patron = self.authenticator.authenticated_patron(
            self._db, dict(username="******", password="******"))
        self.bob_patron.authorization_identifier = "5"

    def test_uuid(self):
        u = self.model.uuid()
        # All UUIDs need to start with a 0 and end with the same node
        # value.
        assert u.startswith('urn:uuid:0')
        assert u.endswith('685b35c00f05')

    def test_uuid_and_label_respects_existing_id(self):
        uuid, label = self.model.uuid_and_label(self.bob_patron)
        uuid2, label2 = self.model.uuid_and_label(self.bob_patron)
        eq_(uuid, uuid2)
        eq_(label, label2)

    def test_create_authdata(self):
        credential = self.model.create_authdata(self.bob_patron)

        # There's now a temporary token associated with Bob's
        # patron account, and that's the token returned by create_authdata()
        bob_authdata = Credential.lookup(
            self._db, self.data_source, self.model.TEMPORARY_TOKEN_TYPE,
            self.bob_patron, None)
        eq_(credential.credential, bob_authdata.credential)

    def test_standard_lookup_success(self):
        urn, label = self.model.standard_lookup(dict(username="******", password="******"))

        # There is now a UUID associated with Bob's patron account,
        # and that's the UUID returned by standard_lookup().
        bob_uuid = Credential.lookup(
            self._db, self.data_source, self.model.VENDOR_ID_UUID_TOKEN_TYPE,
            self.bob_patron, None)
        eq_("Card number 5", label)
        eq_(urn, bob_uuid.credential)
        assert urn.startswith("urn:uuid:0")
        assert urn.endswith('685b35c00f05')

    def test_authdata_lookup_success(self):
        now = datetime.datetime.utcnow()
        temp_token, ignore = Credential.temporary_token_create(
            self._db, self.data_source, self.model.TEMPORARY_TOKEN_TYPE,
            self.bob_patron, datetime.timedelta(seconds=60))
        old_expires = temp_token.expires
        assert temp_token.expires > now
        urn, label = self.model.authdata_lookup(temp_token.credential)

        # There is now a UUID associated with Bob's patron account,
        # and that's the UUID returned by standard_lookup().
        bob_uuid = Credential.lookup(
            self._db, self.data_source, self.model.VENDOR_ID_UUID_TOKEN_TYPE,
            self.bob_patron, None)
        eq_(urn, bob_uuid.credential)
        eq_("Card number 5", label)

        # Having been used once, the temporary token has been expired.
        assert temp_token.expires < now

    def test_authdata_lookup_failure_no_token(self):
        urn, label = self.model.authdata_lookup("nosuchauthdata")
        eq_(None, urn)
        eq_(None, label)

    def test_authdata_lookup_failure_wrong_token(self):
        temp_token, ignore = Credential.temporary_token_create(
            self._db, self.data_source, self.model.TEMPORARY_TOKEN_TYPE,
            self.bob_patron, datetime.timedelta(seconds=60))
        urn, label = self.model.authdata_lookup("nosuchauthdata")
        eq_(None, urn)
        eq_(None, label)

    def test_urn_to_label_success(self):
        urn, label = self.model.standard_lookup(dict(username="******", password="******"))
        label2 = self.model.urn_to_label(urn)
        eq_(label, label2)
        eq_("Card number 5", label)

    def test_urn_to_label_failure_no_active_credential(self):
        label = self.model.urn_to_label("bad urn")
        eq_(None, label)

    def test_urn_to_label_failure_incorrect_urn(self):
        urn, label = self.model.standard_lookup(dict(username="******", password="******"))
        label = self.model.urn_to_label("bad urn")
        eq_(None, label)