Skip to content

Commit a2df481

Browse files
authored
Port to the Graph SDK for authentication scripts (#1510)
* Configure Azure Developer Pipeline * Configure Azure Developer Pipeline * Update pricing calculator link * Port to Graph SDK * Port to Graph SDK * Add msgraph to mypy overrides * Typing fixes * Use pyproject.toml for everything * Fix jose jwt exception import * change import to match stubs
1 parent 96a4493 commit a2df481

File tree

13 files changed

+239
-205
lines changed

13 files changed

+239
-205
lines changed

.github/workflows/python-test.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ jobs:
5151
- name: Check types with mypy
5252
run: |
5353
cd scripts/
54-
python3 -m mypy .
55-
cd ../app/
56-
python3 -m mypy .
54+
python3 -m mypy . --config-file=../pyproject.toml
55+
cd ../app/backend/
56+
python3 -m mypy . --config-file=../../pyproject.toml
5757
- name: Check formatting with black
5858
run: black . --check --verbose
5959
- name: Run Python tests

app/backend/app.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ async def setup_clients():
356356
vault_url=f"https://{AZURE_KEY_VAULT_NAME}.vault.azure.net", credential=azure_credential
357357
) as key_vault_client:
358358
search_key = (
359-
AZURE_SEARCH_SECRET_NAME and (await key_vault_client.get_secret(AZURE_SEARCH_SECRET_NAME)).value
359+
AZURE_SEARCH_SECRET_NAME and (await key_vault_client.get_secret(AZURE_SEARCH_SECRET_NAME)).value # type: ignore[attr-defined]
360360
)
361361

362362
# Set up clients for AI Search and Storage
@@ -394,6 +394,10 @@ async def setup_clients():
394394

395395
if USE_USER_UPLOAD:
396396
current_app.logger.info("USE_USER_UPLOAD is true, setting up user upload feature")
397+
if not AZURE_USERSTORAGE_ACCOUNT or not AZURE_USERSTORAGE_CONTAINER:
398+
raise ValueError(
399+
"AZURE_USERSTORAGE_ACCOUNT and AZURE_USERSTORAGE_CONTAINER must be set when USE_USER_UPLOAD is true"
400+
)
397401
user_blob_container_client = FileSystemClient(
398402
f"https://{AZURE_USERSTORAGE_ACCOUNT}.dfs.core.windows.net",
399403
AZURE_USERSTORAGE_CONTAINER,
@@ -504,7 +508,8 @@ async def setup_clients():
504508

505509
if USE_GPT4V:
506510
current_app.logger.info("USE_GPT4V is true, setting up GPT4V approach")
507-
511+
if not AZURE_OPENAI_GPT4V_MODEL:
512+
raise ValueError("AZURE_OPENAI_GPT4V_MODEL must be set when USE_GPT4V is true")
508513
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
509514

510515
current_app.config[CONFIG_ASK_VISION_APPROACH] = RetrieveThenReadVisionApproach(
@@ -565,7 +570,7 @@ def create_app():
565570
# This tracks OpenAI SDK requests:
566571
OpenAIInstrumentor().instrument()
567572
# This middleware tracks app route requests:
568-
app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) # type: ignore[method-assign]
573+
app.asgi_app = OpenTelemetryMiddleware(app.asgi_app) # type: ignore[assignment]
569574

570575
# Level should be one of https://docs.python.org/3/library/logging.html#logging-levels
571576
default_level = "INFO" # In development, log more verbosely

app/backend/core/authentication.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from azure.search.documents.aio import SearchClient
99
from azure.search.documents.indexes.models import SearchIndex
1010
from jose import jwt
11+
from jose.exceptions import ExpiredSignatureError, JWTClaimsError
1112
from msal import ConfidentialClientApplication
1213
from msal.token_cache import TokenCache
1314
from tenacity import (
@@ -323,9 +324,9 @@ async def validate_access_token(self, token: str):
323324

324325
try:
325326
jwt.decode(token, rsa_key, algorithms=["RS256"], audience=audience, issuer=issuer)
326-
except jwt.ExpiredSignatureError as jwt_expired_exc:
327+
except ExpiredSignatureError as jwt_expired_exc:
327328
raise AuthError({"code": "token_expired", "description": "token is expired"}, 401) from jwt_expired_exc
328-
except jwt.JWTClaimsError as jwt_claims_exc:
329+
except JWTClaimsError as jwt_claims_exc:
329330
raise AuthError(
330331
{"code": "invalid_claims", "description": "incorrect claims," "please check the audience and issuer"},
331332
401,

app/backend/prepdocs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ def setup_embeddings_service(
120120
azure_credential: AsyncTokenCredential,
121121
openai_host: str,
122122
openai_model_name: str,
123-
openai_service: str,
124-
openai_deployment: str,
123+
openai_service: Union[str, None],
124+
openai_deployment: Union[str, None],
125125
openai_dimensions: int,
126126
openai_key: Union[str, None],
127127
openai_org: Union[str, None],

app/backend/prepdocslib/embeddings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ class AzureOpenAIEmbeddingService(OpenAIEmbeddings):
159159

160160
def __init__(
161161
self,
162-
open_ai_service: str,
163-
open_ai_deployment: str,
162+
open_ai_service: Union[str, None],
163+
open_ai_deployment: Union[str, None],
164164
open_ai_model_name: str,
165165
open_ai_dimensions: int,
166166
credential: Union[AsyncTokenCredential, AzureKeyCredential],

app/backend/requirements.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ msal
2020
azure-keyvault-secrets
2121
cryptography
2222
python-jose[cryptography]
23+
types-python-jose
2324
Pillow
2425
types-Pillow
2526
pypdf
2627
PyMuPDF
2728
beautifulsoup4
2829
types-beautifulsoup4
30+
msgraph-sdk==1.1.0

app/backend/requirements.txt

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
aiofiles==23.2.1
88
# via quart
99
aiohttp==3.9.3
10-
# via -r requirements.in
10+
# via
11+
# -r requirements.in
12+
# microsoft-kiota-authentication-azure
1113
aiosignal==1.3.1
1214
# via aiohttp
1315
annotated-types==0.6.0
@@ -35,11 +37,14 @@ azure-core==1.30.1
3537
# azure-search-documents
3638
# azure-storage-blob
3739
# azure-storage-file-datalake
40+
# microsoft-kiota-authentication-azure
3841
# msrest
3942
azure-core-tracing-opentelemetry==1.0.0b11
4043
# via azure-monitor-opentelemetry
4144
azure-identity==1.15.0
42-
# via -r requirements.in
45+
# via
46+
# -r requirements.in
47+
# msgraph-sdk
4348
azure-keyvault-secrets==4.8.0
4449
# via -r requirements.in
4550
azure-monitor-opentelemetry==1.3.0
@@ -104,13 +109,18 @@ h11==0.14.0
104109
# uvicorn
105110
# wsproto
106111
h2==4.1.0
107-
# via hypercorn
112+
# via
113+
# httpx
114+
# hypercorn
108115
hpack==4.0.0
109116
# via h2
110117
httpcore==1.0.4
111118
# via httpx
112-
httpx==0.27.0
113-
# via openai
119+
httpx[http2]==0.27.0
120+
# via
121+
# microsoft-kiota-http
122+
# msgraph-core
123+
# openai
114124
hypercorn==0.16.0
115125
# via quart
116126
hyperframe==6.0.1
@@ -144,13 +154,37 @@ markupsafe==2.1.5
144154
# jinja2
145155
# quart
146156
# werkzeug
157+
microsoft-kiota-abstractions==1.3.2
158+
# via
159+
# microsoft-kiota-authentication-azure
160+
# microsoft-kiota-http
161+
# microsoft-kiota-serialization-json
162+
# microsoft-kiota-serialization-text
163+
# msgraph-core
164+
# msgraph-sdk
165+
microsoft-kiota-authentication-azure==1.0.0
166+
# via
167+
# msgraph-core
168+
# msgraph-sdk
169+
microsoft-kiota-http==1.3.1
170+
# via
171+
# msgraph-core
172+
# msgraph-sdk
173+
microsoft-kiota-serialization-json==1.1.0
174+
# via msgraph-sdk
175+
microsoft-kiota-serialization-text==1.0.0
176+
# via msgraph-sdk
147177
msal==1.27.0
148178
# via
149179
# -r requirements.in
150180
# azure-identity
151181
# msal-extensions
152182
msal-extensions==1.1.0
153183
# via azure-identity
184+
msgraph-core==1.0.0
185+
# via msgraph-sdk
186+
msgraph-sdk==1.1.0
187+
# via -r requirements.in
154188
msrest==0.7.1
155189
# via azure-monitor-opentelemetry-exporter
156190
multidict==6.0.5
@@ -170,6 +204,9 @@ opentelemetry-api==1.23.0
170204
# via
171205
# azure-core-tracing-opentelemetry
172206
# azure-monitor-opentelemetry-exporter
207+
# microsoft-kiota-abstractions
208+
# microsoft-kiota-authentication-azure
209+
# microsoft-kiota-http
173210
# opentelemetry-instrumentation
174211
# opentelemetry-instrumentation-aiohttp-client
175212
# opentelemetry-instrumentation-asgi
@@ -237,6 +274,9 @@ opentelemetry-resource-detector-azure==0.1.3
237274
opentelemetry-sdk==1.23.0
238275
# via
239276
# azure-monitor-opentelemetry-exporter
277+
# microsoft-kiota-abstractions
278+
# microsoft-kiota-authentication-azure
279+
# microsoft-kiota-http
240280
# opentelemetry-resource-detector-azure
241281
opentelemetry-semantic-conventions==0.44b0
242282
# via
@@ -274,6 +314,8 @@ pandas==2.2.1
274314
# via openai
275315
pandas-stubs==2.2.0.240218
276316
# via openai
317+
pendulum==3.0.0
318+
# via microsoft-kiota-serialization-json
277319
pillow==10.3.0
278320
# via -r requirements.in
279321
portalocker==2.8.2
@@ -291,17 +333,19 @@ pydantic==2.6.3
291333
pydantic-core==2.16.3
292334
# via pydantic
293335
pyjwt[crypto]==2.8.0
294-
# via
295-
# msal
296-
# pyjwt
336+
# via msal
297337
pymupdf==1.23.26
298338
# via -r requirements.in
299339
pymupdfb==1.23.22
300340
# via pymupdf
301341
pypdf==4.1.0
302342
# via -r requirements.in
303343
python-dateutil==2.9.0.post0
304-
# via pandas
344+
# via
345+
# microsoft-kiota-serialization-text
346+
# pandas
347+
# pendulum
348+
# time-machine
305349
python-jose[cryptography]==3.3.0
306350
# via -r requirements.in
307351
pytz==2024.1
@@ -338,10 +382,14 @@ sniffio==1.3.1
338382
# openai
339383
soupsieve==2.5
340384
# via beautifulsoup4
385+
std-uritemplate==0.0.55
386+
# via microsoft-kiota-abstractions
341387
tenacity==8.2.3
342388
# via -r requirements.in
343389
tiktoken==0.6.0
344390
# via -r requirements.in
391+
time-machine==2.14.1
392+
# via pendulum
345393
tqdm==4.66.2
346394
# via openai
347395
types-beautifulsoup4==4.12.0.20240229
@@ -350,6 +398,10 @@ types-html5lib==1.1.11.20240228
350398
# via types-beautifulsoup4
351399
types-pillow==10.2.0.20240213
352400
# via -r requirements.in
401+
types-pyasn1==0.6.0.20240402
402+
# via types-python-jose
403+
types-python-jose==3.3.4.20240106
404+
# via -r requirements.in
353405
types-pytz==2024.1.0.20240203
354406
# via pandas-stubs
355407
typing-extensions==4.10.0
@@ -364,7 +416,9 @@ typing-extensions==4.10.0
364416
# pydantic
365417
# pydantic-core
366418
tzdata==2024.1
367-
# via pandas
419+
# via
420+
# pandas
421+
# pendulum
368422
urllib3==2.2.1
369423
# via requests
370424
uvicorn==0.27.1

app/mypy.ini

Lines changed: 0 additions & 7 deletions
This file was deleted.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ python_version = 3.9
2626

2727
[[tool.mypy.overrides]]
2828
module = [
29-
"msal.*"
29+
"msal.*",
30+
"msgraph.*",
31+
"kiota_abstractions.*",
32+
"kiota.*"
3033
]
3134
ignore_missing_imports = true

scripts/auth_common.py

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,16 @@
11
import os
2-
from typing import Dict, Optional
2+
from typing import Optional
33

4-
import aiohttp
5-
from azure.core.credentials_async import AsyncTokenCredential
4+
from kiota_abstractions.api_error import APIError
5+
from msgraph import GraphServiceClient
66

7-
TIMEOUT = 60
87

9-
10-
async def get_auth_headers(credential: AsyncTokenCredential):
11-
token_result = await credential.get_token("https://graph.microsoft.com/.default")
12-
return {"Authorization": f"Bearer {token_result.token}"}
13-
14-
15-
async def get_application(auth_headers: Dict[str, str], app_id: str) -> Optional[str]:
16-
async with aiohttp.ClientSession(headers=auth_headers, timeout=aiohttp.ClientTimeout(total=TIMEOUT)) as session:
17-
async with session.get(f"https://graph.microsoft.com/v1.0/applications(appId='{app_id}')") as response:
18-
if response.status == 200:
19-
response_json = await response.json()
20-
return response_json["id"]
21-
22-
return None
23-
24-
25-
async def update_application(auth_headers: Dict[str, str], object_id: str, app_payload: object):
26-
async with aiohttp.ClientSession(headers=auth_headers, timeout=aiohttp.ClientTimeout(total=TIMEOUT)) as session:
27-
async with session.patch(
28-
f"https://graph.microsoft.com/v1.0/applications/{object_id}", json=app_payload
29-
) as response:
30-
if not response.ok:
31-
response_json = await response.json()
32-
raise Exception(response_json)
33-
34-
return True
8+
async def get_application(graph_client: GraphServiceClient, client_id: str) -> Optional[str]:
9+
try:
10+
app = await graph_client.applications_with_app_id(client_id).get()
11+
return app.id
12+
except APIError:
13+
return None
3514

3615

3716
def test_authentication_enabled():

0 commit comments

Comments
 (0)