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