diff options
author | Colin Watson <cjwatson@debian.org> | 2017-12-12 15:20:49 +0000 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2017-12-12 15:20:49 +0000 |
commit | 9e4403035a9953c99117083e6373ae3c441a76b5 (patch) | |
tree | d91b137df6767bfb8cb72de6b9fd21efb0c3dee4 /macaroonbakery/httpbakery/_browser.py | |
parent | 949b7072cabce0daed6c94993ad44c8ea8648dbd (diff) |
Import py-macaroon-bakery_1.1.0.orig.tar.gz
Diffstat (limited to 'macaroonbakery/httpbakery/_browser.py')
-rw-r--r-- | macaroonbakery/httpbakery/_browser.py | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/macaroonbakery/httpbakery/_browser.py b/macaroonbakery/httpbakery/_browser.py new file mode 100644 index 0000000..a1ccbb0 --- /dev/null +++ b/macaroonbakery/httpbakery/_browser.py @@ -0,0 +1,89 @@ +# Copyright 2017 Canonical Ltd. +# Licensed under the LGPLv3, see LICENCE file for details. +import base64 +from collections import namedtuple + +import requests +from ._error import InteractionError +from ._interactor import ( + WEB_BROWSER_INTERACTION_KIND, + DischargeToken, + Interactor, + LegacyInteractor, +) +from macaroonbakery._utils import visit_page_with_browser + +from six.moves.urllib.parse import urljoin + + +class WebBrowserInteractor(Interactor, LegacyInteractor): + ''' Handles web-browser-based interaction-required errors by opening a + web browser to allow the user to prove their credentials interactively. + ''' + def __init__(self, open=visit_page_with_browser): + '''Create a WebBrowserInteractor that uses the given function + to open a browser window. The open function is expected to take + a single argument of string type, the URL to open. + ''' + self._open_web_browser = open + + def kind(self): + return WEB_BROWSER_INTERACTION_KIND + + def legacy_interact(self, ctx, location, visit_url): + '''Implement LegacyInteractor.legacy_interact by opening the + web browser window''' + self._open_web_browser(visit_url) + + def interact(self, ctx, location, ir_err): + '''Implement Interactor.interact by opening the browser window + and waiting for the discharge token''' + p = ir_err.interaction_method(self.kind(), WebBrowserInteractionInfo) + if not location.endswith('/'): + location += '/' + visit_url = urljoin(location, p.visit_url) + wait_token_url = urljoin(location, p.wait_token_url) + self._open_web_browser(visit_url) + return self._wait_for_token(ctx, wait_token_url) + + def _wait_for_token(self, ctx, wait_token_url): + ''' Returns a token from a the wait token URL + @param wait_token_url URL to wait for (string) + :return DischargeToken + ''' + resp = requests.get(wait_token_url) + if resp.status_code != 200: + raise InteractionError('cannot get {}'.format(wait_token_url)) + json_resp = resp.json() + kind = json_resp.get('kind') + if kind is None: + raise InteractionError( + 'cannot get kind token from {}'.format(wait_token_url)) + token_val = json_resp.get('token') + if token_val is None: + token_val = json_resp.get('token64') + if token_val is None: + raise InteractionError( + 'cannot get token from {}'.format(wait_token_url)) + token_val = base64.b64decode(token_val) + return DischargeToken(kind=kind, value=token_val) + + +class WebBrowserInteractionInfo(namedtuple('WebBrowserInteractionInfo', + 'visit_url, wait_token_url')): + ''' holds the information expected in the browser-window interaction + entry in an interaction-required error. + + :param visit_url holds the URL to be visited in a web browser. + :param wait_token_url holds a URL that will block on GET until the browser + interaction has completed. + ''' + @classmethod + def from_dict(cls, info_dict): + '''Create a new instance of WebBrowserInteractionInfo, as expected + by the Error.interaction_method method. + @param info_dict The deserialized JSON object + @return a new WebBrowserInteractionInfo object. + ''' + return WebBrowserInteractionInfo(visit_url=info_dict.get('VisitURL'), + wait_token_url=info_dict('WaitURL')) |