Skip to content

Commit

Permalink
[Tables] Adds support for AzureNamedKeyCredential (#18456)
Browse files Browse the repository at this point in the history
  • Loading branch information
seankane-msft authored May 5, 2021
1 parent 6bc810e commit 4aaa88a
Show file tree
Hide file tree
Showing 53 changed files with 1,146 additions and 1,009 deletions.
2 changes: 2 additions & 0 deletions sdk/tables/azure-data-tables/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
* Changed optional `value` and `type` arguments of `EntityProperty` to required.
* Renamed `EntityProperty.type` to `EntityProperty.edm_type`.
* `BatchErrorException` has been renamed to `TableTransactionError`.
* The only supported credentials are `AzureNamedKeyCredential`, `AzureSasCredential`, or authentication by connection string
* `EntityProperty` is now a tuple.
* Removed `date` and `api_version` from the `TableItem` class.


**Fixes**
* Fixed issue with Cosmos merge operations.
* Removed legacy Storage policies from pipeline.
Expand Down
25 changes: 13 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,8 @@
# --------------------------------------------------------------------------

import logging
import sys
from typing import Union

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

if sys.version_info > (3, 5):
from typing import Awaitable # pylint: disable=ungrouped-imports

logger = logging.getLogger(__name__)


Expand All @@ -45,11 +50,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 +84,10 @@ def _get_canonicalized_resource(self, request):
)
):
uri_path = URL(uri_path)
return "/" + self.account_name + str(uri_path)
return "/" + self._credential.named_key.name + str(uri_path)
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 +103,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")
# if "sharedaccesssignature" in conn_settings:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ class SharedAccessSignature(object):
generate_*_shared_access_signature method directly.
"""

def __init__(self, account_name, account_key, x_ms_version=DEFAULT_X_MS_VERSION):
def __init__(self, credential, 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:
The access key to generate the shares access signatures.
:param credential: The credential used for authenticating requests
:type credential: :class:`~azure.core.credentials.NamedKeyCredential`
: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_name = credential.named_key.name
self.account_key = credential.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,9 @@ 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,9 @@ 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 @@ -3,7 +3,7 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
from typing import Union
from typing import Union, Any

from ._models import AccountSasPermissions
from ._common_conversion import _sign_string
Expand All @@ -17,8 +17,7 @@


def generate_account_sas(
account_name, # type:str
account_key, # type:str
credential, # type: AzureNamedKeyCredential
resource_types, # type:ResourceTypes
permission, # type:Union[str,AccountSasPermissions]
expiry, # type:Union[datetime,str]
Expand All @@ -29,10 +28,8 @@ def generate_account_sas(
Generates a shared access signature for the table service.
Use the returned signature with the sas_token parameter of TableService.
:param account_name: Account name
:type account_name: str
:param account_key: Account key
:type account_key: str
:param credential: Credential for the Azure account
:type credential: :class:`~azure.core.credentials.AzureNamedKeyCredential`
:param resource_types:
Specifies the resource types that are accessible with the account SAS.
:type resource_types: ResourceTypes
Expand Down Expand Up @@ -70,11 +67,11 @@ def generate_account_sas(
:return: A Shared Access Signature (sas) token.
:rtype: str
"""
_validate_not_none("account_name", account_name)
_validate_not_none("account_key", account_key)
_validate_not_none("account_name", credential.named_key.name)
_validate_not_none("account_key", credential.named_key.key)
if permission is str:
permission = AccountSasPermissions.from_string(permission=permission)
sas = TableSharedAccessSignature(account_name, account_key)
sas = TableSharedAccessSignature(credential)
return sas.generate_account(
"t",
resource_types,
Expand All @@ -87,8 +84,7 @@ def generate_account_sas(


def generate_table_sas(
account_name, # type: str
account_key, # type: str
credential, # type: AzureNamedKeyCredential
table_name, # type: str
**kwargs # type: Any
): # type: (...) -> str
Expand Down Expand Up @@ -143,7 +139,7 @@ def generate_table_sas(
:rtype: str
"""

sas = TableSharedAccessSignature(account_name, account_key)
sas = TableSharedAccessSignature(credential)
return sas.generate_table(
table_name=table_name,
permission=kwargs.pop("permission", None),
Expand All @@ -168,17 +164,13 @@ class TableSharedAccessSignature(SharedAccessSignature):
generate_*_shared_access_signature method directly.
"""

def __init__(self, account_name, account_key):
def __init__(self, credential):
"""
:param account_name:
The storage account name used to generate the shared access signatures.
:type account_name: str
:param account_key:
The access key to generate the shares access signatures.
:type account_key: str
:param credential: The credential used for authenticating requests
:type credential: :class:`~azure.core.credentials.NamedKeyCredential`
"""
super(TableSharedAccessSignature, self).__init__(
account_name, account_key, x_ms_version=X_MS_VERSION
credential, x_ms_version=X_MS_VERSION
)

def generate_table(
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,9 @@ 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 +111,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 +122,9 @@ 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
Loading

0 comments on commit 4aaa88a

Please sign in to comment.