From 2b603f5c9643ece12b40cfcaf1e4005cbca141e1 Mon Sep 17 00:00:00 2001 From: vgrem Date: Sat, 22 Jul 2023 17:12:18 +0300 Subject: [PATCH] #713: support for oauth2 device code auth introduced --- examples/sharepoint/connect_device_flow.py | 15 ++ generator/import_metadata.py | 4 +- generator/metadata/MicrosoftGraph.xml | 135 +++++++++++++++++- generator/metadata/SharePoint.xml | 24 +++- .../runtime/auth/authentication_context.py | 36 +++++ office365/sharepoint/client_context.py | 11 ++ 6 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 examples/sharepoint/connect_device_flow.py diff --git a/examples/sharepoint/connect_device_flow.py b/examples/sharepoint/connect_device_flow.py new file mode 100644 index 00000000..7972a779 --- /dev/null +++ b/examples/sharepoint/connect_device_flow.py @@ -0,0 +1,15 @@ +""" +Demonstrates how to authenticate users on devices or operating systems that don't provide a web browser. +Device code flow lets the user use another device such as a computer or a mobile phone to sign in interactively. + +https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code +""" + +from office365.sharepoint.client_context import ClientContext +from tests import test_tenant, test_client_id, test_site_url + +ctx = ClientContext(test_site_url).with_device_flow(test_tenant, test_client_id) +me = ctx.web.current_user.get().execute_query() +print(me.login_name) +web = ctx.web.get().execute_query() +print(web.title) diff --git a/generator/import_metadata.py b/generator/import_metadata.py index f55be611..6ade2f5e 100644 --- a/generator/import_metadata.py +++ b/generator/import_metadata.py @@ -15,9 +15,9 @@ def export_to_file(path, content): parser = ArgumentParser() parser.add_argument("-e", "--endpoint", dest="endpoint", - help="Import metadata endpoint", default="sharepoint") + help="Import metadata endpoint", default="microsoftgraph") parser.add_argument("-p", "--path", - dest="path", default="./metadata/SharePoint.xml", + dest="path", default="./metadata/MicrosoftGraph.xml", help="Import metadata endpoint") args = parser.parse_args() diff --git a/generator/metadata/MicrosoftGraph.xml b/generator/metadata/MicrosoftGraph.xml index 7164e03c..3cd8efd9 100644 --- a/generator/metadata/MicrosoftGraph.xml +++ b/generator/metadata/MicrosoftGraph.xml @@ -25406,14 +25406,20 @@ + + + + + + @@ -25448,10 +25454,6 @@ - - - - @@ -30510,9 +30512,13 @@ + + + + @@ -36065,7 +36071,6 @@ - @@ -36292,6 +36297,12 @@ + + + + + + @@ -36378,6 +36389,14 @@ + + + + + + + + @@ -36393,6 +36412,22 @@ + + + + + + + + + + + + + + + + @@ -36758,6 +36793,22 @@ + + + + + + + + + + + + + + + + @@ -36765,6 +36816,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -36793,6 +36872,7 @@ + @@ -36830,6 +36910,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/generator/metadata/SharePoint.xml b/generator/metadata/SharePoint.xml index df39f090..c94425c8 100644 --- a/generator/metadata/SharePoint.xml +++ b/generator/metadata/SharePoint.xml @@ -1857,6 +1857,7 @@ + @@ -5436,6 +5437,12 @@ + + + + + + @@ -12712,12 +12719,15 @@ - + + + + @@ -14529,7 +14539,7 @@ - + @@ -90891,6 +90901,9 @@ + + + @@ -92853,6 +92866,9 @@ + + + @@ -100324,6 +100340,10 @@ + + + + diff --git a/office365/runtime/auth/authentication_context.py b/office365/runtime/auth/authentication_context.py index 14bc7de0..8139a938 100644 --- a/office365/runtime/auth/authentication_context.py +++ b/office365/runtime/auth/authentication_context.py @@ -1,3 +1,6 @@ +import json +import sys + from office365.runtime.auth.client_credential import ClientCredential from office365.runtime.auth.providers.acs_token_provider import ACSTokenProvider from office365.runtime.auth.providers.saml_token_provider import SamlTokenProvider @@ -82,6 +85,39 @@ def _acquire_token(): self.with_access_token(_acquire_token) return self + def with_device_flow(self, tenant, client_id, scopes=None): + """ + Obtain token by a device flow object, with customizable polling effect. + + :param str tenant: Tenant name, for example: contoso.onmicrosoft.com + :param str client_id: The OAuth client id of the calling application. + :param list[str] or None scopes: Scopes requested to access a protected API (a resource) + """ + if scopes is None: + resource = get_absolute_url(self.url) + scopes = ["{url}/.default".format(url=resource)] + + def _acquire_token(): + import msal + app = msal.PublicClientApplication( + client_id, + authority='https://login.microsoftonline.com/{0}'.format(tenant), + client_credential=None + ) + + flow = app.initiate_device_flow(scopes=scopes) + if "user_code" not in flow: + raise ValueError( + "Failed to create device flow: %s" % json.dumps(flow, indent=4)) + + print(flow["message"]) + sys.stdout.flush() + + result = app.acquire_token_by_device_flow(flow) + return TokenResponse.from_json(result) + self.with_access_token(_acquire_token) + return self + def with_access_token(self, token_func): """ Initializes a client to acquire a token from a callback diff --git a/office365/sharepoint/client_context.py b/office365/sharepoint/client_context.py index 45dc2fc8..14e62db0 100644 --- a/office365/sharepoint/client_context.py +++ b/office365/sharepoint/client_context.py @@ -86,6 +86,17 @@ def with_interactive(self, tenant, client_id, scopes=None): self.authentication_context.with_interactive(tenant, client_id, scopes) return self + def with_device_flow(self, tenant, client_id, scopes=None): + """ + Initializes a client to acquire a token via device flow auth. + + :param str tenant: Tenant name, for example: contoso.onmicrosoft.com + :param str client_id: The OAuth client id of the calling application. + :param list[str] or None scopes: Scopes requested to access a protected API (a resource) + """ + self.authentication_context.with_device_flow(tenant, client_id, scopes) + return self + def with_access_token(self, token_func): """ Initializes a client to acquire a token from a callback