microsoft-teams-graph 2.0.0a9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- microsoft_teams_graph-2.0.0a9/.gitignore +45 -0
- microsoft_teams_graph-2.0.0a9/PKG-INFO +177 -0
- microsoft_teams_graph-2.0.0a9/README.md +163 -0
- microsoft_teams_graph-2.0.0a9/pyproject.toml +39 -0
- microsoft_teams_graph-2.0.0a9/src/microsoft/teams/graph/__init__.py +26 -0
- microsoft_teams_graph-2.0.0a9/src/microsoft_teams/graph/__init__.py +12 -0
- microsoft_teams_graph-2.0.0a9/src/microsoft_teams/graph/auth_provider.py +87 -0
- microsoft_teams_graph-2.0.0a9/src/microsoft_teams/graph/graph.py +62 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
|
|
7
|
+
# Environments
|
|
8
|
+
.env
|
|
9
|
+
.venv
|
|
10
|
+
env/
|
|
11
|
+
venv/
|
|
12
|
+
ENV/
|
|
13
|
+
env.bak/
|
|
14
|
+
venv.bak/
|
|
15
|
+
|
|
16
|
+
# mypy
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.dmypy.json
|
|
19
|
+
dmypy.json
|
|
20
|
+
|
|
21
|
+
.copilot-instructions.md
|
|
22
|
+
|
|
23
|
+
# other
|
|
24
|
+
.DS_STORE
|
|
25
|
+
*.bak
|
|
26
|
+
*~
|
|
27
|
+
*.tmp
|
|
28
|
+
|
|
29
|
+
ref/
|
|
30
|
+
py.typed
|
|
31
|
+
CLAUDE.md
|
|
32
|
+
|
|
33
|
+
.env.claude/
|
|
34
|
+
.claude/
|
|
35
|
+
|
|
36
|
+
examples/**/.vscode/
|
|
37
|
+
examples/**/appPackage/
|
|
38
|
+
examples/**/infra/
|
|
39
|
+
examples/**/teamsapp*
|
|
40
|
+
examples/**/aad.manifest.json
|
|
41
|
+
|
|
42
|
+
# Node (from tab)
|
|
43
|
+
node_modules
|
|
44
|
+
dist/
|
|
45
|
+
build/
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: microsoft-teams-graph
|
|
3
|
+
Version: 2.0.0a9
|
|
4
|
+
Summary: The Graph package for a Microsoft Teams agent
|
|
5
|
+
Author-email: Microsoft <TeamsAISDKFeedback@microsoft.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: agents,ai,bot,graph,microsoft,teams
|
|
8
|
+
Requires-Python: <3.15,>=3.12
|
|
9
|
+
Requires-Dist: azure-core>=1.31.0
|
|
10
|
+
Requires-Dist: microsoft-teams-common
|
|
11
|
+
Requires-Dist: msgraph-sdk<2.0.0,>=1.30.0
|
|
12
|
+
Requires-Dist: pyjwt[crypto]>=2.10.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Microsoft Teams Graph Integration
|
|
16
|
+
|
|
17
|
+
<p>
|
|
18
|
+
<a href="https://pypi.org/project/microsoft-teams-graph" target="_blank">
|
|
19
|
+
<img src="https://img.shields.io/pypi/v/microsoft-teams-graph" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://pypi.org/project/microsoft-teams-graph" target="_blank">
|
|
22
|
+
<img src="https://img.shields.io/pypi/dw/microsoft-teams-graph" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
This package provides seamless access to Microsoft Graph APIs from Teams bots and agents built with the Microsoft Teams SDK for Python.
|
|
27
|
+
|
|
28
|
+
<a href="https://microsoft.github.io/teams-sdk" target="_blank">
|
|
29
|
+
<img src="https://img.shields.io/badge/📖 Getting Started-blue?style=for-the-badge" />
|
|
30
|
+
</a>
|
|
31
|
+
|
|
32
|
+
## Key Features
|
|
33
|
+
|
|
34
|
+
- **Token Integration**: Unified token handling using the Token type from microsoft-teams-common
|
|
35
|
+
- **Flexible Token Sources**: Supports strings, StringLike objects, callables, async callables, or None
|
|
36
|
+
- **Automatic Token Resolution**: Leverages common resolve_token utility for consistent handling
|
|
37
|
+
|
|
38
|
+
## Requirements
|
|
39
|
+
|
|
40
|
+
- Teams SDK for Python
|
|
41
|
+
- Microsoft Graph SDK for Python (msgraph-sdk)
|
|
42
|
+
- Azure Core library (azure-core)
|
|
43
|
+
- Microsoft Teams Common library (microsoft-teams-common)
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Basic Usage with Teams Bot
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from microsoft_teams.graph import get_graph_client
|
|
51
|
+
from microsoft_teams.apps import App, ActivityContext
|
|
52
|
+
from microsoft_teams.api import MessageActivity
|
|
53
|
+
|
|
54
|
+
app = App()
|
|
55
|
+
|
|
56
|
+
@app.on_message
|
|
57
|
+
async def handle_message(ctx: ActivityContext[MessageActivity]):
|
|
58
|
+
if not ctx.is_signed_in:
|
|
59
|
+
await ctx.sign_in()
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Create Graph client using user's token
|
|
63
|
+
graph = get_graph_client(ctx.user_token)
|
|
64
|
+
|
|
65
|
+
# Get user profile
|
|
66
|
+
me = await graph.me.get()
|
|
67
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
68
|
+
|
|
69
|
+
# Get user's Teams
|
|
70
|
+
teams = await graph.me.joined_teams.get()
|
|
71
|
+
if teams and teams.value:
|
|
72
|
+
team_names = [team.display_name for team in teams.value]
|
|
73
|
+
await ctx.send(f"You're in {len(team_names)} teams: {', '.join(team_names)}")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Token Integration
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from microsoft_teams.common.http.client_token import Token
|
|
80
|
+
|
|
81
|
+
def create_token_callable(ctx: ActivityContext) -> Token:
|
|
82
|
+
"""Create a callable token that refreshes automatically."""
|
|
83
|
+
def get_fresh_token():
|
|
84
|
+
# This is called on each Graph API request
|
|
85
|
+
return ctx.user_token # Always returns current valid token
|
|
86
|
+
|
|
87
|
+
return get_fresh_token
|
|
88
|
+
|
|
89
|
+
# Use with Graph client
|
|
90
|
+
graph = get_graph_client(create_token_callable(ctx))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
await ctx.sign_in()
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Use the user token that's already available in the context
|
|
97
|
+
graph = get_graph_client(ctx.user_token)
|
|
98
|
+
|
|
99
|
+
# Make Graph API calls
|
|
100
|
+
me = await graph.me.get()
|
|
101
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
102
|
+
|
|
103
|
+
# Make Graph API calls
|
|
104
|
+
me = await graph.me.get()
|
|
105
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
106
|
+
|
|
107
|
+
````
|
|
108
|
+
|
|
109
|
+
## Token Type Usage
|
|
110
|
+
|
|
111
|
+
The package uses the Token type from microsoft-teams-common for flexible token handling. You can provide tokens in several formats:
|
|
112
|
+
|
|
113
|
+
### String Token (Simplest)
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# Direct string token
|
|
117
|
+
graph = get_graph_client("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...")
|
|
118
|
+
````
|
|
119
|
+
|
|
120
|
+
### Callable Token (Dynamic)
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
def get_token():
|
|
124
|
+
"""Callable that returns a string token."""
|
|
125
|
+
# Get your access token from wherever (Teams API, cache, etc.)
|
|
126
|
+
return get_access_token_from_somewhere()
|
|
127
|
+
|
|
128
|
+
# Use the callable with get_graph_client
|
|
129
|
+
graph = get_graph_client(get_token)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Async Callable Token
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
async def get_token_async():
|
|
136
|
+
"""Async callable that returns a string token."""
|
|
137
|
+
# Fetch token asynchronously
|
|
138
|
+
token_response = await some_api_call()
|
|
139
|
+
return token_response.access_token
|
|
140
|
+
|
|
141
|
+
graph = get_graph_client(get_token_async)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Dynamic Token Retrieval
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
def get_fresh_token():
|
|
148
|
+
"""Callable that fetches a fresh token on each invocation."""
|
|
149
|
+
# This will be called each time the Graph client needs a token
|
|
150
|
+
fresh_token = fetch_latest_token_from_api()
|
|
151
|
+
return fresh_token
|
|
152
|
+
|
|
153
|
+
graph = get_graph_client(get_fresh_token)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Authentication
|
|
157
|
+
|
|
158
|
+
The package uses Token-based authentication with automatic resolution through the common library. Teams tokens are pre-authorized through the OAuth connection configured in your Azure Bot registration.
|
|
159
|
+
|
|
160
|
+
## API Usage Examples
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Get user profile
|
|
164
|
+
me = await graph.me.get()
|
|
165
|
+
|
|
166
|
+
# Get recent emails with specific fields
|
|
167
|
+
from msgraph.generated.users.item.messages.messages_request_builder import MessagesRequestBuilder
|
|
168
|
+
|
|
169
|
+
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
|
|
170
|
+
select=["subject", "from", "receivedDateTime"],
|
|
171
|
+
top=5
|
|
172
|
+
)
|
|
173
|
+
request_config = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
|
|
174
|
+
query_parameters=query_params
|
|
175
|
+
)
|
|
176
|
+
messages = await graph.me.messages.get(request_configuration=request_config)
|
|
177
|
+
```
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Microsoft Teams Graph Integration
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<a href="https://pypi.org/project/microsoft-teams-graph" target="_blank">
|
|
5
|
+
<img src="https://img.shields.io/pypi/v/microsoft-teams-graph" />
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://pypi.org/project/microsoft-teams-graph" target="_blank">
|
|
8
|
+
<img src="https://img.shields.io/pypi/dw/microsoft-teams-graph" />
|
|
9
|
+
</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
This package provides seamless access to Microsoft Graph APIs from Teams bots and agents built with the Microsoft Teams SDK for Python.
|
|
13
|
+
|
|
14
|
+
<a href="https://microsoft.github.io/teams-sdk" target="_blank">
|
|
15
|
+
<img src="https://img.shields.io/badge/📖 Getting Started-blue?style=for-the-badge" />
|
|
16
|
+
</a>
|
|
17
|
+
|
|
18
|
+
## Key Features
|
|
19
|
+
|
|
20
|
+
- **Token Integration**: Unified token handling using the Token type from microsoft-teams-common
|
|
21
|
+
- **Flexible Token Sources**: Supports strings, StringLike objects, callables, async callables, or None
|
|
22
|
+
- **Automatic Token Resolution**: Leverages common resolve_token utility for consistent handling
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Teams SDK for Python
|
|
27
|
+
- Microsoft Graph SDK for Python (msgraph-sdk)
|
|
28
|
+
- Azure Core library (azure-core)
|
|
29
|
+
- Microsoft Teams Common library (microsoft-teams-common)
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Basic Usage with Teams Bot
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from microsoft_teams.graph import get_graph_client
|
|
37
|
+
from microsoft_teams.apps import App, ActivityContext
|
|
38
|
+
from microsoft_teams.api import MessageActivity
|
|
39
|
+
|
|
40
|
+
app = App()
|
|
41
|
+
|
|
42
|
+
@app.on_message
|
|
43
|
+
async def handle_message(ctx: ActivityContext[MessageActivity]):
|
|
44
|
+
if not ctx.is_signed_in:
|
|
45
|
+
await ctx.sign_in()
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Create Graph client using user's token
|
|
49
|
+
graph = get_graph_client(ctx.user_token)
|
|
50
|
+
|
|
51
|
+
# Get user profile
|
|
52
|
+
me = await graph.me.get()
|
|
53
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
54
|
+
|
|
55
|
+
# Get user's Teams
|
|
56
|
+
teams = await graph.me.joined_teams.get()
|
|
57
|
+
if teams and teams.value:
|
|
58
|
+
team_names = [team.display_name for team in teams.value]
|
|
59
|
+
await ctx.send(f"You're in {len(team_names)} teams: {', '.join(team_names)}")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Token Integration
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from microsoft_teams.common.http.client_token import Token
|
|
66
|
+
|
|
67
|
+
def create_token_callable(ctx: ActivityContext) -> Token:
|
|
68
|
+
"""Create a callable token that refreshes automatically."""
|
|
69
|
+
def get_fresh_token():
|
|
70
|
+
# This is called on each Graph API request
|
|
71
|
+
return ctx.user_token # Always returns current valid token
|
|
72
|
+
|
|
73
|
+
return get_fresh_token
|
|
74
|
+
|
|
75
|
+
# Use with Graph client
|
|
76
|
+
graph = get_graph_client(create_token_callable(ctx))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
await ctx.sign_in()
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# Use the user token that's already available in the context
|
|
83
|
+
graph = get_graph_client(ctx.user_token)
|
|
84
|
+
|
|
85
|
+
# Make Graph API calls
|
|
86
|
+
me = await graph.me.get()
|
|
87
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
88
|
+
|
|
89
|
+
# Make Graph API calls
|
|
90
|
+
me = await graph.me.get()
|
|
91
|
+
await ctx.send(f"Hello {me.display_name}!")
|
|
92
|
+
|
|
93
|
+
````
|
|
94
|
+
|
|
95
|
+
## Token Type Usage
|
|
96
|
+
|
|
97
|
+
The package uses the Token type from microsoft-teams-common for flexible token handling. You can provide tokens in several formats:
|
|
98
|
+
|
|
99
|
+
### String Token (Simplest)
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# Direct string token
|
|
103
|
+
graph = get_graph_client("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...")
|
|
104
|
+
````
|
|
105
|
+
|
|
106
|
+
### Callable Token (Dynamic)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
def get_token():
|
|
110
|
+
"""Callable that returns a string token."""
|
|
111
|
+
# Get your access token from wherever (Teams API, cache, etc.)
|
|
112
|
+
return get_access_token_from_somewhere()
|
|
113
|
+
|
|
114
|
+
# Use the callable with get_graph_client
|
|
115
|
+
graph = get_graph_client(get_token)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Async Callable Token
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
async def get_token_async():
|
|
122
|
+
"""Async callable that returns a string token."""
|
|
123
|
+
# Fetch token asynchronously
|
|
124
|
+
token_response = await some_api_call()
|
|
125
|
+
return token_response.access_token
|
|
126
|
+
|
|
127
|
+
graph = get_graph_client(get_token_async)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Dynamic Token Retrieval
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
def get_fresh_token():
|
|
134
|
+
"""Callable that fetches a fresh token on each invocation."""
|
|
135
|
+
# This will be called each time the Graph client needs a token
|
|
136
|
+
fresh_token = fetch_latest_token_from_api()
|
|
137
|
+
return fresh_token
|
|
138
|
+
|
|
139
|
+
graph = get_graph_client(get_fresh_token)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Authentication
|
|
143
|
+
|
|
144
|
+
The package uses Token-based authentication with automatic resolution through the common library. Teams tokens are pre-authorized through the OAuth connection configured in your Azure Bot registration.
|
|
145
|
+
|
|
146
|
+
## API Usage Examples
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# Get user profile
|
|
150
|
+
me = await graph.me.get()
|
|
151
|
+
|
|
152
|
+
# Get recent emails with specific fields
|
|
153
|
+
from msgraph.generated.users.item.messages.messages_request_builder import MessagesRequestBuilder
|
|
154
|
+
|
|
155
|
+
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
|
|
156
|
+
select=["subject", "from", "receivedDateTime"],
|
|
157
|
+
top=5
|
|
158
|
+
)
|
|
159
|
+
request_config = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
|
|
160
|
+
query_parameters=query_params
|
|
161
|
+
)
|
|
162
|
+
messages = await graph.me.messages.get(request_configuration=request_config)
|
|
163
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "microsoft-teams-graph"
|
|
3
|
+
version = "2.0.0a9"
|
|
4
|
+
description = "The Graph package for a Microsoft Teams agent"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "Microsoft", email = "TeamsAISDKFeedback@microsoft.com" }]
|
|
8
|
+
keywords = ["microsoft", "teams", "ai", "bot", "agents", "graph"]
|
|
9
|
+
requires-python = ">=3.12,<3.15"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"azure-core>=1.31.0",
|
|
12
|
+
"msgraph-sdk>=1.30.0,<2.0.0",
|
|
13
|
+
"microsoft-teams-common",
|
|
14
|
+
"pyjwt[crypto]>=2.10.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[dependency-groups]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.4.0",
|
|
20
|
+
"pytest-asyncio>=1.0.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
asyncio_mode = "auto"
|
|
25
|
+
testpaths = ["tests"]
|
|
26
|
+
python_paths = ["src"]
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/microsoft_teams", "src/microsoft"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
include = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.uv.sources]
|
|
39
|
+
microsoft-teams-common = { workspace = true }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
Licensed under the MIT License.
|
|
4
|
+
|
|
5
|
+
Backward compatibility shim for microsoft.teams.graph.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
DEPRECATED: This import path is deprecated and will be removed in version 2.0.0 GA.
|
|
9
|
+
Please update your imports to use 'microsoft_teams.graph' instead.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import warnings
|
|
14
|
+
|
|
15
|
+
warnings.warn(
|
|
16
|
+
"The 'microsoft.teams.graph' namespace is deprecated and will be removed in "
|
|
17
|
+
"version 2.0.0 GA. Please update your imports to 'microsoft_teams.graph'.",
|
|
18
|
+
DeprecationWarning,
|
|
19
|
+
stacklevel=2,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from microsoft_teams.graph import * # noqa: E402, F401, F403
|
|
23
|
+
from microsoft_teams.graph import __all__ # noqa: E402, F401
|
|
24
|
+
|
|
25
|
+
_new_module = sys.modules["microsoft_teams.graph"]
|
|
26
|
+
sys.modules[__name__] = _new_module
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
Licensed under the MIT License.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
import datetime
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import jwt
|
|
13
|
+
from azure.core.credentials import AccessToken, TokenCredential
|
|
14
|
+
from azure.core.exceptions import ClientAuthenticationError
|
|
15
|
+
from microsoft_teams.common.http.client_token import Token, resolve_token
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthProvider(TokenCredential):
|
|
21
|
+
"""
|
|
22
|
+
Provides authentication for Microsoft Graph using Teams tokens.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, token: Token) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Initialize the AuthProvider.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
token: Token data (string, StringLike, callable, or None)
|
|
32
|
+
"""
|
|
33
|
+
self._token = token
|
|
34
|
+
|
|
35
|
+
def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
|
|
36
|
+
"""
|
|
37
|
+
Retrieve an access token for Microsoft Graph.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
*scopes: Token scopes (required for interface compatibility)
|
|
41
|
+
**kwargs: Additional keyword arguments
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
AccessToken: The access token for Microsoft Graph
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ClientAuthenticationError: If the token is invalid or authentication fails
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Resolve the token using the common utility
|
|
51
|
+
try:
|
|
52
|
+
asyncio.get_running_loop()
|
|
53
|
+
|
|
54
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
55
|
+
future = executor.submit(asyncio.run, resolve_token(self._token))
|
|
56
|
+
token_str = future.result()
|
|
57
|
+
except RuntimeError:
|
|
58
|
+
token_str = asyncio.run(resolve_token(self._token))
|
|
59
|
+
|
|
60
|
+
if not token_str:
|
|
61
|
+
raise ClientAuthenticationError("Token resolved to None or empty string")
|
|
62
|
+
|
|
63
|
+
if not token_str.strip():
|
|
64
|
+
raise ClientAuthenticationError("Token contains only whitespace")
|
|
65
|
+
|
|
66
|
+
# Try to extract expiration from JWT, fallback to 1 hour default
|
|
67
|
+
try:
|
|
68
|
+
# Decode JWT without verification to extract expiration
|
|
69
|
+
payload = jwt.decode(token_str, algorithms=["RS256"], options={"verify_signature": False})
|
|
70
|
+
expires_on = payload.get("exp")
|
|
71
|
+
if not expires_on:
|
|
72
|
+
# Fallback to 1 hour from now if no exp claim
|
|
73
|
+
logger.debug("JWT token missing 'exp' claim, using 1-hour default expiration")
|
|
74
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
75
|
+
expires_on = int((now + datetime.timedelta(hours=1)).timestamp())
|
|
76
|
+
except Exception:
|
|
77
|
+
# Fallback to 1 hour from now if JWT decoding fails (e.g., not a JWT)
|
|
78
|
+
logger.debug("Token is not a valid JWT, using 1-hour default expiration")
|
|
79
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
80
|
+
expires_on = int((now + datetime.timedelta(hours=1)).timestamp())
|
|
81
|
+
|
|
82
|
+
return AccessToken(token=token_str, expires_on=expires_on)
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
if isinstance(e, ClientAuthenticationError):
|
|
86
|
+
raise
|
|
87
|
+
raise ClientAuthenticationError(f"Failed to resolve token: {str(e)}") from e
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
Licensed under the MIT License.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from azure.core.exceptions import ClientAuthenticationError
|
|
9
|
+
from microsoft_teams.common.http.client_token import Token
|
|
10
|
+
from msgraph.graph_service_client import GraphServiceClient
|
|
11
|
+
|
|
12
|
+
from .auth_provider import AuthProvider
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_graph_client(token: Optional[Token] = None) -> GraphServiceClient:
|
|
16
|
+
"""
|
|
17
|
+
Get a configured Microsoft Graph client using a Token.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
token: Token data (string, StringLike, callable, or None). If None,
|
|
21
|
+
will raise ClientAuthenticationError with a clear message.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
GraphServiceClient: A configured client ready for Microsoft Graph API calls
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
ClientAuthenticationError: If the token is None, invalid, or authentication fails
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
```python
|
|
31
|
+
# Using a string token
|
|
32
|
+
graph = get_graph_client("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Using a callable that returns a string
|
|
36
|
+
def get_token():
|
|
37
|
+
return "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs..."
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
graph = get_graph_client(get_token)
|
|
41
|
+
|
|
42
|
+
# Make Graph API calls
|
|
43
|
+
me = await graph.me.get()
|
|
44
|
+
messages = await graph.me.messages.get()
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
# Provide a clear error message for None tokens
|
|
49
|
+
if token is None:
|
|
50
|
+
raise ClientAuthenticationError(
|
|
51
|
+
"Token cannot be None. Please provide a valid token (string, callable, or StringLike object) "
|
|
52
|
+
"to authenticate with Microsoft Graph."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
credential = AuthProvider(token)
|
|
56
|
+
client = GraphServiceClient(credentials=credential)
|
|
57
|
+
return client
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if isinstance(e, ClientAuthenticationError):
|
|
61
|
+
raise # Re-raise authentication errors as-is
|
|
62
|
+
raise ClientAuthenticationError(f"Failed to create Microsoft Graph client: {str(e)}") from e
|