我正在尝试使用API端点https://graph.microsoft.com/v1.0/users/来获取Exchange目录中所有用户的列表 使用Graph Explorer可以正常工作,我得到一个预览,其中包含100个有效结果的列表。
但是,当从我的Python应用访问同一终结点并以同一用户登录时,我得到以下响应:
{'error': {'code': 'Authorization_RequestDenied', 'message': 'Insufficient privileges to complete the operation.', 'innerError': {'request-id': '6924dba1-83ee-4865-9dcf-76b6cafcb808', 'date': '2019-07-04T21:08:28'}}}
这是我正在使用的Python代码,大部分代码是从python-sample-console-app
复制而来的CLIENT_ID = '765bdd8d-b33d-489b-a039-3cdf41223aa4'
AUTHORITY_URL = 'https://login.microsoftonline.com/common'
RESOURCE = 'https://graph.microsoft.com'
API_VERSION = 'v1.0'
import base64
import mimetypes
import os
import urllib
import webbrowser
from adal import AuthenticationContext
import pyperclip
import requests
def device_flow_session(client_id, auto=False):
"""Obtain an access token from Azure AD (via device flow) and create
a Requests session instance ready to make authenticated calls to
Microsoft Graph.
client_id = Application ID for registered "Azure AD only" V1-endpoint app
auto = whether to copy device code to clipboard and auto-launch browser
Returns Requests session object if user signed in successfully. The session
includes the access token in an Authorization header.
User identity must be an organizational account (ADAL does not support MSAs).
"""
ctx = AuthenticationContext(AUTHORITY_URL, api_version=None)
device_code = ctx.acquire_user_code(RESOURCE, client_id)
# display user instructions
if auto:
pyperclip.copy(device_code['user_code']) # copy user code to clipboard
webbrowser.open(device_code['verification_url']) # open browser
print(f'The code {device_code["user_code"]} has been copied to your clipboard, '
f'and your web browser is opening {device_code["verification_url"]}. '
'Paste the code to sign in.')
else:
print(device_code['message'])
token_response = ctx.acquire_token_with_device_code(RESOURCE,
device_code,
client_id)
if not token_response.get('accessToken', None):
return None
session = requests.Session()
session.headers.update({'Authorization': f'Bearer {token_response["accessToken"]}',
'SdkVersion': 'sample-python-adal',
'x-client-SKU': 'sample-python-adal'})
return session
def api_endpoint(url):
"""Convert a relative path such as /me/photo/$value to a full URI based
on the current RESOURCE and API_VERSION settings in config.py.
"""
if urllib.parse.urlparse(url).scheme in ['http', 'https']:
return url # url is already complete
return urllib.parse.urljoin(f'{RESOURCE}/{API_VERSION}/',
url.lstrip('/'))
session = device_flow_session(CLIENT_ID, True)
users = session.get(api_endpoint('users'))
print(users.json())
我还在Microsoft Azure门户中设置了以下权限:
特别地,User.ReadBasic.All
应该足以获得仅具有名称和电子邮件之类的基本属性的所有用户的列表,但仍然无法正常工作。
显然,该问题缺少“用户同意”。这里有些奇怪,因为理论上登录页面应该针对所有已配置的API权限自动请求用户同意。经过一番修补后,我发现了以下解决方案:
解决方案1
解决方案2
CONSENT_URL = f'https://login.microsoftonline.com/common/oauth2/authorize?client_id={CLIENT_ID}&response_type=code&response_mode=query&resource=https://graph.microsoft.com&state=12345&prompt=consent'
#...
response = session.get(api_endpoint('users')).json()
if 'error' in response and response['error']['code'] == 'Authorization_RequestDenied':
print("Access denied. Consent page has been opened in the webbrowser. Please give user consent, then start the script again!")
webbrowser.open(CONSENT_URL)
sys.exit(1)