Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tables] Adds support for AzureNamedKeyCredential #18456

Merged
24 commits merged into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5da8d81
working for sync and async, new preparer, changed tests
seankane-msft Apr 30, 2021
041789d
added async preparers
seankane-msft Apr 30, 2021
915fbfe
more test fixes
seankane-msft Apr 30, 2021
62382a3
one file to go
seankane-msft Apr 30, 2021
e2fc8fa
doc hints
seankane-msft Apr 30, 2021
430de47
changelog and samples
seankane-msft Apr 30, 2021
2f3779f
bumping core
seankane-msft Apr 30, 2021
5c71008
making everything work for python 2.7
seankane-msft Apr 30, 2021
72418a3
pr comments
seankane-msft May 3, 2021
5d1470a
Merge branch 'master' into tables-namedcred
seankane-msft May 3, 2021
b7b78fc
Merge branch 'tables-namedcred' of https://github.com/seankane-msft/a…
seankane-msft May 3, 2021
02b8d7d
lint fixes
seankane-msft May 3, 2021
f41e30e
fixing tests and sas generation
seankane-msft May 3, 2021
29839ca
linting fixes
seankane-msft May 3, 2021
06eb4ec
Merge branch 'master' into tables-namedcred
seankane-msft May 4, 2021
0e931b2
fixing up two tests
seankane-msft May 4, 2021
c598300
fixed sample in py27
seankane-msft May 4, 2021
6e94af3
Merge branch 'tables-namedcred' of https://github.com/seankane-msft/a…
seankane-msft May 4, 2021
1405b4d
Merge branch 'master' into tables-namedcred
seankane-msft May 4, 2021
d6d9ecc
one more sample fix
seankane-msft May 4, 2021
fdaf3a9
Merge branch 'tables-namedcred' of https://github.com/seankane-msft/a…
seankane-msft May 4, 2021
7fee46b
Merge branch 'master' into tables-namedcred
seankane-msft May 5, 2021
7b03752
merge conflicts
seankane-msft May 5, 2021
d7a6480
more conflicts
seankane-msft May 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/tables/azure-data-tables/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* `TableClient.send_batch` has been renamed to `TableClient.submit_transaction`.
* Removed `BatchTransactionResult` object in favor of returning an iterable of batched entities with returned metadata.
* `BatchErrorException` has been renamed to `TableTransactionError`.
* The only supported credentials are `AzureNamedKeyCredential`, `AzureSasCredential`, or authentication by connection string

**Fixes**
* Fixed issue with Cosmos merge operations.
Expand Down
24 changes: 12 additions & 12 deletions sdk/tables/azure-data-tables/azure/data/tables/_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# --------------------------------------------------------------------------

import logging
from typing import TYPE_CHECKING

try:
from urllib.parse import urlparse
Expand Down Expand Up @@ -32,6 +33,9 @@
_wrap_exception,
)

if TYPE_CHECKING:
from typing import Union, Awaitable
seankane-msft marked this conversation as resolved.
Show resolved Hide resolved

logger = logging.getLogger(__name__)


Expand All @@ -45,11 +49,8 @@ class AzureSigningError(ClientAuthenticationError):

# pylint: disable=no-self-use
class SharedKeyCredentialPolicy(SansIOHTTPPolicy):
def __init__(
self, account_name, account_key, is_emulated=False
):
self.account_name = account_name
self.account_key = account_key
def __init__(self, credential, is_emulated=False):
self._credential = credential
self.is_emulated = is_emulated

def _get_headers(self, request, headers_to_sign):
Expand Down Expand Up @@ -82,10 +83,10 @@ def _get_canonicalized_resource(self, request):
)
):
uri_path = URL(uri_path)
return "/" + self.account_name + str(uri_path)
return "/" + self._credential.name + str(uri_path)
annatisch marked this conversation as resolved.
Show resolved Hide resolved
except TypeError:
pass
return "/" + self.account_name + uri_path
return "/" + self._credential.named_key.name + uri_path

def _get_canonicalized_headers(self, request):
string_to_sign = ""
Expand All @@ -101,17 +102,16 @@ def _get_canonicalized_headers(self, request):

def _add_authorization_header(self, request, string_to_sign):
try:
signature = _sign_string(self.account_key, string_to_sign)
auth_string = "SharedKey " + self.account_name + ":" + signature
signature = _sign_string(self._credential.named_key.key, string_to_sign)
auth_string = "SharedKey " + self._credential.named_key.name + ":" + signature
request.headers["Authorization"] = auth_string
except Exception as ex:
# Wrap any error that occurred as signing error
# Doing so will clarify/locate the source of problem
raise _wrap_exception(ex, AzureSigningError)

def on_request(
self, request
): # type: (PipelineRequest) -> Union[None, Awaitable[None]]
def on_request(self, request):
# type: (PipelineRequest) -> Union[None, Awaitable[None]]
self.sign_request(request)

def sign_request(self, request):
Expand Down
29 changes: 6 additions & 23 deletions sdk/tables/azure-data-tables/azure/data/tables/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from urllib2 import quote # type: ignore

import six
from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential
from azure.core.utils import parse_connection_string
from azure.core.pipeline.transport import (
HttpTransport,
Expand All @@ -30,7 +30,7 @@
AzureSasCredentialPolicy,
NetworkTraceLoggingPolicy,
CustomHookPolicy,
RequestIdPolicy
RequestIdPolicy,
)

from ._generated import AzureTable
Expand Down Expand Up @@ -111,7 +111,7 @@ def __init__(
self.account_name = account[0] if len(account) > 1 else None

secondary_hostname = None
self.credential = format_shared_key_credential(account, credential)
self.credential = credential
if self.scheme.lower() != "https" and hasattr(self.credential, "get_token"):
raise ValueError("Token credential is only supported with HTTPS.")
if hasattr(self.credential, "account_name"):
Expand Down Expand Up @@ -259,6 +259,8 @@ def _configure_credential(self, credential):
self._credential_policy = credential
elif isinstance(credential, AzureSasCredential):
self._credential_policy = AzureSasCredentialPolicy(credential)
elif isinstance(credential, AzureNamedKeyCredential):
self._credential_policy = SharedKeyCredentialPolicy(credential)
elif credential is not None:
raise TypeError("Unsupported credential: {}".format(credential))

Expand Down Expand Up @@ -344,32 +346,13 @@ def __exit__(self, *args): # pylint: disable=arguments-differ
pass


def format_shared_key_credential(account, credential):
if isinstance(credential, six.string_types):
if len(account) < 2:
raise ValueError(
"Unable to determine account name for shared key credential."
)
credential = {"account_name": account[0], "account_key": credential}
if isinstance(credential, dict):
if "account_name" not in credential:
raise ValueError("Shared key credential missing 'account_name")
if "account_key" not in credential:
raise ValueError("Shared key credential missing 'account_key")
return SharedKeyCredentialPolicy(**credential)
return credential


def parse_connection_str(conn_str, credential, keyword_args):
conn_settings = parse_connection_string(conn_str)
primary = None
secondary = None
if not credential:
try:
credential = {
"account_name": conn_settings["accountname"],
"account_key": conn_settings["accountkey"],
}
credential = AzureNamedKeyCredential(name=conn_settings["accountname"], key=conn_settings["accountkey"])
except KeyError:
credential = conn_settings.get("sharedaccesssignature")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ def __init__(self, account_name, account_key, x_ms_version=DEFAULT_X_MS_VERSION)
"""
:param str account_name:
The storage account name used to generate the shared access signatures.
:param str account_key:
:param AzureNamedKeyCredential account_key:
annatisch marked this conversation as resolved.
Show resolved Hide resolved
The access key to generate the shares access signatures.
:param str x_ms_version:
The service version used to generate the shared access signatures.
"""
self.account_name = account_name
self.account_key = account_key
self.account_key = account_key.named_key.key
self.x_ms_version = x_ms_version

def generate_account(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(
self,
account_url, # type: str
table_name, # type: str
credential=None, # type: str
credential=None, # type: Union[AzureNamedKeyCredential, AzureSasCredential]
**kwargs # type: Any
):
# type: (...) -> None
Expand All @@ -66,8 +66,7 @@ def __init__(
account URL already has a SAS token, or the connection string already has shared
access key values. The value can be a SAS token string or an account shared access
key.
:type credential: str

:type credential: :class:`~azure.core.credentials.AzureNamedKeyCredential` or :class:`~azure.core.credentials.AzureSasCredential`
:returns: None
"""
if not table_name:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class TableServiceClient(TablesBaseClient):
account URL already has a SAS token, or the connection string already has shared
access key values. The value can be a SAS token string or an account shared access
key.
:type credential: str
:type credential: :class:`~azure.core.credentials.AzureNamedKeyCredential` or :class:`~azure.core.credentials.AzureSasCredential`
:returns: None

.. admonition:: Example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
# license information.
# --------------------------------------------------------------------------

from typing import Any, List, Mapping
from typing import Any, List, Mapping, Optional, Union
from uuid import uuid4

from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential
from azure.core.pipeline.policies import (
ContentDecodePolicy,
AsyncBearerTokenCredentialPolicy,
Expand All @@ -19,7 +19,7 @@
AzureSasCredentialPolicy,
RequestIdPolicy,
CustomHookPolicy,
NetworkTraceLoggingPolicy
NetworkTraceLoggingPolicy,
)
from azure.core.pipeline.transport import (
AsyncHttpTransport,
Expand All @@ -40,9 +40,9 @@ class AsyncTablesBaseClient(AccountHostsMixin):

def __init__(
self,
account_url, # type: str
credential=None, # type: str
**kwargs # type: Any
account_url: str,
credential: Optional[Union[AzureSasCredential, AzureNamedKeyCredential]] = None,
**kwargs: Any
):
# type: (...) -> None
super(AsyncTablesBaseClient, self).__init__(account_url, credential=credential, **kwargs)
Expand Down Expand Up @@ -77,6 +77,8 @@ def _configure_credential(self, credential):
self._credential_policy = credential
elif isinstance(credential, AzureSasCredential):
self._credential_policy = AzureSasCredentialPolicy(credential)
elif isinstance(credential, AzureNamedKeyCredential):
self._credential_policy = SharedKeyCredentialPolicy(credential)
elif credential is not None:
raise TypeError("Unsupported credential: {}".format(credential))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from urlparse import urlparse # type: ignore
from urllib2 import unquote # type: ignore

from azure.core.credentials import AzureSasCredential
from azure.core.credentials import AzureNamedKeyCredential, AzureSasCredential
from azure.core.async_paging import AsyncItemPaged
from azure.core.exceptions import ResourceNotFoundError, HttpResponseError
from azure.core.tracing.decorator import distributed_trace
Expand Down Expand Up @@ -43,7 +43,7 @@ def __init__(
self,
account_url: str,
table_name: str,
credential: Optional[Union[AzureSasCredential]] = None,
credential: Optional[Union[AzureSasCredential, AzureNamedKeyCredential]] = None,
**kwargs
) -> None:
"""Create TableClient from a Credential.
Expand All @@ -58,7 +58,7 @@ def __init__(
account URL already has a SAS token, or the connection string already has shared
access key values. The value can be a SAS token string or an account shared access
key.
:type credential: str
:type credential: :class:`~azure.core.credentials.AzureNamedKeyCredential` or :class:`~azure.core.credentials.AzureSasCredential`

:returns: None
"""
Expand Down Expand Up @@ -109,7 +109,7 @@ def from_connection_string(
def from_table_url(
cls,
table_url: str,
credential: Optional[Union[AzureSasCredential]] = None,
credential: Optional[Union[AzureSasCredential, AzureNamedKeyCredential]] = None,
**kwargs
) -> 'TableClient':
"""A client to interact with a specific Table.
Expand All @@ -120,7 +120,7 @@ def from_table_url(
The credentials with which to authenticate. This is optional if the
account URL already has a SAS token. The value can be a SAS token string, an account
shared access key.
:type credential: str
:type credential: :class:`~azure.core.credentials.AzureNamedKeyCredential` or :class:`~azure.core.credentials.AzureSasCredential`
:returns: A table client.
:rtype: :class:`~azure.data.tables.TableClient`
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import asyncio
from dotenv import find_dotenv, load_dotenv

from azure.core.credentials import AzureNamedKeyCredential

class InsertDeleteEntity(object):

def __init__(self):
Expand Down Expand Up @@ -75,7 +77,7 @@ async def delete_entity(self):
from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError
from azure.core import MatchConditions

table_client = TableClient(account_url=self.account_url, credential=self.access_key, table_name=self.table_name)
table_client = TableClient(account_url=self.account_url, credential=AzureNamedKeyCredential(self.account_name, self.access_key), table_name=self.table_name)
annatisch marked this conversation as resolved.
Show resolved Hide resolved

# [START delete_entity]
async with table_client:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from datetime import datetime, timedelta
import os
from azure.core.credentials import AzureNamedKeyCredential
from dotenv import find_dotenv, load_dotenv


Expand Down Expand Up @@ -60,7 +61,7 @@ def authentication_by_shared_key(self):
# Instantiate a TableServiceClient using a shared access key
# [START auth_from_shared_key]
from azure.data.tables import TableServiceClient
with TableServiceClient(account_url=self.account_url, credential=self.access_key) as table_service:
with TableServiceClient(account_url=self.account_url, credential=AzureNamedKeyCredential(self.account_name, self.access_key)) as table_service:
properties = table_service.get_service_properties()
print("Shared Key: {}".format(properties))
# [END auth_from_shared_key]
Expand Down
4 changes: 3 additions & 1 deletion sdk/tables/azure-data-tables/samples/sample_create_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from dotenv import find_dotenv, load_dotenv
import os

from azure.core.credentials import AzureNamedKeyCredential

class CreateClients(object):

def __init__(self):
Expand Down Expand Up @@ -54,7 +56,7 @@ def create_table_service_client(self):
# Instantiate a TableServiceClient using a shared access key
# [START create_table_service_client]
from azure.data.tables import TableServiceClient
with TableServiceClient(account_url=self.account_url, credential=self.access_key) as table_service:
with TableServiceClient(account_url=self.account_url, credential=AzureNamedKeyCredential(self.account_name, self.access_key)) as table_service:
annatisch marked this conversation as resolved.
Show resolved Hide resolved
properties = table_service.get_service_properties()
print("Properties: {}".format(properties))
# [END create_table_service_client]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import os
from dotenv import find_dotenv, load_dotenv

from azure.core.credentials import AzureNamedKeyCredential

class InsertDeleteEntity(object):

def __init__(self):
Expand Down Expand Up @@ -72,7 +74,7 @@ def delete_entity(self):
from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError
from azure.core import MatchConditions

with TableClient(account_url=self.account_url, credential=self.access_key, table_name=self.table_name) as table_client:
with TableClient(account_url=self.account_url, credential=AzureNamedKeyCredential(self.account_name, self.access_key), table_name=self.table_name) as table_client:

# Create entity to delete (to showcase etag)
try:
Expand Down
2 changes: 1 addition & 1 deletion sdk/tables/azure-data-tables/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
'azure.data',
]),
install_requires=[
"azure-core<2.0.0,>=1.13.0",
"azure-core<2.0.0,>=1.14.0",
"msrest>=0.6.19"
],
extras_require={
Expand Down
4 changes: 2 additions & 2 deletions sdk/tables/azure-data-tables/tests/_shared/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pytest

from devtools_testutils import AzureTestCase
from azure.core.credentials import AccessToken
from azure.core.credentials import AccessToken, AzureNamedKeyCredential
from azure.data.tables import generate_account_sas, AccountSasPermissions, ResourceTypes

LOGGING_FORMAT = '%(asctime)s %(name)-20s %(levelname)-5s %(message)s'
Expand Down Expand Up @@ -62,7 +62,7 @@ def generate_sas_token(self):

return '?' + generate_account_sas(
account_name = 'test', # name of the storage account
account_key = fake_key, # key for the storage account
account_key = AzureNamedKeyCredential(name="fakename", key=fake_key), # key for the storage account
resource_types = ResourceTypes(object=True),
permission = AccountSasPermissions(read=True,list=True),
start = datetime.now() - timedelta(hours = 24),
Expand Down
Loading