#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2014 Gu1
# Licensed under the MIT license
import os
import pyrax
import pyrax.exceptions as exc
import requests
import re
import urlparse
import ConfigParser
import time
from pyrax.base_identity import BaseIdentity, Service
from requests.compat import quote, quote_plus
OAUTH_ENDPOINT = "https://api.hubic.com/oauth/"
API_ENDPOINT = "https://api.hubic.com/1.0/"
TOKENS_FILE = os.path.expanduser("~/.hubic_tokens")
[docs]class BearerTokenAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, req):
req.headers['Authorization'] = 'Bearer ' + self.token
return req
[docs]class HubicIdentity(BaseIdentity):
def _get_auth_endpoint(self):
return ""
[docs] 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 = urlparse.urlsplit(resp.headers['location']).query
qs = dict(urlparse.parse_qsl(query))
return {'error': qs['error'], 'error_description': qs['error_description']}
def _get_access_token(self, code):
r = requests.post(
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:
err = {}
raise exc.AuthenticationFailed("Unable to get oauth access token, "
"wrong client_id or client_secret ? (%s)" %
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(
"Unable to get oauth access token, wrong client_id or client_secret ? (%s)" %
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(
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:
err = {}
raise exc.AuthenticationFailed(
"Unable to get oauth access token, wrong client_id or client_secret ? (%s)" %
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")
[docs] 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 + 'auth/?client_id={0}&redirect_uri={1}'
'&scope=credentials.r,account.r&response_type=code&state={2}'.format(
quote(self._client_id),
quote_plus(self._redirect_uri),
pyrax.utils.random_ascii() # csrf ? wut ?..
),
allow_redirects=False
)
if r.status_code != 200:
raise exc.AuthenticationFailed("Incorrect/unauthorized "
"client_id (%s)" % 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(
OAUTH_ENDPOINT + 'auth/',
data={
'action': 'accepted',
'oauth': oauth,
'login': self._email,
'user_pwd': self._password,
'account': 'r',
'credentials': 'r',
},
allow_redirects=False
)
try:
query = urlparse.urlsplit(r.headers['location']).query
code = dict(urlparse.parse_qsl(query))['code']
except:
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(
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