Azure AD: "scope" Property Missing from Access Token Response Body - python

I am writing a Python script to edit an Excel spreadsheet that lives in SharePoint. We have Office 365 for Business. I am using Microsoft Graph API.
I registered the Python app with Microsoft Azure Active Directory (AD) and added the following 2 (app-only) permissions for Graph API: (1) Read and write files in all site collections (preview) and (2) Read directory data. (I had our company administrator register the app for me.)
My Python script uses the requests library to send REST requests:
import json
import requests
MSGRAPH_URI = 'https://graph.microsoft.com'
VERSION = 'v1.0'
def requestAccessToken(tenant_id, app_id, app_key):
MSLOGIN_URI = 'https://login.microsoftonline.com'
ACCESS_TOKEN_PATH = 'oauth2/token'
GRANT_TYPE = 'client_credentials'
endpoint = '{0}/{1}/{2}'.format(MSLOGIN_URI, tenant_id, ACCESS_TOKEN_PATH)
headers = {'Content-Type': 'Application/Json'}
data = {
'grant_type': GRANT_TYPE,
'client_id': app_id,
'client_secret': app_key,
'resource': MSGRAPH_URI
}
access_token = response.json()['access_token']
return access_token
def getWorkbookID(access_token, fileName):
endpoint = '{0}/{1}/me/drive/root/children'.format(MSGRAPH_URI, VERSION)
headers = {
'Content-Type': 'Application/Json',
'Authorization': 'Bearer {}'.format(access_token)
}
response = requests.get(endpoint, headers=headers)
print response.text
assert response.status_code == 200
items = response.json()['value']
workbook_id = None
for item in items:
if item['name'] == fileName:
workbook_id = item['id']
return workbook_id
access_token = requestAccessToken(TENANT_ID, APP_ID, APP_KEY)
workbook_id = getWorkbookID(access_token, WORKBOOK_FILENAME)
The Python app successfully requests and receives an access_token from the Microsoft server. The access token starts like this
eyJ0eXAiOiJKV1QiLCJub25jZSI6...
Then it requests a list of my files in getWorkbookID():
GET https://graph.microsoft.com/v1.0/me/drive/root/children
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6...
This is the response to that REST request:
{
"error": {
"code": "InternalServerError",
"message": "Object reference not set to an instance of an object.",
"innerError": {
"request-id": "aa97a822-7ac5-4986-8ac0-9852146e149a",
"date": "2016-12-26T22:13:54"
}
}
}
Note that I successfully get a list of my files when I request it via Graph Explorer (https://graph.microsoft.io/en-us/graph-explorer).
EDIT:
Changed the title from "Microsoft Graph API Returns Object reference not set to an instance of an object" to "Azure AD "scope" Missing from Access Token Response".
Changed the "me" in the uri of the GET request to "myOrganization", after reading this: graph.microsft.io/en-us/docs/overview/call_api
That is,
GET https://graph.microsoft.com/v1.0/myOrganization/drive/root/children
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6...
Now get the following response.
{
"error": {
"code": "AccessDenied",
"message": "Either scp or roles claim need to be present in the token.",
"innerError": {
"request-id": "bddb8c51-535f-456b-b43e-5cfdf32bd8a5",
"date": "2016-12-28T22:39:25"
}
}
}
Looking at an example in graph.microsoft.io/en-us/docs/authorization/app_authorization, I see that the access token response body contains a "scope" property that lists the permissions granted for the app during the app's registration. However, the access token response I receive from the server does not have the "scope" property. Here is what my access token response looks like.
{
"token_type":"Bearer",
"expires_in":"3599",
"ext_expires_in":"0",
"expires_on":"1482968367",
"not_before":"1482964467",
"resource":"https://graph.microsoft.com",
"access_token":"eyJ0eXAiOiJKV..."
}
Questions:
I had the administrator register the app in Azure AD and check the boxes for the Microsoft Graph API application permissions needed. Apparently that is not enough. What else is needed? Why are the permissions not in the access token response body?
What is the correct URI for the GET request? Is "MyOrganization" the correct value in the URI?

Thanks all for your responses. After more research, I found the answer to my question.
The original problem: "Microsoft Graph API Returns Object reference not set to an instance of an object"
The request
GET https://graph.microsoft.com/v1.0/me/drive/root/children
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6...
gets the response
{
"error": {
"code": "InternalServerError",
"message": "Object reference not set to an instance of an object.",
"innerError": {
"request-id": "aa97a822-7ac5-4986-8ac0-9852146e149a",
"date": "2016-12-26T22:13:54"
}
}
}
As #SriramDhanasekaran-MSFT noted, /me refers to the currently signed-in user. In our case, we do not have a signed-in user, so we cannot use /me. Instead, we can either use /myOrganization or nothing, it is optional.
The updated problem: "Azure AD "scope" Property Missing from Access Token Response"
The updated (replaces /me with /myOrganization) request
GET https://graph.microsoft.com/v1.0/myOrganization/drive/root/children
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6...
gets the response
{
"error": {
"code": "AccessDenied",
"message": "Either scp or roles claim need to be present in the token.",
"innerError": {
"request-id": "bddb8c51-535f-456b-b43e-5cfdf32bd8a5",
"date": "2016-12-28T22:39:25"
}
}
}
As #SriramDhanasekaran-MSFT and #DanKershaw-MSFT mentioned, the reason why the access_token response was missing the scope property is that the permissions had not been "granted" in Azure AD by the administrator.
However, the solution that #SriramDhanasekaran-MSFT provided:
https://login.microsoftonline.com/common/oauth2/authorize?client_id=&response_type=code&redirect_uri=http://localhost&resource=https://graph.microsoft.com&prompt=consent
doesn't help me because my app doesn't have a redirect uri. The solution to granting the permissions is simpler than that: simply have the administrator login in to Azure AD and click the "Grant Permissions" link to grant the permissions.
Additionally, /myOrganization/driver/root/children lists the contents of the administrator's drive. As #PeterPan-MSFT noted, to list a different user's drive, replace /myOrganization with /users/<user-id>.
Success:
My application can now edit my Excel spreadsheets online, without the intervention of a human user. Contrary to what #PeterPan-MSFT stated, this is possible with Graph API and there is no need to download the Excel spreadsheet and edit offline.
Summary:
There were two problems: (1) using /me and (2) the application permissions had not been granted in Azure AD by the administrator.

Since client_credential token flow is being used (i.e., there is no authenticated user context), any request with /me is not valid as /me refers to current signed-in user. Please try with delegated token if you to access files in user's drive.
To access root drive in Sharepoint, request url is /drive/root/children (myorganization is optional).
With regards to missing claims, admin has to consent the app. You can force consent by asking the admin to access the below url (replace with that of your app's)
https://login.microsoftonline.com/common/oauth2/authorize?client_id=&response_type=code&redirect_uri=http://localhost&resource=https://graph.microsoft.com&prompt=consent

As #juunas said, the first error information below should be the NULL exception in .NET which be caused at the server end, but also not an API bug or a permission issue.
error": {
"code": "InternalServerError",
"message": "Object reference not set to an instance of an object.",
You can refer to the SO thread to know this case which seems to update for backend, please try again later.
To explain for the second error when you changed the option me to myOrganization in the Graph API url, as #SriramDhanasekaran-MSFT, it's accessing files for the current user or a specified user with <user-id> instead of me.
However, based on my understanding, you want to edit an Excel spreadsheet lived in SharePoint for your orgnization, it seems to be not suitable via using Graph APIs. Per my experience, it should be done via using the APIs of OneDrive for Business to copy the Excel file to local to edit & upload to overwrite it, please refer to the dev documents for OneDrive and try to use the API for drive resource.
Hope it helps.

Related

Incorrect value in api/rest-auth/facebook/

I have an issue related to access_token which I've received from a React Native app. The React Native app uses the expo-facebook library and when the pop-up of authentication disappears the token is created and sent to the backend API. The token is created by logInWithReadPermissionsAsync method.
const { type, token, expirationDate, permissions, declinedPermissions, graphDomain } =
await Facebook.logInWithReadPermissionsAsync({
permissions: ["public_profile", "email"],
});
I see that the server received this token on http://localhost:8000/api/rest-auth/facebook/ endpoint and sends it to the Facebook endpoint verify. The problem occurs on the response from Facebook. I expect that it should be valid by Facebook, but it seems that something went wrong.
HTTP 400 Bad Request
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"non_field_errors": [
"Incorrect value."
],
"code": 400,
"message": "Bad Request"
}
An access token that I generate in Graph API Explorer is shorter (when I use it, it works in the backend app) than the token which is generated in the React Native expo app. Why are these two tokens different? And why doesn't it work as I am expecting?
I discovered where the issue was. I knew that the issue is was in the token, a good direction was a response from Facebook.
{"error":{"message":"Invalid appsecret_proof provided in the API argument","type":"GraphMethodException","code":100}}.
After that, I realized that probably something is wrong with React Native Expo. Expo-facebook doesn't react when you even pass the app id, it used the wrong APP ID which was defined in the expo environment(APP_ID=1696089354000816). App-id was set in settings and also in the
await Facebook.initializeAsync({
appId: '<APP_ID>',
});".
So the main issue was that I relied on an access_token that didn't belong to my app.

How can I test AWS Cognito protected APIs in Python?

I'm trying to test out some AWS APIs that are protected by Cognito. I found the first part on how to get the Json token but I can't figure out how to use the token correctly so I can authenticate on the API.
Here's my code :
import boto3 as boto3;
import requests
username='test#gmail.com'
password='test1234567'
client = boto3.client('cognito-idp')
response = client.initiate_auth(
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
"USERNAME": username,
"PASSWORD": password,
},
ClientId='12121212121212',
)
token = response['AuthenticationResult']['AccessToken']
#print("Log in success")
#print("Access token:", response['AuthenticationResult']['AccessToken'])
#print("ID token:", response['AuthenticationResult']['IdToken'])
url = 'https://XXXXXXXX.execute-api.eu-west-1.amazonaws.com/Prod/incidents'
#print('url:', url)
#response = requests.get(url, headers={'authorization': token })
#print('GET:', response.status_code)
head = {'Authorization': token}
response = requests.get(url, headers=head)
print(response.content)
I'm getting the following error message :
b'{"message":"Authorization header requires \'Credential\' parameter. Authorization header requires \'Signature\' parameter. Authorization header requires \'SignedHeaders\' parameter. Authorization header requires existence of either a \'X-Amz-Date\' or a \'Date\' header. Authorization=
Ok so I found the problem and it's working fine now, 2 things were wrong :
The endpoint was wrong - AWS doesn't send a correct error message (!)
The request has to be sent with response['AuthenticationResult']['IdToken']
Please make sure you have selected Cognito or IAM in your API Gateway. From the error message it seems you have selected IAM for protecting the API.
Check the Authorization header's name which you configured for your Cognito Authorizer. You need to use same header name while passing the Cognito token.
If you have configured OAuth scopes in API Gateway side, then you must use access token. And no scope is configured then you can use ID token for authorization.
That said, you can try from Postman application for testing purpose.

How to perform 302 Redirect in AWS Lambda python integrated with API Gateway?

I am trying to build a very light weight static website with a form using a serverless architecture:
User populates the form with login info
Submit action routes to AWS API Gateway which then triggers AWS Lambda Python function which logs the user in to 3rd party site, gets some data and saves it to s3
Once the data is saved, I would like to provide a download button for the data back to the user at the client
I've tried to do this using a 302 redirect in API Gateway both with and without lambda proxy integration so that I could redirect the user to a different webpage that pulls their data from s3.
Using proxy integration I get an Internal Server Error every time.
Without it I just get the json response back to the user instead of an actual redirect.
Here is the python code for the response in my lambda function (this json is what comes back to the user currently instead of taking them to the url https://example.com):
return {
"isBase64Encoded": False,
"statusCode": 302,
"headers": {
"Location": "https://example.com"
},
"multiValueHeaders": {},
"body": "Success!!!"
}
I added the "Location" header to the Method Response in API Gateway and mapped it to integration.response.headers.location in the Integration Response (when I tried without lambda proxy).
No success with any of this though.
With proxy its an internal server error that only happens from the html form (doesn't happen when testing in api gateway console or lambda console), and without proxy it doesn't redirect to the value in the Location header, just prints the json to the url of the api.
Any help, guidance or suggestions is much appreciated!
Thanks for your time.
Sharing how I figured this out in case someone else ever runs into a similar issue.
I was getting an Internal Server error when using Lambda Proxy Integration because of the way I was parsing my event variable from API Gateway.
Assuming your API Gateway is configured to Lambda Proxy Integration and that your Lambda function is written in python, the below code should result in a successful 302 Redirect when triggered from a static s3 http form:
import urllib.parse
from furl import furl
def lambda_handler(event, context):
body = urllib.parse.unquote(event["body"])
furledBody = furl("/abc?" + body)
parameter1 = furledBody.args["parameter1"]
parameter2 = furledBody.args["parameter2"]
parameter3 = furledBody.args["parameter3"]
#Do stuff with body parameters
return {
"isBase64Encoded": False,
"statusCode": 302,
"headers": {
"Location": "https://example.com"
}
"multiValueHeaders": {},
"body": ""
}
This however is not the design I went with for my use case of providing the data my lambda function downloaded back to the client for the user to download.
Instead I configured my lambda response to contain html that automatically rendered at the client as a response to the http form request. Within the html I include S3 pre-signed URLs that were generated earlier in the lambda:
import boto3
s3 = boto3.client('s3')
presignedURL = s3.generate_presigned_url('get_object',{'Bucket': '[insert bucket name]', 'Key': '[insert key aka file name including the full path]'}, 500, 'GET'}
responseBody = (
"<html>"
"<head></head>"
"<body>"
"<a href=\"" + presignedURL + "\"download><button>Download</button></a>"
"</body>"
"</html>")
return {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {
"Content-Type": "text/html"
}
"multiValueHeaders": {},
"body": responseBody
}

Downloading and uploading files from OneDrive using MS Drive API

I've created an Azure app to update OneDrive files automatically via Python. I want it to enter someone else's drive and download files from or upload files there. However, I'm struggling forming the correct request link.
I'm currently doing something like this:
r = requests.post(f"https://graph.microsoft.com/v1.0/me/drive", headers = {"Authorization": "Bearer "+at, "content-type":"Application/Json"}) #at = authorization token
and get the error
'{\r\n "error": {\r\n "code": "BadRequest",\r\n "message": "Empty Payload. JSON content expected.",\r\n "innerError": {\r\n "date": "2021-02-01T07:52:02",\r\n "request-id": "6d159ace-252a-41a3-8805-dad6cd633348",\r\n "client-request-id": "6d159ace-252a-41a3-8805-dad6cd633348"\r\n }\r\n }\r\n}'
I'd like to enter one drive with link like this: https://xxx-my.sharepoint.com/personal/someemail_xxx_ru/_layouts/15/onedrive.aspx
How should I form the request?
You just sent a post request to the endpoint, but in this request, you did not specify other required information, such as directory, source file object, etc.These informations should be included in the json carried in the request.By the way, your endpoint also seems to be missing your onddrive path
Here's exampel document
you can using python like this:
requests.post(
'<endpoint>',
headers={'Authorization': 'Bearer ' + <token>, 'Content-type': 'application/json'},
data=json.dumps(<json_information>).json())
Fill in the <> with your information

Google Cloud Storage JSON API Simple Upload Authentication

I'm having a difficult time getting the 'Simple Upload' method in the GCS JSON API to work in Python. The documentation (https://developers.google.com/storage/docs/json_api/v1/how-tos/upload#simple) makes it appear trivial, but I can't get past authorization. I don't fully understand authorization/authentication/keys/tokens, but I'm trying. The bucket(s) that I've been trying to upload to allow for full read-write (they're public), and I've generated and tried every combination and permutation of keys I can think of.
My code:
def postTest():
headers = {'Host':'www.googleapis.com','Authorization':'Bearer? WHATGOESINHERE?','Content-Length': 0, 'Content-Type': "text/plain"}
files = {'file': ('report.txt', 'justsomeblankityblanktext\n')}
r = requests.post("https://storage.googleapis.com/upload/storage/v1beta2/b/NOTACTUALLYAREALBUCKETOBVIOUSLY/o?uploadType=media&name=testi.txt", headers=headers, files=files)
print(r.request.headers)
print(r.url)
print(r.text)
And the response:
CaseInsensitiveDict({'Content-Length': '172', 'Accept-Encoding': 'gzip, deflate, compress', 'Accept': '*/*', 'User-Agent': 'python-requests/2.2.1 CPython/2.7.6 Darwin/13.1.0', 'Host': 'www.googleapis.com', 'Content-Type': 'text/plain', 'Authorization': 'Bearer? WHATGOESINHERE?'})
https://storage.googleapis.com/upload/storage/v1beta2/b/NOTACTUALLYAREALBUCKETOBVIOUSLY/o?uploadType=media&name=testi.txt
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
So, my question is in regards to the 'Authorization' key-value pair in the headers. What sort of an 'authorization token' should be included?
OAuth2 authorization is a bit tricky. The idea is that, instead of using your real password all the time for everything, you use your password once to get a temporary code that works for the next hour or so. Then you use that code for everything, and if for some reason it's compromised, it will stop working in an hour or so anyway. To add even more complication, OAuth also allows you to ask for credentials to act as an end user (for example, if you were making a cloud storage viewer on the web, you might want to let people login, but you don't want to know their Google password).
The way you get the access token is by asking Google's OAuth2 service for one. You can do this in a few ways, depending on how you want to authorize. If your app always acts as itself, you can download a private key for it that it can use to create an assertion. Here're the instructions for this.
Another common use case is an app that acts as you. In this case, you would create a link to a login URL, have the user navigate there and log in, and then your app will receive back a key (or the user will get a key to paste into your app) that will give you a permanent "refresh token" that you can use to ask for a new access token whenever you need. Instructions for installed apps are here.
There are a few other kinds of things as well. Ultimately it's somewhat complicated, which is why Google provides a Python client that can take care of OAuth2 authorization for you. You'll still need to think about what kind of auth you want to use, but the Google APIs Python Library can take care of most of the messy details.

Categories

Resources