2016-04-29 15 views
2

Wir arbeiten an einem Daemon-Dienst, der regelmäßig automatisch eine Verbindung zur Microsoft Graph-API herstellt, um alle Dateien auf allen Laufwerken des Benutzers mit vertraulichem Inhalt aufzulisten. Wir haben eine benutzerdefinierte App in unserem Azure/Office365-Mandantenkonto eingerichtet, die viele Privilegien aktiviert hat (alle Graph- und Sharepoint-Rechte (und einige andere), um Tests durchzuführen).Wie kann ich über einen benutzerdefinierten Daemon und die REST-API für Diagramme auf die OneDrive-Dateien mehrerer Benutzer zugreifen?

Mit dem Graph Explorer und meinem persönlichen Anmeldekonto kann ich Dateien in meinem eigenen Laufwerkskonto unter Verwendung des Endpunkts /me/drive/root/children und des Endpunkts /users('<user-id>')/drive/root/children auflisten (wenn die Benutzer-ID meine eigene ist). Wenn ich versuche, mit curl und grant_typeclient_credentials, die client_id und client_secret von unserer benutzerdefinierten App in Azure verwenden, gibt /users('<user-id>')/drive die richtige Laufwerks-ID zurück, aber /users('<user-id>')/drive/root/children gibt nur eine leere Liste der untergeordneten zurück.

Gibt es eine Erlaubnis, dass ich vermisse, dass wir irgendwo einstellen müssen?

Ist dies eine Einschränkung des aktuellen Status der Graph-API?

Ist dies eine Beschränkung der client_credentials Grant-Typ?

Antwort

3

Dies ist eine Einschränkung des aktuellen Status der Graph-API - es existiert kein Berechtigungsbereich für die App, der mit dem Client-Credentials-Flow verwendet werden kann, damit eine App auf Laufwerke/Dateien von beliebigen zugreifen kann Benutzer. Die Bereiche für Dateien. * Können nur als delegierte Berechtigungen verwendet werden - siehe https://graph.microsoft.io/en-us/docs/authorization/permission_scopes.

0

Dies ist heute möglich (mit Anwendungsberechtigungen), indem Sie das neue Microsoft App Dev Portal verwenden und die Anweisungen here befolgen. Oder wenn Sie Ihre App im Azure-Portal erstellt (registriert) haben, müssen Sie ein X509-Zertifikat anstelle eines gemeinsamen Geheimnisses (Client-Secret) verwenden. Die hilfreichen Ressourcen, zumindest für mich, dass die Arbeit zu bekommen sind:

Hier einiger Python-Code (für den zweiten Fall), die eine URL für den Benutzer erzeugt zu besuchen, so kann sie Ihre App autorisieren, und für das Anfordern des Zugangstokens:

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