def check_client_authorization(self, collection, client): """Verify that an IntegrationClient is whitelisted for access to the collection.""" external_library_urls = ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, collection.external_integration ).json_value if client.url not in [IntegrationClient.normalize_url(url) for url in external_library_urls]: raise AuthorizationFailedException()
def update_client_url(self): """Updates the URL of a IntegrationClient""" client = authenticated_client_from_request(self._db) if isinstance(client, ProblemDetail): return client url = request.args.get('client_url') if not url: return INVALID_INPUT.detailed("No 'client_url' provided") client.url = IntegrationClient.normalize_url(urllib.unquote(url)) return make_response("", HTTP_OK)
def test_register(self): # An auth document URL is required to register. assert_raises(InvalidInputException, self.shared_collection.register, self.collection, None) # If the url doesn't return a valid auth document, there's an exception. auth_response = "not json" def do_get(*args, **kwargs): return MockRequestsResponse(200, content=auth_response) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # The auth document also must have a link to the library's catalog. auth_response = json.dumps({"links": []}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # If no external library URLs are configured, no one can register. auth_response = json.dumps( {"links": [{ "href": "http://library.org", "rel": "start" }]}) ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration).value = None assert_raises(AuthorizationFailedException, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # If the library's URL isn't in the configuration, it can't register. auth_response = json.dumps({ "links": [{ "href": "http://differentlibrary.org", "rel": "start" }] }) ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration).value = json.dumps( ["http://library.org"]) assert_raises(AuthorizationFailedException, self.shared_collection.register, self.collection, "http://differentlibrary.org/auth", do_get=do_get) # Or if the public key is missing from the auth document. auth_response = json.dumps( {"links": [{ "href": "http://library.org", "rel": "start" }]}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) auth_response = json.dumps({ "public_key": { "type": "not RSA", "value": "123" }, "links": [{ "href": "http://library.org", "rel": "start" }] }) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) auth_response = json.dumps({ "public_key": { "type": "RSA" }, "links": [{ "href": "http://library.org", "rel": "start" }] }) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # Here's an auth document with a valid key. key = RSA.generate(2048) public_key = key.publickey().exportKey() encryptor = PKCS1_OAEP.new(key) auth_response = json.dumps({ "public_key": { "type": "RSA", "value": public_key }, "links": [{ "href": "http://library.org", "rel": "start" }] }) response = self.shared_collection.register(self.collection, "http://library.org/auth", do_get=do_get) # An IntegrationClient has been created. client = get_one( self._db, IntegrationClient, url=IntegrationClient.normalize_url("http://library.org/")) decrypted_secret = encryptor.decrypt( base64.b64decode( response.get("metadata", {}).get("shared_secret"))) eq_(client.shared_secret, decrypted_secret)
_("Your library's URL is not one of the allowed URLs for this collection. Ask the collection administrator to add %(library_url)s to the list of allowed URLs.", library_url=start_url)) public_key = auth_document.get("public_key") if not public_key or not public_key.get( "type") == "RSA" or not public_key.get("value"): raise RemoteInitiatedServerError( _("Authentication document at %(auth_document_url)s did not contain an RSA public key.", auth_document_url=auth_document_url), _("Remote authentication document")) public_key = public_key.get("value") public_key = RSA.importKey(public_key) encryptor = PKCS1_OAEP.new(public_key) normalized_url = IntegrationClient.normalize_url(start_url) client = get_one(self._db, IntegrationClient, url=normalized_url) if not client: client, ignore = IntegrationClient.register(self._db, start_url) shared_secret = client.shared_secret encrypted_secret = encryptor.encrypt(str(shared_secret)) return dict(metadata=dict( shared_secret=base64.b64encode(encrypted_secret))) def check_client_authorization(self, collection, client): """Verify that an IntegrationClient is whitelisted for access to the collection.""" external_library_urls = ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, collection.external_integration).json_value if client.url not in [
def register(self, collection, auth_document_url, do_get=HTTP.get_with_timeout): """Register a library on an external circulation manager for access to this collection. The library's auth document url must be whitelisted in the collection's settings.""" if not auth_document_url: raise InvalidInputException( _("An authentication document URL is required to register a library.") ) auth_response = do_get(auth_document_url, allowed_response_codes=["2xx", "3xx"]) try: auth_document = json.loads(auth_response.content) except ValueError as e: raise RemoteInitiatedServerError( _( "Authentication document at %(auth_document_url)s was not valid JSON.", auth_document_url=auth_document_url, ), _("Remote authentication document"), ) links = auth_document.get("links") start_url = None for link in links: if link.get("rel") == "start": start_url = link.get("href") break if not start_url: raise RemoteInitiatedServerError( _( "Authentication document at %(auth_document_url)s did not contain a start link.", auth_document_url=auth_document_url, ), _("Remote authentication document"), ) external_library_urls = ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, collection.external_integration, ).json_value if not external_library_urls or start_url not in external_library_urls: raise AuthorizationFailedException( _( "Your library's URL is not one of the allowed URLs for this collection. Ask the collection administrator to add %(library_url)s to the list of allowed URLs.", library_url=start_url, ) ) public_key = auth_document.get("public_key") if ( not public_key or not public_key.get("type") == "RSA" or not public_key.get("value") ): raise RemoteInitiatedServerError( _( "Authentication document at %(auth_document_url)s did not contain an RSA public key.", auth_document_url=auth_document_url, ), _("Remote authentication document"), ) public_key = public_key.get("value") encryptor = Configuration.cipher(public_key) normalized_url = IntegrationClient.normalize_url(start_url) client = get_one(self._db, IntegrationClient, url=normalized_url) if not client: client, ignore = IntegrationClient.register(self._db, start_url) shared_secret = client.shared_secret.encode("utf-8") encrypted_secret = encryptor.encrypt(shared_secret) return dict(metadata=dict(shared_secret=base64.b64encode(encrypted_secret)))
def test_register(self): # An auth document URL is required to register. assert_raises(InvalidInputException, self.shared_collection.register, self.collection, None) # If the url doesn't return a valid auth document, there's an exception. auth_response = "not json" def do_get(*args, **kwargs): return MockRequestsResponse(200, content=auth_response) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # The auth document also must have a link to the library's catalog. auth_response = json.dumps({"links": []}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # If no external library URLs are configured, no one can register. auth_response = json.dumps({"links": [{"href": "http://library.org", "rel": "start"}]}) ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration ).value = None assert_raises(AuthorizationFailedException, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # If the library's URL isn't in the configuration, it can't register. auth_response = json.dumps({"links": [{"href": "http://differentlibrary.org", "rel": "start"}]}) ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, self.collection.external_integration ).value = json.dumps(["http://library.org"]) assert_raises(AuthorizationFailedException, self.shared_collection.register, self.collection, "http://differentlibrary.org/auth", do_get=do_get) # Or if the public key is missing from the auth document. auth_response = json.dumps({"links": [{"href": "http://library.org", "rel": "start"}]}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) auth_response = json.dumps({"public_key": { "type": "not RSA", "value": "123" }, "links": [{"href": "http://library.org", "rel": "start"}]}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) auth_response = json.dumps({"public_key": { "type": "RSA" }, "links": [{"href": "http://library.org", "rel": "start"}]}) assert_raises(RemoteInitiatedServerError, self.shared_collection.register, self.collection, "http://library.org/auth", do_get=do_get) # Here's an auth document with a valid key. key = RSA.generate(2048) public_key = key.publickey().exportKey() encryptor = PKCS1_OAEP.new(key) auth_response = json.dumps({"public_key": { "type": "RSA", "value": public_key }, "links": [{"href": "http://library.org", "rel": "start"}]}) response = self.shared_collection.register(self.collection, "http://library.org/auth", do_get=do_get) # An IntegrationClient has been created. client = get_one(self._db, IntegrationClient, url=IntegrationClient.normalize_url("http://library.org/")) decrypted_secret = encryptor.decrypt(base64.b64decode(response.get("metadata", {}).get("shared_secret"))) eq_(client.shared_secret, decrypted_secret)
if not external_library_urls or start_url not in external_library_urls: raise AuthorizationFailedException( _("Your library's URL is not one of the allowed URLs for this collection. Ask the collection administrator to add %(library_url)s to the list of allowed URLs.", library_url=start_url)) public_key = auth_document.get("public_key") if not public_key or not public_key.get("type") == "RSA" or not public_key.get("value"): raise RemoteInitiatedServerError( _("Authentication document at %(auth_document_url)s did not contain an RSA public key.", auth_document_url=auth_document_url), _("Remote authentication document")) public_key = public_key.get("value") encryptor = Configuration.cipher(public_key) normalized_url = IntegrationClient.normalize_url(start_url) client = get_one(self._db, IntegrationClient, url=normalized_url) if not client: client, ignore = IntegrationClient.register(self._db, start_url) shared_secret = client.shared_secret encrypted_secret = encryptor.encrypt(str(shared_secret)) return dict(metadata=dict(shared_secret=base64.b64encode(encrypted_secret))) def check_client_authorization(self, collection, client): """Verify that an IntegrationClient is whitelisted for access to the collection.""" external_library_urls = ConfigurationSetting.for_externalintegration( BaseSharedCollectionAPI.EXTERNAL_LIBRARY_URLS, collection.external_integration ).json_value if client.url not in [IntegrationClient.normalize_url(url) for url in external_library_urls]: raise AuthorizationFailedException()