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

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))

Related

Unable to fetch OAuth2.0 Token from Azure-DevOps , using Python

I am trying to use OAuth2 to access the Azure DevopsAPI, to query work-items.
But I am unable to get the access tokene.
I am using Python and Flask. My approach is based on these resources:
Microsoft documentation , there currently Step 3 is relevant
OAuth Tutorial, which worked fine for Github, but is not working for Azure.
Relevant libraries:
from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, url_for
Parameters:
client_id = "..."
client_secret = "..."
authorization_base_url = "https://app.vssps.visualstudio.com/oauth2/authorize"
token_url = "https://app.vssps.visualstudio.com/oauth2/token"
callback_url = "..."
Step 1: User Authorization. (works fine)
#app.route("/")
def demo():
azure = OAuth2Session(client_id)
authorization_url, state = azure.authorization_url(authorization_base_url)
session['oauth_state'] = state
authorization_url += "&scope=" + authorized_scopes + "&redirect_uri=" + callback_url
print(authorization_url)
return redirect(authorization_url)
Step 2: Retrieving an access token (generates an error)
#app.route("/callback", methods=["GET"])
def callback():
fetch_body = "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
"&client_assertion=" + client_secret + \
"&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
"&assertion=" + request.args["code"] + \
"&redirect_uri=" + callback_url
azure = OAuth2Session(client_id, state=session['oauth_state'])
token = azure.fetch_token(token_url=token_url, client_secret=client_secret,
body=fetch_body,
authorization_response=request.url)
azure.request()
session['oauth_token'] = token
return redirect(url_for('.profile'))
The application-registration and adhoc-SSL-certification are working fine (using it just temporary).
When I use the client_assertion in Postman, I get a correct response from Azure:
But when I execute the code, this error is thrown:
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.
Which only lets me know, that no token was received.
There is one issue in the generated request body, where the grant_type is added twice:
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
grant_type=authorization_code
The first value is expected by Azure, but the second one is generated automatically by the library.
Now when I specify the grant_type in the fetch_token call, like this:
token = azure.fetch_token(token_url=token_url, client_secret=client_secret,
body=fetch_body, grant_type="urn:ietf:params:oauth:grant-type:jwt-bearer",
authorization_response=request.url)
I get this error
TypeError: prepare_token_request() got multiple values for argument 'grant_type'
And the actual request to Azure is not even sent.
I see in the web_application.py that is used by oauth2_session.py, that grant_type ='authorization_code' is set fixed, so I guess this library is generally incompatible with Azure.
Is that the case?
If so, what would be the simplest way to connect to Azure-OAuth with Python (Flask)?
I would be very grateful for any advice and help that point me in the right direction.
I just found the azure.devops library that solves my problem.
Ressources
https://github.com/Microsoft/azure-devops-python-api
https://github.com/microsoft/azure-devops-python-samples/blob/main/src/samples/work_item_tracking.py
azure-devops-python-api query for work item where field == string
from azure.devops.connection import Connection
from azure.devops.v5_1.work_item_tracking import Wiql
from msrest.authentication import BasicAuthentication
import pprint
# Fill in with your personal access token and org URL
personal_access_token = '... PAT'
organization_url = 'https://dev.azure.com/....'
# Create a connection to the org
credentials = BasicAuthentication('', personal_access_token)
connection = Connection(base_url=organization_url, creds=credentials)
# Get a client (the "core" client provides access to projects, teams, etc)
core_client = connection.clients.get_core_client()
wit_client = connection.clients.get_work_item_tracking_client()
query = "SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State]," \
"[System.Tags] FROM workitems WHERE [System.TeamProject] = 'Test'"
wiql = Wiql(query=query)
query_results = wit_client.query_by_wiql(wiql).work_items
for item in query_results:
work_item = wit_client.get_work_item(item.id)
pprint.pprint(work_item.fields['System.Title'])

Get Spotify access token with spotipy on Django and Python

I'm new to Django and I'm trying to link Spotify to my webapp. I'm using Spotify to do it and it correctly access to Spotify.
To do it I have a button that opens the view below
views.py
#authenticated_user
def spotify_login(request):
sp_auth = SpotifyOAuth(client_id=str(os.getenv('SPOTIPY_CLIENT_ID')),
client_secret=str(os.getenv('SPOTIPY_CLIENT_SECRET')),
redirect_uri="http://127.0.0.1:8000/",
scope="user-library-read")
redirect_url = sp_auth.get_authorize_url()
auth_token = sp_auth.get_access_token()
print(auth_token)
print("----- this is the AUTH_TOKEN url -------", auth_token)
return HttpResponseRedirect(redirect_url)
If I don't use auth_token = sp_auth.get_access_token() everything works fine and I got redirected to the correct. Unfortunately, if I add that line of code to access the access token, instead of staying on the same page, it opens another tab on the browser with the Spotify auth_code and lets the original page load forever.
Is there a way to retrieve the access token in the background without making my view reload or open another tab in the browser?
You are being redirected to exactly where you are telling django to go.
redirect_url is just a spotify api redirect containing a code, which is captured and used to get the access token.
Set your expected response as return value.
By the way, keep in mind:
redirect_uri="http://127.0.0.1:8000/", should be added in spotify app (usually as http://127.0.0.1:8000/callback",)
auth_token is a json, you can find token in auth_token['access_token']
The solution was to create a new view to access the URL
views.py
from .utils import is_user_already_auth_spotify, spotify_oauth2
#authenticated_user
def spotify_login(request):
if is_user_already_auth_spotify(request.user.username):
messages.error(request, "You have already linked your Spotify account")
return HttpResponseRedirect('account/' + str(request.user.username))
sp_auth = spotify_oauth2()
redirect_url = sp_auth.get_authorize_url()
return HttpResponseRedirect(redirect_url)
#authenticated_user
def spotify_callback(request):
full_path = request.get_full_path()
parsed_url = urlparse(full_path)
spotify_code = parse_qs(parsed_url.query)['code'][0]
sp_auth = spotify_oauth2()
token = sp_auth.get_access_token(spotify_code)
data = {
str(request.user.username): token
}
with open('spotify_auth.json', 'w') as f:
json.dump(data, f)
messages.success(request, "You have correctly linked your Spotify account")
return HttpResponseRedirect('account/' + str(request.user.username))
urls.py
urlpatterns = [
path('account/<str:username>/', views.account_user, name="account"),
path('spotify_login', views.spotify_login, name="spotify_login"),
path('spotify_callback', views.spotify_callback, name="spotify_callback"),
]
utils.py
import json
from spotipy import oauth2
import os
def is_user_already_auth_spotify(username):
my_loaded_dict = {}
with open('spotify_auth.json', 'r') as f:
try:
my_loaded_dict = json.load(f)
except:
# file vuoto
pass
if str(username) in my_loaded_dict:
# controllare scadenza token ed in caso rinnovarlo
return True
else:
return False
def spotify_oauth2():
sp_auth = oauth2.SpotifyOAuth(client_id=str(os.getenv('SPOTIPY_CLIENT_ID')),
client_secret=str(os.getenv('SPOTIPY_CLIENT_SECRET')),
redirect_uri="http://127.0.0.1:8000/members/spotify_callback",
scope="user-library-read")
return sp_auth
The code also saves the token in a JSON and search for it if it has already been saved

Accessing & Manipulating Azure AD B2C Users via Web Application

I'm struggling with basic user management while working with Azure's AD B2C framework.
I've successfully set up an Azure AD B2C resource, registered my consumer-facing web application (created a client secret and granted permission, both delegated and application, to User.ReadWrite.All), created custom attributes, and added out-of-the-box signup and signin user flows. Furthermore, I've successfully registered and signed-in users to my web application.
To get to this point, I followed the Python sample provided within the documentation (ms-identity-python-webapp-master):
app.py
#app.route("/login")
def login():
session["state"] = str(uuid.uuid4())
# Technically we could use empty list [] as scopes to do just sign in,
# here we choose to also collect end user consent upfront
auth_url = _build_auth_url(scopes=app_config_b2c.SCOPE, state=session["state"])
return render_template("templates/login.html", auth_url=auth_url, version=msal.__version__)
#app.route(app_config_b2c.REDIRECT_PATH) # Its absolute URL must match your app's redirect_uri set in AAD
def authorized():
if request.args.get('state') != session.get("state"):
return redirect(url_for("index")) # No-OP. Goes back to Index page
if "error" in request.args: # Authentication/Authorization failure
return render_template("auth_error.html", result=request.args)
if request.args.get('code'):
cache = _load_cache()
result = _build_msal_app(cache=cache).acquire_token_by_authorization_code(
request.args['code'],
scopes=app_config_b2c.SCOPE, # Misspelled scope would cause an HTTP 400 error here
redirect_uri=url_for("authorized", _external=True))
if "error" in result:
return render_template("auth_error.html", result=result)
session["user"] = result.get("id_token_claims")
_save_cache(cache)
return redirect(url_for("index"))
#app.route("/logout")
def logout():
session.clear() # Wipe out user and its token cache from session
return redirect( # Also logout from your tenant's web session
app_config_b2c.AUTHORITY + "/oauth2/v2.0/logout" +
"?post_logout_redirect_uri=" + url_for("index", _external=True))
#app.route("/graphcall")
def graphcall():
token = _get_token_from_cache(app_config_b2c.SCOPE)
if not token:
return redirect(url_for("login"))
graph_data = requests.get( # Use token to call downstream service
app_config_b2c.ENDPOINT,
headers={'Authorization': 'Bearer ' + token['access_token']},
).json()
return render_template('templates/display.html', result=graph_data)
def _load_cache():
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def _save_cache(cache):
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
app_config_b2c.CLIENT_ID, authority=authority or app_config_b2c.AUTHORITY,
client_credential=app_config_b2c.CLIENT_SECRET, token_cache=cache)
def _build_auth_url(authority=None, scopes=None, state=None):
return _build_msal_app(authority=authority).get_authorization_request_url(
scopes or [],
state=state or str(uuid.uuid4()),
redirect_uri=url_for("authorized", _external=True))
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache=cache)
accounts = cca.get_accounts()
if accounts: # So all account(s) belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result
app_config_b2c.py
import os
b2c_tenant = "myapplication"
signupsignin_user_flow = "b2c_1_signupsignin1"
editprofile_user_flow = "b2c_1_profileediting1"
resetpassword_user_flow = "b2c_1_passwordreset1"
authority_template = "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{user_flow}"
CLIENT_SECRET = "Enter_the_Client_Secret_Here" # Our Quickstart uses this placeholder
# In your production app, we recommend you to use other ways to store your secret,
# such as KeyVault, or environment variable as described in Flask's documentation here
# https://flask.palletsprojects.com/en/1.1.x/config/#configuring-from-environment-variables
# CLIENT_SECRET = os.getenv("CLIENT_SECRET")
# if not CLIENT_SECRET:
# raise ValueError("Need to define CLIENT_SECRET environment variable")
AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=signupsignin_user_flow)
B2C_PROFILE_AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=editprofile_user_flow)
B2C_RESET_PASSWORD_AUTHORITY = authority_template.format(
tenant=b2c_tenant, user_flow=resetpassword_user_flow)
CLIENT_ID = "xxx.xxxxxx"
REDIRECT_PATH = "/getAToken" # It will be used to form an absolute URL
# And that absolute URL must match your app's redirect_uri set in AAD
# This is the resource that you are going to access in your B2C tenant
ENDPOINT = 'https://graph.microsoft.com/v1.0/users'
# These are the scopes that you defined for the web API
SCOPE = ["User.ReadWrite.All"]
SESSION_TYPE = "filesystem" # So token cache will be stored in server-side session
The graphcall doesn't work within this framework (perhaps it's a b2c issue), which I'm sure is part of the problem, but ultimately I'd just like the application to consume the logged-in user's AD attributes (particularly the custom attributes I've enabled), and modify them when necessary. For instance, say a custom attribute is "paid_subscriber". When a user registers, the attribute is empty. When the user purchases content, I'd like to set the value of the attribute to something relevant (like "true").
Is this possible? Do I need other user flows? What am I missing here (theoretically and practically)?
Microsoft Graph does not support the tokens issued by the Azure AD B2C.
You need to have the access token generated by the Azure AD.
There is a process using Azure ad b2c custom policy where you can integrated the Microsoft Graph and add custom attributes to the claims.
This document helps you to get the Azure AD access token to call Graph, With the above implementation there wont be much changes in the phython code
for adding the custom attributes go through the document

How to make a long live Picasa token (or general Google oAuth 2.0 enabled services) using Python?

Got to say, I am not that clear how to use oAuth 2.0 with Picasa API version 2. From the Google doc itself, (cmiiw), I got the impression that Picasa API version 1 was deprecated, which means Python gdata for Picasa (that only support version 1), is simply useless.
Therefore, I develop my own class using the rudimentary Picasa documentation. But, I found that the token generated is very short lived: user got to reauthenticate him/herself again. Can we make user only reauthenticate one, and somehow the token got refreshed automatically if it's expired?
This is my oAuth class that I develop to solve this Picasa problem. Would love to have suggestion on how to fix this class to allow a long term lived token
class OAuth():
"""
Simplify oauth process
"""
def __init__(self, **kwargs):
self.client_secret_file = kwargs.get('client_secret_file')
self.token_location = os.path.join(kwargs.get('token_location'), kwargs.get('token_filename'))
self.scope = kwargs.get('scope')
self.credentials = None
def is_authenticated(self):
storage = Storage(self.token_location)
self.credentials = storage.get()
authenticated = self.credentials is not None and not self.credentials.invalid
Logger.debug('oAuth: authenticated = %s' % authenticated)
return authenticated
def authenticate(self):
if self.scope is None:
Logger.error('oauth: please specify scope')
return
missing_message = '''
WARNING: Please configure OAuth 2.0
To make this code run you will need to populate the client_secrets.json file
found at: %s
with information from the Developers Console
https://console.developers.google.com/
For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
''' % self.client_secret_file
flow = flow_from_clientsecrets(self.client_secret_file,
message=missing_message,
scope=self.scope)
# ca_certs_file = os.path.join('cacert.pem')
flags = argparser.parse_args()
storage = Storage(self.token_location)
self.credentials = run_flow(flow, storage, flags)#, http=httplib2.Http(ca_certs=ca_certs_file))
EDITED:
I add my solution here.
def get_album_list(self):
if self._album_list is not None:
return self._album_list
http = httplib2.Http(ca_certs=os.environ['REQUESTS_CA_BUNDLE'])
http = self.oauth.credentials.authorize(http)
response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
if response['status'] == '403':
self.oauth.credentials.refresh(http)
response, album_list = http.request(Picasa.PHOTOS_URL, 'GET')
album_list = json.load(StringIO(album_list))
self._album_list = {}
for e in album_list['feed']['entry']:
album_id = unicode(e['id']['$t'].split('/')[9]).split('?')[0]
self._album_list[e['title']['$t']] = album_id
return self._album_list
There are two types of tokens: access token and refresh token. Your application should receive both when a user grants you an access for the first time.
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer",
"refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
It is important to save refresh token for further use. When the first access token expired you have to use the refresh token to obtain a new one.
https://developers.google.com/accounts/docs/OAuth2WebServer#refresh
Also consider using https://github.com/google/oauth2client

How to pass oauth_callback value to oauth/request_token with Twython

Twitter just recently made the following mandatory:
1) You must pass an oauth_callback value to oauth/request_token. It's not optional. Even if you have one already set on dev.twitter.com. If you're doing out of band OAuth, pass oauth_callback=oob.
2) You must pass along the oauth_verifier you either received from your executed callback or that you received hand-typed by your end user to oauth/access_token.
Here is the twitter thread (https://dev.twitter.com/discussions/16443)
This has caused Twython get_authorized_tokens to throw this error:
Request: oauth/access_token
Error: Required oauth_verifier parameter not provided
I have two questions:
1. How do you pass the oauth_callback value to oauth/request_token with Twython?
2. How do you pass along the oauth_verifier?
I can get the oauth_verifier with request.GET['oauth_verifier'] from the callback url but I have no idea what to do from there using Twython. I've search everywhere but haven't found any answers so I decided to post this. This is my first post so please be kind ;)
Here is my code:
def register_twitter(request):
# Instantiate Twython with the first leg of our trip.
twitter = Twython(
twitter_token = settings.TWITTER_KEY,
twitter_secret = settings.TWITTER_SECRET,
callback_url = request.build_absolute_uri(reverse('account.views.twitter_thanks'))
)
# Request an authorization url to send the user to
auth_props = twitter.get_authentication_tokens()
# Then send them over there
request.session['request_token'] = auth_props
return HttpResponseRedirect(auth_props['auth_url'])
def twitter_thanks(request, redirect_url=settings.LOGIN_REDIRECT_URL):
# Now that we've got the magic tokens back from Twitter, we need to exchange
# for permanent ones and store them...
twitter = Twython(
twitter_token = settings.TWITTER_KEY,
twitter_secret = settings.TWITTER_SECRET,
oauth_token = request.session['request_token']['oauth_token'],
oauth_token_secret = request.session['request_token']['oauth_token_secret'],
)
# Retrieve the tokens
authorized_tokens = twitter.get_authorized_tokens()
# Check if twitter user has a UserProfile
try:
profile = UserProfile.objects.get(twitter_username=authorized_tokens['screen_name'])
except ObjectDoesNotExist:
profile = None
I solved my own answer. Here is the solution if it can help anyone else:
In the file Twython.py, I added a new parameter oauth_verifier to the Twython class constructor . I get the oauth_verifier value from the callback_url in my twitter_thanks view.
In get_authorized_tokens I removed this line of code:
response = self.client.get(self.access_token_url)
and added the following code:
callback_url = self.callback_url or 'oob'
request_args = urllib.urlencode({'oauth_callback': callback_url, 'oauth_verifier':self.oauth_verifier })
response = self.client.post(self.access_token_url, params=request_args)
It now works like a charm and is OAuth 1.0A compliant.

Categories

Resources