acquire_token_by_device_flow method not executing - python-3.x

from msal import PublicClientApplication
TENANT_ID = '********-bca4-4be6-8a1d-********'
CLIENT_ID = '********-2f0b-4b9c-84ed-*******'
AUTHORITY = 'https://login.microsoftonline.com/consumers'
ENDPOINT = 'https://graph.microsoft.com/v1.0'
SCOPES = [
'Files.ReadWrite',
'Files.ReadWrite.All',
'Sites.ReadWrite.All',
'User.Read',
'User.ReadBasic.All',
'User.ReadWrite'
]
app = PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
flow = app.initiate_device_flow(scopes=SCOPES)
print(flow)
if 'user_code' not in flow:
raise Exception('Failed to create device flow')
print(flow['message'])
result = app.acquire_token_by_device_flow(flow)
print(result)
print(flow) prints following data:
{'user_code': 'W57NE', 'device_code': 'SAQABAAEmX5eRRF9rmeBg4brEBmPRxtxI9LWyWTGPCHL6GEu7_PLvbhtOe9flSsOWYBPGifMtxiDU6gWx3n3qyNIc2MHmkghc74onQIzUzvQOJVXQgc5QH-wwjIAA', 'verification_uri': 'https://www.microsoft.com/link', 'expires_in': 900, 'interval': 5, 'message': 'To sign in, use a web browser to open the page https://www.microsoft.com/link and enter the code W57NE to authenticate.', 'expires_at': 1613721382.451286, '_correlation_id': 'ea1f9e06-39b1-47f5-bac0-e3d0188cabbd'}
I open the url in the browser and entered code mentioned in the response but it says The code that you entered has expired. Get a new code from the device you're trying to sign in to and try again.
Basically my use case is get access token using python script.
Thank you for help in advance.

Related

Query on Microsoft Graph API Python

I want to pull emails by Graph API from client inbox using python.
I started with a tutorial and successfully experimented over my personal inbox.
My problem,
Every time my code generates an authorization URL.
I have to browse through it (using web browser library) , sign in using my credentials and copy paste the authorization code for generating access token.
Which is a lot of manual work every time.
Question :
Is there a way to automate the whole process of token generation ?
Such that my client only shares his application id and client secret, and email is pulled without his sign in credentials ?
My code is attached below -
import msal
from msal import PublicClientApplication
import webbrowser
import requests
import pandas as pd
APPLICATION_ID="app id"
CLIENT_SECRET="client secret"
authority_url='https://login.microsoftonline.com/common/'
base_url = 'https://graph.microsoft.com/v1.0/'
endpoint_url = base_url+'me'
SCOPES = ['Mail.Read','Mail.ReadBasic']
client_instance = msal.ConfidentialClientApplication(client_id = APPLICATION_ID,client_credential = CLIENT_SECRET,authority = authority_url)
authorization_request_url=client_instance.get_authorization_request_url(SCOPES)
#print(authorization_request_url)
# browsing authorization request URL for retrieving authorization code.
webbrowser.open(authorization_request_url,new=True)
# Manually pasting authorization code.
authorization_code='authorization code from authorization URL'
access_token = client_instance.acquire_token_by_authorization_code(code=authorization_code,scopes=SCOPES)
access_token_id=access_token['access_token']
# Rest of the codes are for hitting the end point and retrieving the messages
Any help with code suggestions will be much appreciated.
Thanks in advance
If you would like to authenticate only with a clientId and clientSecret, without any user context, you should leverage a client credentials flow.
You can check this official MS sample that uses the same MSAL library to handle the client credentials flow. It is quite straightforward, as you can see below:
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
import json
import logging
import requests
import msal
# Optional logging
# logging.basicConfig(level=logging.DEBUG)
config = json.load(open(sys.argv[1]))
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
# token_cache=... # Default cache is in memory only.
# You can learn how to use SerializableTokenCache from
# https://msal-python.rtfd.io/en/latest/#msal.SerializableTokenCache
)
# The pattern to acquire a token looks like this.
result = None
# Firstly, looks up a token from cache
# Since we are looking for token for the current app, NOT for an end user,
# notice we give account parameter as None.
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
print("Graph API call result: ")
print(json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
The sample is retrieving a list of users from MS Graph, but it should be just a matter of adapting it to retrieve the list of emails of a specific user by changing the "endpoint" parameter in the parameters.json file to:
"endpoint": "https://graph.microsoft.com/v1.0/users//users/{id | userPrincipalName}/messages"
You can check here more information regarding the MS Graph request to list emails.
register your app
get your tenant id from azure portal and disable mfa
application_id = "xxxxxxxxxx"
client_secret = "xxxxxxxxxxxxx"
#authority_url = "xxxxxxxxxxx"
authority_url = 'xxxxxxxxxxxxxxxxxxxx'
base_url = "https://graph.microsoft.com/v1.0/"
endpoint = base_url+"me"
scopes = ["User.Read"]
tenant_id = "xxxxxxxxxxxx"
token_url = 'https://login.microsoftonline.com/'+tenant_id+'/oauth2/token'
token_data = {
'grant_type': 'password',
'client_id': application_id,
'client_secret': client_secret,
'resource': 'https://graph.microsoft.com',
'scope':'https://graph.microsoft.com',
'username':'xxxxxxxxxxxxxxxx', # Account with no 2MFA
'password':'xxxxxxxxxxxxxxxx',
}
token_r = requests.post(token_url, data=token_data)
token = token_r.json().get('access_token')
print(token)

How to use the Google Sign In access token instead of authorization code for getting the data from the Google Search Console?

I want to access the listed websites data in the Google Search Console using the Google Sign-In access_token (that one can get as the response when using Google Sign-In).
But, the thing is I can access that data only by using the authorization_code that can be copied from the OAuth2-Consent screen by going to the generated authorize_url and signing in using the registered Google account.
Here's the minimum reproducible version of the code:
from oauth2client.client import OAuth2WebServerFlow
import httplib2
from apiclient.discovery import build
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
OAUTH_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, redirect_uri=REDIRECT_URI)
authorize_url = flow.step1_get_authorize_url()
print ('Go to the following link in your browser: ' + authorize_url)
code = input('Enter verification code: ').strip()
credentials = flow.step2_exchange(code)
http = httplib2.Http()
http = credentials.authorize(http)
webmasters_service = build('webmasters', 'v3', http=http)
def get_property_list(webmasters_service):
'''
Get a list of validated properties from GSC
'''
site_list = webmasters_service.sites().list().execute()
# Filter for verified websites
verified_sites_urls = [s['siteUrl'] for s in site_list['siteEntry']
if s['permissionLevel'] != 'siteUnverifiedUser'
and s['siteUrl'][:4] == 'http']
return verified_sites_urls
print({"available_websites": get_property_list(webmasters_service)})
Consider that I'll be provided with the Google Sign-In access-token as the request-parameter from another server which has implemented Google Sign-In feature.
So, again my question is how can I access the same data using that token instead of manually getting the auth_code from the OAuth2 consent screen ?
I have followed the documentation shared by DaImTo in the comments above. And modified the code as shown below:
from oauth2client.client import OAuth2WebServerFlow
import httplib2
from apiclient.discovery import build
from oauth2client import tools, file
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
OAUTH_SCOPE = 'https://www.googleapis.com/auth/webmasters.readonly'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Acquire and store oauth token.
storage = file.Storage('token.json')
credentials = storage.get()
if credentials is None or credentials.invalid:
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, redirect_uri=REDIRECT_URI)
authorize_url = flow.step1_get_authorize_url()
credentials = tools.run_flow(flow, storage)
http = httplib2.Http()
http = credentials.authorize(http)
webmasters_service = build('webmasters', 'v3', http=http)
def get_property_list(webmasters_service):
'''
Get a list of validated properties from GSC
'''
site_list = webmasters_service.sites().list().execute()
# Filter for verified websites
verified_sites_urls = [s['siteUrl'] for s in site_list['siteEntry']
if s['permissionLevel'] != 'siteUnverifiedUser'
and s['siteUrl'][:4] == 'http']
return verified_sites_urls
print({"available_websites": get_property_list(webmasters_service)})
It's working fine now, without any manual interaction for copying and pasting the authorization_code from the OAuth2-Consent screen.

401 Error - Google API with service account

Hello fellow User.
I'm trying to write to a Google sheets document via a Google service account. The account has writing permission the the document, so that's not the issue.
The reason I'm using a service account is to avoid having to constantly refresh access tokens. I think there lies the issue. Because this is the response from the API:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential.
See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED"
}
}
This occurs when I try sending a put request to the spreadsheet to edit data. I previously connected the service account to the sheets API, aquired the credentials from there and used them in the request.
If anyone knows, where the error might be, I'd be very thankful.
Here is the Python code that triggered the error:
iat = time.time()
exp = iat + 3600
payload = {'iss': <SERVICE ACCOUNT EMAIL>,
'sub': <SERVICE ACCOUNT EMAIL>,
'aud': f'https://sheets.googleapis.com/v4/spreadsheets/<SHEETID>/values/Sheet1!A2:B5',
'iat': iat,
'exp': exp}
additional_headers = {'kid': <PRIVATE KEY ID>}
signed_jwt = jwt.encode(payload, key=<PRIVATE KEY>,
headers=additional_headers, algorithm='RS256')
headers = {'authorization': f'Bearer {signed_jwt}'}
params = {'range': f'Sheet1!A2B5',
'majorDimension':'ROWS',
'values':[
<LISTS OF ENTRIES>
]}
spreadsheetId = <SHEETID>
resp = requests.put(url=f'https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/
values/Sheet1!A2:B5?valueInputOption=USER_ENTERED',
data = params, headers = headers)
For anyone interested, I was following these guides:
How to use service account for authentication: https://developers.google.com/identity/protocols/oauth2/service-account#python_1
How to issue correct put request: https://developers.google.com/sheets/api/samples/writing
How to get correct endpoint for jwt-encoded credentials: https://developers.google.com/sheets/api/reference/rest
Expected OAuth 2 access token, login cookie or other valid authentication credential.
The coded used for accessing APIs with a service account is not the same as the code used for Oauth2. The code you are using appears to be trying to authenticate using Oauth2.
You should be using something like this.
credentials = ServiceAccountCredentials.from_json_keyfile_name(
KEY_FILE_LOCATION, SCOPES)
service = build('calendar', 'v3', credentials=credentials)
Reference:
oauth2client.service_account module

How to get AccessToken and also close browser window in ReadyAPI

I wrote some code that gets an access token. When the code runs, a browser displays on the screen which contains the access token.
But when I try to get the access token and log it, a null string is being displayed. Also, I do not know if there is a way to force the browser to close using my code. Right now when I run this code, the browser window opens but I have to click on it to close it.
Could you please let me know what I am doing wrong ?
import com.eviware.soapui.impl.rest.actions.oauth.OltuOAuth2ClientFacade
import com.eviware.soapui.support.editor.inspectors.auth.TokenType
def project = context.getTestCase().getTestSuite().getProject();
def oAuthProfile = project.getAuthRepository().getEntry("IMAGEN_Profile");
def clientSecret = testRunner.testCase.testSuite.getPropertyValue("Client_Secret")
def clientID = testRunner.testCase.testSuite.getPropertyValue("Client_ID")
oAuthProfile.setClientSecret(clientSecret);
oAuthProfile.setClientID(clientID);
log.info("Client Secret:"+clientSecret)
log.info("Client ID:"+clientID)
// the following code for getting new access token
def oAuthClientFacade = new OltuOAuth2ClientFacade(TokenType.ACCESS);
oAuthClientFacade.requestAccessToken(oAuthProfile, true);
def accessToken = oAuthProfile.getAccessToken()
testRunner.testCase.testSuite.setPropertyValue("Auth_Code",accessToken)
log.info("Access Token:"+accessToken)
Unfortunately, the SoapUI documentation is rather terse.
Change your
oAuthClientFacade.requestAccessToken(oAuthProfile, true);
to
oAuthClientFacade.requestAccessToken(oAuthProfile, true, true);
source

How to get saved tracks for a specific user, or change the current user?

The Spotify API has an endpoint "Get a Users's Saved Tracks" GET https://api.spotify.com/v1/me/tracks but as you can see from me in the url, and in the documentation, this is only for the current user. How can I access information about a non current user, or change the current user?
For example, userA logs in, I get an access and refresh token for userA. userB logs in, replacing userA as the current user, I get userB's tokens. How can I now make make requests for information about userA?
You need to store the tokens you get from authenticating users.
Say you're using user sessions:
User A logs in.
You get the access and refresh tokens for user A.
You save these tokens to User A's session.
User B logs in.
You get the access and refresh tokens for user B.
You save these tokens to User B's session.
You'd do this the same way that you have already implemented user sessions.
And so when a user lands on your redirect URI, you save the tokens you received to their session.
And then when you need to use the Spotify API you use the tokens saved in the users session.
If you however want to do this for one end-user, then with a web server things get a little harder.
But with a CLI app things can be a little easier.
What you want to do is log user A and B into your application, manually saving both tokens independently.
This is as easy as making an authentication function that you call twice and save the results to two variables.
After this you can then call the API with the saved tokens.
And use user A's token when you want to get user A's saved tracks.
Here's a low-level example implementation in Python 3 using Requests, of getting the users tracks and user information, using different scopes. Where the comments are the part the code's at in the authorization code flow:
import time
import urllib.parse as parse
import webbrowser
import requests
from requests.auth import HTTPBasicAuth
OAUTH_AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token'
# Change to your application settings
class Settings:
client_id = ''
client_secret = ''
redirect_uri = ''
def authenticate(scope=None):
'''Implement OAuth 2 Spotify authentication'''
# Application: Request authorization to access data
payload = {'client_id': Settings.client_id,
'response_type': 'code',
'redirect_uri': Settings.redirect_uri,
'show_dialog': 'true'} # allow second account to login
if scope:
payload['scope'] = scope
auth_url = '{}?{}'.format(OAUTH_AUTHORIZE_URL, parse.urlencode(payload))
# Spotify: Displays scopes & prompts user to login (if required)
# User: Logs in, authorizes access
webbrowser.open(auth_url)
response = input('Enter the URL you were redirected to: ')
code = parse.parse_qs(parse.urlparse(response).query)['code'][0]
payload = {'redirect_uri': Settings.redirect_uri,
'code': code,
'grant_type': 'authorization_code'}
if scope:
payload['scope'] = scope
# Application: Request access and refresh tokens
# Spotify: Returns access and refresh tokens
auth = HTTPBasicAuth(Settings.client_id, Settings.client_secret)
response = requests.post(OAUTH_TOKEN_URL, data=payload, auth=auth)
if response.status_code != 200:
response.raise_for_status()
token_info = response.json()
token_info['expires_at'] = int(time.time()) + token_info['expires_in']
token_info['scope'] = scope
return token_info
if __name__ == '__main__':
user_a = authenticate(scope='user-library-read')
user_b = authenticate(scope='user-read-email user-read-private user-read-birthdate')
print('user_a', user_a)
print('user_b', user_b)
for url in ['https://api.spotify.com/v1/me/tracks',
'https://api.spotify.com/v1/me']:
for user in [user_a, user_b]:
token = 'Bearer ' + user['access_token']
# Application: Uses access token in requests to Web API
# Spotify: Returns request data
r = requests.get(url, headers={'authorization': token})
if r.status_code != 200:
print(r.text)
else:
print([
'{}: {}'.format(key, str(value)[:20])
for key, value in r.json().items()
])

Resources