%PDF- %PDF-
Direktori : /lib/python3/dist-packages/duplicity/backends/pyrax_identity/ |
Current File : //lib/python3/dist-packages/duplicity/backends/pyrax_identity/hubic.py |
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf-8 -*- # # Copyright (c) 2014 Gu1 # Licensed under the MIT license import configparser import os import re import time import urllib.parse from requests.compat import ( quote, quote_plus, ) import requests try: import pyrax from pyrax.base_identity import ( BaseIdentity, Service, ) import pyrax.exceptions as exc except ImportError as e: raise BackendException( f"""Hubic backend requires the pyrax library available from Rackspace. Exception: {str(e)}""" ) OAUTH_ENDPOINT = "https://api.hubic.com/oauth/" API_ENDPOINT = "https://api.hubic.com/1.0/" TOKENS_FILE = os.path.expanduser("~/.hubic_tokens") class BearerTokenAuth(requests.auth.AuthBase): def __init__(self, token): self.token = token def __call__(self, req): req.headers["Authorization"] = f"Bearer {self.token}" return req class HubicIdentity(BaseIdentity): def _get_auth_endpoint(self): return "" def set_credentials( self, email, password, client_id, client_secret, redirect_uri, authenticate=False, ): """Sets the username and password directly.""" self._email = email self._password = password self._client_id = client_id self.tenant_id = client_id self._client_secret = client_secret self._redirect_uri = redirect_uri if authenticate: self.authenticate() def _read_credential_file(self, cfg): """ Parses the credential file with Rackspace-specific labels. """ self._email = cfg.get("hubic", "email") self._password = cfg.get("hubic", "password") self._client_id = cfg.get("hubic", "client_id") self.tenant_id = self._client_id self._client_secret = cfg.get("hubic", "client_secret") self._redirect_uri = cfg.get("hubic", "redirect_uri") def _parse_error(self, resp): if "location" not in resp.headers: return None query = urllib.parse.urlsplit(resp.headers["location"]).query qs = dict(urllib.parse.parse_qsl(query)) return {"error": qs["error"], "error_description": qs["error_description"]} def _get_access_token(self, code): r = requests.post( f"{OAUTH_ENDPOINT}token/", data={ "code": code, "redirect_uri": self._redirect_uri, "grant_type": "authorization_code", }, auth=(self._client_id, self._client_secret), ) if r.status_code != 200: try: err = r.json() err["code"] = r.status_code except Exception as e: err = {} raise exc.AuthenticationFailed( f"Unable to get oauth access token, wrong client_id or client_secret ? " f"({str(err)})" ) oauth_token = r.json() config = configparser.ConfigParser() config.read(TOKENS_FILE) if not config.has_section("hubic"): config.add_section("hubic") if oauth_token["access_token"] is not None: config.set("hubic", "access_token", oauth_token["access_token"]) with open(TOKENS_FILE, "wb") as configfile: config.write(configfile) else: raise exc.AuthenticationFailed( f"Unable to get oauth access token, wrong client_id or client_secret ? ({str(err)})" ) if oauth_token["refresh_token"] is not None: config.set("hubic", "refresh_token", oauth_token["refresh_token"]) with open(TOKENS_FILE, "wb") as configfile: config.write(configfile) else: raise exc.AuthenticationFailed("Unable to get the refresh token.") # removing username and password from .hubic_tokens if config.has_option("hubic", "email"): config.remove_option("hubic", "email") with open(TOKENS_FILE, "wb") as configfile: config.write(configfile) print("username has been removed from the .hubic_tokens file sent to the CE.") if config.has_option("hubic", "password"): config.remove_option("hubic", "password") with open(TOKENS_FILE, "wb") as configfile: config.write(configfile) print("password has been removed from the .hubic_tokens file sent to the CE.") return oauth_token def _refresh_access_token(self): config = configparser.ConfigParser() config.read(TOKENS_FILE) refresh_token = config.get("hubic", "refresh_token") if refresh_token is None: raise exc.AuthenticationFailed("refresh_token is null. Not acquiered before ?") success = False max_retries = 20 retries = 0 sleep_time = 30 max_sleep_time = 3600 while retries < max_retries and not success: r = requests.post( f"{OAUTH_ENDPOINT}token/", data={ "refresh_token": refresh_token, "grant_type": "refresh_token", }, auth=(self._client_id, self._client_secret), ) if r.status_code != 200: if r.status_code == 509: print("status_code 509: attempt #", retries, " failed") retries += 1 time.sleep(sleep_time) sleep_time = sleep_time * 2 if sleep_time > max_sleep_time: sleep_time = max_sleep_time else: try: err = r.json() err["code"] = r.status_code except Exception as e: err = {} raise exc.AuthenticationFailed( f"Unable to get oauth access token, wrong client_id or client_secret ? ({str(err)})" ) else: success = True if not success: raise exc.AuthenticationFailed( "All the attempts failed to get the refresh token: " "status_code = 509: Bandwidth Limit Exceeded" ) oauth_token = r.json() if oauth_token["access_token"] is not None: return oauth_token else: raise exc.AuthenticationFailed("Unable to get oauth access token from json") def authenticate(self): config = configparser.ConfigParser() config.read(TOKENS_FILE) if config.has_option("hubic", "refresh_token"): oauth_token = self._refresh_access_token() else: r = requests.get( OAUTH_ENDPOINT + f"auth/?client_id={quote(self._client_id)}&redirect_uri={quote_plus(self._redirect_uri)}" f"&scope=credentials.r,account.r&response_type=code&state={pyrax.utils.random_ascii()}", allow_redirects=False, ) if r.status_code != 200: raise exc.AuthenticationFailed(f"Incorrect/unauthorized client_id ({str(self._parse_error(r))})") try: from lxml import html as lxml_html except ImportError: lxml_html = None if lxml_html: oauth = lxml_html.document_fromstring(r.content).xpath('//input[@name="oauth"]') oauth = oauth[0].value if oauth else None else: oauth = re.search( r'<input\s+[^>]*name=[\'"]?oauth[\'"]?\s+[^>]*value=[\'"]?(\d+)[\'"]?>', r.content, ) oauth = oauth.group(1) if oauth else None if not oauth: raise exc.AuthenticationFailed("Unable to get oauth_id from authorization page") if self._email is None or self._password is None: raise exc.AuthenticationFailed( "Cannot retrieve email and/or password. " "Please run expresslane-hubic-setup.sh" ) r = requests.post( f"{OAUTH_ENDPOINT}auth/", data={ "action": "accepted", "oauth": oauth, "login": self._email, "user_pwd": self._password, "account": "r", "credentials": "r", }, allow_redirects=False, ) try: query = urllib.parse.urlsplit(r.headers["location"]).query code = dict(urllib.parse.parse_qsl(query))["code"] except Exception as e: raise exc.AuthenticationFailed("Unable to authorize client_id, " "invalid login/password ?") oauth_token = self._get_access_token(code) if oauth_token["token_type"].lower() != "bearer": raise exc.AuthenticationFailed("Unsupported access token type") r = requests.get( f"{API_ENDPOINT}account/credentials", auth=BearerTokenAuth(oauth_token["access_token"]), ) swift_token = r.json() self.authenticated = True self.token = swift_token["token"] self.expires = swift_token["expires"] self.services["object_store"] = Service( self, { "name": "HubiC", "type": "cloudfiles", "endpoints": [{"public_url": swift_token["endpoint"]}], }, ) self.username = self.password = None