如何通过自定义守护程序和图形REST API访问多个用户的OneDrive文件?

时间:2016-04-29 17:52:41

标签: office365 onedrive microsoft-graph office365-restapi

我们正在开发一种守护程序服务,该服务将定期自动连接到Microsoft Graph API,以列出所有用户敏感内容的驱动器中的所有文件。我们已在Azure / Office365租户帐户中设置了一个自定义应用程序,该帐户启用了许多权限(所有Graph和Sharepoint权限(以及其他一些权限),以便进行测试)。

使用Graph Explorer工具和我的个人登录帐户,我可以使用/me/drive/root/children端点和/users('<user-id>')/drive/root/children端点列出我自己的驱动器帐户中的文件(当用户ID为my时)拥有)。当我尝试使用curl和grant_type client_credentials进行连接时,使用Azure中自定义应用的client_idclient_secret/users('<user-id>')/drive会返回正确的驱动器id,但/users('<user-id>')/drive/root/children只返回一个空的子列表。

我是否缺少某些我需要设置的权限?

这是Graph API当前状态的限制吗?

这是client_credentials授权类型的限制吗?

2 个答案:

答案 0 :(得分:3)

这是Graph API当前状态的限制 - 不存在仅限应用程序的权限范围,与客户端凭据流一起使用,这将允许应用程序访问驱动器/文件任何用户。 Files。*范围只能用作委派权限 - 请参阅https://graph.microsoft.io/en-us/docs/authorization/permission_scopes

答案 1 :(得分:2)

今天(使用应用程序权限)可以使用新的Microsoft App Dev Portal并按照here说明进行操作。或者,如果您在Azure门户中创建(注册)了您的应用程序,则必须使用X509证书而不是共享密钥(客户端密钥)。至少对我来说,最有用的资源是:

这里有一些python代码(第二种情况)为用户生成一个访问的URL,因此她可以授权你的应用程序,并请求访问令牌:

import calendar
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from datetime import datetime, timedelta
import jwt
from jwt.exceptions import InvalidTokenError
from oauthlib.common import generate_nonce, generate_token
from oauthlib.oauth2 import BackendApplicationClient
import requests
from requests_oauthlib import OAuth2Session
import uuid

def to_unix(obj):
    if isinstance(obj, datetime):
        if obj.utcoffset() is not None:
            obj = obj - obj.utcoffset()
    millis = calendar.timegm(obj.timetuple()) + obj.microsecond / 1e6
    return millis

def validate_id_token(token):
    '''Validates the given id token.

    Args:
        token (str): An encoded ID token.
    Returns:
        The decoded token which is a dict.
    '''
    # Extract kid from token header
    try:
        header = jwt.get_unverified_header(token)
    except InvalidTokenError as e:
        raise Exception('No valid id token provided.')
        })
    else:
        kid = header.get('kid', '')

    if not kid:
        raise Exception("Unable to find 'kid' claim in token header.")

    # Fetch public key info
    url = 'https://login.microsoftonline.com/common/discovery/keys'
    try:
        response = requests.get(url)
    except RequestException as e:
        raise Exception('Failed to get public key info: %s' % e)
    else:
        if not response.ok:
            raise Exception('Failed to get public key info: %s' %
                              response.content)
        else:
            public_keys = response.json().get('keys', [])

    # Find public key, used to sign id token
    public_key = None
    for k in public_keys:
        if kid == k['kid']:
            public_key = k['x5c'][0]
            break
    if not public_key:
        raise Exception("Unable to find public key for given kid '%s'" % kid)

    # Verify id token signature
    # NOTE: The x5c value is actually a X509 certificate. The public key
    # could also be generated from the n (modulos) and e (exponent) values.
    # But that's more involved.
    cert_string = ('-----BEGIN CERTIFICATE-----\n' +
                   public_key +
                   '\n-----END CERTIFICATE-----').encode('UTF-8')
    try:
        cert = x509.load_pem_x509_certificate(
            cert_string, default_backend())
    except ValueError as e:
        raise Exception('Failed to load certificate for token signature'
                          'verification: %s' % e)
    else:
        public_key = cert.public_key()

    try:
        decoded = jwt.decode(token, public_key, audience=self.key)
    except InvalidTokenError as e:
        raise Exception('Failed to decode token: %s' % e)
    else:
        return decoded

def generate_client_assertion(tenant_id, fp_hash, private_key, private_key_passphrase):
    """Generate a client assertion (jwt token).

    This token is required to fetch an oauth app-only access token.

    Args:
        fp_hash (str): Base64 encoded SHA1 has of certificate fingerprint
        private_key (str): Private key used to sign the jwt token
        tenant_id (str): The tenant to which this token is bound.
    Returns:
        On success a tuple of the client assertion and the token type
        indicator.
    """
    valid_from = str(int(ts.to_unix(datetime.utcnow() - timedelta(0, 1))))
    expires_at = str(int(ts.to_unix(datetime.utcnow() + timedelta(7))))
    jwt_payload = {
        'aud': ('https://login.microsoftonline.com/%s/'
                'oauth2/token' % tenant_id),
        'iss': client_id,
        'sub': client_id,
        'jti': str(uuid.uuid1()),
        'nbf': valid_from,
        'exp': expires_at,
    }
    headers = {
        'x5t': fp_hash
    }

    if not private_key_passphrase:
        secret = private_key
    else:
        try:
            secret = serialization.load_pem_private_key(
                str(private_key), password=str(private_key_passphrase),
                backend=default_backend())
        except Exception as e:
            raise Exception('Failed to load private key: %s' % e)

    try:
        client_assertion = jwt.encode(jwt_payload, secret,
                                      algorithm='RS256', headers=headers)
    except ValueError as e:
        raise Exception('Failed to encode jwt_payload: %s' % e)

    client_assertion_type = ('urn:ietf:params:oauth:client-assertion-type:'
                             'jwt-bearer')

    return client_assertion, client_assertion_type

def generate_auth_url(client_id, redirect_uri):
    nonce = generate_nonce()
    state = generate_token()
    query_params = {
        'client_id': client_id,
        'nonce': nonce,
        'prompt': 'admin_consent',
        'redirect_uri': redirect_uri,
        'response_mode': 'fragment',
        'response_type': 'id_token',
        'scope': 'openid',
        'state': state
    }
    tenant = 'common'
    auth_url = ('https://login.microsoftonline.com/%s'
                '/oauth2/authorize?%s') % (tenant, urllib.urlencode(query_params))

    return nonce, auth_url

def get_access_token(client_id, id_token, nonce=None):
    '''id_token is returned w/ the url after the user authorized the app'''
    decoded_id_token = validate_id_token(id_token)

    # Compare the nonce values, to mitigate token replay attacks
    if not nonce:
        raise Exception("No nonce value provided.")
    elif nonce != decoded_id_token['nonce']:
        raise Exception("Nonce values don't match!")

    # Prepare the JWT token for fetching an access token
    tenant_id = decoded_id_token['tid']
    client_assertion, client_assertion_type = generate_client_assertion(tenant_id)

    # Fetch the access token
    client = BackendApplicationClient(self.key)
    oauth = OAuth2Session(client=client)
    resource = 'https://graph.microsoft.com/'
    url = https://login.microsoftonline.com/common/oauth2/token
    query_params = {
        'client_id': client_id,
        'client_assertion': client_assertion,
        'client_assertion_type': client_assertion_type,
        'resource': resource
    }
    try:
        fetch_token_response = oauth.fetch_token(url, **query_params)
    except Exception as e:
        raise Exception('Failed to obtain access token: %s' % e)
    else:
        return fetch_token_response