From fbda355b7b6b9a5ed32cf27095c735412a83ed42 Mon Sep 17 00:00:00 2001 From: Amy Slagle Date: Wed, 20 Jan 2016 11:29:24 -0500 Subject: [PATCH] Added account controller that provides username and barcode. (Fixes #30) --- app.py | 6 ++++++ controller.py | 12 ++++++++++++ firstbook.py | 3 +++ millenium_patron.py | 8 ++++++++ opds.py | 9 +++++++++ tests/test_controller.py | 22 ++++++++++++++++++++++ tests/test_firstbook.py | 4 ++++ tests/test_millenium_patron.py | 8 ++++++++ 8 files changed, 72 insertions(+) diff --git a/app.py b/app.py index 5e4b2970d..1c21e52ed 100644 --- a/app.py +++ b/app.py @@ -104,6 +104,12 @@ def feed(languages, lane_name): def lane_search(languages, lane_name): return app.manager.opds_feeds.search(languages, lane_name) +@app.route('/me', methods=['GET']) +@requires_auth +@returns_problem_detail +def account(): + return app.manager.accounts.account() + @app.route('/loans/', methods=['GET', 'HEAD']) @requires_auth @returns_problem_detail diff --git a/controller.py b/controller.py index bc08c8b40..745aedffa 100644 --- a/controller.py +++ b/controller.py @@ -202,6 +202,7 @@ def setup_controllers(self): self.index_controller = IndexController(self) self.opds_feeds = OPDSFeedController(self) self.loans = LoanController(self) + self.accounts = AccountController(self) self.urn_lookup = URNLookupController(self._db) self.work_controller = WorkController(self) @@ -505,6 +506,17 @@ def search(self, languages, lane_name): ) return feed_response(opds_feed) +class AccountController(CirculationManagerController): + + def account(self): + header = flask.request.authorization + + patron_info = self.manager.auth.patron_info(header.username) + return json.dumps(dict( + username=patron_info.get('username', None), + barcode=patron_info.get('barcode'), + )) + class LoanController(CirculationManagerController): def sync(self): diff --git a/firstbook.py b/firstbook.py index b5b2c14d5..4c08f8816 100644 --- a/firstbook.py +++ b/firstbook.py @@ -43,6 +43,9 @@ def request(self, url): def dump(self, barcode): return {} + def patron_info(self, barcode): + return dict(barcode=barcode) + def pintest(self, barcode, pin): url = self.root + "&accesscode=%s&pin=%s" % tuple(map( urllib.quote, (barcode, pin) diff --git a/millenium_patron.py b/millenium_patron.py index 97d9ce09f..323b721ac 100644 --- a/millenium_patron.py +++ b/millenium_patron.py @@ -109,6 +109,14 @@ def update_patron(self, patron, identifier, dump=None): expires, self.EXPIRATION_DATE_FORMAT).date() patron.authorization_expires = expires + def patron_info(self, identifier): + """Get patron information from the ILS.""" + dump = self.dump(identifier) + return dict( + barcode = dump.get(self.BARCODE_FIELD), + username = dump.get(self.USERNAME_FIELD), + ) + def authenticated_patron(self, db, identifier, password): # If they fail basic validation, there is no authenticated patron. if not self.server_side_validation(identifier, password): diff --git a/opds.py b/opds.py index 6aebece6d..d209492c0 100644 --- a/opds.py +++ b/opds.py @@ -221,6 +221,15 @@ def annotate_feed(self, feed, lane): if self.patron: self.add_patron(feed) + # Add an account info link + account_url = self.url_for('account', _external=True) + account_link = dict( + rel="http://librarysimplified.org/terms/rel/account", + type='application/json', + href=account_url, + ) + feed.add_link(**account_link) + # Add a 'search' link. if isinstance(lane, Lane): lane_name = lane.url_name diff --git a/tests/test_controller.py b/tests/test_controller.py index 80a7f378a..61d45db48 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -244,6 +244,24 @@ def test_authenticated_patron_root_lane(self): eq_("http://cdn/groups/", response.headers['location']) +class TestAccountController(ControllerTest): + + def test_patron_info_no_username(self): + with self.app.test_request_context( + "/", headers=dict(Authorization=self.valid_auth)): + account_info = json.loads(self.manager.accounts.account()) + eq_(None, account_info.get('username')) + eq_("200", account_info.get('barcode')) + + def test_patron_info_with_username(self): + auth = 'Basic ' + base64.b64encode('0:2222') + with self.app.test_request_context( + "/", headers=dict(Authorization=auth)): + account_info = json.loads(self.manager.accounts.account()) + eq_("alice", account_info.get('username')) + eq_("0", account_info.get('barcode')) + + class TestLoanController(ControllerTest): def setup(self): super(TestLoanController, self).setup() @@ -536,6 +554,10 @@ def test_active_loans(self): assert "%s/%s/borrow" % (threem_pool.data_source.name, threem_pool.identifier.identifier) in borrow_link eq_(0, len(threem_revoke_links)) + links = feed['feed']['links'] + account_links = [link for link in links if link['rel'] == 'http://librarysimplified.org/terms/rel/account'] + eq_(1, len(account_links)) + assert 'me' in account_links[0]['href'] class TestWorkController(ControllerTest): diff --git a/tests/test_firstbook.py b/tests/test_firstbook.py index 7729afb8d..ad9cdab4b 100644 --- a/tests/test_firstbook.py +++ b/tests/test_firstbook.py @@ -25,3 +25,7 @@ def test_server_side_validation(self): def test_dump(self): eq_({}, self.api.dump("abcd")) + + def test_patron_info(self): + eq_("1234", self.api.patron_info("1234").get('barcode')) + diff --git a/tests/test_millenium_patron.py b/tests/test_millenium_patron.py index efd58d3b3..4ad485b00 100644 --- a/tests/test_millenium_patron.py +++ b/tests/test_millenium_patron.py @@ -108,3 +108,11 @@ def test_authenticated_patron_success(self): alice = self.api.authenticated_patron(self._db, "alice", "4444") eq_("44444444444447", alice.authorization_identifier) eq_("alice", alice.username) + + def test_patron_info(self): + self.api.enqueue("dump.success.html") + patron_info = self.api.patron_info("alice") + eq_("44444444444447", patron_info.get('barcode')) + eq_("alice", patron_info.get('username')) + +