Skip to content

Commit

Permalink
[ACR] Add auth scope (#19022)
Browse files Browse the repository at this point in the history
  • Loading branch information
seankane-msft authored Jun 4, 2021
1 parent 52258ca commit d040674
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Licensed under the MIT License.
# ------------------------------------

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from azure.core.pipeline.policies import HTTPPolicy

Expand All @@ -20,14 +20,14 @@
class ContainerRegistryChallengePolicy(HTTPPolicy):
"""Authentication policy for ACR which accepts a challenge"""

def __init__(self, credential, endpoint):
# type: (TokenCredential, str) -> None
def __init__(self, credential, endpoint, **kwargs):
# type: (TokenCredential, str, **Any) -> None
super(ContainerRegistryChallengePolicy, self).__init__()
self._credential = credential
if self._credential is None:
self._exchange_client = AnonymousACRExchangeClient(endpoint)
else:
self._exchange_client = ACRExchangeClient(endpoint, self._credential)
self._exchange_client = ACRExchangeClient(endpoint, self._credential, **kwargs)

def on_request(self, request):
# type: (PipelineRequest) -> None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,19 @@ class ContainerRegistryBaseClient(object):
:param str endpoint: Azure Container Registry endpoint
:param credential: AAD Token for authenticating requests with Azure
:type credential: :class:`azure.identity.DefaultTokenCredential`
:keyword authentication_scope: URL for credential authentication if different from the default
:paramtype authentication_scope: str
"""

def __init__(self, endpoint, credential, **kwargs):
# type: (str, Optional[TokenCredential], Dict[str, Any]) -> None
auth_policy = ContainerRegistryChallengePolicy(credential, endpoint)
auth_policy = ContainerRegistryChallengePolicy(credential, endpoint, **kwargs)
self._client = ContainerRegistry(
credential=credential,
url=endpoint,
sdk_moniker=USER_AGENT,
authentication_policy=auth_policy,
credential_scopes="https://management.core.windows.net/.default",
credential_scopes=kwargs.get("credential_scopes", "https://management.core.windows.net/.default"),
**kwargs
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def __init__(self, endpoint, credential=None, **kwargs):
:param str endpoint: An ACR endpoint
:param credential: The credential with which to authenticate
:type credential: :class:`~azure.core.credentials.TokenCredential`
:keyword authentication_scope: URL for credential authentication if different from the default
:paramtype authentication_scope: str
:returns: None
:raises: None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ def __init__(self, endpoint, credential, **kwargs):
if not endpoint.startswith("https://") and not endpoint.startswith("http://"):
endpoint = "https://" + endpoint
self._endpoint = endpoint
self.credential_scope = "https://management.core.windows.net/.default"
self.credential_scope = kwargs.get("authentication_scope", "https://management.core.windows.net/.default")
self._client = ContainerRegistry(
credential=credential,
url=endpoint,
sdk_moniker=USER_AGENT,
authentication_policy=ExchangeClientAuthenticationPolicy(),
credential_scopes=kwargs.pop("credential_scopes", self.credential_scope),
credential_scopes=self.credential_scope,
**kwargs
)
self._credential = credential
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from azure.core.pipeline.policies import AsyncHTTPPolicy

Expand All @@ -18,13 +18,13 @@
class ContainerRegistryChallengePolicy(AsyncHTTPPolicy):
"""Authentication policy for ACR which accepts a challenge"""

def __init__(self, credential: "AsyncTokenCredential", endpoint: str) -> None:
def __init__(self, credential: "AsyncTokenCredential", endpoint: str, **kwargs: Any) -> None:
super().__init__()
self._credential = credential
if self._credential is None:
self._exchange_client = AnonymousACRExchangeClient(endpoint)
else:
self._exchange_client = ACRExchangeClient(endpoint, self._credential)
self._exchange_client = ACRExchangeClient(endpoint, self._credential, **kwargs)

async def on_request(self, request):
# type: (PipelineRequest) -> None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ class ContainerRegistryBaseClient(object):
:type endpoint: str
:param credential: AAD Token for authenticating requests with Azure
:type credential: :class:`~azure.identity.DefaultTokenCredential`
:keyword authentication_scope: URL for credential authentication if different from the default
:paramtype authentication_scope: str
"""

def __init__(self, endpoint: str, credential: Optional["AsyncTokenCredential"] = None, **kwargs) -> None:
auth_policy = ContainerRegistryChallengePolicy(credential, endpoint)
auth_policy = ContainerRegistryChallengePolicy(credential, endpoint, **kwargs)
self._client = ContainerRegistry(
credential=credential,
url=endpoint,
sdk_moniker=USER_AGENT,
authentication_policy=auth_policy,
credential_scopes="https://management.core.windows.net/.default",
credential_scopes=kwargs.get("credential_scopes", "https://management.core.windows.net/.default"),
**kwargs
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def __init__(self, endpoint: str, credential: Optional["AsyncTokenCredential"] =
:type endpoint: str
:param credential: The credential with which to authenticate
:type credential: :class:`~azure.core.credentials_async.AsyncTokenCredential`
:keyword authentication_scope: URL for credential authentication if different from the default
:paramtype authentication_scope: str
:returns: None
:raises: None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ def __init__(self, endpoint: str, credential: "AsyncTokencredential", **kwargs:
if not endpoint.startswith("https://") and not endpoint.startswith("http://"):
endpoint = "https://" + endpoint
self._endpoint = endpoint
self._credential_scope = "https://management.core.windows.net/.default"
self._credential_scope = kwargs.get("authentication_scope", "https://management.core.windows.net/.default")
self._client = ContainerRegistry(
credential=credential,
url=endpoint,
sdk_moniker=USER_AGENT,
authentication_policy=ExchangeClientAuthenticationPolicy(),
credential_scopes=kwargs.pop("credential_scopes", self._credential_scope),
credential_scopes=self._credential_scope,
**kwargs
)
self._credential = credential
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- azsdk-python-azure-containerregistry/1.0.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0)
method: GET
uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world
response:
body:
string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required,
visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type":
"repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}'
headers:
access-control-expose-headers:
- Docker-Content-Digest
- WWW-Authenticate
- Link
- X-Ms-Correlation-Request-Id
connection:
- keep-alive
content-length:
- '222'
content-type:
- application/json; charset=utf-8
date:
- Fri, 04 Jun 2021 14:58:10 GMT
docker-distribution-api-version:
- registry/2.0
server:
- openresty
strict-transport-security:
- max-age=31536000; includeSubDomains
- max-age=31536000; includeSubDomains
www-authenticate:
- Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token"
x-content-type-options:
- nosniff
status:
code: 401
message: Unauthorized
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
User-Agent:
- azsdk-python-azure-containerregistry/1.0.0b3 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0)
method: GET
uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world
response:
body:
string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required,
visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type":
"repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}'
headers:
access-control-expose-headers: X-Ms-Correlation-Request-Id
connection: keep-alive
content-length: '222'
content-type: application/json; charset=utf-8
date: Fri, 04 Jun 2021 14:58:11 GMT
docker-distribution-api-version: registry/2.0
server: openresty
strict-transport-security: max-age=31536000; includeSubDomains
www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token"
x-content-type-options: nosniff
status:
code: 401
message: Unauthorized
url: https://seankane.azurecr.io/acr/v1/library%2Fhello-world
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
ArtifactTagProperties,
TagOrder,
)
from azure.core.exceptions import ResourceNotFoundError
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
from azure.core.paging import ItemPaged

from testcase import ContainerRegistryTestClass
Expand Down Expand Up @@ -544,3 +544,12 @@ def test_delete_manifest_does_not_exist(self, containerregistry_endpoint):
digest = digest[:-10] + u"a" * 10

client.delete_manifest(repo, digest)

# Live only, the fake credential doesn't check auth scope the same way
@pytest.mark.live_test_only
@acr_preparer()
def test_incorrect_authentication_scope(self, containerregistry_endpoint):
client = self.create_registry_client(containerregistry_endpoint, authentication_scope="https://microsoft.com")

with pytest.raises(ClientAuthenticationError):
properties = client.get_repository_properties(HELLO_WORLD)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
ArtifactTagProperties,
TagOrder,
)
from azure.core.exceptions import ResourceNotFoundError
from azure.core.exceptions import ResourceNotFoundError, ClientAuthenticationError
from azure.core.async_paging import AsyncItemPaged

from asynctestcase import AsyncContainerRegistryTestClass
Expand Down Expand Up @@ -137,39 +137,31 @@ async def test_update_repository_properties_kwargs(self, containerregistry_endpo
assert received.can_list == True
assert received.can_read == True
assert received.can_write == True
# assert received.teleport_enabled == True

received = await client.update_repository_properties(repo, can_read=False)
assert received.can_delete == False
assert received.can_list == True
assert received.can_read == False
assert received.can_write == True
# assert received.teleport_enabled == True

received = await client.update_repository_properties(repo, can_write=False)
assert received.can_delete == False
assert received.can_list == True
assert received.can_read == False
assert received.can_write == False
# assert received.teleport_enabled == True

received = await client.update_repository_properties(repo, can_list=False)
assert received.can_delete == False
assert received.can_list == False
assert received.can_read == False
assert received.can_write == False
# assert received.teleport_enabled == True

# received = await client.update_repository_properties(repo, teleport_enabled=True)
# self.assert_all_properties(received, True)

received = await client.update_repository_properties(
repo,
can_delete=True,
can_read=True,
can_write=True,
can_list=True,
# teleport_enabled=True,
)

self.assert_all_properties(received, True)
Expand Down Expand Up @@ -545,3 +537,12 @@ async def test_delete_manifest_does_not_exist(self, containerregistry_endpoint):
digest = digest[:-10] + u"a" * 10

await client.delete_manifest(repo, digest)

# Live only, the fake credential doesn't check auth scope the same way
@pytest.mark.live_test_only
@acr_preparer()
async def test_incorrect_authentication_scope(self, containerregistry_endpoint):
client = self.create_registry_client(containerregistry_endpoint, authentication_scope="https://microsoft.com")

with pytest.raises(ClientAuthenticationError):
properties = await client.get_repository_properties(HELLO_WORLD)

0 comments on commit d040674

Please sign in to comment.