Hello community,
here is the log from the commit of package python-msrestazure for
openSUSE:Factory checked in at 2018-05-13 16:03:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msrestazure (Old)
and /work/SRC/openSUSE:Factory/.python-msrestazure.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-msrestazure"
Sun May 13 16:03:40 2018 rev:5 rq:601546 version:0.4.28
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msrestazure/python-msrestazure.changes
2018-02-14 09:27:33.936299577 +0100
+++
/work/SRC/openSUSE:Factory/.python-msrestazure.new/python-msrestazure.changes
2018-05-13 16:03:42.677295231 +0200
@@ -1,0 +2,12 @@
+Tue Apr 24 11:06:26 UTC 2018 - [email protected]
+
+- New upstream release
+ + Version 0.4.28
+ + For detailed information about changes see the
+ README.rst file provided with this package
+- Move LICENSE.txt from %doc to %license section
+- Refresh patches for new version
+ + m_drop-compatible-releases-operator.patch
+- Update Requires from setup.py
+
+-------------------------------------------------------------------
Old:
----
msrestazure-0.4.20.tar.gz
New:
----
msrestazure-0.4.28.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-msrestazure.spec ++++++
--- /var/tmp/diff_new_pack.WMNJqk/_old 2018-05-13 16:03:43.441267359 +0200
+++ /var/tmp/diff_new_pack.WMNJqk/_new 2018-05-13 16:03:43.445267213 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-msrestazure
-Version: 0.4.20
+Version: 0.4.28
Release: 0
Summary: AutoRest swagger generator
License: MIT
@@ -32,10 +32,9 @@
BuildRequires: fdupes
BuildRequires: python-rpm-macros
Requires: python-adal < 1.0.0
-Requires: python-adal >= 0.4.7
-Requires: python-keyring >= 5.6
+Requires: python-adal >= 0.5.0
Requires: python-msrest < 2.0.0
-Requires: python-msrest >= 0.4.25
+Requires: python-msrest >= 0.4.28
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildArch: noarch
@@ -59,7 +58,8 @@
%files %{python_files}
%defattr(-,root,root,-)
-%doc LICENSE.md README.rst
+%doc README.rst
+%license LICENSE.md
%{python_sitelib}/*
%changelog
++++++ m_drop-compatible-releases-operator.patch ++++++
--- /var/tmp/diff_new_pack.WMNJqk/_old 2018-05-13 16:03:43.509264878 +0200
+++ /var/tmp/diff_new_pack.WMNJqk/_new 2018-05-13 16:03:43.513264732 +0200
@@ -1,11 +1,11 @@
-diff -Nru msrestazure-0.4.20.orig/setup.py msrestazure-0.4.20/setup.py
---- msrestazure-0.4.20.orig/setup.py 2018-01-08 23:26:51.000000000 +0100
-+++ msrestazure-0.4.20/setup.py 2018-01-26 13:33:02.600469484 +0100
-@@ -51,6 +51,6 @@
+diff -Nru msrestazure-0.4.28.orig/setup.py msrestazure-0.4.28/setup.py
+--- msrestazure-0.4.28.orig/setup.py 2018-04-24 01:45:02.000000000 +0200
++++ msrestazure-0.4.28/setup.py 2018-04-24 13:00:24.806436987 +0200
+@@ -50,6 +50,6 @@
+ 'Topic :: Software Development'],
install_requires=[
- "msrest>=0.4.25,<2.0.0",
- "keyring>=5.6",
-- "adal~=0.4.7"
-+ "adal>=0.4.7"
+ "msrest>=0.4.28,<2.0.0",
+- "adal~=0.5.0"
++ "adal>=0.5.0"
],
)
++++++ msrestazure-0.4.20.tar.gz -> msrestazure-0.4.28.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/PKG-INFO
new/msrestazure-0.4.28/PKG-INFO
--- old/msrestazure-0.4.20/PKG-INFO 2018-01-08 23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/PKG-INFO 2018-04-24 01:45:43.000000000 +0200
@@ -1,12 +1,11 @@
Metadata-Version: 1.1
Name: msrestazure
-Version: 0.4.20
+Version: 0.4.28
Summary: AutoRest swagger generator Python client runtime. Azure-specific
module.
Home-page: https://github.com/Azure/msrestazure-for-python
Author: Microsoft Corporation
Author-email: [email protected]
License: MIT License
-Description-Content-Type: UNKNOWN
Description: AutoRest: Python Client Runtime - Azure Module
===============================================
@@ -29,6 +28,76 @@
Release History
---------------
+ 2018-04-23 Version 0.4.28
+ +++++++++++++++++++++++++
+
+ **Disclaimer**
+
+ Do to some stability issues with "keyring" dependency that highly
change from one system to another,
+ this package is no longer a dependency of "msrestazure".
+ If you were using the secured token cache of
`ServicePrincipalCredentials` and `UserPassCredentials`,
+ the feature is still available, but you need to install manually
"keyring". The functionnality will activate automatically.
+
+ 2018-04-18 Version 0.4.27
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Implements new features of msrest 0.4.28 on session improvement. See
msrest ChangeLog for details.
+
+ Update msrest dependency to 0.4.28
+
+ 2018-04-17 Version 0.4.26
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - IMDS/MSI: Retry on more error codes (#87)
+ - IMDS/MSI: fix a boundary case on timeout (#86)
+
+ 2018-03-29 Version 0.4.25
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - MSIAuthentication now uses IMDS endpoint if available
+ - MSIAuthentication can be used in any environment that defines
MSI_ENDPOINT env variable
+
+ 2018-03-26 Version 0.4.24
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Fix parse_resource_id() tool to be case-insensitive to keywords when
matching #81
+ - Add missing baseclass init call for AdalAuthentication #82
+
+ 2018-03-19 Version 0.4.23
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Fix LRO result if POST uses AsyncOperation header (Autorest.Python
3.0 only) #79
+
+ 2018-02-27 Version 0.4.22
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Remove a possible infinite loop with MSIAuthentication #77
+
+ **Disclaimer**
+
+ From this version, MSIAuthentication will fail instantly if you try to
get MSI token
+ from a VM where the extension is not installed, or not yet ready.
+ You need to do your own retry mechanism if you think the extension is
provisioning and
+ the call might succeed later.
+ This behavior is consistent with other Azure SDK implementation of MSI
scenarios.
+
+ 2018-01-26 Version 0.4.21
+ +++++++++++++++++++++++++
+
+ - Update allowed ADAL dependency to 0.5.x
+
2018-01-08 Version 0.4.20
+++++++++++++++++++++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/README.rst
new/msrestazure-0.4.28/README.rst
--- old/msrestazure-0.4.20/README.rst 2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/README.rst 2018-04-24 01:45:02.000000000 +0200
@@ -20,6 +20,76 @@
Release History
---------------
+2018-04-23 Version 0.4.28
++++++++++++++++++++++++++
+
+**Disclaimer**
+
+Do to some stability issues with "keyring" dependency that highly change from
one system to another,
+this package is no longer a dependency of "msrestazure".
+If you were using the secured token cache of `ServicePrincipalCredentials` and
`UserPassCredentials`,
+the feature is still available, but you need to install manually "keyring".
The functionnality will activate automatically.
+
+2018-04-18 Version 0.4.27
++++++++++++++++++++++++++
+
+**Features**
+
+- Implements new features of msrest 0.4.28 on session improvement. See msrest
ChangeLog for details.
+
+Update msrest dependency to 0.4.28
+
+2018-04-17 Version 0.4.26
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- IMDS/MSI: Retry on more error codes (#87)
+- IMDS/MSI: fix a boundary case on timeout (#86)
+
+2018-03-29 Version 0.4.25
++++++++++++++++++++++++++
+
+**Features**
+
+- MSIAuthentication now uses IMDS endpoint if available
+- MSIAuthentication can be used in any environment that defines MSI_ENDPOINT
env variable
+
+2018-03-26 Version 0.4.24
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Fix parse_resource_id() tool to be case-insensitive to keywords when
matching #81
+- Add missing baseclass init call for AdalAuthentication #82
+
+2018-03-19 Version 0.4.23
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only)
#79
+
+2018-02-27 Version 0.4.22
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Remove a possible infinite loop with MSIAuthentication #77
+
+**Disclaimer**
+
+From this version, MSIAuthentication will fail instantly if you try to get MSI
token
+from a VM where the extension is not installed, or not yet ready.
+You need to do your own retry mechanism if you think the extension is
provisioning and
+the call might succeed later.
+This behavior is consistent with other Azure SDK implementation of MSI
scenarios.
+
+2018-01-26 Version 0.4.21
++++++++++++++++++++++++++
+
+- Update allowed ADAL dependency to 0.5.x
+
2018-01-08 Version 0.4.20
+++++++++++++++++++++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/msrestazure-0.4.20/msrestazure/azure_active_directory.py
new/msrestazure-0.4.28/msrestazure/azure_active_directory.py
--- old/msrestazure-0.4.20/msrestazure/azure_active_directory.py
2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_active_directory.py
2018-04-24 01:45:02.000000000 +0200
@@ -42,7 +42,7 @@
MismatchingStateError,
OAuth2Error,
TokenExpiredError)
-from requests import RequestException, ConnectionError
+from requests import RequestException, ConnectionError, HTTPError
import requests
import requests_oauthlib as oauth
@@ -57,11 +57,12 @@
from msrest.exceptions import AuthenticationError, raise_with_traceback
from msrestazure.azure_cloud import AZURE_CHINA_CLOUD, AZURE_PUBLIC_CLOUD
+from msrestazure.azure_configuration import AzureConfiguration
_LOGGER = logging.getLogger(__name__)
if not keyring:
- _LOGGER.warning("Cannot load keyring on your system: %s",
KEYRING_EXCEPTION)
+ _LOGGER.warning("Cannot load 'keyring' on your system (either not
installed, or not configured correctly): %s", KEYRING_EXCEPTION)
def _build_url(uri, paths, scheme):
"""Combine URL parts.
@@ -110,6 +111,7 @@
class AADMixin(OAuthTokenAuthentication):
"""Mixin for Authentication object.
Provides some AAD functionality:
+
- State validation
- Token caching and retrieval
- Default AAD configuration
@@ -124,6 +126,7 @@
"""Configure authentication endpoint.
Optional kwargs may include:
+
- cloud_environment (msrestazure.azure_cloud.Cloud): A targeted
cloud environment
- china (bool): Configure auth for China-based service,
default is 'False'.
@@ -191,13 +194,6 @@
if self.token.get('expires_at'):
countdown = float(self.token['expires_at']) - time.time()
self.token['expires_in'] = countdown
- kwargs = {}
- if self.token.get('refresh_token'):
- kwargs['auto_refresh_url'] = self.token_uri
- kwargs['auto_refresh_kwargs'] = {'client_id': self.id,
- 'resource': self.resource}
- kwargs['token_updater'] = self._default_token_cache
- return kwargs
def _default_token_cache(self, token):
"""Store token for future sessions.
@@ -225,28 +221,23 @@
self.token = ast.literal_eval(str(token))
self.signed_session()
- def signed_session(self):
+ def signed_session(self, session=None):
"""Create token-friendly Requests session, using auto-refresh.
Used internally when a request is made.
- :rtype: requests_oauthlib.OAuth2Session
- :raises: TokenExpiredError if token can no longer be refreshed.
- """
- kwargs = self._parse_token()
- try:
- new_session = oauth.OAuth2Session(
- self.id,
- token=self.token,
- **kwargs)
- return new_session
- except TokenExpiredError as err:
- raise_with_traceback(Expired, "", err)
+ If a session object is provided, configure it directly. Otherwise,
+ create a new session and return it.
+ :param session: The session to configure for authentication
+ :type session: requests.Session
+ """
+ self._parse_token()
+ return super(AADMixin, self).signed_session(session)
+
def clear_cached_token(self):
"""Clear any stored tokens.
:raises: KeyError if failed to clear token.
- :rtype: None
"""
try:
keyring.delete_password(self.cred_store, self.store_key)
@@ -254,32 +245,13 @@
raise_with_traceback(KeyError, "Unable to clear token.")
-class AADRefreshMixin(object):
- """
- Additional token refresh logic
- """
-
- def refresh_session(self):
- """Return updated session if token has expired, attempts to
- refresh using newly acquired token.
-
- :rtype: requests.Session.
- """
- if self.token.get('refresh_token'):
- try:
- return self.signed_session()
- except Expired:
- pass
- self.set_token()
- return self.signed_session()
-
-
class AADTokenCredentials(AADMixin):
"""
Credentials objects for AAD token retrieved through external process
e.g. Python ADAL lib.
Optional kwargs may include:
+
- cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud
environment
- china (bool): Configure auth for China-based service,
default is 'False'.
@@ -313,12 +285,12 @@
"""Create AADTokenCredentials from a cached token if it has not
yet expired.
"""
- session = cls(None, None, client_id=client_id, cached=True)
+ session = cls(None, client_id=client_id, cached=True)
session._retrieve_stored_token()
return session
-class UserPassCredentials(AADRefreshMixin, AADMixin):
+class UserPassCredentials(AADMixin):
"""Credentials object for Headless Authentication,
i.e. AAD authentication via username and password.
@@ -327,6 +299,7 @@
that 2-factor auth be disabled.
Optional kwargs may include:
+
- cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud
environment
- china (bool): Configure auth for China-based service,
default is 'False'.
@@ -392,7 +365,8 @@
if self.secret:
optional['client_secret'] = self.secret
try:
- token = session.fetch_token(self.token_uri, client_id=self.id,
+ token = session.fetch_token(self.token_uri,
+ client_id=self.id,
username=self.username,
password=self.password,
resource=self.resource,
@@ -404,13 +378,47 @@
raise_with_traceback(AuthenticationError, "", err)
self.token = token
+ self._default_token_cache(self.token)
+
+ def refresh_session(self, session=None):
+ """Return updated session if token has expired, attempts to
+ refresh using newly acquired token.
+
+ If a session object is provided, configure it directly. Otherwise,
+ create a new session and return it.
+
+ :param session: The session to configure for authentication
+ :type session: requests.Session
+ :rtype: requests.Session.
+ """
+ with self._setup_session() as session:
+ optional = {}
+ if self.secret:
+ optional['client_secret'] = self.secret
+ try:
+ token = session.refresh_token(self.token_uri,
+ client_id=self.id,
+ username=self.username,
+ password=self.password,
+ resource=self.resource,
+ verify=self.verify,
+ proxies=self.proxies,
+ timeout=self.timeout,
+ **optional)
+ except (RequestException, OAuth2Error, InvalidGrantError) as err:
+ raise_with_traceback(AuthenticationError, "", err)
+
+ self.token = token
+ self._default_token_cache(self.token)
+ return self.signed_session(session)
-class ServicePrincipalCredentials(AADRefreshMixin, AADMixin):
+class ServicePrincipalCredentials(AADMixin):
"""Credentials object for Service Principle Authentication.
Authenticates via a Client ID and Secret.
Optional kwargs may include:
+
- cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud
environment
- china (bool): Configure auth for China-based service,
default is 'False'.
@@ -462,7 +470,8 @@
"""
with self._setup_session() as session:
try:
- token = session.fetch_token(self.token_uri, client_id=self.id,
+ token = session.fetch_token(self.token_uri,
+ client_id=self.id,
resource=self.resource,
client_secret=self.secret,
response_type="client_credentials",
@@ -473,9 +482,29 @@
raise_with_traceback(AuthenticationError, "", err)
else:
self.token = token
+ self._default_token_cache(self.token)
+
+ def refresh_session(self, session=None):
+ """Alias to signed_session().
+
+ SP flow does not contain refresh_token, so this method is just asking
a new
+ token to AD.
+
+ If a session object is provided, configure it directly. Otherwise,
+ create a new session and return it.
+
+ :param session: The session to configure for authentication
+ :type session: requests.Session
+ :rtype: requests.Session.
+ """
+ self.set_token()
+ return self.signed_session(session)
+
# For backward compatibility of import, but I doubt someone uses that...
class InteractiveCredentials(object):
+ """This class has been removed and using it will raise a
NotImplementedError error.
+ """
def __init__(self, *args, **kwargs):
raise NotImplementedError("InteractiveCredentials was not functionning
and was removed. Please use ADAL and device code instead.")
@@ -483,64 +512,68 @@
"""A wrapper to use ADAL for Python easily to authenticate on Azure.
.. versionadded:: 0.4.5
- """
- def __init__(self, adal_method, *args, **kwargs):
- """Take an ADAL `acquire_token` method and its parameters.
+ Take an ADAL `acquire_token` method and its parameters.
- :Example:
+ :Example:
- .. code:: python
+ .. code:: python
- context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
- RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph
resource
- token = context.acquire_token_with_client_credentials(
- RESOURCE,
- "http://PythonSDK",
- "Key-Configured-In-Portal")
+ context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+ RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+ token = context.acquire_token_with_client_credentials(
+ RESOURCE,
+ "http://PythonSDK",
+ "Key-Configured-In-Portal")
- can be written here:
+ can be written here:
- .. code:: python
+ .. code:: python
- context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
- RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph
resource
- credentials = AdalAuthentication(
- context.acquire_token_with_client_credentials,
- RESOURCE,
- "http://PythonSDK",
- "Key-Configured-In-Portal")
+ context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+ RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+ credentials = AdalAuthentication(
+ context.acquire_token_with_client_credentials,
+ RESOURCE,
+ "http://PythonSDK",
+ "Key-Configured-In-Portal")
- or using a lambda if you prefer:
+ or using a lambda if you prefer:
- .. code:: python
+ .. code:: python
- context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
- RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph
resource
- credentials = AdalAuthentication(
- lambda: context.acquire_token_with_client_credentials(
- RESOURCE,
- "http://PythonSDK",
- "Key-Configured-In-Portal"
- )
+ context =
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+ RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+ credentials = AdalAuthentication(
+ lambda: context.acquire_token_with_client_credentials(
+ RESOURCE,
+ "http://PythonSDK",
+ "Key-Configured-In-Portal"
)
+ )
- :param adal_method: A lambda with no args, or `acquire_token` method
with args using args/kwargs
- :param args: Optional args for the method
- :param kwargs: Optional kwargs for the method
- """
+ :param callable adal_method: A lambda with no args, or `acquire_token`
method with args using args/kwargs
+ :param args: Optional positional args for the method
+ :param kwargs: Optional kwargs for the method
+ """
+
+ def __init__(self, adal_method, *args, **kwargs):
+ super(AdalAuthentication, self).__init__()
self._adal_method = adal_method
self._args = args
self._kwargs = kwargs
- def signed_session(self):
- """Get a signed session for requests.
+ def signed_session(self, session=None):
+ """Create requests session with any required auth headers applied.
- Usually called by the Azure SDKs for you to authenticate queries.
+ If a session object is provided, configure it directly. Otherwise,
+ create a new session and return it.
+ :param session: The session to configure for authentication
+ :type session: requests.Session
:rtype: requests.Session
"""
- session = super(AdalAuthentication, self).signed_session()
+ session = super(AdalAuthentication, self).signed_session(session)
try:
raw_token = self._adal_method(*self._args, **self._kwargs)
@@ -558,17 +591,18 @@
session.headers['Authorization'] = header
return session
-
def get_msi_token(resource, port=50342, msi_conf=None):
- """Get MSI token from inside a VM/VMSS.
+ """Get MSI token if MSI_ENDPOINT is set.
+
+ IF MSI_ENDPOINT is not set, will try legacy access through
'http://localhost:{}/oauth2/token'.format(port).
If msi_conf is used, must be a dict of one key in ["client_id",
"object_id", "msi_res_id"]
:param str resource: The resource where the token would be use.
- :param int port: The port is not the default 50342 is used.
- :param dict[str, str] msi_conf: msi_conf if to request a token through a
User Assigned Identity (if not specified, assume System Assigned)
+ :param int port: The port if not the default 50342 is used. Ignored if
MSI_ENDPOINT is set.
+ :param dict[str,str] msi_conf: msi_conf if to request a token through a
User Assigned Identity (if not specified, assume System Assigned)
"""
- request_uri = 'http://localhost:{}/oauth2/token'.format(port)
+ request_uri = os.environ.get("MSI_ENDPOINT",
'http://localhost:{}/oauth2/token'.format(port))
payload = {
'resource': resource
}
@@ -577,28 +611,15 @@
raise ValueError("{} are mutually
exclusive".format(list(msi_conf.keys())))
payload.update(msi_conf)
- # retry as the token endpoint might not be available yet, one example is
you use CLI in a
- # custom script extension of VMSS, which might get provisioned before the
MSI extensioon
- while True:
- err = None
- try:
- result = requests.post(request_uri, data=payload,
headers={'Metadata': 'true'})
- _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s",
request_uri, payload)
- if result.status_code != 200:
- err = result.text
- except Exception as ex: # pylint: disable=broad-except
- err = str(ex)
-
- if err:
- # we might need some error code checking to avoid silly waiting.
The bottom line is users can
- # always press ctrl+c to stop it
- _LOGGER.warning("MSI: Failed to retrieve a token from '%s' with an
error of '%s'. This could be caused "
- "by the MSI extension not yet fullly provisioned.
Will retry in 60 seconds...",
- request_uri, err)
- time.sleep(60)
- else:
- _LOGGER.debug('MSI: token retrieved')
- break
+ try:
+ result = requests.post(request_uri, data=payload, headers={'Metadata':
'true'})
+ _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s",
request_uri, payload)
+ result.raise_for_status()
+ except Exception as ex: # pylint: disable=broad-except
+ _LOGGER.warning("MSI: Failed to retrieve a token from '%s' with an
error of '%s'. This could be caused "
+ "by the MSI extension not yet fullly provisioned.",
+ request_uri, ex)
+ raise
token_entry = result.json()
return token_entry['token_type'], token_entry['access_token'], token_entry
@@ -606,8 +627,9 @@
"""Get a MSI token from inside a webapp or functions.
Env variable will look like:
- MSI_ENDPOINT = http://127.0.0.1:41741/MSI/token/
- MSI_SECRET = 69418689F1E342DD946CB82994CDA3CB
+
+ - MSI_ENDPOINT = http://127.0.0.1:41741/MSI/token/
+ - MSI_SECRET = 69418689F1E342DD946CB82994CDA3CB
"""
try:
msi_endpoint = os.environ['MSI_ENDPOINT']
@@ -653,6 +675,7 @@
"""Credentials object for MSI authentication,.
Optional kwargs may include:
+
- client_id: Identifies, by Azure AD client id, a specific explicit
identity to use when authenticating to Azure AD. Mutually exclusive with
object_id and msi_res_id.
- object_id: Identifies, by Azure AD object id, a specific explicit
identity to use when authenticating to Azure AD. Mutually exclusive with
client_id and msi_res_id.
- msi_res_id: Identifies, by ARM resource id, a specific explicit identity
to use when authenticating to Azure AD. Mutually exclusive with client_id and
object_id.
@@ -660,27 +683,118 @@
- resource (str): Alternative authentication resource, default
is 'https://management.core.windows.net/'.
- :param int port: MSI local port if VM/VMSS context (ignored otherwise)
+ .. versionadded:: 0.4.14
"""
def __init__(self, port=50342, **kwargs):
super(MSIAuthentication, self).__init__(None)
+ if port != 50342:
+ warnings.warn("The 'port' argument is no longer used, and will be
removed in a future release", DeprecationWarning)
self.port = port
+
self.msi_conf = {k:v for k,v in kwargs.items() if k in ["client_id",
"object_id", "msi_res_id"]}
self.cloud_environment = kwargs.get('cloud_environment',
AZURE_PUBLIC_CLOUD)
self.resource = kwargs.get('resource',
self.cloud_environment.endpoints.active_directory_resource_id)
- def set_token(self):
if _is_app_service():
if self.msi_conf:
raise AuthenticationError("User Assigned Entity is not
available on WebApp yet.")
+ elif "MSI_ENDPOINT" not in os.environ:
+ # Use IMDS if no MSI_ENDPOINT
+ self._vm_msi = _ImdsTokenProvider(self.resource, self.msi_conf)
+
+ def set_token(self):
+ if _is_app_service():
self.scheme, _, self.token = get_msi_token_webapp(self.resource)
- else:
+ elif "MSI_ENDPOINT" in os.environ:
self.scheme, _, self.token = get_msi_token(self.resource,
self.port, self.msi_conf)
+ else:
+ token_entry = self._vm_msi.get_token()
+ self.scheme, self.token = token_entry['token_type'], token_entry
+
+ def signed_session(self, session=None):
+ """Create requests session with any required auth headers applied.
+
+ If a session object is provided, configure it directly. Otherwise,
+ create a new session and return it.
- def signed_session(self):
+ :param session: The session to configure for authentication
+ :type session: requests.Session
+ :rtype: requests.Session
+ """
# Token cache is handled by the VM extension, call each time to avoid
expiration
self.set_token()
- return super(MSIAuthentication, self).signed_session()
+ return super(MSIAuthentication, self).signed_session(session)
+
+
+class _ImdsTokenProvider(object):
+ """A help class handling token acquisitions through Azure IMDS plugin.
+ """
+
+ def __init__(self, resource, msi_conf=None):
+ self._user_agent = AzureConfiguration(None).user_agent
+ self.identity_type, self.identity_id = None, None
+ if msi_conf:
+ if len(msi_conf.keys()) > 1:
+ raise ValueError('"client_id", "object_id", "msi_res_id" are
mutually exclusive')
+ elif len(msi_conf.keys()) == 1:
+ self.identity_type, self.identity_id =
next(iter(msi_conf.items()))
+ # default to system assigned identity on an empty configuration object
+
+ self.cache = {}
+ self.resource = resource
+
+ def get_token(self):
+ import datetime
+ # let us hit the cache first
+ token_entry = self.cache.get(self.resource, None)
+ if token_entry:
+ expires_on = int(token_entry['expires_on'])
+ expires_on_datetime = datetime.datetime.fromtimestamp(expires_on)
+ expiration_margin = 5 # in minutes
+ if datetime.datetime.now() +
datetime.timedelta(minutes=expiration_margin) <= expires_on_datetime:
+ _LOGGER.info("MSI: token is found in cache.")
+ return token_entry
+ _LOGGER.info("MSI: cache is found but expired within %s minutes,
so getting a new one.", expiration_margin)
+ self.cache.pop(self.resource)
+
+ token_entry = self._retrieve_token_from_imds_with_retry()
+ self.cache[self.resource] = token_entry
+ return token_entry
+
+ def _retrieve_token_from_imds_with_retry(self):
+ import random
+ import json
+ # 169.254.169.254 is a well known ip address hosting the web service
that provides the Azure IMDS metadata
+ request_uri = 'http://169.254.169.254/metadata/identity/oauth2/token'
+ payload = {
+ 'resource': self.resource,
+ 'api-version': '2018-02-01'
+ }
+ if self.identity_id:
+ payload[self.identity_type] = self.identity_id
+
+ retry, max_retry = 1, 20
+ # simplified version of
https://en.wikipedia.org/wiki/Exponential_backoff
+ slots = [100 * ((2 << x) - 1) / 1000 for x in range(max_retry)]
+ while retry <= max_retry:
+ result = requests.get(request_uri, params=payload,
headers={'Metadata': 'true', 'User-Agent':self._user_agent})
+ _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s",
request_uri, payload)
+ if result.status_code in [404, 429] or (499 < result.status_code <
600):
+ wait = random.choice(slots[:retry])
+ _LOGGER.warning("MSI: Wait: %ss and retry: %s", wait, retry)
+ time.sleep(wait)
+ retry += 1
+ elif result.status_code != 200:
+ raise HTTPError(request=result.request, response=result.raw)
+ else:
+ break
+
+ if retry > max_retry:
+ raise TimeoutError('MSI: Failed to acquire tokens after {}
times'.format(max_retry))
+
+ _LOGGER.debug('MSI: Token retrieved')
+ token_entry = json.loads(result.content.decode())
+ return token_entry
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/msrestazure-0.4.20/msrestazure/azure_configuration.py
new/msrestazure-0.4.28/msrestazure/azure_configuration.py
--- old/msrestazure-0.4.20/msrestazure/azure_configuration.py 2018-01-08
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_configuration.py 2018-04-24
01:45:02.000000000 +0200
@@ -72,7 +72,6 @@
:param str filepath: Path to save file to.
:raises: ValueError if supplied filepath cannot be written to.
- :rtype: None
"""
self._config.add_section("Azure")
self._config.set("Azure",
@@ -85,7 +84,6 @@
:param str filepath: Path to existing config file.
:raises: ValueError if supplied config file is invalid.
- :rtype: None
"""
try:
self._config.read(filepath)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/azure_operation.py
new/msrestazure-0.4.28/msrestazure/azure_operation.py
--- old/msrestazure-0.4.20/msrestazure/azure_operation.py 2018-01-08
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_operation.py 2018-04-24
01:45:02.000000000 +0200
@@ -309,6 +309,13 @@
if not self.status:
raise BadResponse("No status found in body")
+ # Status can contains information, see ARM spec:
+ #
https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/Addendum.md#operation-resource-format
+ # "properties": {
+ # /\* The resource provider can choose the values here, but it should
only be
+ # returned on a successful operation (status being "Succeeded"). \*/
+ #},
+ # So try to parse it
try:
self.resource = self.get_outputs(response)
except Exception:
@@ -328,6 +335,9 @@
"""Initiates long running operation and polls status in separate
thread.
+ This class is used in old SDK and has been replaced. See "polling"
+ submodule now.
+
:param callable send_cmd: The API request to initiate the operation.
:param callable update_cmd: The API reuqest to check the status of
the operation.
@@ -492,7 +502,7 @@
:param int timeout: Perion of time to wait for the long running
operation to complete.
- :raises CloudError: Server problem with the query.
+ :raises ~msrestazure.azure_exceptions.CloudError: Server problem with
the query.
"""
if self._thread is None:
return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/msrestazure-0.4.20/msrestazure/polling/arm_polling.py
new/msrestazure-0.4.28/msrestazure/polling/arm_polling.py
--- old/msrestazure-0.4.20/msrestazure/polling/arm_polling.py 2018-01-08
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/polling/arm_polling.py 2018-04-24
01:45:02.000000000 +0200
@@ -269,6 +269,18 @@
if not self.status:
raise BadResponse("No status found in body")
+ # Status can contains information, see ARM spec:
+ #
https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/Addendum.md#operation-resource-format
+ # "properties": {
+ # /\* The resource provider can choose the values here, but it should
only be
+ # returned on a successful operation (status being "Succeeded"). \*/
+ #},
+ # So try to parse it
+ try:
+ self.resource = self._deserialize(response)
+ except Exception:
+ self.resource = None
+
def set_async_url_if_present(self, response):
async_url = get_header_url(response, 'azure-asyncoperation')
if async_url:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/tools.py
new/msrestazure-0.4.28/msrestazure/tools.py
--- old/msrestazure-0.4.20/msrestazure/tools.py 2018-01-08 23:26:51.000000000
+0100
+++ new/msrestazure-0.4.28/msrestazure/tools.py 2018-04-24 01:45:02.000000000
+0200
@@ -32,15 +32,18 @@
_LOGGER = logging.getLogger(__name__)
_ARMID_RE = re.compile(
-
'/subscriptions/(?P<subscription>[^/]*)(/resource[gG]roups/(?P<resource_group>[^/]*))?'
+
'(?i)/subscriptions/(?P<subscription>[^/]*)(/resourceGroups/(?P<resource_group>[^/]*))?'
'/providers/(?P<namespace>[^/]*)/(?P<type>[^/]*)/(?P<name>[^/]*)(?P<children>.*)')
-_CHILDREN_RE = re.compile('(/providers/(?P<child_namespace>[^/]*))?/'
+_CHILDREN_RE = re.compile('(?i)(/providers/(?P<child_namespace>[^/]*))?/'
'(?P<child_type>[^/]*)/(?P<child_name>[^/]*)')
def register_rp_hook(r, *args, **kwargs):
"""This is a requests hook to register RP automatically.
+ You should not use this command manually, this is added automatically
+ by the SDK.
+
See requests documentation for details of the signature of this function.
http://docs.python-requests.org/en/master/user/advanced/#event-hooks
"""
@@ -115,6 +118,7 @@
- child_namespace_{level}: Namespace for the child resoure of that
level
- child_type_{level}: Type of the child resource of that level
- child_name_{level}: Name of the child resource of that level
+ - last_child_num: Level of the last child
- resource_parent: Computed parent in the following pattern:
providers/{namespace}\
/{parent}/{type}/{name}
- resource_namespace: Same as namespace. Note that this may be
different than the \
@@ -122,7 +126,7 @@
- resource_type: Type of the target resource (not the parent)
- resource_name: Name of the target resource (not the parent)
- :rtype: dict
+ :rtype: dict[str,str]
"""
if not rid:
return {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/version.py
new/msrestazure-0.4.28/msrestazure/version.py
--- old/msrestazure-0.4.20/msrestazure/version.py 2018-01-08
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/version.py 2018-04-24
01:45:02.000000000 +0200
@@ -24,4 +24,5 @@
#
# --------------------------------------------------------------------------
-msrestazure_version = "0.4.20"
+#: version of the package. Use msrestazure.__version__ instead.
+msrestazure_version = "0.4.28"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure.egg-info/PKG-INFO
new/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO
--- old/msrestazure-0.4.20/msrestazure.egg-info/PKG-INFO 2018-01-08
23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO 2018-04-24
01:45:43.000000000 +0200
@@ -1,12 +1,11 @@
Metadata-Version: 1.1
Name: msrestazure
-Version: 0.4.20
+Version: 0.4.28
Summary: AutoRest swagger generator Python client runtime. Azure-specific
module.
Home-page: https://github.com/Azure/msrestazure-for-python
Author: Microsoft Corporation
Author-email: [email protected]
License: MIT License
-Description-Content-Type: UNKNOWN
Description: AutoRest: Python Client Runtime - Azure Module
===============================================
@@ -29,6 +28,76 @@
Release History
---------------
+ 2018-04-23 Version 0.4.28
+ +++++++++++++++++++++++++
+
+ **Disclaimer**
+
+ Do to some stability issues with "keyring" dependency that highly
change from one system to another,
+ this package is no longer a dependency of "msrestazure".
+ If you were using the secured token cache of
`ServicePrincipalCredentials` and `UserPassCredentials`,
+ the feature is still available, but you need to install manually
"keyring". The functionnality will activate automatically.
+
+ 2018-04-18 Version 0.4.27
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Implements new features of msrest 0.4.28 on session improvement. See
msrest ChangeLog for details.
+
+ Update msrest dependency to 0.4.28
+
+ 2018-04-17 Version 0.4.26
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - IMDS/MSI: Retry on more error codes (#87)
+ - IMDS/MSI: fix a boundary case on timeout (#86)
+
+ 2018-03-29 Version 0.4.25
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - MSIAuthentication now uses IMDS endpoint if available
+ - MSIAuthentication can be used in any environment that defines
MSI_ENDPOINT env variable
+
+ 2018-03-26 Version 0.4.24
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Fix parse_resource_id() tool to be case-insensitive to keywords when
matching #81
+ - Add missing baseclass init call for AdalAuthentication #82
+
+ 2018-03-19 Version 0.4.23
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Fix LRO result if POST uses AsyncOperation header (Autorest.Python
3.0 only) #79
+
+ 2018-02-27 Version 0.4.22
+ +++++++++++++++++++++++++
+
+ **Bugfix**
+
+ - Remove a possible infinite loop with MSIAuthentication #77
+
+ **Disclaimer**
+
+ From this version, MSIAuthentication will fail instantly if you try to
get MSI token
+ from a VM where the extension is not installed, or not yet ready.
+ You need to do your own retry mechanism if you think the extension is
provisioning and
+ the call might succeed later.
+ This behavior is consistent with other Azure SDK implementation of MSI
scenarios.
+
+ 2018-01-26 Version 0.4.21
+ +++++++++++++++++++++++++
+
+ - Update allowed ADAL dependency to 0.5.x
+
2018-01-08 Version 0.4.20
+++++++++++++++++++++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure.egg-info/requires.txt
new/msrestazure-0.4.28/msrestazure.egg-info/requires.txt
--- old/msrestazure-0.4.20/msrestazure.egg-info/requires.txt 2018-01-08
23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure.egg-info/requires.txt 2018-04-24
01:45:43.000000000 +0200
@@ -1,3 +1,2 @@
-msrest<2.0.0,>=0.4.25
-keyring>=5.6
-adal~=0.4.7
+msrest<2.0.0,>=0.4.28
+adal~=0.5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/msrestazure-0.4.20/setup.py
new/msrestazure-0.4.28/setup.py
--- old/msrestazure-0.4.20/setup.py 2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/setup.py 2018-04-24 01:45:02.000000000 +0200
@@ -28,7 +28,7 @@
setup(
name='msrestazure',
- version='0.4.20',
+ version='0.4.28',
author='Microsoft Corporation',
author_email='[email protected]',
packages=find_packages(exclude=["tests", "tests.*"]),
@@ -49,8 +49,7 @@
'License :: OSI Approved :: MIT License',
'Topic :: Software Development'],
install_requires=[
- "msrest>=0.4.25,<2.0.0",
- "keyring>=5.6",
- "adal~=0.4.7"
+ "msrest>=0.4.28,<2.0.0",
+ "adal~=0.5.0"
],
)