Skip to content

Commit fb0ac00

Browse files
authored
[Corehttp] Support no auth in auth policy (Azure#41274)
Enable per-operation auth_flow configuration, and also allow an empty auth_flow to indicate no auth. Signed-off-by: Paul Van Eck <[email protected]>
1 parent dec9ecb commit fb0ac00

File tree

4 files changed

+131
-8
lines changed

4 files changed

+131
-8
lines changed

sdk/core/corehttp/corehttp/runtime/policies/_authentication.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def on_request(
110110
:keyword auth_flows: A list of authentication flows to use for the credential.
111111
:paramtype auth_flows: list[dict[str, Union[str, list[dict[str, str]]]]]
112112
"""
113+
# If auth_flows is an empty list, we should not attempt to authorize the request.
114+
if auth_flows is not None and len(auth_flows) == 0:
115+
return
113116
self._enforce_https(request)
114117

115118
if self._token is None or self._need_new_token:
@@ -142,7 +145,9 @@ def send(self, request: PipelineRequest[HTTPRequestType]) -> PipelineResponse[HT
142145
:return: The pipeline response object
143146
:rtype: ~corehttp.runtime.pipeline.PipelineResponse
144147
"""
145-
self.on_request(request, auth_flows=self._auth_flows)
148+
op_auth_flows = request.context.options.pop("auth_flows", None)
149+
auth_flows = op_auth_flows if op_auth_flows is not None else self._auth_flows
150+
self.on_request(request, auth_flows=auth_flows)
146151
try:
147152
response = self.next.send(request)
148153
except Exception:

sdk/core/corehttp/corehttp/runtime/policies/_authentication_async.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ async def on_request(
6868
:paramtype auth_flows: list[dict[str, Union[str, list[dict[str, str]]]]]
6969
:raises: :class:`~corehttp.exceptions.ServiceRequestError`
7070
"""
71+
# If auth_flows is an empty list, we should not attempt to authorize the request.
72+
if auth_flows is not None and len(auth_flows) == 0:
73+
return
7174
_BearerTokenCredentialPolicyBase._enforce_https(request) # pylint:disable=protected-access
7275

7376
if self._token is None or self._need_new_token:
@@ -107,7 +110,9 @@ async def send(
107110
:return: The pipeline response object
108111
:rtype: ~corehttp.runtime.pipeline.PipelineResponse
109112
"""
110-
await await_result(self.on_request, request, auth_flows=self._auth_flows)
113+
op_auth_flows = request.context.options.pop("auth_flows", None)
114+
auth_flows = op_auth_flows if op_auth_flows is not None else self._auth_flows
115+
await await_result(self.on_request, request, auth_flows=auth_flows)
111116
try:
112117
response = await self.next.send(request)
113118
except Exception:

sdk/core/corehttp/tests/async_tests/test_authentication_async.py

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,71 @@ async def test_need_new_token():
323323

324324
@pytest.mark.asyncio
325325
async def test_send_with_auth_flows():
326-
auth_flows = [{"type": "flow1"}, {"type": "flow2"}]
326+
auth_flows = [
327+
{
328+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
329+
"type": "client_credentials",
330+
"scopes": [
331+
{"value": "https://test.microsoft.com/.default"},
332+
],
333+
}
334+
]
327335
credential = Mock(
328336
spec_set=["get_token_info"],
329337
get_token_info=Mock(return_value=get_completed_future(AccessTokenInfo("***", int(time.time()) + 3600))),
330338
)
331-
policy = AsyncBearerTokenCredentialPolicy(credential, "scope", auth_flows=auth_flows)
339+
policy = AsyncBearerTokenCredentialPolicy(credential, "https://test.microsoft.com/.default", auth_flows=auth_flows)
332340
transport = Mock(send=Mock(return_value=get_completed_future(Mock(status_code=200))))
333341

334342
pipeline = AsyncPipeline(transport=transport, policies=[policy])
335343
await pipeline.run(HttpRequest("GET", "https://localhost"))
336-
policy._credential.get_token_info.assert_called_with("scope", options={"auth_flows": auth_flows})
344+
policy._credential.get_token_info.assert_called_with(
345+
"https://test.microsoft.com/.default", options={"auth_flows": auth_flows}
346+
)
347+
348+
349+
@pytest.mark.asyncio
350+
async def test_disable_authorization_header():
351+
"""Tests that we can disable the Authorization header in the request"""
352+
credential = Mock(
353+
spec_set=["get_token_info"],
354+
get_token_info=Mock(return_value=get_completed_future(AccessTokenInfo("***", int(time.time()) + 3600))),
355+
)
356+
policy = AsyncBearerTokenCredentialPolicy(credential, "scope", auth_flows={"foo": "bar"})
357+
transport = Mock(send=Mock(return_value=get_completed_future(Mock(status_code=200))))
358+
359+
pipeline = AsyncPipeline(transport=transport, policies=[policy])
360+
request = HttpRequest("GET", "https://localhost")
361+
await pipeline.run(request, auth_flows=[])
362+
assert "Authorization" not in request.headers
363+
364+
365+
@pytest.mark.asyncio
366+
async def test_auth_flow_operation_override():
367+
"""Tests that the operation level auth_flow is passed to the credential's get_token_info method."""
368+
auth_flows = [
369+
{
370+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
371+
"type": "client_credentials",
372+
"scopes": [{"value": "https://graph.microsoft.com/.default"}],
373+
}
374+
]
375+
credential = Mock(
376+
spec_set=["get_token_info"],
377+
get_token_info=Mock(return_value=get_completed_future(AccessTokenInfo("***", int(time.time()) + 3600))),
378+
)
379+
policy = AsyncBearerTokenCredentialPolicy(credential, "https://graph.microsoft.com/.default", auth_flows=auth_flows)
380+
transport = Mock(send=Mock(return_value=get_completed_future(Mock(status_code=200))))
381+
382+
pipeline = AsyncPipeline(transport=transport, policies=[policy])
383+
op_auth_flow = [
384+
{
385+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
386+
"type": "client_credentials",
387+
"scopes": [{"value": "https://foo.microsoft.com/.default"}],
388+
}
389+
]
390+
await pipeline.run(HttpRequest("GET", "https://localhost"), auth_flows=op_auth_flow)
391+
policy._credential.get_token_info.assert_called_with(
392+
"https://graph.microsoft.com/.default", options={"auth_flows": op_auth_flow}
393+
)

sdk/core/corehttp/tests/test_authentication.py

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,70 @@ def test_need_new_token():
418418

419419

420420
def test_send_with_auth_flows():
421-
auth_flows = [{"type": "flow1"}, {"type": "flow2"}]
421+
auth_flows = [
422+
{
423+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
424+
"type": "client_credentials",
425+
"scopes": [
426+
{"value": "https://test.microsoft.com/.default"},
427+
],
428+
}
429+
]
422430
credential = Mock(
423431
spec_set=["get_token_info"],
424432
get_token_info=Mock(return_value=AccessTokenInfo("***", int(time.time()) + 3600)),
425433
)
426-
policy = BearerTokenCredentialPolicy(credential, "scope", auth_flows=auth_flows)
434+
policy = BearerTokenCredentialPolicy(credential, "https://test.microsoft.com/.default", auth_flows=auth_flows)
427435
transport = Mock(send=Mock(return_value=Mock(status_code=200)))
428436

429437
pipeline = Pipeline(transport=transport, policies=[policy])
430438
pipeline.run(HttpRequest("GET", "https://localhost"))
431-
policy._credential.get_token_info.assert_called_with("scope", options={"auth_flows": auth_flows})
439+
policy._credential.get_token_info.assert_called_with(
440+
"https://test.microsoft.com/.default", options={"auth_flows": auth_flows}
441+
)
442+
443+
444+
def test_disable_authorization_header():
445+
"""Tests that we can disable the Authorization header in the request"""
446+
credential = Mock(
447+
spec_set=["get_token_info"],
448+
get_token_info=Mock(return_value=AccessTokenInfo("***", int(time.time()) + 3600)),
449+
)
450+
policy = BearerTokenCredentialPolicy(credential, "scope", auth_flows={"foo": "bar"})
451+
transport = Mock(send=Mock(return_value=Mock(status_code=200)))
452+
453+
pipeline = Pipeline(transport=transport, policies=[policy])
454+
request = HttpRequest("GET", "https://localhost")
455+
pipeline.run(request, auth_flows=[])
456+
assert "Authorization" not in request.headers
457+
458+
459+
def test_auth_flow_operation_override():
460+
"""Tests that the operation level auth_flow is passed to the credential's get_token_info method."""
461+
auth_flows = [
462+
{
463+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
464+
"type": "client_credentials",
465+
"scopes": [{"value": "https://graph.microsoft.com/.default"}],
466+
}
467+
]
468+
credential = Mock(
469+
spec_set=["get_token_info"],
470+
get_token_info=Mock(return_value=AccessTokenInfo("***", int(time.time()) + 3600)),
471+
)
472+
policy = BearerTokenCredentialPolicy(credential, "https://graph.microsoft.com/.default", auth_flows=auth_flows)
473+
transport = Mock(send=Mock(return_value=Mock(status_code=200)))
474+
475+
op_auth_flow = [
476+
{
477+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/token",
478+
"type": "client_credentials",
479+
"scopes": [{"value": "https://foo.microsoft.com/.default"}],
480+
}
481+
]
482+
483+
pipeline = Pipeline(transport=transport, policies=[policy])
484+
pipeline.run(HttpRequest("GET", "https://localhost"), auth_flows=op_auth_flow)
485+
policy._credential.get_token_info.assert_called_with(
486+
"https://graph.microsoft.com/.default", options={"auth_flows": op_auth_flow}
487+
)

0 commit comments

Comments
 (0)