migrate to msgraph (#966)

* migrate to msgraph

* add subscription id to query_microsoft_graph

* migrating remaingin references

* formatting

* adding missing dependencies

* flake fix

* fix get_tenant_id

* cleanup

* formatting

* migrate application creation in deploy.py

* foramt

* mypy fix

* isort

* isort

* format

* bug fixes

* specify the correct signInAudience

* fix backing service principal creation
fix preauthorized application

* remove remaining references to graphrbac

* fix ms graph authentication

* formatting

* fix typo

* format

* deployment fix

* set implicitGrantSettings in the deployment

* format

* fix deployment

* fix graph authentication on the server

* use the current cli logged in account to retrive the backend token cache

* assign the the msgraph app role permissions to the web app during the deployment

* formatting

* fix build

* build fix

* fix bandit issue

* mypy fix

* isort

* deploy fixes

* formatting

* remove assign_app_permissions

* mypy fix

* build fix

* mypy fix

* format

* formatting

* flake fix

* remove webapp identity permission assignment

* remove unused reference to assign_app_role

* remove manual registration message

* fixing name and logging

* address PR coments

* address PR comments

* build fix

* lint

* lint

* mypy fix

* mypy fix

* formatting

* address PR comments

* linting

* lint

* remove ONEFUZZ_AAD_GROUP_ID check

* regenerate webhook_events.md

* change return type of query_microsoft_graph_list

* fix tenant_id

Co-authored-by: Marc Greisen <marc@greisen.org>
Co-authored-by: Stas <stishkin@live.com>
This commit is contained in:
Cheick Keita
2021-10-22 11:59:05 -07:00
committed by GitHub
parent c97395a37f
commit 98cd7c9c56
9 changed files with 586 additions and 474 deletions

View File

@ -6,12 +6,12 @@
import functools
import logging
import os
from typing import Any, Callable, List, TypeVar, cast
import urllib.parse
from typing import Any, Callable, Dict, List, Optional, TypeVar, cast
from uuid import UUID
import requests
from azure.core.exceptions import ClientAuthenticationError
from azure.graphrbac import GraphRbacManagementClient
from azure.graphrbac.models import CheckGroupMembershipParameters
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.mgmt.resource import ResourceManagementClient
@ -23,6 +23,10 @@ from onefuzztypes.primitives import Container, Region
from .monkeypatch import allow_more_workers, reduce_logging
# https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0
GRAPH_RESOURCE = "https://graph.microsoft.com"
GRAPH_RESOURCE_ENDPOINT = "https://graph.microsoft.com/v1.0"
@cached
def get_msi() -> MSIAuthentication:
@ -99,18 +103,77 @@ def get_regions() -> List[Region]:
return sorted([Region(x.name) for x in locations])
@cached
def get_graph_client() -> GraphRbacManagementClient:
return GraphRbacManagementClient(get_msi(), get_subscription())
class GraphQueryError(Exception):
def __init__(self, message: str, status_code: Optional[int]) -> None:
super(GraphQueryError, self).__init__(message)
self.message = message
self.status_code = status_code
def query_microsoft_graph(
method: str,
resource: str,
params: Optional[Dict] = None,
body: Optional[Dict] = None,
) -> Dict:
cred = get_identity()
access_token = cred.get_token(f"{GRAPH_RESOURCE}/.default")
url = urllib.parse.urljoin(f"{GRAPH_RESOURCE_ENDPOINT}/", resource)
headers = {
"Authorization": "Bearer %s" % access_token.token,
"Content-Type": "application/json",
}
response = requests.request(
method=method, url=url, headers=headers, params=params, json=body
)
if 200 <= response.status_code < 300:
if response.content and response.content.strip():
json = response.json()
if isinstance(json, Dict):
return json
else:
raise GraphQueryError(
"invalid data expected a json object: HTTP"
f" {response.status_code} - {json}",
response.status_code,
)
else:
return {}
else:
error_text = str(response.content, encoding="utf-8", errors="backslashreplace")
raise GraphQueryError(
f"request did not succeed: HTTP {response.status_code} - {error_text}",
response.status_code,
)
def query_microsoft_graph_list(
method: str,
resource: str,
params: Optional[Dict] = None,
body: Optional[Dict] = None,
) -> List[Any]:
result = query_microsoft_graph(
method,
resource,
params,
body,
)
value = result.get("value")
if isinstance(value, list):
return value
else:
raise GraphQueryError("Expected data containing a list of values", None)
def is_member_of(group_id: str, member_id: str) -> bool:
client = get_graph_client()
return bool(
client.groups.is_member_of(
CheckGroupMembershipParameters(group_id=group_id, member_id=member_id)
).value
body = {"groupIds": [group_id]}
response = query_microsoft_graph_list(
method="POST", resource=f"users/{member_id}/checkMemberGroups", body=body
)
return group_id in response
@cached