def test_estimate_language_collection_for_library(self): library = self._default_library # We thought we'd have big collections. old_settings = { Configuration.LARGE_COLLECTION_LANGUAGES : ["spa", "fre"], Configuration.SMALL_COLLECTION_LANGUAGES : ["chi"], Configuration.TINY_COLLECTION_LANGUAGES : ["rus"], } for key, value in old_settings.items(): ConfigurationSetting.for_library( key, library).value = json.dumps(value) # But there's nothing in our database, so when we call # Configuration.estimate_language_collections_for_library... Configuration.estimate_language_collections_for_library(library) # ...it gets reset to the default. eq_(["eng"], ConfigurationSetting.for_library( Configuration.LARGE_COLLECTION_LANGUAGES, library ).json_value) eq_([], ConfigurationSetting.for_library( Configuration.SMALL_COLLECTION_LANGUAGES, library ).json_value) eq_([], ConfigurationSetting.for_library( Configuration.TINY_COLLECTION_LANGUAGES, library ).json_value)
def test_key_pair(self): # Test the ability to create, replace, or look up a # public/private key pair in a ConfigurationSetting. setting = ConfigurationSetting.sitewide( self._db, Configuration.KEY_PAIR ) setting.value = "nonsense" # If you pass in a ConfigurationSetting that is missing its # value, or whose value is not a public key pair, a new key # pair is created. public_key, private_key = Configuration.key_pair(setting) assert 'BEGIN PUBLIC KEY' in public_key assert 'BEGIN RSA PRIVATE KEY' in private_key eq_([public_key, private_key], setting.json_value) setting.value = None public_key, private_key = Configuration.key_pair(setting) assert 'BEGIN PUBLIC KEY' in public_key assert 'BEGIN RSA PRIVATE KEY' in private_key eq_([public_key, private_key], setting.json_value) # If the setting has a good value already, the key pair is # returned as is. new_public, new_private = Configuration.key_pair(setting) eq_(new_public, public_key) eq_(new_private, private_key)
def __init__(self): providers = [] if Configuration.integration('3M'): providers.append(BibliothecaBibliographicCoverageProvider) if Configuration.integration('Overdrive'): providers.append(OverdriveBibliographicCoverageProvider) if Configuration.integration('Axis 360'): providers.append(Axis360BibliographicCoverageProvider) if not providers: raise Exception("No licensed book sources configured, nothing to get coverage from!") super(BibliographicCoverageProvidersScript, self).__init__(providers)
def __init__(self): providers = [] if Configuration.integration('3M'): providers.append(ThreeMBibliographicCoverageProvider) if Configuration.integration('Overdrive'): providers.append(OverdriveBibliographicCoverageProvider) if Configuration.integration('Axis 360'): providers.append(Axis360BibliographicCoverageProvider) if not providers: raise Exception("No licensed book sources configured, nothing to get coverage from!") super(BibliographicCoverageProvidersScript, self).__init__(providers)
def filter_facets(self, values, group_name): allowable = Configuration.enabled_facets(group_name) default = Configuration.default_facet(group_name) if not values: return [default] filtered = [] for v in values: if v in allowable: filtered.append(v) else: self.log.warn('Ignoring unrecognized value "%s"', v) return filtered
def run_migrations_online(): """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. """ from api.config import Configuration api_config = Configuration() alembic_config = config.get_section(config.config_ini_section) alembic_config['sqlalchemy.url'] = api_config.database_url connectable = engine_from_config( alembic_config, prefix="sqlalchemy.", poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( connection=connection, target_metadata=target_metadata ) with context.begin_transaction(): context.run_migrations()
def copy_library_registry_information(_db, library): config = Configuration.integration("Adobe Vendor ID") if not config: print u"No Adobe Vendor ID configuration, not setting short name or secret." library.short_name = config.get("library_short_name") library.library_registry_short_name = config.get("library_short_name") library.library_registry_shared_secret = config.get("authdata_secret")
def __init__(self, metadata_web_app_url=None): self.metadata_url = ( metadata_web_app_url or Configuration.integration_url( Configuration.METADATA_WRANGLER_INTEGRATION ) ) self.lookup = SimplifiedOPDSLookup(self.metadata_url)
def admin_view(collection=None, book=None, **kwargs): setting_up = (app.manager.admin_sign_in_controller.auth == None) if not setting_up: admin = app.manager.admin_sign_in_controller.authenticated_admin_from_request() csrf_token = app.manager.admin_sign_in_controller.get_csrf_token() if isinstance(admin, ProblemDetail) or csrf_token is None or isinstance(csrf_token, ProblemDetail): redirect_url = flask.request.url if (collection): quoted_collection = urllib.quote(collection) redirect_url = redirect_url.replace( quoted_collection, quoted_collection.replace("/", "%2F")) if (book): quoted_book = urllib.quote(book) redirect_url = redirect_url.replace( quoted_book, quoted_book.replace("/", "%2F")) return redirect(app.manager.url_for('admin_sign_in', redirect=redirect_url)) else: csrf_token = None show_circ_events_download = ( "core.local_analytics_provider" in (Configuration.policy("analytics") or []) ) return flask.render_template_string( admin_template, csrf_token=csrf_token, home_url=app.manager.url_for('acquisition_groups'), show_circ_events_download=show_circ_events_download, setting_up=setting_up, )
def assert_borrowing_privileges(cls, patron): """Raise an exception unless the patron currently has borrowing privileges. :raise AuthorizationExpired: If the patron's authorization has expired. :raise OutstandingFines: If the patron has too many outstanding fines. """ now = datetime.datetime.utcnow() if not cls.authorization_is_active(patron): # The patron's card has expired. raise AuthorizationExpired() if patron.fines: max_fines = Configuration.max_outstanding_fines(patron.library) if patron.fines >= max_fines.amount: raise OutstandingFines() from api.authenticator import PatronData if patron.block_reason is not None: if patron.block_reason is PatronData.EXCESSIVE_FINES: # The authentication mechanism itself may know that # the patron has outstanding fines, even if the circulation # manager is not configured to make that deduction. raise OutstandingFines() raise AuthorizationBlocked()
def copy_library_registry_information(_db, library): config = Configuration.integration("Adobe Vendor ID") if not config: print u"No Adobe Vendor ID configuration, not setting short name or secret." return library.short_name = config.get("library_short_name") library.library_registry_short_name = config.get("library_short_name") library.library_registry_shared_secret = config.get("authdata_secret")
def setup_admin_controllers(manager): """Set up all the controllers that will be used by the admin parts of the web app.""" if not manager.testing: try: manager.config = Configuration.load() except CannotLoadConfiguration, e: self.log.error("Could not load configuration file: %s" % e) sys.exit()
def do_run(self): integration = Configuration.integration( Configuration.ELASTICSEARCH_INTEGRATION, ) old_index = integration.get(Configuration.ELASTICSEARCH_INDEX_KEY, ) new_index = old_index + "-v2" alias = old_index + "-current" search_index_client = ExternalSearchIndex(works_index=new_index) search_index_client.indices.put_alias(index=new_index, name=alias)
def __init__(self): os.environ['AUTOINITIALIZE'] = "False" from api.app import app del os.environ['AUTOINITIALIZE'] app.manager = CirculationManager(self._db) self.app = app self.base_url = Configuration.integration_url( Configuration.CIRCULATION_MANAGER_INTEGRATION, required=True )
class RateBackendBinding: """Return concrete implementation of the `RateBackend` abstract class """ config = Configuration() @classmethod def get(cls) -> RateBackend: return FixerRateBackend(cls.config.fixer_access_key)
def __init__(self, _db=None, testing=False): super(LaneSweeperScript, self).__init__(_db) os.environ['AUTOINITIALIZE'] = "False" from api.app import app del os.environ['AUTOINITIALIZE'] app.manager = CirculationManager(self._db, testing=testing) self.app = app self.base_url = Configuration.integration_url( Configuration.CIRCULATION_MANAGER_INTEGRATION, required=True)
def test_cipher(self): """Test the cipher() helper method.""" # Generate a public/private key pair. key = RSA.generate(2048) cipher = PKCS1_OAEP.new(key) public = key.publickey().exportKey() private = key.exportKey() # Pass the public key into cipher() to get something that can # encrypt. encryptor = Configuration.cipher(public) encrypted = encryptor.encrypt("some text") # Pass the private key into cipher() to get something that can # decrypt. decryptor = Configuration.cipher(private) decrypted = decryptor.decrypt(encrypted) eq_("some text", decrypted)
def do_generate(self, lane): feeds = [] annotator = self.app.manager.annotator(lane) if isinstance(lane, Lane): languages = lane.language_key lane_name = None else: languages = None lane_name = None url = self.app.manager.cdn_url_for( "feed", languages=lane.languages, lane_name=lane_name ) order_facets = Configuration.enabled_facets( Facets.ORDER_FACET_GROUP_NAME ) availability = Configuration.default_facet( Facets.AVAILABILITY_FACET_GROUP_NAME ) collection = Configuration.default_facet( Facets.COLLECTION_FACET_GROUP_NAME ) for sort_order in order_facets: pagination = Pagination.default() facets = Facets( collection=collection, availability=availability, order=sort_order, order_ascending=True ) title = lane.display_name for pagenum in (0, 2): feeds.append( AcquisitionFeed.page( self._db, title, url, lane, annotator, facets=facets, pagination=pagination, force_refresh=True ) ) pagination = pagination.next_page return feeds
def run(self): languages = Configuration.localization_languages() for language in languages: base_path = "translations/%s/LC_MESSAGES" % language if not os.path.exists(base_path): logging.warn("No translations for configured language %s" % language) continue os.system("rm %(path)s/messages.po" % dict(path=base_path)) os.system("cat %(path)s/*.po > %(path)s/messages.po" % dict(path=base_path)) os.system("pybabel compile -f -d translations")
def convert_content_server(_db, library): config = Configuration.integration("Content Server") if not config: print u"No content server configuration, not creating a Collection for it." return url = config.get('url') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.OPDS_IMPORT, name="Open Access Content Server") library.collections.append(collection) collection.url = url
def do_generate(self, lane): feeds = [] annotator = self.app.manager.annotator(lane) if isinstance(lane, Lane) and lane.parent: languages = lane.language_key lane_name = lane.name else: languages = None lane_name = None url = self.app.manager.cdn_url_for("feed", languages=lane.languages, lane_name=lane_name) order_facets = Configuration.enabled_facets( Facets.ORDER_FACET_GROUP_NAME) availability = Configuration.default_facet( Facets.AVAILABILITY_FACET_GROUP_NAME) collection = Configuration.default_facet( Facets.COLLECTION_FACET_GROUP_NAME) for sort_order in self.orders: for availability in self.availabilities: for collection in self.collections: pagination = Pagination.default() facets = Facets(collection=collection, availability=availability, order=sort_order, order_ascending=True) title = lane.display_name for pagenum in range(0, self.pages): yield AcquisitionFeed.page(self._db, title, url, lane, annotator, facets=facets, pagination=pagination, force_refresh=True) pagination = pagination.next_page
def convert_content_server(_db, library): config = Configuration.integration("Content Server") if not config: print u"No content server configuration, not creating a Collection for it." return url = config.get('url') collection, ignore = get_one_or_create( _db, Collection, protocol=Collection.OPDS_IMPORT, name="Open Access Content Server" ) collection.external_integration.setting("data_source").value = DataSource.OA_CONTENT_SERVER library.collections.append(collection)
def get_app() -> falcon.API: """WSGI entry point """ session_manager = SessionManagerMiddleware() api = falcon.API(middleware=[session_manager]) config = Configuration() api.add_route('{}/currencies'.format(config.base_uri), CurrenciesController()) api.add_route('{}/rates'.format(config.base_uri), RatesController()) api.add_route('{}/trades'.format(config.base_uri), TradesController()) return api
def convert_content_server(_db, library): config = Configuration.integration("Content Server") if not config: print u"No content server configuration, not creating a Collection for it." return url = config.get('url') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.OPDS_IMPORT, name="Open Access Content Server") collection.external_integration.setting( "data_source").value = DataSource.OA_CONTENT_SERVER library.collections.append(collection)
def run(self): inp = self.open() tag_fields = { 'tags': Subject.NYPL_APPEAL, } integ = Configuration.integration( Configuration.STAFF_PICKS_INTEGRATION) fields = integ.get(Configuration.LIST_FIELDS, {}) importer = CustomListFromCSV(DataSource.LIBRARY_STAFF, CustomList.STAFF_PICKS_NAME, **fields) reader = csv.DictReader(inp, dialect='excel-tab') importer.to_customlist(self._db, reader) self._db.commit()
def do_run(self): integration = Configuration.integration( Configuration.ELASTICSEARCH_INTEGRATION, ) old_index = integration.get( Configuration.ELASTICSEARCH_INDEX_KEY, ) new_index = old_index + "-v2" alias = old_index + "-current" search_index_client = ExternalSearchIndex(works_index=new_index) search_index_client.indices.put_alias( index=new_index, name=alias )
def run(self): inp = self.open() tag_fields = { 'tags': Subject.NYPL_APPEAL, } integ = Configuration.integration(Configuration.STAFF_PICKS_INTEGRATION) fields = integ.get(Configuration.LIST_FIELDS, {}) importer = CustomListFromCSV( DataSource.LIBRARY_STAFF, CustomList.STAFF_PICKS_NAME, **fields ) reader = csv.DictReader(inp) importer.to_customlist(self._db, reader) self._db.commit()
def convert_bibliotheca(_db, library): config = Configuration.integration('3M') if not config: print u"No Bibliotheca configuration, not creating a Collection for it." return print u"Creating Collection object for Bibliotheca collection." username = config.get('account_id') password = config.get('account_key') library_id = config.get('library_id') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.BIBLIOTHECA, name="Bibliotheca") library.collections.append(collection) collection.external_integration.username = username collection.external_integration.password = password collection.external_account_id = library_id
def has_excess_fines(cls, patron): """Does this patron have fines in excess of the maximum fine amount set for their library? :param a Patron: :return: A boolean """ if not patron.fines: return False if isinstance(patron.fines, Money): patron_fines = patron.fines else: patron_fines = MoneyUtility.parse(patron.fines) actual_fines = patron_fines.amount max_fines = Configuration.max_outstanding_fines(patron.library) if max_fines is not None and actual_fines > max_fines.amount: return True return False
def convert_bibliotheca(_db, library): config = Configuration.integration('3M') if not config: print u"No Bibliotheca configuration, not creating a Collection for it." return print u"Creating Collection object for Bibliotheca collection." username = config.get('account_id') password = config.get('account_key') library_id = config.get('library_id') collection, ignore = get_one_or_create( _db, Collection, protocol=Collection.BIBLIOTHECA, name="Bibliotheca" ) library.collections.append(collection) collection.external_integration.username = username collection.external_integration.password = password collection.external_account_id = library_id
def convert_overdrive(_db, library): config = Configuration.integration('Overdrive') if not config: print u"No Overdrive configuration, not creating a Collection for it." print u"Creating Collection object for Overdrive collection." username = config.get('client_key') password = config.get('client_secret') library_id = config.get('library_id') website_id = config.get('website_id') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.OVERDRIVE, name="Overdrive") library.collections.append(collection) collection.username = username collection.password = password collection.external_account_id = library_id collection.set_setting("website_id", website_id)
def _create_registration_payload(self, url_for, stage): """Collect the key-value pairs to be sent when kicking off the registration protocol. :param url_for: An implementation of Flask url_for. :param state: The registrant's opinion about what stage this registration should be in. :return: A dictionary suitable for passing into requests.post. """ auth_document_url = url_for("authentication_document", library_short_name=self.library.short_name) payload = dict(url=auth_document_url, stage=stage) # Find the email address the administrator should use if they notice # a problem with the way the library is using an integration. contact = Configuration.configuration_contact_uri(self.library) if contact: payload['contact'] = contact return payload
def open(self): if len(sys.argv) > 1: return open(sys.argv[1]) url = Configuration.integration_url( Configuration.STAFF_PICKS_INTEGRATION, True ) if not url.startswith('https://') or url.startswith('http://'): url = self.DEFAULT_URL_TEMPLATE % url self.log.info("Retrieving %s", url) representation, cached = Representation.get( self._db, url, do_get=Representation.browser_http_get, accept="text/csv", max_age=timedelta(days=1)) if representation.status_code != 200: raise ValueError("Unexpected status code %s" % representation.status_code) if not representation.media_type.startswith("text/csv"): raise ValueError("Unexpected media type %s" % representation.media_type) return StringIO(representation.content)
def convert_overdrive(_db, library): config = Configuration.integration('Overdrive') if not config: print u"No Overdrive configuration, not creating a Collection for it." return print u"Creating Collection object for Overdrive collection." username = config.get('client_key') password = config.get('client_secret') library_id = config.get('library_id') website_id = config.get('website_id') collection, ignore = get_one_or_create( _db, Collection, protocol=Collection.OVERDRIVE, name="Overdrive" ) library.collections.append(collection) collection.external_integration.username = username collection.external_integration.password = password collection.external_account_id = library_id collection.external_integration.set_setting("website_id", website_id)
def _create_registration_payload(self, url_for, stage): """Collect the key-value pairs to be sent when kicking off the registration protocol. :param url_for: An implementation of Flask url_for. :param state: The registrant's opinion about what stage this registration should be in. :return: A dictionary suitable for passing into requests.post. """ auth_document_url = url_for( "authentication_document", library_short_name=self.library.short_name ) payload = dict(url=auth_document_url, stage=stage) # Find the email address the administrator should use if they notice # a problem with the way the library is using an integration. contact = Configuration.configuration_contact_uri(self.library) if contact: payload['contact'] = contact return payload
def convert_one_click(_db, library): config = Configuration.integration('OneClick') if not config: print u"No OneClick configuration, not creating a Collection for it." print u"Creating Collection object for OneClick collection." basic_token = config.get('basic_token') library_id = config.get('library_id') url = config.get('url') ebook_loan_length = config.get('ebook_loan_length') eaudio_loan_length = config.get('eaudio_loan_length') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.ONE_CLICK, name="OneClick") library.collections.append(collection) collection.password = basic_token collection.external_account_id = library_id collection.url = url collection.set_setting("ebook_loan_length", ebook_loan_length) collection.set_setting("eaudio_loan_length", eaudio_loan_length)
def convert_axis(_db, library): config = Configuration.integration('Axis 360') if not config: print u"No Axis 360 configuration, not creating a Collection for it." return print u"Creating Collection object for Axis 360 collection." username = config.get('username') password = config.get('password') library_id = config.get('library_id') # This is not technically a URL, it's u"production" or u"staging", # but it's converted into a URL internally. url = config.get('server') collection, ignore = get_one_or_create(_db, Collection, protocol=Collection.AXIS_360, name="Axis 360") library.collections.append(collection) collection.external_integration.username = username collection.external_integration.password = password collection.external_account_id = library_id collection.external_integration.url = url
def convert_axis(_db, library): config = Configuration.integration('Axis 360') if not config: print u"No Axis 360 configuration, not creating a Collection for it." return print u"Creating Collection object for Axis 360 collection." username = config.get('username') password = config.get('password') library_id = config.get('library_id') # This is not technically a URL, it's u"production" or u"staging", # but it's converted into a URL internally. url = config.get('server') collection, ignore = get_one_or_create( _db, Collection, protocol=Collection.AXIS_360, name="Axis 360" ) library.collections.append(collection) collection.external_integration.username = username collection.external_integration.password = password collection.external_account_id = library_id collection.external_integration.url = url
def assert_borrowing_privileges(cls, patron): """Raise an exception unless the patron currently has borrowing privileges. :raise AuthorizationExpired: If the patron's authorization has expired. :raise OutstandingFines: If the patron has too many outstanding fines. """ now = datetime.datetime.utcnow() if not cls.authorization_is_active(patron): # The patron's card has expired. raise AuthorizationExpired() if patron.fines: max_fines = Configuration.max_outstanding_fines() if patron.fines >= max_fines.amount: raise OutstandingFines() if patron.block_reason is not None: raise AuthorizationBlocked()
def convert_one_click(_db, library): config = Configuration.integration('OneClick') if not config: print u"No OneClick configuration, not creating a Collection for it." return print u"Creating Collection object for OneClick collection." basic_token = config.get('basic_token') library_id = config.get('library_id') url = config.get('url') ebook_loan_length = config.get('ebook_loan_length') eaudio_loan_length = config.get('eaudio_loan_length') collection, ignore = get_one_or_create( _db, Collection, protocol=Collection.ONECLICK, name="OneClick" ) library.collections.append(collection) collection.external_integration.password = basic_token collection.external_account_id = library_id collection.external_integration.url = url collection.external_integration.set_setting("ebook_loan_length", ebook_loan_length) collection.external_integration.set_setting("eaudio_loan_length", eaudio_loan_length)
def setUpClass(cls): cls.engine = sqlalchemy.create_engine(Configuration().database_url)
def push(self, stage, url_for, catalog_url=None, do_get=HTTP.debuggable_get, do_post=HTTP.debuggable_post): """Attempt to register a library with a RemoteRegistry. NOTE: This method is designed to be used in a controller. Other callers may use this method, but they must be able to render a ProblemDetail when there's a failure. NOTE: The application server must be running when this method is called, because part of the OPDS Directory Registration Protocol is the remote server retrieving the library's Authentication For OPDS document. :param stage: Either TESTING_STAGE or PRODUCTION_STAGE :param url_for: Flask url_for() or equivalent, used to generate URLs for the application server. :param do_get: Mockable method to make a GET request. :param do_post: Mockable method to make a POST request. :return: A ProblemDetail if there was a problem; otherwise True. """ # Assume that the registration will fail. # # TODO: If a registration has previously succeeded, failure to # re-register probably means a maintenance of the status quo, # not a change of success to failure. But we don't have any way # of being sure. self.status_field.value = self.FAILURE_STATUS if stage not in self.VALID_REGISTRATION_STAGES: return INVALID_INPUT.detailed( _("%r is not a valid registration stage") % stage ) # Verify that a public/private key pair exists for this library. # This key pair is created during initialization of the # LibraryAuthenticator, so this should always be present. # # We can't just create the key pair here because the process # of pushing a registration involves the other site making a # request to the circulation manager. This means the key pair # needs to be committed to the database _before_ the push # attempt starts. key_pair = ConfigurationSetting.for_library( Configuration.KEY_PAIR, self.library).json_value if not key_pair: # TODO: We could create the key pair _here_. The database # session will be committed at the end of this request, # so the push attempt would succeed if repeated. return SHARED_SECRET_DECRYPTION_ERROR.detailed( _("Library %(library)s has no key pair set.", library=self.library.short_name) ) public_key, private_key = key_pair cipher = Configuration.cipher(private_key) # Before we can start the registration protocol, we must fetch # the remote catalog's URL and extract the link to the # registration resource that kicks off the protocol. catalog_url = catalog_url or self.integration.url response = do_get(catalog_url) if isinstance(response, ProblemDetail): return response result = self._extract_catalog_information(response) if isinstance(result, ProblemDetail): return result register_url, vendor_id = result # Store the vendor id as a ConfigurationSetting on the integration # -- it'll be the same value for all libraries. if vendor_id: ConfigurationSetting.for_externalintegration( AuthdataUtility.VENDOR_ID_KEY, self.integration ).value = vendor_id # Build the document we'll be sending to the registration URL. payload = self._create_registration_payload(url_for, stage) if isinstance(payload, ProblemDetail): return payload headers = self._create_registration_headers() if isinstance(headers, ProblemDetail): return headers # Send the document. response = self._send_registration_request( register_url, headers, payload, do_post ) if isinstance(response, ProblemDetail): return response catalog = json.loads(response.content) # Process the result. return self._process_registration_result(catalog, cipher, stage)
def test_push(self): """Test the other methods orchestrated by the push() method. """ class Mock(Registration): def _extract_catalog_information(self, response): self.initial_catalog_response = response return "register_url", "vendor_id" def _create_registration_payload(self, url_for, stage): self.payload_ingredients = (url_for, stage) return dict(payload="this is it") def _create_registration_headers(self): self._create_registration_headers_called = True return dict(Header="Value") def _send_registration_request(self, register_url, headers, payload, do_post): self._send_registration_request_called_with = (register_url, headers, payload, do_post) return MockRequestsResponse(200, content=json.dumps("you did it!")) def _process_registration_result(self, catalog, encryptor, stage): self._process_registration_result_called_with = (catalog, encryptor, stage) return "all done!" def mock_do_get(self, url): self.do_get_called_with = url return "A fake catalog" # If there is no preexisting key pair set up for the library, # registration fails. (This normally won't happen because the # key pair is set up when the LibraryAuthenticator is # initialized. library = self._default_library registration = Mock(self.registry, library) stage = Registration.TESTING_STAGE url_for = object() catalog_url = "http://catalog/" do_post = object() def push(): return registration.push(stage, url_for, catalog_url, registration.mock_do_get, do_post) result = push() expect = "Library %s has no key pair set." % library.short_name eq_(expect, result.detail) # When a key pair is present, registration is kicked off, and # in this case it succeeds. key_pair_setting = ConfigurationSetting.for_library( Configuration.KEY_PAIR, library) public_key, private_key = Configuration.key_pair(key_pair_setting) result = registration.push(stage, url_for, catalog_url, registration.mock_do_get, do_post) eq_("all done!", result) # But there were many steps towards this result. # First, do_get was called on the catalog URL. eq_(catalog_url, registration.do_get_called_with) # Then, the catalog was passed into _extract_catalog_information. eq_("A fake catalog", registration.initial_catalog_response) # _extract_catalog_information returned a registration URL and # a vendor ID. The registration URL was used later on... # # The vendor ID was set as a ConfigurationSetting on # the ExternalIntegration associated with this registry. eq_( "vendor_id", ConfigurationSetting.for_externalintegration( AuthdataUtility.VENDOR_ID_KEY, self.integration).value) # _create_registration_payload was called to create the body # of the registration request. eq_((url_for, stage), registration.payload_ingredients) # _create_registration_headers was called to create the headers # sent along with the request. eq_(True, registration._create_registration_headers_called) # Then _send_registration_request was called, POSTing the # payload to "register_url", the registration URL we got earlier. results = registration._send_registration_request_called_with eq_(("register_url", { "Header": "Value" }, dict(payload="this is it"), do_post), results) # Finally, the return value of that method was loaded as JSON # and passed into _process_registration_result, along with # a cipher created from the private key. (That cipher would be used # to decrypt anything the foreign site signed using this site's # public key.) results = registration._process_registration_result_called_with message, cipher, actual_stage = results eq_("you did it!", message) eq_(cipher._key.exportKey(), private_key) eq_(actual_stage, stage) # If a nonexistent stage is provided a ProblemDetail is the result. result = registration.push("no such stage", url_for, catalog_url, registration.mock_do_get, do_post) eq_(INVALID_INPUT.uri, result.uri) eq_("'no such stage' is not a valid registration stage", result.detail) # Now in reverse order, let's replace the mocked methods so # that they return ProblemDetail documents. This tests that if # there is a failure at any stage, the ProblemDetail is # propagated. # The push() function will no longer push anything, so rename it. cause_problem = push def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not process registration result") registration._process_registration_result = fail problem = cause_problem() eq_("could not process registration result", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not send registration request") registration._send_registration_request = fail problem = cause_problem() eq_("could not send registration request", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not create registration payload") registration._create_registration_payload = fail problem = cause_problem() eq_("could not create registration payload", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not extract catalog information") registration._extract_catalog_information = fail problem = cause_problem() eq_("could not extract catalog information", problem.detail)
""" API module for the application """ from flask import Flask from api.config import Configuration from api.metrics import Metrics from api.request_logger import add_request_logging app = Flask(__name__) add_request_logging(app) configuration = Configuration() metrics = Metrics(app, configuration) @app.route('/health') def health(): """ Health endpoint which returns the configuration """ return configuration.config
def load_config(self): if not Configuration.instance: Configuration.load()
def test_sitewide_registration_post_success(self): # A service to register with metadata_wrangler_service = self._external_integration( ExternalIntegration.METADATA_WRANGLER, goal=ExternalIntegration.METADATA_GOAL, url=self._url ) # The service knows this site's public key, and is going # to use it to encrypt a shared secret. public_key, private_key = self.manager.sitewide_key_pair encryptor = Configuration.cipher(public_key) # A catalog with registration url register_link_type = self.manager.admin_settings_controller.METADATA_SERVICE_URI_TYPE registration_url = self._url catalog = dict( id = metadata_wrangler_service.url, links = [ dict(rel='collection-add', href=self._url, type='collection'), dict(rel='register', href=registration_url, type=register_link_type), dict(rel='collection-remove', href=self._url, type='collection'), ] ) headers = { 'Content-Type' : 'application/opds+json' } self.responses.append( MockRequestsResponse(200, content=json.dumps(catalog), headers=headers) ) # A registration document with an encrypted secret shared_secret = os.urandom(24).encode('hex') encrypted_secret = base64.b64encode(encryptor.encrypt(shared_secret)) registration = dict( id = metadata_wrangler_service.url, metadata = dict(shared_secret=encrypted_secret) ) self.responses.insert(0, MockRequestsResponse(200, content=json.dumps(registration))) with self.request_context_with_admin('/', method='POST'): flask.request.form = MultiDict([ ('integration_id', metadata_wrangler_service.id), ]) response = self.manager.admin_metadata_services_controller.process_sitewide_registration( metadata_wrangler_service, do_get=self.do_request, do_post=self.do_request ) eq_(None, response) # We made two requests: a GET to get the service document from # the metadata wrangler, and a POST to the registration # service, with the entity-body containing a callback URL and # a JWT. metadata_wrangler_service_request, registration_request = self.requests url, i1, i2 = metadata_wrangler_service_request eq_(metadata_wrangler_service.url, url) url, [document], ignore = registration_request eq_(url, registration_url) for k in 'url', 'jwt': assert k in document # The end result is that our ExternalIntegration for the metadata # wrangler has been updated with a (decrypted) shared secret. eq_(shared_secret, metadata_wrangler_service.password)
#!/usr/bin/env python """Create a -current alias for the index being used""" 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 api.config import Configuration as C from core.external_search import ExternalSearchIndex C.load() config_index = C.integration(C.ELASTICSEARCH_INTEGRATION).get(C.ELASTICSEARCH_INDEX_KEY) if not config_index: print "No action taken. Elasticsearch not configured." sys.exit() search = ExternalSearchIndex() update_required_text = ( "\n\tConfiguration update required for given alias \"%s\".\n" "\t============================================\n" "\tReplace Elasticsearch configuration \"works_index\" value with alias.\n" "\te.g. \"works_index\" : \"%s\" ===> \"works_index\" : \"%s\"\n\n" ) misplaced_alias_text = ( "\n\tExpected Elasticsearch alias \"%s\" is being used with\n" "\tindex \"%s\" instead of configured index \"%s\".\n" "\t============================================\n"
def test_push(self): """Test the other methods orchestrated by the push() method. """ class Mock(Registration): def _extract_catalog_information(self, response): self.initial_catalog_response = response return "register_url", "vendor_id" def _create_registration_payload(self, url_for, stage): self.payload_ingredients = (url_for, stage) return dict(payload="this is it") def _create_registration_headers(self): self._create_registration_headers_called = True return dict(Header="Value") def _send_registration_request( self, register_url, headers, payload, do_post ): self._send_registration_request_called_with = ( register_url, headers, payload, do_post ) return MockRequestsResponse( 200, content=json.dumps("you did it!") ) def _process_registration_result(self, catalog, encryptor, stage): self._process_registration_result_called_with = ( catalog, encryptor, stage ) return "all done!" def mock_do_get(self, url): self.do_get_called_with = url return "A fake catalog" # If there is no preexisting key pair set up for the library, # registration fails. (This normally won't happen because the # key pair is set up when the LibraryAuthenticator is # initialized. library = self._default_library registration = Mock(self.registry, library) stage = Registration.TESTING_STAGE url_for = object() catalog_url = "http://catalog/" do_post = object() def push(): return registration.push( stage, url_for, catalog_url, registration.mock_do_get, do_post ) result = push() expect = "Library %s has no key pair set." % library.short_name eq_(expect, result.detail) # When a key pair is present, registration is kicked off, and # in this case it succeeds. key_pair_setting = ConfigurationSetting.for_library( Configuration.KEY_PAIR, library ) public_key, private_key = Configuration.key_pair(key_pair_setting) result = registration.push( stage, url_for, catalog_url, registration.mock_do_get, do_post ) eq_("all done!", result) # But there were many steps towards this result. # First, do_get was called on the catalog URL. eq_(catalog_url, registration.do_get_called_with) # Then, the catalog was passed into _extract_catalog_information. eq_("A fake catalog", registration.initial_catalog_response) # _extract_catalog_information returned a registration URL and # a vendor ID. The registration URL was used later on... # # The vendor ID was set as a ConfigurationSetting on # the ExternalIntegration associated with this registry. eq_( "vendor_id", ConfigurationSetting.for_externalintegration( AuthdataUtility.VENDOR_ID_KEY, self.integration ).value ) # _create_registration_payload was called to create the body # of the registration request. eq_((url_for, stage), registration.payload_ingredients) # _create_registration_headers was called to create the headers # sent along with the request. eq_(True, registration._create_registration_headers_called) # Then _send_registration_request was called, POSTing the # payload to "register_url", the registration URL we got earlier. results = registration._send_registration_request_called_with eq_( ("register_url", {"Header": "Value"}, dict(payload="this is it"), do_post), results ) # Finally, the return value of that method was loaded as JSON # and passed into _process_registration_result, along with # a cipher created from the private key. (That cipher would be used # to decrypt anything the foreign site signed using this site's # public key.) results = registration._process_registration_result_called_with message, cipher, actual_stage = results eq_("you did it!", message) eq_(cipher._key.exportKey(), private_key) eq_(actual_stage, stage) # If a nonexistent stage is provided a ProblemDetail is the result. result = registration.push( "no such stage", url_for, catalog_url, registration.mock_do_get, do_post ) eq_(INVALID_INPUT.uri, result.uri) eq_("'no such stage' is not a valid registration stage", result.detail) # Now in reverse order, let's replace the mocked methods so # that they return ProblemDetail documents. This tests that if # there is a failure at any stage, the ProblemDetail is # propagated. # The push() function will no longer push anything, so rename it. cause_problem = push def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not process registration result" ) registration._process_registration_result = fail problem = cause_problem() eq_("could not process registration result", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not send registration request" ) registration._send_registration_request = fail problem = cause_problem() eq_("could not send registration request", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not create registration payload" ) registration._create_registration_payload = fail problem = cause_problem() eq_("could not create registration payload", problem.detail) def fail(*args, **kwargs): return INVALID_REGISTRATION.detailed( "could not extract catalog information" ) registration._extract_catalog_information = fail problem = cause_problem() eq_("could not extract catalog information", problem.detail)
ExternalIntegration as EI, Library, get_one_or_create, production_session, ) from api.adobe_vendor_id import AuthdataUtility from api.config import Configuration log = logging.getLogger(name="Circulation manager configuration import") def log_import(integration_or_setting): log.info("CREATED: %r" % integration_or_setting) try: Configuration.load() _db = production_session() LIBRARIES = _db.query(Library).all() # Import Circulation Manager base url. circ_manager_conf = Configuration.integration('Circulation Manager') if circ_manager_conf: url = circ_manager_conf.get('url') if url: setting = ConfigurationSetting.sitewide(_db, Configuration.BASE_URL_KEY) setting.value = unicode(url) log_import(setting) # Import Metadata Wrangler configuration. metadata_wrangler_conf = Configuration.integration('Metadata Wrangler')