"Missing needed parameter state" when hitting the /auth/complete/ url? - python

I'm using the Python social auth with a custom backend. Here is my code so far:
from requests import HTTPError
from social.backends.oauth import BaseOAuth2
from social.exceptions import AuthFailed
class MyOAuth2(BaseOAuth2):
#testing OAuth authentication backend
name = 'testing'
AUTHORIZATION_URL = 'https://<my_drupal_oauth2_server>/oauth2/authorize'
ACCESS_TOKEN_URL = 'https://<my_drupal_oauth2_server>/oauth2/token'
SCOPE_SEPARATOR = ','
STATE_PARAMETER = 'False'
REDIRECT_STATE = 'False'
EXTRA_DATA = [
('id', 'id'),
('expires', 'expires'),
]
def get_user_details(self, response):
#Return user details from test account
return {'username': response.get('login'),
'email': response.get('email') or '',
'first_name': response.get('name')}
#def user_data(self, access_token, *args, **kwargs):
# #Loads user data from service
This is my first attempt to implement an OAuth2 setup using a custom back end -- first attempt to write one.
The OAuth2 server works with Google's OAuth2 playground. My django app (which is Open edX) however is throwing the error.
If I manually add ?state=xyz to the url, I get another error:
Session value state missing
I've searched for good information on both of these errors and I'm coming up short on what a root cause might be, and a solution.

Related

eBay Marketplace Account Deletion/Closure Notifications

I am trying to start an eBay API in Python and I can't find a single answer as to how to get an API key with eBay's new requirements of "Account Deletion/Closure Notifications." Here's the link: https://developer.ebay.com/marketplace-account-deletion
Specifically, I am told that "Your Keyset is currently disabled" because I have not completed whatever process is needed for this marketplace account deletion/closure notification.
The problems?
I have no idea if I need this.
I have no idea how to actually do this.
Re: 1. It looks like this is for anyone who stores user data. I don’t think that’s me intentionally because I really just want to get sold data and current listings, but is it actually me?
Re: 2. I don’t understand how to validate it and send back the proper responses. I’ve gotten quite good at python but I’m lost here.
eBay forums are completely useless and I see no one with an answer to this. Any help is greatly appreciated.
Re: 1. Same. Here's my interpretation: In order to use their APIs, you need to provide (and configure) your own API, so they can communicate with you —programatically— and tell you what users have asked to have their accounts/data deleted.
Re: 2. To handle their GET and POST requests, I guess you'll need to configure a website's URL as an API endpoint. In Django, I might use something like this (untested) code:
import hashlib
import json
from django.http import (
HttpResponse,
JsonResponse,
HttpResponseBadRequest
)
def your_api_endpoint(request):
"""
API Endpoint to handle the verification's challenge code and
receive eBay's Marketplace Account Deletion/Closure Notifications.
"""
# STEP 1: Handle verification's challenge code
challengeCode = request.GET.get('challenge_code')
if challengeCode is not None:
# Token needs to be 32-80 characters long
verificationToken = "your-token-012345678901234567890123456789"
# URL needs to use HTTPS protocol
endpoint_url = "https://your-domain.com/your-endpoint"
# Hash elements need to be ordered as follows
m = hashlib.sha256((challengeCode+verificationToken+endpoint_url).encode('utf-8'))
# JSON field needs to be called challengeResponse
return JsonResponse({"challengeResponse": m.hexdigest()}, status=200)
# STEP 2: Handle account deletion/closure notification
elif request.method == 'POST':
notification_details = json.loads(request.body)
# Verify notification is actually from eBay
# ...
# Delete/close account
# ...
# Acknowledge notification reception
return HttpResponse(status=200)
else:
return HttpResponseBadRequest()
If you find the answer to question number one, please do let me know.
Re: 1. You need to comply with eBay's Marketplace Account Deletion/Closure Notification workflow if you are storing user data into your own database. For example, using eBay's Buy APIs, you may get access to what users are selling on eBay (for ex. an eBay feed of products). If those eBay sellers decide they want to remove all of their personal data from eBay's database, eBay is requesting you remove their data from your database as well. If you are NOT storing any eBay user data into your database, you do not need to comply. Here is where you can find more info: https://partnerhelp.ebay.com/helpcenter/s/article/Complying-with-the-eBay-Marketplace-Account-Deletion-Closure-Notification-workflow?language=en_US
Re: 2. To be honest I've spent days trying to figure this out in Python (Django), but I have a solution now and am happy to share it with whoever else comes across this issue. Here's my solution:
import os
import json
import base64
import hashlib
import requests
import logging
from OpenSSL import crypto
from rest_framework import status
from rest_framework.views import APIView
from django.http import JsonResponse
logger = logging.getLogger(__name__)
class EbayMarketplaceAccountDeletion(APIView):
"""
This is required as per eBay Marketplace Account Deletion Requirements.
See documentation here: https://developer.ebay.com/marketplace-account-deletion
"""
# Ebay Config Values
CHALLENGE_CODE = 'challenge_code'
VERIFICATION_TOKEN = os.environ.get('VERIFICATION_TOKEN')
# ^ NOTE: You can make this value up so long as it is between 32-80 characters.
ENDPOINT = 'https://example.com/ebay_marketplace_account_deletion'
# ^ NOTE: Replace this with your own endpoint
X_EBAY_SIGNATURE = 'X-Ebay-Signature'
EBAY_BASE64_AUTHORIZATION_TOKEN = os.environ.get('EBAY_BASE64_AUTHORIZATION_TOKEN')
# ^ NOTE: Here's how you can get your EBAY_BASE64_AUTHORIZATION_TOKEN:
# import base64
# base64.b64encode(b'{CLIENT_ID}:{CLIENT_SECRET}')
def __init__(self):
super(EbayMarketplaceAccountDeletion, self).__init__()
def get(self, request):
"""
Get challenge code and return challengeResponse: challengeCode + verificationToken + endpoint
:return: Response
"""
challenge_code = request.GET.get(self.CHALLENGE_CODE)
challenge_response = hashlib.sha256(challenge_code.encode('utf-8') +
self.VERIFICATION_TOKEN.encode('utf-8') +
self.ENDPOINT.encode('utf-8'))
response_parameters = {
"challengeResponse": challenge_response.hexdigest()
}
return JsonResponse(response_parameters, status=status.HTTP_200_OK)
def post(self, request):
"""
Return 200 status code and remove from db.
See how to validate the notification here:
https://developer.ebay.com/api-docs/commerce/notification/overview.html#use
"""
# Verify notification is actually from eBay #
# 1. Use a Base64 function to decode the X-EBAY-SIGNATURE header and retrieve the public key ID and signature
x_ebay_signature = request.headers[self.X_EBAY_SIGNATURE]
x_ebay_signature_decoded = json.loads(base64.b64decode(x_ebay_signature).decode('utf-8'))
kid = x_ebay_signature_decoded['kid']
signature = x_ebay_signature_decoded['signature']
# 2. Call the getPublicKey Notification API method, passing in the public key ID ("kid") retrieved from the
# decoded signature header. Documentation on getPublicKey:
# https://developer.ebay.com/api-docs/commerce/notification/resources/public_key/methods/getPublicKey
public_key = None
try:
ebay_verification_url = f'https://api.ebay.com/commerce/notification/v1/public_key/{kid}'
oauth_access_token = self.get_oauth_token()
headers = {
'Authorization': f'Bearer {oauth_access_token}'
}
public_key_request = requests.get(url=ebay_verification_url, headers=headers, data={})
if public_key_request.status_code == 200:
public_key_response = public_key_request.json()
public_key = public_key_response['key']
except Exception as e:
message_title = "Ebay Marketplace Account Deletion: Error calling getPublicKey Notfication API."
logger.error(f"{message_title} Error: {e}")
return JsonResponse({}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 3. Initialize the cryptographic library to perform the verification with the public key that is returned from
# the getPublicKey method. If the signature verification fails, an HTTP status of 412 Precondition Failed is returned.
pkey = crypto.load_publickey(crypto.FILETYPE_PEM, self.get_public_key_into_proper_format(public_key))
certification = crypto.X509()
certification.set_pubkey(pkey)
notification_payload = request.body
signature_decoded = base64.b64decode(signature)
try:
crypto.verify(certification, signature_decoded, notification_payload, 'sha1')
except crypto.Error as e:
message_title = f"Ebay Marketplace Account Deletion: Signature Invalid. " \
f"The signature is invalid or there is a problem verifying the signature. "
logger.warning(f"{message_title} Error: {e}")
return JsonResponse({}, status=status.HTTP_412_PRECONDITION_FAILED)
except Exception as e:
message_title = f"Ebay Marketplace Account Deletion: Error performing cryptographic validation."
logger.error(f"{message_title} Error: {e}")
return JsonResponse({}, status=status.HTTP_412_PRECONDITION_FAILED)
# Take appropriate action to delete the user data. Deletion should be done in a manner such that even the
# highest system privilege cannot reverse the deletion #
# TODO: Replace with your own data removal here
# Acknowledge notification reception
return JsonResponse({}, status=status.HTTP_200_OK)
def get_oauth_token(self):
"""
Returns the OAuth Token from eBay which can be used for making other API requests such as getPublicKey
"""
url = 'https://api.ebay.com/identity/v1/oauth2/token'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': f"Basic {self.EBAY_BASE64_AUTHORIZATION_TOKEN}"
}
payload = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope'
request = requests.post(url=url, headers=headers, data=payload)
data = request.json()
return data['access_token']
#staticmethod
def get_public_key_into_proper_format(public_key):
"""
Public key needs to have \n in places to be properly assessed by crypto library.
"""
return public_key[:26] + '\n' + public_key[26:-24] + '\n' + public_key[-24:]
This is how I am dealing with the ebay notification requirement using Python3 cgi. Because bytes are sent, cannot use cgi.FieldStorage()
import os
import sys
import hashlib
import json
from datetime import datetime
from html import escape
import cgi
import cgitb
import io
include_path = '/var/domain_name/www'
sys.path.insert(0, include_path)
cgitb.enable(display=0, logdir=f"""{include_path}/tmp_errors""") # include_path is OUTDIR
dt_now = datetime.now()
current_dt_now = dt_now.strftime("%Y-%m-%d_%H-%M-%S")
def enc_print(string='', encoding='utf8'):
sys.stdout.buffer.write(string.encode(encoding) + b'\n')
html = ''
challengeCode = ''
# GET
myQuery = os.environ.get('QUERY_STRING')
if myQuery.find('=') != -1:
pos = myQuery.find('=')
var_name = myQuery[:pos]
var_val = myQuery[pos+1:]
challengeCode = var_val
# POST
if os.environ.get('CONTENT_LENGTH') != None:
totalBytes=int(os.environ.get('CONTENT_LENGTH'))
reqbytes=io.open(sys.stdin.fileno(),"rb").read(totalBytes)
if challengeCode != '' :
"""
API Endpoint to handle the verification's challenge code and
receive eBay's Marketplace Account Deletion/Closure Notifications.
"""
# STEP 1: Handle verification's challenge code
# Token needs to be 32-80 characters long
verificationToken = "0123456789012345678901234567890123456789" #sample token
# URL needs to use HTTPS protocol
endpoint = "https://domain_name.com/ebay/notification.py" # sample endpoint
# Hash elements need to be ordered as follows
m = hashlib.sha256( (challengeCode+verificationToken+endpoint).encode('utf-8') )
# JSON field needs to be called challengeResponse
enc_print("Content-Type: application/json")
enc_print("Status: 200 OK")
enc_print()
enc_print('{"challengeResponse":"' + m.hexdigest() + '"}')
exit()
else :
#html += 'var length:' + str(totalBytes) + '\n'
html += reqbytes.decode('utf-8') + '\n'
# STEP 2: Handle account deletion/closure notification
# Verify notification is actually from eBay
# ...
# Delete/close account
# ...
# Acknowledge notification reception
with open( f"""./notifications/{current_dt_now}_user_notification.txt""", 'w') as f:
f.write(html)
enc_print("Content-Type: application/json")
enc_print("Status: 200 OK")
enc_print()
exit()
I've been trying #José Matías Arévalo code. It works except "STEP 2" branch - Django returns 403 error. This is because of by default Django uses CSRF middleware (Cross Site Request Forgery protection). To avoid 403 error we need to marks a view as being exempt from the protection as described here https://docs.djangoproject.com/en/dev/ref/csrf/#utilities so add couple strings in code:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def your_api_endpoint(request):
And in my case I use url "https://your-domain.com/your-endpoint/" with slash symbol "/" at the end of url. Without this slash eBay doesn't confirm subscription.
I am using Flask and this is the code I have used:
from flask import Flask, request
import hashlib
# Create a random verification token, it needs to be 32-80 characters long
verification_token = 'a94cbd68e463cb9780e2008b1f61986110a5fd0ff8b99c9cba15f1f802ad65f9'
endpoint_url = 'https://dev.example.com'
app = Flask(__name__)
# There will be errors if you just use '/' as the route as it will redirect eBays request
# eBay will send a request to https://dev.example.com?challenge_code=123
# The request will get redirected by Flask to https://dev.example.com/?challenge_code=123 which eBay will not accept
endpoint = endpoint_url + '/test'
# The Content-Type header will be added automatically by Flask as 'application/json'
#app.route('/test')
def test():
code = request.args.get('challenge_code')
print('Requests argument:', code)
code = code + token + endpoint
code = code.encode('utf-8')
code = hashlib.sha256(code)
code = code.hexdigest()
print('Hexdigest:', code)
final = {"challengeResponse": code}
return final
## To run locally first use this:
# app.run(port=29)

I keep getting authentication errors with the Google API

I've been trying to make a script that update the YouTube title with the video views, like the Tom Scott video. But I keep getting this error with the authentication.
I get the code to authenticate it but it just says I don't have enough permission.
<HttpError 400 when requesting https://youtube.googleapis.com/youtube/v3/videos?part=%28%27snippet%2Cstatus%2Clocalizations%27%2C%29&alt=json returned "'('snippet'". Details: "[{'message': "'('snippet'", 'domain': 'youtube.part', 'reason': 'unknownPart', 'location': 'part', 'locationType': 'parameter'}]"> File "C:\Users\erik\Documents\PYTHON CODE\view_changer\example.py", line 49, in main response_update = request_update.execute().
Here's my code:
import os
import google_auth_oauthlib.flow
import googleapiclient.discovery
import googleapiclient.errors
scopes = ["https://www.googleapis.com/auth/youtube"]
api_key = "..."
video_1_part_view = 'statistics'
video_1_id_view = 'hVuoPgZJ3Yw'
video_1_part_update= "snippet,status,localizations",
video_1_part_body_update= { "id": "1y94SadSsMU",
"localizations": {"es": { "title": "no hay nada a ver aqui",
"description": "Esta descripcion es en español."
}
}
}
def main():
# Disable OAuthlib's HTTPS verification when running locally.
# *DO NOT* leave this option enabled in production.
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
api_service_name = "youtube"
api_version = "v3"
client_secrets_file = (r"C:\Users\erik\Documents\PYTHON CODE\view_changer\client_secret_379587650859-bbl63je7sh6kva19l33auonkledt09a9.apps.googleusercontent.com.json")
# Get credentials and create an API client
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file(client_secrets_file, scopes)
credentials = flow.run_console()
youtube = googleapiclient.discovery.build(api_service_name, api_version, credentials=credentials)
#check views
request_view= youtube.videos().list( part = video_1_part_view, id = video_1_id_view )
respons_view = request_view.execute()
views = respons_view['items'][0]['statistics']['viewCount']
print (views)
#update video
request_update = youtube.videos().update(part= video_1_part_update, body = video_1_part_body_update)
response_update = request_update.execute()
print(response_update)
main()
# if __name__ == "__main__":
# main()
If the problem is not in the code I might have an other idea of what it might be. When I was first setting up the OAuth, I didn't know I needed to pick what the user would be giving permission to (I know rookie mistake(: ), but since I updated it, when I ask for permission after going to the authentication link the code gives me back, it doesn't ask permission for all the things I told it to do.
According to the official documentation, the request parameter part of the Videos.list API endpoint is defined as:
part (string)
The part parameter specifies a comma-separated list of one or more video resource properties that the API response will include.
[...]
But you're having that request parameter set as:
('snippet,status,localizations',).
That's because (though your code above doesn't show it) you have the variable video_1_part_view defined as:
video_1_part_view = "snippet,status,localizations",
(Do notice the trailing comma). That makes video_1_part_view a Python tuple, which is not what you need to send to the API endpoint.
The same is true for your variable video_1_part_update.
Just remove the trailing commas and things will work OK.

Passwordless authentication flow using Cognito & API Gateway & Lambda (Python)

I've been trying to implement passwordless authentication using AWS Cognito & API Gateway & Lambda (Python)
I have followed these articles:
https://medium.com/digicred/password-less-authentication-in-cognito-cafa016d4db7
https://medium.com/#pjatocheseminario/passwordless-api-using-cognito-and-serverless-framework-7fa952191352
I have configured Cognito (to accept CUSTOM_AUTH), added the Lambdas, and created the API endpoints:
/sign-up
/initiate-auth (aka initiate login)
/respond-to-auth-challenge (aka (verify login)
When calling initiateAuth I receive the following response:
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Incorrect username or password."
I'm using CUSTOM_AUTH which doesn't require password, and the user name is definitely correct because it actually initiates the authentication flow and I receive a code, however because boto3 doesn't respond with a session I can't continue the authentication.
This is how I call Cognito:
res = cognito.initiate_auth(
ClientId=client_id,
AuthFlow="CUSTOM_AUTH",
AuthParameters={
"USERNAME": email,
"PASSWORD": random_password
}
)
It's probably something small I'm missing but I can't figure out what.
Your client code looks OK, mine has ClientId param in it but if your code is not raising an exception then it should be fine. Unless you had Generate client secret option checked when you created your app client.
If that is the case then you have to pass in SECRET_HASH in AuthParameters like the following:
import hmac
import hashlib
import base64
def get_secret_hash(email, client_id, client_secret):
"""
A keyed-hash message authentication code (HMAC) calculated using
the secret key of a user pool client and username plus the client
ID in the message.
"""
message = email + client_id
client_secret = str.encode(client_secret)
dig = hmac.new(client_secret, msg=message.encode('UTF-8'), digestmod=hashlib.sha256).digest()
return base64.b64encode(dig).decode()
client.admin_initiate_auth(
UserPoolId=COGNITO_USER_POOL_ID,
ClientId=CLIENT_ID,
AuthFlow='CUSTOM_AUTH',
AuthParameters={
'USERNAME': email,
'SECRET_HASH': get_secret_hash(email, CLIENT_ID, CLIENT_SECRET) # Omit if secret key option is disabled.
},
)
Next, double check the following:
Under App clients > * > Auth Flows Configuration, is ALLOW_CUSTOM_AUTH option enabled for your client?
Under App integration > App client settings > * > Enabled Identity Providers, is your user pool selected?
If you have Cognito setup correctly and your code still doesn't work then it is probably the lambda code. You probably know this but for password-less custom auth you need to use 3 lambda triggers: Define Auth Challenge, Create Auth Challenge, and Verify Auth Challenge.
Custom auth lambdas events are triggered in the following order:
DefineAuthChallenge_Authentication:
Technically, issueTokens can be set to True here to return tokens without going through the rest of the steps.
def lambda_handler(event, context):
if event['triggerSource'] == 'DefineAuthChallenge_Authentication':
event['response']['challengeName'] = 'CUSTOM_CHALLENGE'
event['response']['issueTokens'] = False
event['response']['failAuthentication'] = False
if event['request']['session']: # Needed for step 4.
# If all of the challenges are answered, issue tokens.
event['response']['issueTokens'] = all(
answered_challenge['challengeResult'] for answered_challenge in event['request']['session'])
return event
CreateAuthChallenge_Authentication:
def lambda_handler(event, context):
if event['triggerSource'] == 'CreateAuthChallenge_Authentication':
if event['request']['challengeName'] == 'CUSTOM_CHALLENGE':
event['response']['privateChallengeParameters'] = {}
event['response']['privateChallengeParameters']['answer'] = 'YOUR CHALLENGE ANSWER HERE'
event['response']['challengeMetadata'] = 'AUTHENTICATE_AS_CHALLENGE'
return event
Then your client must respond to the challenge:
client.respond_to_auth_challenge(
ClientId=CLIENT_ID,
ChallengeName='CUSTOM_CHALLENGE',
Session=session,
ChallengeResponses={
'USERNAME': email,
'ANSWER': 'Extra Protection!',
'SECRET_HASH': get_secret_hash(email, CLIENT_ID, CLIENT_SECRET) # Omit if secret key option is disabled.
}
)
VerifyAuthChallengeResponse_Authentication:
def lambda_handler(event, context):
if event['triggerSource'] == 'VerifyAuthChallengeResponse_Authentication':
if event['request']['challengeAnswer'] == event['request']['privateChallengeParameters']['answer']:
event['response']['answerCorrect'] = True
return event
DefineAuthChallenge_Authentication:
Set event['response']['issueTokens'] to True to return tokens (code shown in step 1), or issue another challenge to keep repeating steps 1-3.
Finally, make sure that if case-insensitivity option is enabled for your user pool too. Also, I can't exactly recall if CUSTOM_AUTH flow worked if the user is in FORCE_CHANGE_PASSWORD status. If the user is in that state, then try settings a permanent password with the sdk to set the status to CONFIRMED.
I was facing the same error, and I think that the error message is misleading.
When you did not respond correctly in Create-Auth-Challenge lambda, you will get this error. So make sure everything is right in your lambda.

Unable to authenitcate with python minds api

I'm trying to access the social site, minds.com via this python api by by installing the module locally with python3 setup.py install && pipenv run python and following the instructions to log in.
However, I get this error message when trying to authenticate:
(For some reason, Stackoverflow doesn't allow me to post the python error-log becuse it isn't indented properly, so here it is https://pastebin.com/0sWa1hmY)
The code, which seems to be called from the python api looks like this:
minds/api.py
# -*- coding: utf-8 -*-
from pprint import pprint
from requests.utils import dict_from_cookiejar, cookiejar_from_dict
from minds.connections import XSRFSession
from minds.exceptions import AuthenticationError
from minds.profile import Profile
from minds.utils import add_url_kwargs
from minds.endpoints import *
from minds.sections import NewsfeedAPI, ChannelAPI, NotificationsAPI,
PostingAPI, InteractAPI
class Minds(NewsfeedAPI, ChannelAPI, NotificationsAPI, PostingAPI, ...):
_xsrf_retries = 5
def __init__(self, profile: Profile = None, login=True):
self.con = XSRFSession()
self.profile = profile
if self.profile:
if profile.cookie:
self.con.cookies = cookiejar_from_dict(profile.cookie)
if profile.proxy:
self.con.proxies = {'https': profile.proxy, 'http':\
profile.proxy}
self._establish_xsrf()
if self.profile and login and not self.is_authenticated:
if profile.username and profile.password:
self.authenticate(profile.username, profile.password)
(...)
def authenticate(self, username, password, save_profile=True) -> dict:
"""
Authenticate current instance with given user
:param: save_profile: whether to save profile locally
"""
auth = {
'username': username,
'password': password
}
resp = self.con.post(AUTHENTICATE_URL, json=auth)
self.user = resp.json()
if self.user['status'] == 'failed':
raise AuthenticationError("Couldn't log in with the ...")
self.guid = self.user['user']['guid']
if save_profile:
Profile(
username=username,
password=password,
cookie=self.get_cookies(),
proxy=self.con.proxies.get('https'),
).save()
return resp.json()
The python API doesn't seem to be maintained, but I think minds.com newly uses jsonwebtokens for authentication. Is the something missing from the api to be jwt-enabled?
After compare to browser request again and again. The reason why occurred error is you gave a wrong content-type, but actually you need to send as json data (it seems website is making joke). So you need to specify headers["content-type"] = "text/plain". Otherwise website will response 500.
Note: To avoid broad discussion, i can only answer this error.
Change the code to below, but there is only one line differs from source code :).
def authenticate(self, username, password, save_profile=True) -> dict:
"""
Authenticate current instance with given user
:param: save_profile: whether to save profile locally
"""
auth = {
'username': username,
'password': password
}
self.con.headers["content-type"] = "text/plain" ####
resp = self.con.post(AUTHENTICATE_URL, json=auth)
self.user = resp.json()
if self.user['status'] == 'failed':
raise AuthenticationError("Couldn't log in with the given credentials")
self.guid = self.user['user']['guid']
if save_profile:
Profile(
username=username,
password=password,
cookie=self.get_cookies(),
proxy=self.con.proxies.get('https'),
).save()
return resp.json()

How to retrieve Google Contacts in Django using oauth2.0?

My app is registered in Google and I have enabled the contacts API.
In the first view I am getting the access token and I am redirecting the user to the Google confirmation page where he will be prompted to give access to his contacts:
SCOPE = 'https://www.google.com/m8/feeds/'
CLIENT_ID = 'xxxxxxxx'
CLIENT_SECRET = 'xxxxxxxxx'
APPLICATION= 'example.com'
USER_AGENT = 'dummy-sample'
APPLICATION_REDIRECT_URI = 'http://example.com/oauth2callback/'
def import_contacts(request):
auth_token = gdata.gauth.OAuth2Token(
client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
scope=SCOPE, user_agent=USER_AGENT)
authorize_url = auth_token.generate_authorize_url(
redirect_uri=APPLICATION_REDIRECT_URI)
return redirect(authorize_url)
If the user clicks Allow, then Google redirects to my handler which shall retrieve the contacts:
def oauth2callback(request):
code = request.GET.get('code', '')
redirect_url = 'http://example.com/oauth2callback?code=%s' % code
url = atom.http_core.ParseUri(redirect_url)
auth_token.get_access_token(url.query)
client = gdata.contacts.service.ContactsService(source=APPLICATION)
auth_token.authorize(client)
feed = client.GetContactsFeed()
As you can see, my problem is how to get the auth_token object in the second view, because this code is failing on the line auth_token.get_access_token(url.query).
I have tried without success multiple options like putting the object in the session but it is not serializable. I tried also gdata.gauth.token_to_blob(auth_token) but then I can retrieve only the token string and not the object. Working with gdata.gauth.ae_save() and ae_load() seem to require in some way Google App Engine.
The alternative approach that I see in order to get the contacts is to request them directly in the first Django view with the access token, instead exchanging the token with the code:
r = requests.get('https://www.google.com/m8/feeds/contacts/default/full?access_token=%s&alt=json&max-results=1000&start-index=1' % (self.access_token))
But this is not redirecting the users to the google page so that they can give explicitly their approval. Instead, it fetches the contacts directly using the token as credentials. Is this a common practice? What do you think? I think that the first approach is the preferred one, but first I have to manage to get the auth_token object..
Finally I was able to serialize the object and put it in the session, which is not a secure way to go but at least it will point me to the right direction so that I can continue with my business logic related with the social apps.
import gdata.contacts.client
def import_contacts(request):
auth_token = gdata.gauth.OAuth2Token(
client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
scope=SCOPE, user_agent=USER_AGENT)
authorize_url = auth_token.generate_authorize_url(
redirect_uri=APPLICATION_REDIRECT_URI)
# Put the object in the sesstion
request.session['auth_token'] = gdata.gauth.token_to_blob(auth_token)
return redirect(authorize_url)
def oauth2callback(request):
code = request.GET.get('code', '')
redirect_url = 'http://myapp.com/oauth2callback?code=%s' % code
url = atom.http_core.ParseUri(redirect_url)
# Retrieve the object from the session
auth_token = gdata.gauth.token_from_blob(request.session['auth_token'])
# Here is the tricky part: we need to add the redirect_uri to the object in addition
auth_token.redirect_uri = APPLICATION_REDIRECT_URI
# And this was my problem in my question above. Now I have the object in the handler view and can use it to retrieve the contacts.
auth_token.get_access_token(url.query)
# The second change I did was to create a ContactsClient instead of ContactsService
client = gdata.contacts.client.ContactsClient(source=APPLICATION)
auth_token.authorize(client)
feed = client.GetContacts()
all_emails = []
for i, entry in enumerate(feed.entry):
# Loop and fill the list with emails here
...
return render_to_response('xxx/import_contacts.html', {'all_emails': all_emails},
context_instance=RequestContext(request))

Categories

Resources