upload picture through external website to gravatar profile - python

I was wondering if it is possible to create an upload function to upload picture through my own site to the gravatar site?

Yes, this is possible. See http://en.gravatar.com/site/implement/xmlrpc/ , specifically the grav.saveData or grav.SaveUrl calls.

Yes it's possible!
import base64
from xmlrpclib import ServerProxy, Fault
from hashlib import md5
class GravatarXMLRPC(object):
API_URI = 'https://secure.gravatar.com/xmlrpc?user={0}'
def __init__(self, request, password=''):
self.request = request
self.password = password
self.email = sanitize_email(request.user.email)
self.email_hash = md5_hash(self.email)
self._server = ServerProxy(
self.API_URI.format(self.email_hash))
def saveData(self, image):
""" Save binary image data as a userimage for this account """
params = { 'data': base64_encode(image.read()), 'rating': 0, }
return self._call('saveData', params)
#return self.useUserimage(image)
def _call(self, method, params={}):
""" Call a method from the API, gets 'grav.' prepended to it. """
args = { 'password': self.password, }
args.update(params)
try:
return getattr(self._server, 'grav.' + method, None)(args)
except Fault as error:
error_msg = "Server error: {1} (error code: {0})"
print error_msg.format(error.faultCode, error.faultString)
def base64_encode(obj):
return base64.b64encode(obj)
def sanitize_email(email):
return email.lower().strip()
def md5_hash(string):
return md5(string.encode('utf-8')).hexdigest()
Just call the class in your view :)

Related

Coinbase Pro API Authentication - Invalid Signature

Hoping to get some help on making calls to the Coinbase Pro API.
I created a key, noted my phasephrase, key and secret, and am running the below Python script. The response I get is "invalid signature".
On the CBPro documentation site, when I try running it with my credentials on this page, I get a "Sorry, you couldn't be authenticated with those credentails" message.
I've seen some sources that encode to base64 and have success, and others that don't, but neither works for me. What am I don't wrong?
Code:
import requests
import time
import base64
import json
url = "https://api.exchange.coinbase.com/accounts/account_id/transfers?limit=100"
key = "key"
secret = "secret"
passphrase = "pass"
timestamp = str(time.time())
headers = {
"Accept": "application/json",
"cb-access-key": key,
"cb-access-passphrase": passphrase,
"cb-access-sign": encodedData,
"cb-access-timestamp": timestamp
}
response = requests.request("GET", url, headers=headers)
print(response.text)
Signing a request is probably the worst part of the coinbase api.
Here is the documentation for it. some things to note:
signature is only good for 30 seconds so you have to hurry and copy / paste the encoded data and timestamp into the docs form.
the timestamp has to be the same value you use in the encodedData.
the non-pro api signs a little different so make sure you're on the right set of docs. This one I don't think works for pro.
If you're still having trouble authenticating your requests to coinbase here's an example of what i'm using. You might have to change a few things but does the job.
What you're looking for in this example are the HMACAuth & CoinbaseSession classes for your particular need.
# -*- coding: UTF-8 -*-
from base64 import b64encode, b64decode
from collections import namedtuple
from datetime import datetime, timezone
from hashlib import sha256
from hmac import HMAC
from json import loads, JSONDecodeError
from types import SimpleNamespace
from typing import Union, Generator
from requests import Session
from requests.adapters import HTTPAdapter
from requests.auth import AuthBase
from requests.exceptions import HTTPError
from requests.models import PreparedRequest
from requests.utils import to_native_string
from urllib3.util.retry import Retry
EXCHANGE: str = r"https://api.exchange.coinbase.com"
ACCOUNT = namedtuple(
"ACCOUNT",
(
"id",
"currency",
"balance",
"hold",
"available",
"profile_id",
"trading_enabled",
)
)
PRODUCT = namedtuple(
"PRODUCT",
(
"id",
"base_currency",
"quote_currency",
"base_min_size",
"base_max_size",
"quote_increment",
"base_increment",
"display_name",
"min_market_funds",
"max_market_funds",
"margin_enabled",
"fx_stablecoin",
"max_slippage_percentage",
"post_only",
"limit_only",
"cancel_only",
"trading_disabled",
"status",
"status_message",
"auction_mode",
)
)
def encode(value: Union[str, bytes]) -> bytes:
"""Encode the string `value` with UTF-8."""
if isinstance(value, str) is True:
value = value.encode("UTF-8")
return value
def decode(value: Union[bytes, str]) -> str:
"""Decode the bytes-like object `value` with UTF-8."""
if isinstance(value, bytes) is True:
value = value.decode("UTF-8")
return value
def req_time():
"""POSIX timestamp as float. Number of seconds since Unix Epoch in UTC."""
utc = get_utc()
return utc.timestamp()
def get_utc() -> datetime:
"""Construct a datetime object with UTC time zone info."""
return datetime.now(timezone.utc)
class TimeoutHTTPAdapter(HTTPAdapter):
"""Custom HTTP adapter with timeout capability."""
def __init__(self, *args, **kwargs):
self._timeout = kwargs.pop("timeout")
super(TimeoutHTTPAdapter, self).__init__(*args, **kwargs)
def send(self, request, **kwargs):
kwargs.update({"timeout": self._timeout})
return super(TimeoutHTTPAdapter, self).send(request, **kwargs)
class CoinbaseSession(Session):
"""Coinbase Session handle."""
_headers: dict = {
"Accept": "application/json",
"Content-Type": "application/json",
"Accept-Charset": "utf-8",
}
#staticmethod
def http_adapter(retries: int = 3, backoff: int = 1, timeout: int = 30):
return TimeoutHTTPAdapter(
max_retries=Retry(total=retries, backoff_factor=backoff),
timeout=timeout
)
def __init__(self):
super(CoinbaseSession, self).__init__()
self.headers.update(self._headers)
self.auth = HMACAuth()
self.mount("https://", self.http_adapter())
self.mount("http://", self.http_adapter())
class HMACAuth(AuthBase):
"""Requests signing handle."""
#staticmethod
def __pre_hash(timestamp: Union[str, int, float], request: PreparedRequest) -> bytes:
"""
Create the pre-hash string by concatenating the timestamp with
the request method, path_url and body if exists.
"""
message = f"{timestamp}{request.method.upper()}{request.path_url}"
body = request.body
if body is not None:
message = f"{message}{decode(body)}"
return encode(message)
#staticmethod
def __sign(message: bytes) -> bytes:
"""Create a sha256 HMAC and sign the required `message`."""
key = b64decode(encode(API.SECRET)) # be careful were you keep this!
hmac = HMAC(key=key, msg=message, digestmod=sha256).digest()
return b64encode(hmac)
def __call__(self, request: PreparedRequest):
timestamp = req_time()
message = self.__pre_hash(timestamp, request)
cb_access_sign = self.__sign(message)
request.headers.update(
{
to_native_string('CB-ACCESS-KEY'): API.KEY, # be careful where you keep this!
to_native_string('CB-ACCESS-SIGN'): cb_access_sign,
to_native_string('CB-ACCESS-TIMESTAMP'): str(timestamp),
to_native_string('CB-ACCESS-PASSPHRASE'): API.PASSPHRASE, # be careful where you keep this!
}
)
return request
class CoinbaseAPI(object):
"""Coinbase API handle."""
def __init__(self):
self._session = CoinbaseSession()
def request(self, **kwargs):
"""
Send HTTP requests to the Coinbase API.
Raises HTTPError if response is not 200.
"""
print(f"DEBUG: Requesting resource (url = {kwargs.get('url')})")
try:
results = self.__request(**kwargs)
except HTTPError as http_error:
print(f"ERROR: Resource not found!", f"Cause: {http_error}")
else:
print(f"DEBUG: Resource found (url = {kwargs.get('url')})")
return results
def __request(self, **kwargs):
"""
Send HTTP requests to the Coinbase API.
Raises HTTPError if response is not 200.
"""
method = getattr(self._session, kwargs.pop("method"))
response = method(**kwargs)
if response.status_code != 200:
response.raise_for_status()
else:
try:
results = loads(response.text)
except JSONDecodeError as json_error:
print("WARNING: Decoding JSON object failed!", f"Cause: {json_error}")
kwargs.update({"method": method.__name__})
return self.__request(**kwargs)
else:
return results
class Endpoints(object):
"""Coinbase server endpoints."""
_server = None
#staticmethod
def join(*path, **params) -> str:
"""
Construct the resource url by appending all `path`
items to base url and join `params` if any.
"""
url = "/".join(path)
if len(params) > 0:
params = [f"{key}={value}" for key, value in params.items()]
url = f"{url}?{'&'.join(params)}"
return url
class ExchangeEndpoints(Endpoints):
"""Coinbase exchange server endpoints."""
_server = EXCHANGE
def __init__(self):
self.products = self.join(self._server, "products")
self.accounts = self.join(self._server, "accounts")
class Exchange(CoinbaseAPI):
"""Coinbase exchange API client handle."""
_endpoints = ExchangeEndpoints()
def __init__(self):
super(Exchange, self).__init__()
def get_accounts(self) -> Generator:
"""Get a list of trading accounts from the profile of the API key."""
response = self._accounts()
for item in response:
yield ACCOUNT(**item)
def get_account(self, account_id: str) -> ACCOUNT:
"""
Information for a single account. Use this endpoint when you know the account_id.
API key must belong to the same profile as the account.
"""
response = self._accounts(account_id)
return ACCOUNT(**response)
def get_products(self, query: str = None) -> Generator:
"""
Gets a list of available currency pairs for trading.
:param query: `type` query parameter (unknown).
"""
response = self._products(type=query)
for item in response:
yield PRODUCT(**item)
def get_product(self, product_id: str) -> PRODUCT:
"""
Get information on a single product.
:param product_id: The `id` string of the product/currency pair (ex: BTC-USD).
"""
result = self._products(product_id)
return PRODUCT(**result)
def _accounts(self, *path, **params):
"""Access resources from the `accounts` endpoint of the exchange API."""
params = self.clean_params(**params)
_url = self._endpoints.join(
self._endpoints.accounts,
*path,
**params
)
return self.request(method="get", url=_url)
def _products(self, *path, **params):
"""Access resources from the `products` endpoint of the exchange API."""
params = self.clean_params(**params)
_url = self._endpoints.join(
self._endpoints.products,
*path,
**params
)
return self.request(method="get", url=_url)
def clean_params(self, **params) -> dict:
"""Clean `params` by removing None values."""
temp = dict()
for key, value in params.items():
if value is None:
continue
if isinstance(value, dict) is True:
value = self.clean_params(**value)
temp.update({key: value})
return temp
if __name__ == '__main__':
# we're using PBKDF2HMAC (with symmetrically derived encryption key)
# not included in this example
key_vault = KeyVault() # custom class for encrypting and storing secrets to keyring
key_vault.cypher.password(value="your_password", salt="salty_password")
api_credentials = loads(
key_vault.get_password("coinbase", "pro-coinbase-api")
)
# accounts:
ADA: str = "your_crypto_account_id" # ex: 8b9806a4-7395-11ec-9b1a-f02f74d9105d
API = SimpleNamespace(
NAME="pro-coinbase-api",
VERSION="2021-08-27",
KEY=api_credentials.get("key"),
PASSPHRASE=api_credentials.get("passphrase"),
SECRET=api_credentials.get("secret"),
) # not the best example but does the job as long as you don't invite hackers in your PC :)
api = Exchange()
accounts = api.get_accounts()
for account in accounts:
print(account)
account = api.get_account(account_id=ADA)
print(account)
products = api.get_products()
for product in products:
print(product)
product = api.get_product("ATOM-EUR")
print(product)

requests.session() object not recognized in another class

I am trying to pass my session object from one class to another. But I am not sure whats happening.
class CreateSession:
def __init__(self, user, pwd, url="http://url_to_hit"):
self.url = url
self.user = user
self.pwd = pwd
def get_session(self):
sess = requests.Session()
r = sess.get(self.url + "/", auth=(self.user, self.pwd))
print(r.content)
return sess
class TestGet(CreateSession):
def get_response(self):
s = self.get_session()
print(s)
data = s.get(self.url + '/some-get')
print(data.status_code)
print(data)
if __name__ == "__main__":
TestGet(user='user', pwd='pwd').get_response()
I am getting 401 for get_response(). Not able to understand this.
What's a 401?
The response you're getting means that you're unauthorised to access the resource.
A session is used in order to persist headers and other prerequisites throughout requests, why are you creating the session every time rather than storing it in a variable?
As is, the session should work the only issue is that you're trying to call a resource that you don't have access to. - You're not passing the url parameter either in the initialisation.
Example of how you can effectively use Session:
from requests import Session
from requests.exceptions import HTTPError
class TestGet:
__session = None
__username = None
__password = None
def __init__(self, username, password):
self.__username = username
self.__password = password
#property
def session(self):
if self.__session is None:
self.__session = Session()
self.__session.auth = (self.__user, self.__pwd)
return self.__session
#session.setter
def session(self, value):
raise AttributeError('Setting \'session\' attribute is prohibited.')
def get_response(self, url):
try:
response = self.session.get(url)
# raises if the status code is an error - 4xx, 5xx
response.raise_for_status()
return response
except HTTPError as e:
# you received an http error .. handle it here (e contains the request and response)
pass
test_get = TestGet('my_user', 'my_pass')
first_response = test_get.get_response('http://your-website-with-basic-auth.com')
second_response = test_get.get_response('http://another-url.com')
my_session = test_get.session
my_session.get('http://url.com')

Getting type error in python

I am using a class based service in python and I get error whenever I want to use it. Unable to figure out the reason.
#!/usr/bin/python
# -*- coding: utf-8 -*-
from xml.dom import minidom
from pysimplesoap.client import SoapClient
from pysimplesoap.helpers import sort_dict
MEDIA_ROOT = '/User/sunand/documents/resumes/'
parser = ResumeParser()
names = parser.get_names(MEDIA_ROOT)
print names
class ParserClient(SoapClient):
""" Extends the soap client to encode the response with utf-8 encoding.
"""
def wsdl_call(
self,
method,
*args,
**kwargs
):
""" Override wsdl_call method to make sure unmarshall is not called.
"""
operation = self.get_operation(method)
# get i/o type declarations:
inp = operation['input']
header = operation.get('header')
if 'action' in operation:
self.action = operation['action']
# construct header and parameters
if header:
self.__call_headers = sort_dict(header, self.__headers)
(method, params) = self.wsdl_call_get_params(method, inp,
*args, **kwargs)
response = self.call(method, *params)
return response
def send(self, method, xml):
""" Overrides the send method to get the actual xml content.
"""
content = super(ParserClient, self).send(method, xml)
self.result = content
return content
class ResumeParser(object):
""" Connects to the Resume Parser's XML api to get parsed data.
"""
def __init__(self, simple=True, timeout=60):
""" Initializes the ResumeParser class.
"""
self.wsdl = \
'http://jobsite.onlineresumeparser.com/rPlusParseResume.asmx?WSDL'
self.secret = 'my-secret-key' # Enter key here
self.encoding = 'base64'
self.simple = simple
self.client = ParserClient(wsdl=self.wsdl, timeout=timeout)
self.names = []
def get_file_content(self, file_path):
""" Return the encoded content for the given file.
"""
file_obj = open(os.path.abspath(file_path), 'r')
content = file_obj.read().encode(self.encoding)
file_obj.close()
return content
def get_names(self, path):
"""
Given a path to a folder that contains resume files this method
will parse the resumes and will return the names of the candidates
as a list.
"""
opt = os.path
resumes = [opt.join(path, r) for r in os.listdir(path)
if opt.isfile(opt.join(path, r))]
# Parse information for each resume.
for resume in resumes:
try:
xml_data = self.get_xml(resume)
name = self.get_name_from_xml(xml_data)
if name:
self.names.append(name)
except Exception, err:
# print name
print 'Error parsing resume: %s' % str(err)
return list(set(self.names))
def get_name_from_xml(self, data):
""" Returns the full name from the xml data given.
"""
xmldata = minidom.parseString(data)
name = xmldata.getElementsByTagName('CANDIDATE_FULL_NAME')
name = name[0].childNodes[0].data.title()
return name
def get_xml(self, filepath):
""" Fetches and returns the xml for the given file from the api.
"""
filename = os.path.basename(filepath)
extension = os.path.splitext(filepath)[1]
base64 = self.get_file_content(filepath)
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret,
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
return self.process_raw_xml()
def process_raw_xml(self, data=None):
""" Processes and returns the clean XML.
"""
raw = (data if data else self.client.result)
parsed = minidom.parseString(raw)
result = parsed.getElementsByTagName('GetSimpleXMLResult')[0]
text_node = result.childNodes[0]
data = text_node.data.encode('UTF-8')
return data
Upon running the code I am getting an error
TypeError: wsdl_call_get_params() got an unexpected keyword argument 'secretKey'
What am I doing wrong?
It looks like you are incorrectly overriding wsdl_call.
Firstly, we can see that SoapClient (which you extend in ParserClient), has a __getattr__ function that fetches pseudo-attributes of the SoapClient.
def __getattr__(self, attr):
"Return a pseudo-method that can be called"
if not self.services: # not using WSDL?
return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs)
else: # using WSDL:
return lambda *args, **kwargs: self.wsdl_call(attr,*args,**kwargs)
You can see that this function is using wsdl_call to help it map functions to unknown attributes.
The specific pseudo-method that is causing the problem is in your code (or appears to be):
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret, # <-- the secretKey key word argument
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
# here client is an instance of your `ParserClient` (and `SoapClient`).
This above bit took me a while to track down. With a full stack trace I would have found it much quicker. Please always post stack traces (when there is one) in future when asking for help.
How to solve this
Provide a concrete implementation of GetSimpleXML and getHRXML. This will solve the immediate problem, but not the larger problem.
Rewrite wsdl_call
The rewritten section of code should check the value of the method argument and either do what you want, or delegate to the SoapClient implementation.
eg.
def wsdl_call(self, method, *args, **kwargs):
if method == "some_method":
return self._my_wsdl_call(method, *args, **kwargs)
else:
return super(ParserClient, self).wsdl_call(method, *args, **kwargs)
def _my_wsdl_call(self, method, *args, **kwargs):
...

flask http-auth and unittesting

Hi!
I have a route that I have protected using HTTP Basic authentication, which is implemented by Flask-HTTPAuth. Everything works fine (i can access the route) if i use curl, but when unit testing, the route can't be accessed, even though i provide it with the right username and password.
Here are the relevant code snippets in my testing module:
class TestClient(object):
def __init__(self, app):
self.client = app.test_client()
def send(self, url, method, data=None, headers={}):
if data:
data = json.dumps(data)
rv = method(url, data=data, headers=headers)
return rv, json.loads(rv.data.decode('utf-8'))
def delete(self, url, headers={}):
return self.send(url, self.client.delete, headers)
class TestCase(unittest.TestCase):
def setUp(self):
app.config.from_object('test_config')
self.app = app
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
self.client = TestClient(self.app)
def test_delete_user(self):
# create new user
data = {'username': 'john', 'password': 'doe'}
self.client.post('/users', data=data)
# delete previously created user
headers = {}
headers['Authorization'] = 'Basic ' + b64encode((data['username'] + ':' + data['password'])
.encode('utf-8')).decode('utf-8')
headers['Content-Type'] = 'application/json'
headers['Accept'] = 'application/json'
rv, json = self.client.delete('/users', headers=headers)
self.assertTrue(rv.status_code == 200) # Returns 401 instead
Here are the callback methods required by Flask-HTTPAuth:
auth = HTTPBasicAuth()
#auth.verify_password
def verify_password(username, password):
# THIS METHOD NEVER GETS CALLED
user = User.query.filter_by(username=username).first()
if not user or not user.verify_password(password):
return False
g.user = user
return True
#auth.error_handler
def unauthorized():
response = jsonify({'status': 401, 'error': 'unauthorized', 'message': 'Please authenticate to access this API.'})
response.status_code = 401
return response
Any my route:
#app.route('/users', methods=['DELETE'])
#auth.login_required
def delete_user():
db.session.delete(g.user)
db.session.commit()
return jsonify({})
The unit test throws the following exception:
Traceback (most recent call last):
File "test_api.py", line 89, in test_delete_user
self.assertTrue(rv.status_code == 200) # Returns 401 instead
AssertionError: False is not true
I want to emphazise once more that everything works fine when i run curl with exactly the same arguments i provide for my test client, but when i run the test, verify_password method doesn't even get called.
Thank you very much for your help!
Here is an example how this could be done with pytest and the inbuilt monkeypatch fixture.
If I have this API function in some_flask_app:
from flask_httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
#app.route('/api/v1/version')
#auth.login_required
def api_get_version():
return jsonify({'version': get_version()})
I can create a fixture that returns a flask test client and patches the authenticate function in HTTPBasicAuth to always return True:
import pytest
from some_flask_app import app, auth
#pytest.fixture(name='client')
def initialize_authorized_test_client(monkeypatch):
app.testing = True
client = app.test_client()
monkeypatch.setattr(auth, 'authenticate', lambda x, y: True)
yield client
app.testing = False
def test_settings_tracking(client):
r = client.get("/api/v1/version")
assert r.status_code == 200
You are going to love this.
Your send method:
def send(self, url, method, data=None, headers={}):
pass
Your delete method:
def delete(self, url, headers={}):
return self.send(url, self.client.delete, headers)
Note you are passing headers as third positional argument, so it's going as data into send().

Google Oauth 2 using flask_googlelogin

Hello Stackoveflow members
I am making a Google Plus user authentication using Oauth and at the same time I need to fetch user profile, pics and drive information as well. The best way to do this is to use Oauth. So I am using flask_googlelogin.
I am trying to use the example.py of this library but What I found that the API works well and my Application pages comes up with information and Cancel and Accept button. But when I push accept button, I get the flask error
TypeError: 'instancemethod' object has no attribute 'getitem'
Now please have a look at the example.py code and flask_googlelogin.py code
here
import json
from flask import Flask, url_for, redirect, session
from flask_login import (UserMixin, login_required, login_user, logout_user,
current_user)
from flask_googlelogin import GoogleLogin
users = {}
app = Flask(__name__)
app.config.update(
SECRET_KEY='<secret_key>',
GOOGLE_LOGIN_CLIENT_ID='<client_id>',
GOOGLE_LOGIN_CLIENT_SECRET='<client_secret>',
GOOGLE_LOGIN_REDIRECT_URI='<redirection_url>')
googlelogin = GoogleLogin(app)
class User(UserMixin):
def __init__(self, userinfo):
self.id = userinfo['id']
self.name = userinfo['name']
self.picture = userinfo.get('picture')
#googlelogin.user_loader
def get_user(userid):
return users.get(userid)
#app.route('/')
def index():
return """
<p><a href="%s">Login</p>
<p><a href="%s">Login with extra params</p>
<p><a href="%s">Login with extra scope</p>
""" % (
googlelogin.login_url(approval_prompt='force'),
googlelogin.login_url(approval_prompt='force',
params=dict(extra='large-fries')),
googlelogin.login_url(
approval_prompt='force',
scopes=['https://www.googleapis.com/auth/drive'],
access_type='offline',
),
)
#app.route('/profile')
#login_required
def profile():
return """
<p>Hello, %s</p>
<p><img src="%s" width="100" height="100"></p>
<p>Token: %r</p>
<p>Extra: %r</p>
<p>Logout</p>
""" % (current_user.name, current_user.picture, session.get('token'),
session.get('extra'))
#app.route('/oauth2callback')
#googlelogin.oauth2callback
def login(token, userinfo, **params):
user = users[userinfo['id']] = User(userinfo)
login_user(user)
session['token'] = json.dumps(token)
session['extra'] = params.get('extra')
return redirect(params.get('next', url_for('.profile')))
#app.route('/logout')
def logout():
logout_user()
session.clear()
return """
<p>Logged out</p>
<p>Return to /</p>
"""
app.run(debug=True)
and flask_googlelogin.py code here
"""
Flask-GoogleLogin
"""
from base64 import (urlsafe_b64encode as b64encode,
urlsafe_b64decode as b64decode)
from urllib import urlencode
from urlparse import parse_qsl
from functools import wraps
from flask import request, redirect, abort, current_app, url_for
from flask_login import LoginManager, make_secure_token
import requests
GOOGLE_OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth'
GOOGLE_OAUTH2_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
GOOGLE_OAUTH2_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo'
USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile'
class GoogleLogin(object):
"""
Main extension class
"""
def __init__(self, app=None, login_manager=None):
if login_manager:
self.login_manager = login_manager
else:
self.login_manager = LoginManager()
if app:
self._app = app
self.init_app(app)
def init_app(self, app, add_context_processor=True, login_manager=None):
"""
Initialize with app configuration. Existing
`flask_login.LoginManager` instance can be passed.
"""
if login_manager:
self.login_manager = login_manager
else:
self.login_manager = LoginManager()
# Check if login manager has been init
if not hasattr(app, 'login_manager'):
self.login_manager.init_app(
app,
add_context_processor=add_context_processor)
# Clear flashed messages since we redirect to auth immediately
self.login_manager.login_message = None
self.login_manager.needs_refresh_message = None
# Set default unauthorized callback
self.login_manager.unauthorized_handler(self.unauthorized_callback)
#property
def app(self):
return getattr(self, '_app', current_app)
#property
def scopes(self):
return self.app.config.get('GOOGLE_LOGIN_SCOPES', '')
#property
def client_id(self):
return self.app.config['GOOGLE_LOGIN_CLIENT_ID']
#property
def client_secret(self):
return self.app.config['GOOGLE_LOGIN_CLIENT_SECRET']
#property
def redirect_uri(self):
return self.app.config.get('GOOGLE_LOGIN_REDIRECT_URI')
#property
def redirect_scheme(self):
return self.app.config.get('GOOGLE_LOGIN_REDIRECT_SCHEME', 'http')
def sign_params(self, params):
return b64encode(urlencode(dict(sig=make_secure_token(**params),
**params)))
def parse_state(self, state):
return dict(parse_qsl(b64decode(str(state))))
def login_url(self, params=None, **kwargs):
"""
Return login url with params encoded in state
Available Google auth server params:
response_type: code, token
prompt: none, select_account, consent
approval_prompt: force, auto
access_type: online, offline
scopes: string (separated with commas) or list
redirect_uri: string
login_hint: string
"""
kwargs.setdefault('response_type', 'code')
kwargs.setdefault('access_type', 'online')
if 'prompt' not in kwargs:
kwargs.setdefault('approval_prompt', 'auto')
scopes = kwargs.pop('scopes', self.scopes.split(','))
if USERINFO_PROFILE_SCOPE not in scopes:
scopes.append(USERINFO_PROFILE_SCOPE)
redirect_uri = kwargs.pop('redirect_uri', self.redirect_uri)
state = self.sign_params(params or {})
return GOOGLE_OAUTH2_AUTH_URL + '?' + urlencode(
dict(client_id=self.client_id,
scope=' '.join(scopes),
redirect_uri=redirect_uri,
state=state,
**kwargs))
def unauthorized_callback(self):
"""
Redirect to login url with next param set as request.url
"""
return redirect(self.login_url(params=dict(next=request.url)))
def exchange_code(self, code, redirect_uri):
"""
Exchanges code for token/s
"""
token = requests.post(GOOGLE_OAUTH2_TOKEN_URL, data=dict(
code=code,
redirect_uri=redirect_uri,
grant_type='authorization_code',
client_id=self.client_id,
client_secret=self.client_secret,
)).json
if not token: # or token.get('error'):
abort(400)
return token
def get_userinfo(self, access_token):
userinfo = requests.get(GOOGLE_OAUTH2_USERINFO_URL, params=dict(
access_token=access_token,
)).json
if not userinfo: # or userinfo.get('error'):
abort(400)
return userinfo
def get_access_token(self, refresh_token):
"""
Use a refresh token to obtain a new access token
"""
token = requests.post(GOOGLE_OAUTH2_TOKEN_URL, data=dict(
refresh_token=refresh_token,
grant_type='refresh_token',
client_id=self.client_id,
client_secret=self.client_secret,
)).json
if not token: # or token.get('error'):
return
return token
def oauth2callback(self, view_func):
"""
Decorator for OAuth2 callback. Calls `GoogleLogin.login` then
passes results to `view_func`.
"""
#wraps(view_func)
def decorated(*args, **kwargs):
params = {}
# Check sig
if 'state' in request.args:
params.update(**self.parse_state(request.args.get('state')))
if params.pop('sig', None) != make_secure_token(**params):
return self.login_manager.unauthorized()
code = request.args.get('code')
# Web server flow
if code:
token = self.exchange_code(
code,
url_for(
request.endpoint,
_external=True,
_scheme=self.redirect_scheme,
),
)
#received = get_access_token(token['access_token'])
userinfo = self.get_userinfo(token['access_token'])
params.update(token=token, userinfo=userinfo)
# Browser flow
else:
if params:
params.update(dict(request.args.items()))
else:
return '''
<script>
window.onload = function() {
location.href = '?' + window.location.hash.substr(1);
};
</script>
'''
return view_func(**params)
return decorated
def user_loader(self, func):
"""
Shortcut for `login_manager`'s `flask_login.LoginManager.user_loader`
"""
self.login_manager.user_loader(func)
Please Note down there may be some disturbed code in def oauth2callback and the condition if code the line is
userinfo = self.get_userinfo(token['access_token'])
Here token['access_token'] produces the error names "TypeError: 'instancemethod' object has no attribute 'getitem'"
Please let me know how can I fix it
Looks like a bug or api change.
In exchange_code
token = requests.post(GOOGLE_OAUTH2_TOKEN_URL, data=dict(
code=code,
redirect_uri=redirect_uri,
grant_type='authorization_code',
client_id=self.client_id,
client_secret=self.client_secret,
)).json
token is now the json function. In newer versions of flask_googlelogin this is json().

Categories