예제 #1
0
    def _check_with_osm_api(self):
        """
        Recheck the authorization by requesting a protected resource from the OSM API.

        Returns:
            boolean: True if the source could be request, False if the request
                     failed (repsonse code other than 200)

        Raises:
            OAuthError: failed to get a connection to the OSM API or the API responded with code 500
        """
        oauth = OAuth1(self.config.CLIENT_KEY,
                       client_secret=self.config.CLIENT_SECRET,
                       resource_owner_key=self.access_token,
                       resource_owner_secret=self.access_token_secret)
        try:
            url = "{}user/details".format(self.config.API_URL_BASE)
            r = requests.get(url=url, auth=oauth)
        except ConnectionError as err:
            raise OAuthError("failed to (re)check the authorization",
                             "502 Bad Gateway") from err
        if r.status_code == 200:
            return True
        if r.status_code == 500:
            raise OAuthError(
                "received error 500 when (re)checking the authorization",
                "502 Bad Gateway")
        return False
    def get_access_token_from_api(self):
        """
        Retriev an access_token and an access_token_secret from the OSM API
        using a temporary oauth_token and oauth_token_secret.

        The resulting tokens will be saved as properties of this class.

        Raises:
            OAuthError: if any OAuth related exception occured
        """
        try:
            oauth_token = self.query_params["oauth_token"][0]
            oauth_token_secret_encr = self.query_params[
                "oauth_token_secret_encr"][0]
        except KeyError as err:
            raise OAuthError(
                "oauth_token or oauth_token_secret_encr is missing.",
                "400 Bad Request") from err
        try:
            oauth_token_secret = self.write_crypto_box.decrypt(
                base64.urlsafe_b64decode(oauth_token_secret_encr))
        except Exception as err:
            raise OAuthError("decryption of tokens failed",
                             "400 Bad Request") from err
        oauth = OAuth1(self.config.CLIENT_KEY,
                       client_secret=self.config.CLIENT_SECRET,
                       resource_owner_key=oauth_token,
                       resource_owner_secret=oauth_token_secret)
        r = requests.post(url=self.config.ACCESS_TOKEN_URL, auth=oauth)
        if r.status_code == 401:
            message = "The OSM API returned status \"401 Unauthorized\" when fetching an access token from the OSM API. You most probably declined any requested permissions for this application."
            if len(r.content) > 0:
                message += "\n------\n{}".format(r.content)
            raise OAuthError(message, "401 Unauthorized")
        if r.status_code != 200 or r.headers.get(
                "Content-Type", "").split(";")[0].strip() != "text/plain":
            raise OAuthError(
                "Error: Failed to retrieve access token\nstatus code: {}\ncontent-type: {}\nrepsonse: {}"
                .format(r.status_code, r.headers.get("Content-Type", ""),
                        r.text), "502 Bad Gateway")
        oauth_tokens = urllib.parse.parse_qs(r.text)
        try:
            self.access_token = oauth_tokens["oauth_token"][0]
            self.access_token_secret = oauth_tokens["oauth_token_secret"][0]
        except KeyError as err:
            raise OAuthError(
                "Incomplete response of OSM API, oauth_token or oauth_token_secret is missing.",
                "502 Bad Gateway") from err
예제 #3
0
def request_oauth_token(environ, crypto_box):
    """
    Get a request_token from the OSM API and prepare the authroization URL the use should be redirected to.

    Args:
        crypto_box (nacl.public.Box): encryption used to encrypt oauth_token_secret

    Returns:
        str: authorization URL

    Raises:
        OAuthError: error sending a request to the OSM API or failed to parse its response
    """
    oauth = OAuth1Session(config.CLIENT_KEY, client_secret=config.CLIENT_SECRET)

    try:
        fetch_response = oauth.fetch_request_token(config.REQUEST_TOKEN_URL)
    except ValueError as err:
        raise OAuthError(err.message, "500 Internal Server Error")
    resource_owner_secret = fetch_response.get('oauth_token_secret')

    # encrypt secret
    nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE)
    oauth_token_secret_encr = base64.urlsafe_b64encode(crypto_box.encrypt(resource_owner_secret.encode("utf8"), nonce)).decode("ascii")

    authorization_url = oauth.authorization_url(config.AUTHORIZATION_URL)
    # append callback URL because our callback URL is dynamic and cannot be configured in the consumer settings of osm.org
    query_str_appendix = "oauth_token_secret_encr={}".format(urllib.parse.quote(oauth_token_secret_encr))
    callback_url = urllib.parse.quote(reconstruct_url(environ, True, query_str_appendix, config.LANDING_PAGE_URL_PARAM))
    authorization_url += "&oauth_callback={}".format(callback_url)
    return authorization_url
예제 #4
0
    def get_state(self):
        """
        Check if the signature of the cookie is valid, decrypt the cookie.

        Returns:
            AuthenticationState
        """
        ITERATION2_KEYS = {"oauth_token", "oauth_token_secret_encr"}
        is_redirected_from_osm = False
        if (ITERATION2_KEYS
                & set(iter(self.query_params))) == set(ITERATION2_KEYS):
            #return AuthenticationState.LOGGED_IN
            is_redirected_from_osm = True
        landing_page = self.query_params.get(
            self.config.LANDING_PAGE_URL_PARAM, ["false"])
        if self.cookie is None and landing_page[0] == "true":
            return AuthenticationState.NONE
        elif self.cookie is None and is_redirected_from_osm:
            return AuthenticationState.LOGGED_IN
        elif self.cookie is None:
            return AuthenticationState.SHOW_LANDING_PAGE
        try:
            contents = self.cookie[self.config.COOKIE_NAME].value.split("|")
            if len(contents) < 3 or contents[0] != "login":
                if is_redirected_from_osm:
                    return AuthenticationState.LOGGED_IN
                if landing_page[0] != "true":
                    return AuthenticationState.SHOW_LANDING_PAGE
                # landing page has been seen already
                return AuthenticationState.NONE
            key_name = contents[1]
            self._load_read_keys(key_name)
            signed = contents[2].encode("ascii")
            access_tokens_encr = self.verify_key.verify(
                base64.urlsafe_b64decode(signed))
        except nacl.exceptions.BadSignatureError:
            return AuthenticationState.SIGNATURE_VERIFICATION_FAILED
        except KeyError:
            # if something fails here, they normal authentication-authorization loop should start and
            # users not treated like not having seen the landing page
            return AuthenticationState.NONE
        try:
            parts = self.read_crypto_box.decrypt(access_tokens_encr).decode(
                "ascii").split("|")
            self.access_token = parts[0]
            self.access_token_secret = parts[1]
            self.valid_until = datetime.datetime.strptime(
                parts[2], "%Y-%m-%dT%H:%M:%S")
        except Exception as err:
            raise OAuthError("decryption of tokens failed",
                             "400 Bad Request") from err
        # If users sends us an old cookie but it is too old and has parameters like being redirected back to our site,
        # treat him like being redirected from OSM back to our site.
        if is_redirected_from_osm and datetime.datetime.utcnow(
        ) > self.valid_until:
            return AuthenticationState.LOGGED_IN
        if datetime.datetime.utcnow() > self.valid_until:
            return AuthenticationState.OAUTH_ACCESS_TOKEN_RECHECK
        return AuthenticationState.OAUTH_ACCESS_TOKEN_VALID
예제 #5
0
    def get_access_token_from_api(self):
        """
        Retriev an access_token and an access_token_secret from the OSM API
        using a temporary oauth_token and oauth_token_secret.

        The resulting tokens will be saved as properties of this class.

        Raises:
            OAuthError: if any OAuth related exception occured
        """
        try:
            oauth_token = self.query_params["oauth_token"][0]
            oauth_token_secret_encr = self.query_params[
                "oauth_token_secret_encr"][0]
        except KeyError as err:
            raise OAuthError(
                "oauth_token or oauth_token_secret_encr is missing.",
                "400 Bad Request") from err
        try:
            oauth_token_secret = self.write_crypto_box.decrypt(
                base64.urlsafe_b64decode(oauth_token_secret_encr))
        except Exception as err:
            raise OAuthError("decryption of tokens failed",
                             "400 Bad Request") from err
        try:
            oauth = OAuth1Session(self.config.CLIENT_KEY,
                                  client_secret=self.config.CLIENT_SECRET,
                                  resource_owner_key=oauth_token,
                                  resource_owner_secret=oauth_token_secret)
            oauth_tokens = oauth.fetch_access_token(
                self.config.ACCESS_TOKEN_URL)
        except ValueError as err:
            raise OAuthError(str(err), "500 Internal Server Error") from err
        try:
            self.access_token = oauth_tokens.get("oauth_token")
            self.access_token_secret = oauth_tokens.get("oauth_token_secret")
        except KeyError as err:
            raise OAuthError(
                "Incomplete response of OSM API, oauth_token or oauth_token_secret is missing.",
                "502 Bad Gateway") from err
    def parse_cookie_step3(self, access_tokens_encr):
        """
        Get decrypted access tokens and validity date of the cookie. This method sets the
        properties self.access_token, self.access_token_secret and self.valid_until

        Args:
            access_tokens_encr (str) : result of parse_cookie_step2()

        Throws:
            OAuthError : decryption has failed
        """
        try:
            parts = self.read_crypto_box.decrypt(access_tokens_encr).decode(
                "ascii").split("|")
            self.access_token = parts[0]
            self.access_token_secret = parts[1]
            self.valid_until = datetime.datetime.strptime(
                parts[2], "%Y-%m-%dT%H:%M:%S")
        except Exception as err:
            raise OAuthError("decryption of tokens failed",
                             "400 Bad Request") from err