【发布时间】:2019-11-15 13:49:21
【问题描述】:
我正在尝试使用 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
- 在 portal.azure.com 上注册新应用程序
- 预先添加所需的配置和 API 权限
- 当您更改所需的 API 权限时,用户不会再次被询问
- 您可以随时在 portal.azure.com 上重新创建应用程序,以访问新的 API 并相应地更改应用程序中的应用程序 ID
解决方案 2
- 检测到“Authorization_RequestDenied”错误
- 如果发生这种情况,请打开带有特殊登录 URL 的浏览器窗口以征求用户同意
- 请用户在登录成功并同意后重启应用
- 这里是相关代码
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)
【问题讨论】:
-
感谢您使用解决方案更新您的问题。我尝试在 azure 门户上创建一个新应用程序并开始工作。
标签: python azure-active-directory microsoft-graph-api adal permission-denied