%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