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_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_adobe_id_tags_when_vendor_id_configured(self): """When vendor ID delegation is configured, adobe_id_tags() returns a list containing a single tag. The tag contains the information necessary to get an Adobe ID and a link to the local DRM Device Management Protocol endpoint. """ self.initialize_adobe(self._default_library) patron_identifier = "patron identifier" [element] = self.annotator.adobe_id_tags(patron_identifier) eq_('{http://librarysimplified.org/terms/drm}licensor', element.tag) key = '{http://librarysimplified.org/terms/drm}vendor' eq_(self.adobe_vendor_id.username, element.attrib[key]) [token, device_management_link] = element.getchildren() eq_('{http://librarysimplified.org/terms/drm}clientToken', token.tag) # token.text is a token which we can decode, since we know # the secret. token = token.text authdata = AuthdataUtility.from_config(self._default_library) decoded = authdata.decode_short_client_token(token) expected_url = ConfigurationSetting.for_library( Configuration.WEBSITE_URL, self._default_library).value eq_((expected_url, patron_identifier), decoded) eq_("link", device_management_link.tag) eq_("http://librarysimplified.org/terms/drm/rel/devices", device_management_link.attrib['rel']) expect_url = self.annotator.url_for( 'adobe_drm_devices', library_short_name=self._default_library.short_name, _external=True) eq_(expect_url, device_management_link.attrib['href']) # If we call adobe_id_tags again we'll get a distinct tag # object that renders to the same XML. [same_tag] = self.annotator.adobe_id_tags(patron_identifier) assert same_tag is not element eq_(etree.tostring(element), etree.tostring(same_tag))
def test_from_config(self): library = self._default_library library2 = self._library() self.initialize_adobe(library, [library2]) library_url = library.setting(Configuration.WEBSITE_URL).value library2_url = library2.setting(Configuration.WEBSITE_URL).value utility = AuthdataUtility.from_config(library) registry = ExternalIntegration.lookup( self._db, ExternalIntegration.OPDS_REGISTRATION, ExternalIntegration.DISCOVERY_GOAL, library=library ) eq_(library.short_name + "token", ConfigurationSetting.for_library_and_externalintegration( self._db, ExternalIntegration.USERNAME, library, registry).value) eq_(library.short_name + " token secret", ConfigurationSetting.for_library_and_externalintegration( self._db, ExternalIntegration.PASSWORD, library, registry).value) eq_(self.TEST_VENDOR_ID, utility.vendor_id) eq_(library_url, utility.library_uri) eq_( {library2_url : "%s token secret" % library2.short_name, library_url : "%s token secret" % library.short_name}, utility.secrets_by_library_uri ) eq_( {"%sTOKEN" % library.short_name.upper() : library_url, "%sTOKEN" % library2.short_name.upper() : library2_url }, utility.library_uris_by_short_name ) # If an integration is set up but incomplete, from_config # raises CannotLoadConfiguration. setting = ConfigurationSetting.for_library_and_externalintegration( self._db, ExternalIntegration.USERNAME, library, registry) old_short_name = setting.value setting.value = None assert_raises( CannotLoadConfiguration, AuthdataUtility.from_config, library ) setting.value = old_short_name setting = library.setting(Configuration.WEBSITE_URL) old_value = setting.value setting.value = None assert_raises( CannotLoadConfiguration, AuthdataUtility.from_config, library ) setting.value = old_value setting = ConfigurationSetting.for_library_and_externalintegration( self._db, ExternalIntegration.PASSWORD, library, registry) old_secret = setting.value setting.value = None assert_raises( CannotLoadConfiguration, AuthdataUtility.from_config, library ) setting.value = old_secret # If other libraries are not configured, that's fine. We'll # only have a configuration for ourselves. self.adobe_vendor_id.set_setting( AuthdataUtility.OTHER_LIBRARIES_KEY, None ) authdata = AuthdataUtility.from_config(library) eq_({library_url : "%s token secret" % library.short_name}, authdata.secrets_by_library_uri) eq_({"%sTOKEN" % library.short_name.upper(): library_url}, authdata.library_uris_by_short_name) # Short library names are case-insensitive. If the # configuration has the same library short name twice, you # can't create an AuthdataUtility. self.adobe_vendor_id.set_setting( AuthdataUtility.OTHER_LIBRARIES_KEY, json.dumps({ "http://a/" : ("a", "secret1"), "http://b/" : ("A", "secret2"), }) ) assert_raises(ValueError, AuthdataUtility.from_config, library) # If there is no Adobe Vendor ID integration set up, # from_config() returns None. self._db.delete(registry) eq_(None, AuthdataUtility.from_config(library))
Adobe ID. This makes sure that they don't suddenly change Adobe IDs when they start using a client that employs the new JWT-based authdata system. """ import os import sys from pdb import set_trace bin_dir = os.path.split(__file__)[0] package_dir = os.path.join(bin_dir, "..") sys.path.append(os.path.abspath(package_dir)) from core.model import (production_session, Patron) from api.adobe_vendor_id import AuthdataUtility _db = production_session() authdata = AuthdataUtility.from_config() if not authdata: print "Adobe IDs not configured, doing nothing." count = 0 qu = _db.query(Patron) print "Processing %d patrons." % qu.count() for patron in qu: credential, delegated_identifier = authdata.migrate_adobe_id(patron) count += 1 if not (count % 100): print count _db.commit() if credential is None or delegated_identifier is None: # This patron did not have an Adobe ID in the first place. # Do nothing.