def test_remote_patron_lookup(self): #When the SIP authentication provider needs to look up a patron, #it calls patron_information on its SIP client and passes in None #for the password. patron = self._patron() patron.authorization_identifier = "1234" integration = self._external_integration(self._str) class Mock(MockSIPClient): def patron_information(self, identifier, password): self.patron_information = identifier self.password = password return self.patron_information_parser(TestSIP2AuthenticationProvider.polaris_wrong_pin) client = Mock() auth = SIP2AuthenticationProvider( self._default_library, integration, client=client ) patron = auth._remote_patron_lookup(patron) eq_(patron.__class__, PatronData) eq_("25891000331441", patron.authorization_identifier) eq_("*****@*****.**", patron.email_address) eq_(9.25, patron.fines) eq_("Falk, Jen", patron.personal_name) eq_(datetime(2018, 6, 9, 23, 59, 59), patron.authorization_expires) eq_(client.patron_information, "1234") eq_(client.password, None)
def test_info_to_patrondata_no_validate_password(self): integration = self._external_integration(self._str) integration.url = 'server.local' client = MockSIPClient() provider = SIP2AuthenticationProvider( self._default_library, integration, client=client ) # Test with valid login, should return PatronData info = client.patron_information_parser(TestSIP2AuthenticationProvider.sierra_valid_login) patron = provider.info_to_patrondata(info, validate_password=False) eq_(patron.__class__, PatronData) eq_("12345", patron.authorization_identifier) eq_("*****@*****.**", patron.email_address) eq_("SHELDON, ALICE", patron.personal_name) eq_(0, patron.fines) eq_(None, patron.authorization_expires) eq_(None, patron.external_type) eq_(PatronData.NO_VALUE, patron.block_reason) # Test with invalid login, should return PatronData info = client.patron_information_parser(TestSIP2AuthenticationProvider.sierra_invalid_login) patron = provider.info_to_patrondata(info, validate_password=False) eq_(patron.__class__, PatronData) eq_("12345", patron.authorization_identifier) eq_("*****@*****.**", patron.email_address) eq_("SHELDON, ALICE", patron.personal_name) eq_(0, patron.fines) eq_(None, patron.authorization_expires) eq_(None, patron.external_type) eq_('no borrowing privileges', patron.block_reason)
def test_remote_authenticate_no_password(self): integration = self._external_integration(self._str) p = SIP2AuthenticationProvider integration.setting(p.PASSWORD_KEYBOARD).value = p.NULL_KEYBOARD client = MockSIPClient() auth = SIP2AuthenticationProvider( self._default_library, integration, client=client ) # This Evergreen instance doesn't use passwords. client.queue_response(self.evergreen_active_user) patrondata = auth.remote_authenticate("user", None) eq_("12345", patrondata.authorization_identifier) eq_("863715", patrondata.permanent_id) eq_("Booth Active Test", patrondata.personal_name) eq_(0, patrondata.fines) eq_(datetime(2019, 10, 4), patrondata.authorization_expires) eq_("Adult", patrondata.external_type) # If a password is specified, it is not sent over the wire. client.queue_response(self.evergreen_active_user) patrondata = auth.remote_authenticate("user2", "some password") eq_("12345", patrondata.authorization_identifier) request = client.requests[-1] assert 'user2' in request assert 'some password' not in request
def test_info_to_patrondata_no_validate_password(self): integration = self._external_integration(self._str) integration.url = "server.local" provider = SIP2AuthenticationProvider(self._default_library, integration, client=MockSIPClientFactory()) client = provider._client # Test with valid login, should return PatronData info = client.patron_information_parser( TestSIP2AuthenticationProvider.sierra_valid_login) patron = provider.info_to_patrondata(info, validate_password=False) assert patron.__class__ == PatronData assert "12345" == patron.authorization_identifier assert "*****@*****.**" == patron.email_address assert "LE CARRÉ, JOHN" == patron.personal_name assert 0 == patron.fines assert None == patron.authorization_expires assert None == patron.external_type assert PatronData.NO_VALUE == patron.block_reason # Test with invalid login, should return PatronData info = client.patron_information_parser( TestSIP2AuthenticationProvider.sierra_invalid_login) patron = provider.info_to_patrondata(info, validate_password=False) assert patron.__class__ == PatronData assert "12345" == patron.authorization_identifier assert "*****@*****.**" == patron.email_address assert "SHELDON, ALICE" == patron.personal_name assert 0 == patron.fines assert None == patron.authorization_expires assert None == patron.external_type assert "no borrowing privileges" == patron.block_reason
def test_remote_patron_lookup(self): # When the SIP authentication provider needs to look up a patron, # it calls patron_information on its SIP client and passes in None # for the password. patron = self._patron() patron.authorization_identifier = "1234" integration = self._external_integration(self._str) class Mock(MockSIPClient): def patron_information(self, identifier, password): self.patron_information = identifier self.password = password return self.patron_information_parser( TestSIP2AuthenticationProvider.polaris_wrong_pin) client = Mock() client.queue_response(self.end_session_response) auth = SIP2AuthenticationProvider(self._default_library, integration, client=client) patron = auth._remote_patron_lookup(patron) assert patron.__class__ == PatronData assert "25891000331441" == patron.authorization_identifier assert "*****@*****.**" == patron.email_address assert 9.25 == patron.fines assert "Falk, Jen" == patron.personal_name assert datetime(2018, 6, 9, 23, 59, 59) == patron.authorization_expires assert client.patron_information == "1234" assert client.password == None
def test_patron_block_setting(self): integration_block = self._external_integration( self._str, settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "true"}) integration_noblock = self._external_integration( self._str, settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "false"}, ) # Test with blocked patron, block should be set p = SIP2AuthenticationProvider(self._default_library, integration_block, client=MockSIPClientFactory()) client = p._client info = client.patron_information_parser( TestSIP2AuthenticationProvider.evergreen_expired_card) patron = p.info_to_patrondata(info) assert patron.__class__ == PatronData assert "12345" == patron.authorization_identifier assert "863716" == patron.permanent_id assert "Booth Expired Test" == patron.personal_name assert 0 == patron.fines assert datetime(2008, 9, 7) == patron.authorization_expires assert PatronData.NO_BORROWING_PRIVILEGES == patron.block_reason # Test with blocked patron, block should not be set p = SIP2AuthenticationProvider(self._default_library, integration_noblock, client=MockSIPClientFactory()) client = p._client info = client.patron_information_parser( TestSIP2AuthenticationProvider.evergreen_expired_card) patron = p.info_to_patrondata(info) assert patron.__class__ == PatronData assert "12345" == patron.authorization_identifier assert "863716" == patron.permanent_id assert "Booth Expired Test" == patron.personal_name assert 0 == patron.fines assert datetime(2008, 9, 7) == patron.authorization_expires assert PatronData.NO_VALUE == patron.block_reason
def test_patron_block_setting(self): integration_block = self._external_integration( self._str, settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "true"}) integration_noblock = self._external_integration( self._str, settings={SIP2AuthenticationProvider.PATRON_STATUS_BLOCK: "false"}) client = MockSIPClient() # Test with blocked patron, block should be set p = SIP2AuthenticationProvider(self._default_library, integration_block, client=client) info = client.patron_information_parser( TestSIP2AuthenticationProvider.evergreen_expired_card) patron = p.info_to_patrondata(info) eq_(patron.__class__, PatronData) eq_("12345", patron.authorization_identifier) eq_("863716", patron.permanent_id) eq_("Booth Expired Test", patron.personal_name) eq_(0, patron.fines) eq_(datetime(2008, 9, 7), patron.authorization_expires) eq_(PatronData.NO_BORROWING_PRIVILEGES, patron.block_reason) # Test with blocked patron, block should not be set p = SIP2AuthenticationProvider(self._default_library, integration_noblock, client=client) info = client.patron_information_parser( TestSIP2AuthenticationProvider.evergreen_expired_card) patron = p.info_to_patrondata(info) eq_(patron.__class__, PatronData) eq_("12345", patron.authorization_identifier) eq_("863716", patron.permanent_id) eq_("Booth Expired Test", patron.personal_name) eq_(0, patron.fines) eq_(datetime(2008, 9, 7), patron.authorization_expires) eq_(PatronData.NO_VALUE, patron.block_reason)
def test_ioerror_during_connect_becomes_remoteintegrationexception(self): """If the IP of the circulation manager has not been whitelisted, we generally can't even connect to the server. """ class CannotConnect(MockSIPClient): def connect(self): raise IOError("Doom!") client = CannotConnect() integration = self._external_integration(self._str) provider = SIP2AuthenticationProvider(self._default_library, integration, client=client) assert_raises_regexp( RemoteIntegrationException, "Error accessing unknown server: Doom!", provider.remote_authenticate, "username", "password", )
def test_ioerror_during_send_becomes_remoteintegrationexception(self): """If there's an IOError communicating with the server, it becomes a RemoteIntegrationException. """ class CannotSend(MockSIPClient): def do_send(self, data): raise IOError("Doom!") integration = self._external_integration(self._str) integration.url = "server.local" provider = SIP2AuthenticationProvider(self._default_library, integration, client=CannotSend) with pytest.raises(RemoteIntegrationException) as excinfo: provider.remote_authenticate( "username", "password", ) assert "Error accessing server.local: Doom!" in str(excinfo.value)
def test_ioerror_during_send_becomes_remoteintegrationexception(self): """If there's an IOError communicating with the server, it becomes a RemoteIntegrationException. """ class CannotSend(MockSIPClient): def do_send(self, data): raise IOError("Doom!") client = CannotSend() client.target_server = 'server.local' provider = SIP2AuthenticationProvider( None, None, None, None, None, client=client ) assert_raises_regexp( RemoteIntegrationException, "Error accessing server.local: Doom!", provider.remote_authenticate, "username", "password", )
def test_remote_authenticate(self): integration = self._external_integration(self._str) client = MockSIPClient() auth = SIP2AuthenticationProvider(self._default_library, integration, client=client) # Some examples taken from a Sierra SIP API. client.queue_response(self.sierra_valid_login) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("*****@*****.**", patrondata.email_address) eq_("SHELDON, ALICE", patrondata.personal_name) eq_(0, patrondata.fines) eq_(None, patrondata.authorization_expires) eq_(None, patrondata.external_type) eq_(PatronData.NO_VALUE, patrondata.block_reason) client.queue_response(self.sierra_invalid_login) eq_(None, auth.remote_authenticate("user", "pass")) # Since Sierra provides both the patron's fine amount and the # maximum allowable amount, we can determine just by looking # at the SIP message that this patron is blocked for excessive # fines. client.queue_response(self.sierra_excessive_fines) patrondata = auth.remote_authenticate("user", "pass") eq_(PatronData.EXCESSIVE_FINES, patrondata.block_reason) # Some examples taken from an Evergreen instance that doesn't # use passwords. client.queue_response(self.evergreen_active_user) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("863715", patrondata.permanent_id) eq_("Booth Active Test", patrondata.personal_name) eq_(0, patrondata.fines) eq_(datetime(2019, 10, 4), patrondata.authorization_expires) eq_("Adult", patrondata.external_type) # A patron with an expired card. client.queue_response(self.evergreen_expired_card) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) # SIP extension field XI becomes sipserver_internal_id which # becomes PatronData.permanent_id. eq_("863716", patrondata.permanent_id) eq_("Booth Expired Test", patrondata.personal_name) eq_(0, patrondata.fines) eq_(datetime(2008, 9, 7), patrondata.authorization_expires) eq_(PatronData.NO_BORROWING_PRIVILEGES, patrondata.block_reason) # A patron with excessive fines client.queue_response(self.evergreen_excessive_fines) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("863718", patrondata.permanent_id) eq_("Booth Excessive Fines Test", patrondata.personal_name) eq_(100, patrondata.fines) eq_(datetime(2019, 10, 04), patrondata.authorization_expires) # We happen to know that this patron can't borrow books due to # excessive fines, but that information doesn't show up as a # block, because Evergreen doesn't also provide the # fine limit. This isn't a big deal -- we'll pick it up later # when we apply the site policy. # # This patron also has "Recall privileges denied" set, but # that's not a reason to block them. eq_(PatronData.NO_VALUE, patrondata.block_reason) # "Hold privileges denied" is not a block because you can # still borrow books. client.queue_response(self.evergreen_hold_privileges_denied) patrondata = auth.remote_authenticate("user", "pass") eq_(PatronData.NO_VALUE, patrondata.block_reason) client.queue_response(self.evergreen_card_reported_lost) patrondata = auth.remote_authenticate("user", "pass") eq_(PatronData.CARD_REPORTED_LOST, patrondata.block_reason) # Some examples taken from a Polaris instance. client.queue_response(self.polaris_valid_pin) patrondata = auth.remote_authenticate("user", "pass") eq_("25891000331441", patrondata.authorization_identifier) eq_("*****@*****.**", patrondata.email_address) eq_(9.25, patrondata.fines) eq_("Falk, Jen", patrondata.personal_name) eq_(datetime(2018, 6, 9, 23, 59, 59), patrondata.authorization_expires) client.queue_response(self.polaris_wrong_pin) patrondata = auth.remote_authenticate("user", "pass") eq_(None, patrondata) client.queue_response(self.polaris_no_such_patron) patrondata = auth.remote_authenticate("user", "pass") eq_(None, patrondata) client.queue_response(self.polaris_expired_card) patrondata = auth.remote_authenticate("user", "pass") eq_(datetime(2016, 10, 25, 23, 59, 59), patrondata.authorization_expires) client.queue_response(self.polaris_excess_fines) patrondata = auth.remote_authenticate("user", "pass") eq_(11.50, patrondata.fines)
def test_remote_authenticate(self): integration = self._external_integration(self._str) client = MockSIPClient() auth = SIP2AuthenticationProvider(self._default_library, integration, client=client) # Some examples taken from a Sierra SIP API. client.queue_response(self.sierra_valid_login) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert "12345" == patrondata.authorization_identifier assert "*****@*****.**" == patrondata.email_address assert "LE CARRÉ, JOHN" == patrondata.personal_name assert 0 == patrondata.fines assert None == patrondata.authorization_expires assert None == patrondata.external_type assert PatronData.NO_VALUE == patrondata.block_reason client.queue_response(self.sierra_invalid_login) client.queue_response(self.end_session_response) assert None == auth.remote_authenticate("user", "pass") # Since Sierra provides both the patron's fine amount and the # maximum allowable amount, we can determine just by looking # at the SIP message that this patron is blocked for excessive # fines. client.queue_response(self.sierra_excessive_fines) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert PatronData.EXCESSIVE_FINES == patrondata.block_reason # A patron with an expired card. client.queue_response(self.evergreen_expired_card) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert "12345" == patrondata.authorization_identifier # SIP extension field XI becomes sipserver_internal_id which # becomes PatronData.permanent_id. assert "863716" == patrondata.permanent_id assert "Booth Expired Test" == patrondata.personal_name assert 0 == patrondata.fines assert datetime(2008, 9, 7) == patrondata.authorization_expires assert PatronData.NO_BORROWING_PRIVILEGES == patrondata.block_reason # A patron with excessive fines client.queue_response(self.evergreen_excessive_fines) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert "12345" == patrondata.authorization_identifier assert "863718" == patrondata.permanent_id assert "Booth Excessive Fines Test" == patrondata.personal_name assert 100 == patrondata.fines assert datetime(2019, 10, 4) == patrondata.authorization_expires # We happen to know that this patron can't borrow books due to # excessive fines, but that information doesn't show up as a # block, because Evergreen doesn't also provide the # fine limit. This isn't a big deal -- we'll pick it up later # when we apply the site policy. # # This patron also has "Recall privileges denied" set, but # that's not a reason to block them. assert PatronData.NO_VALUE == patrondata.block_reason # "Hold privileges denied" is not a block because you can # still borrow books. client.queue_response(self.evergreen_hold_privileges_denied) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert PatronData.NO_VALUE == patrondata.block_reason client.queue_response(self.evergreen_card_reported_lost) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert PatronData.CARD_REPORTED_LOST == patrondata.block_reason # Some examples taken from a Polaris instance. client.queue_response(self.polaris_valid_pin) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert "25891000331441" == patrondata.authorization_identifier assert "*****@*****.**" == patrondata.email_address assert 9.25 == patrondata.fines assert "Falk, Jen" == patrondata.personal_name assert datetime(2018, 6, 9, 23, 59, 59) == patrondata.authorization_expires client.queue_response(self.polaris_wrong_pin) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert None == patrondata client.queue_response(self.polaris_expired_card) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert datetime(2016, 10, 25, 23, 59, 59) == patrondata.authorization_expires client.queue_response(self.polaris_excess_fines) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert 11.50 == patrondata.fines # Two cases where the patron's authorization identifier was # just not recognized. One on an ILS that sets # valid_patron_password='******' when that happens. client.queue_response(self.polaris_no_such_patron) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert None == patrondata # And once on an ILS that leaves valid_patron_password blank # when that happens. client.queue_response(self.tlc_no_such_patron) client.queue_response(self.end_session_response) patrondata = auth.remote_authenticate("user", "pass") assert None == patrondata
def test_run_self_tests(self): integration = self._external_integration(self._str) integration.url = "server.com" class MockBadConnection(MockSIPClient): def connect(self): # probably a timeout if the server or port values are not valid raise IOError("Could not connect") class MockSIPLogin(MockSIPClient): def now(self): return datetime(2019, 1, 1).strftime("%Y%m%d0000%H%M%S") def login(self): if not self.login_user_id and not self.login_password: raise IOError("Error logging in") def patron_information(self, username, password): return self.patron_information_parser( TestSIP2AuthenticationProvider.sierra_valid_login) auth = SIP2AuthenticationProvider(self._default_library, integration, client=MockBadConnection) results = [r for r in auth._run_self_tests(self._db)] # If the connection doesn't work then don't bother running the other tests assert len(results) == 1 assert results[0].name == "Test Connection" assert results[0].success == False assert isinstance(results[0].exception, IOError) assert results[0].exception.args == ("Could not connect", ) auth = SIP2AuthenticationProvider(self._default_library, integration, client=MockSIPLogin) results = [x for x in auth._run_self_tests(self._db)] assert len(results) == 2 assert results[0].name == "Test Connection" assert results[0].success == True assert results[ 1].name == "Test Login with username 'None' and password 'None'" assert results[1].success == False assert isinstance(results[1].exception, IOError) assert results[1].exception.args == ("Error logging in", ) # Set the log in username and password integration.username = "******" integration.password = "******" goodLoginClient = MockSIPLogin(login_user_id="user1", login_password="******") auth = SIP2AuthenticationProvider(self._default_library, integration, client=goodLoginClient) results = [x for x in auth._run_self_tests(self._db)] assert len(results) == 3 assert results[0].name == "Test Connection" assert results[0].success == True assert (results[1].name == "Test Login with username 'user1' and password 'pass1'") assert results[1].success == True assert results[2].name == "Authenticating test patron" assert results[2].success == False assert isinstance(results[2].exception, CannotLoadConfiguration) assert results[2].exception.args == ( "No test patron identifier is configured.", ) # Now add the test patron credentials into the mocked client and SIP2 authenticator provider patronDataClient = MockSIPLogin(login_user_id="user1", login_password="******") valid_login_patron = patronDataClient.patron_information_parser( TestSIP2AuthenticationProvider.sierra_valid_login) class MockSIP2PatronInformation(SIP2AuthenticationProvider): def patron_information(self, username, password): return valid_login_patron auth = MockSIP2PatronInformation(self._default_library, integration, client=patronDataClient) # The actual test patron credentials auth.test_username = "******" auth.test_password = "******" results = [x for x in auth._run_self_tests(self._db)] assert len(results) == 6 assert results[0].name == "Test Connection" assert results[0].success == True assert (results[1].name == "Test Login with username 'user1' and password 'pass1'") assert results[1].success == True assert results[2].name == "Authenticating test patron" assert results[2].success == True # Since test patron authentication is true, we can now see self # test results for syncing metadata and the raw data from `patron_information` assert results[3].name == "Syncing patron metadata" assert results[3].success == True assert results[4].name == "Patron information request" assert results[4].success == True assert results[ 4].result == patronDataClient.patron_information_request( "usertest1", "userpassword1") assert results[5].name == "Raw test patron information" assert results[5].success == True assert results[5].result == json.dumps(valid_login_patron, indent=1)
def test_remote_authenticate(self): client = MockSIPClient() auth = SIP2AuthenticationProvider( None, None, None, None, None, None, client=client ) # Some examples taken from a Sierra SIP API. client.queue_response(self.sierra_valid_login) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("*****@*****.**", patrondata.email_address) eq_("SHELDON, ALICE", patrondata.personal_name) eq_(0, patrondata.fines) eq_(None, patrondata.authorization_expires) eq_(None, patrondata.external_type) client.queue_response(self.sierra_invalid_login) eq_(None, auth.remote_authenticate("user", "pass")) # Some examples taken from an Evergreen instance that doesn't # use passwords. client.queue_response(self.evergreen_active_user) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("863715", patrondata.permanent_id) eq_("Booth Active Test", patrondata.personal_name) eq_(0, patrondata.fines) eq_(datetime(2019, 10, 4), patrondata.authorization_expires) eq_("Adult", patrondata.external_type) client.queue_response(self.evergreen_expired_card) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) # SIP extension field XI becomes sipserver_internal_id which # becomes PatronData.permanent_id. eq_("863716", patrondata.permanent_id) eq_("Booth Expired Test", patrondata.personal_name) eq_(0, patrondata.fines) eq_(datetime(2008, 9, 7), patrondata.authorization_expires) client.queue_response(self.evergreen_excessive_fines) patrondata = auth.remote_authenticate("user", "pass") eq_("12345", patrondata.authorization_identifier) eq_("863718", patrondata.permanent_id) eq_("Booth Excessive Fines Test", patrondata.personal_name) eq_(100, patrondata.fines) eq_(datetime(2019, 10, 04), patrondata.authorization_expires) # Some examples taken from a Polaris instance. client.queue_response(self.polaris_valid_pin) patrondata = auth.remote_authenticate("user", "pass") eq_("25891000331441", patrondata.authorization_identifier) eq_("*****@*****.**", patrondata.email_address) eq_(9.25, patrondata.fines) eq_("Falk, Jen", patrondata.personal_name) eq_(datetime(2018, 6, 9, 23, 59, 59), patrondata.authorization_expires) client.queue_response(self.polaris_wrong_pin) patrondata = auth.remote_authenticate("user", "pass") eq_(None, patrondata) client.queue_response(self.polaris_no_such_patron) patrondata = auth.remote_authenticate("user", "pass") eq_(None, patrondata) client.queue_response(self.polaris_expired_card) patrondata = auth.remote_authenticate("user", "pass") eq_(datetime(2016, 10, 25, 23, 59, 59), patrondata.authorization_expires) client.queue_response(self.polaris_excess_fines) patrondata = auth.remote_authenticate("user", "pass") eq_(11.50, patrondata.fines)