Hello community,
here is the log from the commit of package python-msrestazure for openSUSE:Factory checked in at 2018-09-26 16:16:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msrestazure (Old)
and /work/SRC/openSUSE:Factory/.python-msrestazure.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-msrestazure"
Wed Sep 26 16:16:14 2018 rev:6 rq:638008 version:0.5.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msrestazure/python-msrestazure.changes 2018-05-13 16:03:42.677295231 +0200
+++ /work/SRC/openSUSE:Factory/.python-msrestazure.new/python-msrestazure.changes 2018-09-26 16:16:15.915004379 +0200
@@ -1,0 +2,11 @@
+Thu Sep 6 13:00:23 UTC 2018 - John Paul Adrian Glaubitz
+
+- New upstream release
+ + Version 0.5.0
+ + No upstream changelog provided
+- Drop obsolete patches
+ + m_drop-compatible-releases-operator.patch
+- Update Requires from setup.py
+- Update Summary from setup.py
+
+-------------------------------------------------------------------
Old:
----
m_drop-compatible-releases-operator.patch
msrestazure-0.4.28.tar.gz
New:
----
msrestazure-0.5.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-msrestazure.spec ++++++
--- /var/tmp/diff_new_pack.G4B7Lk/_old 2018-09-26 16:16:16.323003704 +0200
+++ /var/tmp/diff_new_pack.G4B7Lk/_new 2018-09-26 16:16:16.327003697 +0200
@@ -18,21 +18,20 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-msrestazure
-Version: 0.4.28
+Version: 0.5.0
Release: 0
-Summary: AutoRest swagger generator
+Summary: AutoRest swagger generator - Azure-specific module
License: MIT
Group: Development/Languages/Python
Url: https://pypi.python.org/pypi/msrestazure
Source: https://files.pythonhosted.org/packages/source/m/msrestazure/msrestazure-%{version}.tar.gz
Source1: LICENSE.md
-Patch0: m_drop-compatible-releases-operator.patch
BuildRequires: %{python_module devel}
BuildRequires: %{python_module setuptools}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
-Requires: python-adal < 1.0.0
-Requires: python-adal >= 0.5.0
+Requires: python-adal < 2.0.0
+Requires: python-adal >= 0.6.0
Requires: python-msrest < 2.0.0
Requires: python-msrest >= 0.4.28
BuildRoot: %{_tmppath}/%{name}-%{version}-build
@@ -45,7 +44,6 @@
%prep
%setup -q -n msrestazure-%{version}
-%patch0 -p1
cp %{SOURCE1} LICENSE.md
%build
++++++ msrestazure-0.4.28.tar.gz -> msrestazure-0.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/PKG-INFO new/msrestazure-0.5.0/PKG-INFO
--- old/msrestazure-0.4.28/PKG-INFO 2018-04-24 01:45:43.000000000 +0200
+++ new/msrestazure-0.5.0/PKG-INFO 2018-08-02 21:17:34.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: msrestazure
-Version: 0.4.28
+Version: 0.5.0
Summary: AutoRest swagger generator Python client runtime. Azure-specific module.
Home-page: https://github.com/Azure/msrestazure-for-python
Author: Microsoft Corporation
@@ -28,6 +28,87 @@
Release History
---------------
+ 2018-08-02 Version 0.5.0
+ ++++++++++++++++++++++++
+
+ **Features**
+
+ - Implementation is now using ADAL and not request-oauthlib. This allows more AD scenarios (like federated) #94
+ - Add additionalInfo parsing for CloudError #102
+
+ **Breaking changes**
+
+ These breaking changes applies to ServicePrincipalCredentials, UserPassCredentials, AADTokenCredentials
+
+ - Remove "auth_uri" attribute and parameter. This was unused.
+ - Remove "state" attribute. This was unused.
+ - Remove "client" attribute. This was exposed by mistake and should have been internal. No replacement is possible.
+ - Remove "token_uri" attribute and parameter. Use "cloud_environment" and "tenant" to impact the login url now.
+ - Remove token caching based on "keyring". Token caching should be implemented using ADAL now. This implies:
+
+ - Remove the "keyring" parameter
+ - Remove the "clear_cached_token" method
+ - Remove the "retrieve_session" method
+
+ 2018-07-03 Version 0.4.35
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - MSIAuthentication regression for KeyVault since IMDS support #109
+
+ 2018-07-02 Version 0.4.34
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - MSIAuthentication should initialize the token attribute on creation #106
+
+ 2018-06-21 Version 0.4.33
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - Fixes refreshToken in UserPassCredentials and AADTokenCredentials #103
+ - Fix US government cloud definition #104
+
+ Thanks to mjcaley for his contribution
+
+ 2018-06-13 Version 0.4.32
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Implement new LRO options of Autorest #101
+
+ **Bug fixes**
+
+ - Reduce max MSI polling time for VM #100
+
+
+ 2018-05-17 Version 0.4.31
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Improve MSI for VM token polling algorithm
+
+ 2018-05-16 Version 0.4.30
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Allow ADAL 0.5.0 to 2.0.0 excluded as valid ADAL dependency
+
+ 2018-04-30 Version 0.4.29
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - Fix refresh Token on `AADTokenCredentials` (was broken in 0.4.27)
+ - Now `UserPasswordCredentials` correctly use the refreshToken, and not user/password to refresh the session (was broken in 0.4.27)
+ - Bring back `keyring`, with minimal dependency 12.0.2 that fixes the installation problem on old Python
+
2018-04-23 Version 0.4.28
+++++++++++++++++++++++++
@@ -35,7 +116,7 @@
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`,
+ 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
@@ -76,7 +157,7 @@
**Bugfix**
- - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
+ - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
2018-02-27 Version 0.4.22
+++++++++++++++++++++++++
@@ -441,5 +522,6 @@
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/README.rst new/msrestazure-0.5.0/README.rst
--- old/msrestazure-0.4.28/README.rst 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/README.rst 2018-08-02 21:16:52.000000000 +0200
@@ -20,6 +20,87 @@
Release History
---------------
+2018-08-02 Version 0.5.0
+++++++++++++++++++++++++
+
+**Features**
+
+- Implementation is now using ADAL and not request-oauthlib. This allows more AD scenarios (like federated) #94
+- Add additionalInfo parsing for CloudError #102
+
+**Breaking changes**
+
+These breaking changes applies to ServicePrincipalCredentials, UserPassCredentials, AADTokenCredentials
+
+- Remove "auth_uri" attribute and parameter. This was unused.
+- Remove "state" attribute. This was unused.
+- Remove "client" attribute. This was exposed by mistake and should have been internal. No replacement is possible.
+- Remove "token_uri" attribute and parameter. Use "cloud_environment" and "tenant" to impact the login url now.
+- Remove token caching based on "keyring". Token caching should be implemented using ADAL now. This implies:
+
+ - Remove the "keyring" parameter
+ - Remove the "clear_cached_token" method
+ - Remove the "retrieve_session" method
+
+2018-07-03 Version 0.4.35
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- MSIAuthentication regression for KeyVault since IMDS support #109
+
+2018-07-02 Version 0.4.34
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- MSIAuthentication should initialize the token attribute on creation #106
+
+2018-06-21 Version 0.4.33
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- Fixes refreshToken in UserPassCredentials and AADTokenCredentials #103
+- Fix US government cloud definition #104
+
+Thanks to mjcaley for his contribution
+
+2018-06-13 Version 0.4.32
++++++++++++++++++++++++++
+
+**Features**
+
+- Implement new LRO options of Autorest #101
+
+**Bug fixes**
+
+- Reduce max MSI polling time for VM #100
+
+
+2018-05-17 Version 0.4.31
++++++++++++++++++++++++++
+
+**Features**
+
+- Improve MSI for VM token polling algorithm
+
+2018-05-16 Version 0.4.30
++++++++++++++++++++++++++
+
+**Features**
+
+- Allow ADAL 0.5.0 to 2.0.0 excluded as valid ADAL dependency
+
+2018-04-30 Version 0.4.29
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- Fix refresh Token on `AADTokenCredentials` (was broken in 0.4.27)
+- Now `UserPasswordCredentials` correctly use the refreshToken, and not user/password to refresh the session (was broken in 0.4.27)
+- Bring back `keyring`, with minimal dependency 12.0.2 that fixes the installation problem on old Python
+
2018-04-23 Version 0.4.28
+++++++++++++++++++++++++
@@ -27,7 +108,7 @@
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`,
+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
@@ -68,7 +149,7 @@
**Bugfix**
-- Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
+- Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
2018-02-27 Version 0.4.22
+++++++++++++++++++++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure/azure_active_directory.py new/msrestazure-0.5.0/msrestazure/azure_active_directory.py
--- old/msrestazure-0.4.28/msrestazure/azure_active_directory.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure/azure_active_directory.py 2018-08-02 21:16:52.000000000 +0200
@@ -36,21 +36,8 @@
from urllib.parse import urlparse, parse_qs
import adal
-from oauthlib.oauth2 import BackendApplicationClient, LegacyApplicationClient
-from oauthlib.oauth2.rfc6749.errors import (
- InvalidGrantError,
- MismatchingStateError,
- OAuth2Error,
- TokenExpiredError)
from requests import RequestException, ConnectionError, HTTPError
import requests
-import requests_oauthlib as oauth
-
-try:
- import keyring
-except Exception as err:
- keyring = False
- KEYRING_EXCEPTION = err
from msrest.authentication import OAuthTokenAuthentication, Authentication, BasicTokenAuthentication
from msrest.exceptions import TokenExpiredError as Expired
@@ -61,65 +48,13 @@
_LOGGER = logging.getLogger(__name__)
-if not keyring:
- _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.
-
- :param str uri: The base URL.
- :param list paths: List of strings that make up the URL.
- :param str scheme: The URL scheme, 'http' or 'https'.
- :rtype: str
- :return: Combined, formatted URL.
- """
- path = [str(p).strip('/') for p in paths]
- combined_path = '/'.join(path)
- parsed_url = urlparse(uri)
- replaced = parsed_url._replace(scheme=scheme)
- if combined_path:
- path = '/'.join([replaced.path, combined_path])
- replaced = replaced._replace(path=path)
-
- new_url = replaced.geturl()
- new_url = new_url.replace('///', '//')
- return new_url
-
-
-def _http(uri, *extra):
- """Convert https URL to http.
-
- :param str uri: The base URL.
- :param str extra: Additional URL paths (optional).
- :rtype: str
- :return: An HTTP URL.
- """
- return _build_url(uri, extra, 'http')
-
-
-def _https(uri, *extra):
- """Convert http URL to https.
-
- :param str uri: The base URL.
- :param str extra: Additional URL paths (optional).
- :rtype: str
- :return: An HTTPS URL.
- """
- return _build_url(uri, extra, 'https')
-
-
class AADMixin(OAuthTokenAuthentication):
"""Mixin for Authentication object.
Provides some AAD functionality:
- - State validation
- Token caching and retrieval
- Default AAD configuration
"""
- _token_uri = "/oauth2/token"
- _auth_uri = "/oauth2/authorize"
- _tenant = "common"
- _keyring = "AzureAAD"
_case = re.compile('([a-z0-9])([A-Z])')
def _configure(self, **kwargs):
@@ -131,54 +66,95 @@
- china (bool): Configure auth for China-based service,
default is 'False'.
- tenant (str): Alternative tenant, default is 'common'.
- - auth_uri (str): Alternative authentication endpoint.
- - token_uri (str): Alternative token retrieval endpoint.
- resource (str): Alternative authentication resource, default
is 'https://management.core.windows.net/'.
- verify (bool): Verify secure connection, default is 'True'.
- - keyring (str): Name of local token cache, default is 'AzureAAD'.
- timeout (int): Timeout of the request in seconds.
- - proxies (dict): Dictionary mapping protocol or protocol and
+ - proxies (dict): Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
+ - cache (adal.TokenCache): A adal.TokenCache, see ADAL configuration
+ for details. This parameter is not used here and directly passed to ADAL.
"""
if kwargs.get('china'):
err_msg = ("china parameter is deprecated, "
"please use "
"cloud_environment=msrestazure.azure_cloud.AZURE_CHINA_CLOUD")
warnings.warn(err_msg, DeprecationWarning)
- self.cloud_environment = AZURE_CHINA_CLOUD
+ self._cloud_environment = AZURE_CHINA_CLOUD
else:
- self.cloud_environment = AZURE_PUBLIC_CLOUD
- self.cloud_environment = kwargs.get('cloud_environment', self.cloud_environment)
+ self._cloud_environment = AZURE_PUBLIC_CLOUD
+ self._cloud_environment = kwargs.get('cloud_environment', self._cloud_environment)
- auth_endpoint = self.cloud_environment.endpoints.active_directory
- resource = self.cloud_environment.endpoints.active_directory_resource_id
+ auth_endpoint = self._cloud_environment.endpoints.active_directory
+ resource = self._cloud_environment.endpoints.active_directory_resource_id
- tenant = kwargs.get('tenant', self._tenant)
- self.auth_uri = kwargs.get('auth_uri', _https(
- auth_endpoint, tenant, self._auth_uri))
- self.token_uri = kwargs.get('token_uri', _https(
- auth_endpoint, tenant, self._token_uri))
- self.verify = kwargs.get('verify', True)
- self.cred_store = kwargs.get('keyring', self._keyring)
+ self._tenant = kwargs.get('tenant', "common")
+ self._verify = kwargs.get('verify') # 'None' will honor ADAL_PYTHON_SSL_NO_VERIFY
self.resource = kwargs.get('resource', resource)
- self.proxies = kwargs.get('proxies')
- self.timeout = kwargs.get('timeout')
- self.state = oauth.oauth2_session.generate_token()
+ self._proxies = kwargs.get('proxies')
+ self._timeout = kwargs.get('timeout')
+ self._cache = kwargs.get('cache')
self.store_key = "{}_{}".format(
auth_endpoint.strip('/'), self.store_key)
+ self.secret = None
+ self._context = None # Future ADAL context
- def _check_state(self, response):
- """Validate state returned by AAD server.
+ def _create_adal_context(self):
+ authority_url = self.cloud_environment.endpoints.active_directory
+ is_adfs = bool(re.match('.+(/adfs|/adfs/)$', authority_url, re.I))
+ if is_adfs:
+ authority_url = authority_url.rstrip('/') # workaround: ADAL is known to reject auth urls with trailing /
+ else:
+ authority_url = authority_url + '/' + self._tenant
- :param str response: URL returned by server redirect.
- :raises: ValueError if state does not match that of the request.
- :rtype: None
- """
- query = parse_qs(urlparse(response).query)
- if self.state not in query.get('state', []):
- raise ValueError(
- "State received from server does not match that of request.")
+ self._context = adal.AuthenticationContext(
+ authority_url,
+ timeout=self._timeout,
+ verify_ssl=self._verify,
+ proxies=self._proxies,
+ validate_authority=not is_adfs,
+ cache=self._cache,
+ api_version=None
+ )
+
+ def _destroy_adal_context(self):
+ self._context = None
+
+ @property
+ def verify(self):
+ return self._verify
+
+ @verify.setter
+ def verify(self, value):
+ self._verify = value
+ self._destroy_adal_context()
+
+ @property
+ def proxies(self):
+ return self._proxies
+
+ @proxies.setter
+ def proxies(self, value):
+ self._proxies = value
+ self._destroy_adal_context()
+
+ @property
+ def timeout(self):
+ return self._timeout
+
+ @timeout.setter
+ def timeout(self, value):
+ self._timeout = value
+ self._destroy_adal_context()
+
+ @property
+ def cloud_environment(self):
+ return self._cloud_environment
+
+ @cloud_environment.setter
+ def cloud_environment(self, value):
+ self._cloud_environment = value
+ self._destroy_adal_context()
def _convert_token(self, token):
"""Convert token fields from camel case.
@@ -186,40 +162,29 @@
:param dict token: An authentication token.
:rtype: dict
"""
+ # Beware that ADAL returns a pointer to its own dict, do
+ # NOT change it in place
+ token = token.copy()
+
+ # If it's from ADAL, expiresOn will be in ISO form.
+ # Bring it back to float, using expiresIn
+ if "expiresOn" in token and "expiresIn" in token:
+ token["expiresOn"] = token['expiresIn'] + time.time()
return {self._case.sub(r'\1_\2', k).lower(): v
for k, v in token.items()}
def _parse_token(self):
- # TODO: We could also check expires_on and use to update expires_in
+ # AD answers 'expires_on', and Python oauthlib expects 'expires_at'
+ if 'expires_on' in self.token and 'expires_at' not in self.token:
+ self.token['expires_at'] = self.token['expires_on']
+
if self.token.get('expires_at'):
countdown = float(self.token['expires_at']) - time.time()
self.token['expires_in'] = countdown
- def _default_token_cache(self, token):
- """Store token for future sessions.
-
- :param dict token: An authentication token.
- :rtype: None
- """
- self.token = token
- if keyring:
- try:
- keyring.set_password(self.cred_store, self.store_key, str(token))
- except Exception as err:
- _LOGGER.warning("Keyring cache token has failed: %s", str(err))
-
- def _retrieve_stored_token(self):
- """Retrieve stored token for new session.
-
- :raises: ValueError if no cached token found.
- :rtype: dict
- :return: Retrieved token.
- """
- token = keyring.get_password(self.cred_store, self.store_key)
- if token is None:
- raise ValueError("No stored token found.")
- self.token = ast.literal_eval(str(token))
- self.signed_session()
+ def set_token(self):
+ if not self._context:
+ self._create_adal_context()
def signed_session(self, session=None):
"""Create token-friendly Requests session, using auto-refresh.
@@ -231,18 +196,33 @@
:param session: The session to configure for authentication
:type session: requests.Session
"""
+ self.set_token() # Adal does the caching.
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.
+ 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.
"""
- try:
- keyring.delete_password(self.cred_store, self.store_key)
- except keyring.errors.PasswordDeleteError:
- raise_with_traceback(KeyError, "Unable to clear token.")
+ if 'refresh_token' in self.token:
+ try:
+ token = self._context.acquire_token_with_refresh_token(
+ self.token['refresh_token'],
+ self.id,
+ self.resource,
+ self.secret # This is needed when using Confidential Client
+ )
+ self.token = self._convert_token(token)
+ except adal.AdalError as err:
+ raise_with_traceback(AuthenticationError, "", err)
+ return self.signed_session(session)
class AADTokenCredentials(AADMixin):
@@ -250,20 +230,22 @@
Credentials objects for AAD token retrieved through external process
e.g. Python ADAL lib.
+ If you just provide "token", refresh will be done on Public Azure with
+ default public Azure "resource". You can set "cloud_environment",
+ "tenant", "resource" and "client_id" to change that behavior.
+
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'.
- tenant (str): Alternative tenant, default is 'common'.
- - auth_uri (str): Alternative authentication endpoint.
- - token_uri (str): Alternative token retrieval endpoint.
- resource (str): Alternative authentication resource, default
is 'https://management.core.windows.net/'.
- verify (bool): Verify secure connection, default is 'True'.
- - keyring (str): Name of local token cache, default is 'AzureAAD'.
- - cached (bool): If true, will not attempt to collect a token,
- which can then be populated later from a cached token.
+ - cache (adal.TokenCache): A adal.TokenCache, see ADAL configuration
+ for details. This parameter is not used here and directly passed to ADAL.
+
:param dict token: Authentication token.
:param str client_id: Client ID, if not set, Xplat Client ID
@@ -276,18 +258,8 @@
client_id = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'
super(AADTokenCredentials, self).__init__(client_id, None)
self._configure(**kwargs)
- if not kwargs.get('cached'):
- self.token = self._convert_token(token)
- self.signed_session()
-
- @classmethod
- def retrieve_session(cls, client_id=None):
- """Create AADTokenCredentials from a cached token if it has not
- yet expired.
- """
- session = cls(None, client_id=client_id, cached=True)
- session._retrieve_stored_token()
- return session
+ self.client = None
+ self.token = self._convert_token(token)
class UserPassCredentials(AADMixin):
@@ -304,17 +276,14 @@
- china (bool): Configure auth for China-based service,
default is 'False'.
- tenant (str): Alternative tenant, default is 'common'.
- - auth_uri (str): Alternative authentication endpoint.
- - token_uri (str): Alternative token retrieval endpoint.
- resource (str): Alternative authentication resource, default
is 'https://management.core.windows.net/'.
- verify (bool): Verify secure connection, default is 'True'.
- - keyring (str): Name of local token cache, default is 'AzureAAD'.
- timeout (int): Timeout of the request in seconds.
- - cached (bool): If true, will not attempt to collect a token,
- which can then be populated later from a cached token.
- proxies (dict): Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
+ - cache (adal.TokenCache): A adal.TokenCache, see ADAL configuration
+ for details. This parameter is not used here and directly passed to ADAL.
:param str username: Account username.
:param str password: Account password.
@@ -335,83 +304,25 @@
self.username = username
self.password = password
self.secret = secret
- self.client = LegacyApplicationClient(client_id=self.id)
- if not kwargs.get('cached'):
- self.set_token()
-
- @classmethod
- def retrieve_session(cls, username, client_id=None):
- """Create ServicePrincipalCredentials from a cached token if it has not
- yet expired.
- """
- session = cls(username, None, client_id=client_id, cached=True)
- session._retrieve_stored_token()
- return session
-
- def _setup_session(self):
- """Create token-friendly Requests session.
+ self.set_token()
- :rtype: requests_oauthlib.OAuth2Session
- """
- return oauth.OAuth2Session(client=self.client)
def set_token(self):
"""Get token using Username/Password credentials.
:raises: AuthenticationError if credentials invalid, or call fails.
"""
- with self._setup_session() as session:
- optional = {}
- if self.secret:
- optional['client_secret'] = self.secret
- try:
- token = session.fetch_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)
-
- 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)
-
+ super(UserPassCredentials, self).set_token()
+ try:
+ token = self._context.acquire_token_with_username_password(
+ self.resource,
+ self.username,
+ self.password,
+ self.id
+ )
+ self.token = self._convert_token(token)
+ except adal.AdalError as err:
+ raise_with_traceback(AuthenticationError, "", err)
class ServicePrincipalCredentials(AADMixin):
"""Credentials object for Service Principle Authentication.
@@ -423,17 +334,14 @@
- china (bool): Configure auth for China-based service,
default is 'False'.
- tenant (str): Alternative tenant, default is 'common'.
- - auth_uri (str): Alternative authentication endpoint.
- - token_uri (str): Alternative token retrieval endpoint.
- resource (str): Alternative authentication resource, default
is 'https://management.core.windows.net/'.
- verify (bool): Verify secure connection, default is 'True'.
- - keyring (str): Name of local token cache, default is 'AzureAAD'.
- timeout (int): Timeout of the request in seconds.
- - cached (bool): If true, will not attempt to collect a token,
- which can then be populated later from a cached token.
- proxies (dict): Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
+ - cache (adal.TokenCache): A adal.TokenCache, see ADAL configuration
+ for details. This parameter is not used here and directly passed to ADAL.
:param str client_id: Client ID.
:param str secret: Client secret.
@@ -443,63 +351,23 @@
self._configure(**kwargs)
self.secret = secret
- self.client = BackendApplicationClient(self.id)
- if not kwargs.get('cached'):
- self.set_token()
-
- @classmethod
- def retrieve_session(cls, client_id):
- """Create ServicePrincipalCredentials from a cached token if it has not
- yet expired.
- """
- session = cls(client_id, None, cached=True)
- session._retrieve_stored_token()
- return session
-
- def _setup_session(self):
- """Create token-friendly Requests session.
-
- :rtype: requests_oauthlib.OAuth2Session
- """
- return oauth.OAuth2Session(self.id, client=self.client)
+ self.set_token()
def set_token(self):
"""Get token using Client ID/Secret credentials.
:raises: AuthenticationError if credentials invalid, or call fails.
"""
- with self._setup_session() as session:
- try:
- token = session.fetch_token(self.token_uri,
- client_id=self.id,
- resource=self.resource,
- client_secret=self.secret,
- response_type="client_credentials",
- verify=self.verify,
- timeout=self.timeout,
- proxies=self.proxies)
- except (RequestException, OAuth2Error, InvalidGrantError) as err:
- 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)
-
+ super(ServicePrincipalCredentials, self).set_token()
+ try:
+ token = self._context.acquire_token_with_client_credentials(
+ self.resource,
+ self.id,
+ self.secret
+ )
+ self.token = self._convert_token(token)
+ except adal.AdalError as err:
+ raise_with_traceback(AuthenticationError, "", err)
# For backward compatibility of import, but I doubt someone uses that...
class InteractiveCredentials(object):
@@ -619,7 +487,7 @@
_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
+ raise
token_entry = result.json()
return token_entry['token_type'], token_entry['access_token'], token_entry
@@ -703,7 +571,9 @@
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)
+ self._vm_msi = _ImdsTokenProvider(self.msi_conf)
+ # Follow the same convention as all Credentials class to check for the token at creation time #106
+ self.set_token()
def set_token(self):
if _is_app_service():
@@ -711,7 +581,7 @@
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()
+ token_entry = self._vm_msi.get_token(self.resource)
self.scheme, self.token = token_entry['token_type'], token_entry
def signed_session(self, session=None):
@@ -733,7 +603,7 @@
"""A help class handling token acquisitions through Azure IMDS plugin.
"""
- def __init__(self, resource, msi_conf=None):
+ def __init__(self, msi_conf=None):
self._user_agent = AzureConfiguration(None).user_agent
self.identity_type, self.identity_id = None, None
if msi_conf:
@@ -744,12 +614,11 @@
# default to system assigned identity on an empty configuration object
self.cache = {}
- self.resource = resource
- def get_token(self):
+ def get_token(self, resource):
import datetime
# let us hit the cache first
- token_entry = self.cache.get(self.resource, None)
+ token_entry = self.cache.get(resource, None)
if token_entry:
expires_on = int(token_entry['expires_on'])
expires_on_datetime = datetime.datetime.fromtimestamp(expires_on)
@@ -758,43 +627,52 @@
_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)
+ self.cache.pop(resource)
- token_entry = self._retrieve_token_from_imds_with_retry()
- self.cache[self.resource] = token_entry
+ token_entry = self._retrieve_token_from_imds_with_retry(resource)
+ self.cache[resource] = token_entry
return token_entry
- def _retrieve_token_from_imds_with_retry(self):
+ def _retrieve_token_from_imds_with_retry(self, resource):
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,
+ 'resource': resource,
'api-version': '2018-02-01'
}
if self.identity_id:
payload[self.identity_type] = self.identity_id
- retry, max_retry = 1, 20
+ retry, max_retry, start_time = 1, 12, time.time()
# 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:
+ while True:
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
+ if result.status_code in [404, 410, 429] or (499 < result.status_code < 600):
+ if retry <= max_retry:
+ wait = random.choice(slots[:retry])
+ _LOGGER.warning("MSI: wait: %ss and retry: %s", wait, retry)
+ time.sleep(wait)
+ retry += 1
+ else:
+ if result.status_code == 410: # For IMDS upgrading, we wait up to 70s
+ gap = 70 - (time.time() - start_time)
+ if gap > 0:
+ _LOGGER.warning("MSI: wait till 70 seconds when IMDS is upgrading")
+ time.sleep(gap)
+ continue
+ break
elif result.status_code != 200:
raise HTTPError(request=result.request, response=result.raw)
else:
break
- if retry > max_retry:
+ if result.status_code != 200:
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
+ return token_entry
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure/azure_cloud.py new/msrestazure-0.5.0/msrestazure/azure_cloud.py
--- old/msrestazure-0.4.28/msrestazure/azure_cloud.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure/azure_cloud.py 2018-08-02 21:16:52.000000000 +0200
@@ -170,7 +170,7 @@
sql_management='https://management.core.usgovcloudapi.net:8443/',
batch_resource_id='https://batch.core.usgovcloudapi.net/',
gallery='https://gallery.usgovcloudapi.net/',
- active_directory='https://login-us.microsoftonline.com',
+ active_directory='https://login.microsoftonline.us',
active_directory_resource_id='https://management.core.usgovcloudapi.net/',
active_directory_graph_resource_id='https://graph.windows.net/'),
suffixes=CloudSuffixes(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure/azure_exceptions.py new/msrestazure-0.5.0/msrestazure/azure_exceptions.py
--- old/msrestazure-0.4.28/msrestazure/azure_exceptions.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure/azure_exceptions.py 2018-08-02 21:16:52.000000000 +0200
@@ -54,6 +54,7 @@
'target': {'key': 'target', 'type': 'str'},
'details': {'key': 'details', 'type': '[CloudErrorData]'},
'innererror': {'key': 'innererror', 'type': 'object'},
+ 'additionalInfo': {'key': 'additionalInfo', 'type': '[TypedErrorInfo]'},
'data': {'key': 'values', 'type': '{str}'}
}
@@ -65,6 +66,7 @@
self.target = kwargs.get('target')
self.details = kwargs.get('details')
self.innererror = kwargs.get('innererror')
+ self.additionalInfo = kwargs.get('additionalInfo')
self.data = kwargs.get('data')
super(CloudErrorData, self).__init__(*args)
@@ -89,10 +91,18 @@
error_str += "\n\tMessage: {}".format(error_obj.message)
if error_obj.target:
error_str += "\n\tTarget: {}".format(error_obj.target)
- if error_obj.innererror:
- error_str += "\nInner error: {}".format(json.dumps(error_obj.innererror, indent=4))
- if self.innererror:
- error_str += "\nInner error: {}".format(json.dumps(self.innererror, indent=4))
+ if error_obj.innererror:
+ error_str += "\nInner error: {}".format(json.dumps(error_obj.innererror, indent=4))
+ if error_obj.additionalInfo:
+ error_str += "\n\tAdditional Information:"
+ for error_info in error_obj.additionalInfo:
+ error_str += "\n\t\t{}".format(str(error_info).replace("\n", "\n\t\t"))
+ if self.innererror:
+ error_str += "\nInner error: {}".format(json.dumps(self.innererror, indent=4))
+ if self.additionalInfo:
+ error_str += "\nAdditional Information:"
+ for error_info in self.additionalInfo:
+ error_str += "\n\t{}".format(str(error_info).replace("\n", "\n\t"))
error_bytes = error_str.encode()
return error_bytes.decode('ascii')
@@ -111,8 +121,9 @@
error data.
"""
try:
- value = eval(value)
- except (SyntaxError, TypeError):
+ import ast
+ value = ast.literal_eval(value)
+ except (SyntaxError, TypeError, ValueError):
pass
try:
value = value.get('value', value)
@@ -142,7 +153,8 @@
def __init__(self, response, error=None, *args, **kwargs):
self.deserializer = Deserializer({
'CloudErrorRoot': CloudErrorRoot,
- 'CloudErrorData': CloudErrorData
+ 'CloudErrorData': CloudErrorData,
+ 'TypedErroInfo': TypedErrorInfo
})
self.error = None
self.message = None
@@ -216,4 +228,26 @@
if not self.message:
msg = "Operation failed with status: {!r}. Details: {}"
self.message = msg.format(
- response.status_code, message)
\ No newline at end of file
+ response.status_code, message)
+
+class TypedErrorInfo(object):
+ """Typed Error Info object, deserialized from error data returned
+ during a failed REST API call. Contains additional error information
+ """
+
+ _validation = {}
+ _attribute_map = {
+ 'type': {'key': 'type', 'type': 'str'},
+ 'info': {'key': 'info', 'type': 'object'}
+ }
+
+ def __init__(self, type, info):
+ self.type = type
+ self.info = info
+
+ def __str__(self):
+ """Cloud error message."""
+ error_str = "Type: {}".format(self.type)
+ error_str += "\nInfo: {}".format(json.dumps(self.info, indent=4))
+ error_bytes = error_str.encode()
+ return error_bytes.decode('ascii')
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure/polling/arm_polling.py new/msrestazure-0.5.0/msrestazure/polling/arm_polling.py
--- old/msrestazure-0.4.28/msrestazure/polling/arm_polling.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure/polling/arm_polling.py 2018-08-02 21:16:52.000000000 +0200
@@ -40,6 +40,8 @@
FAILED = frozenset(['canceled', 'failed'])
SUCCEEDED = frozenset(['succeeded'])
+_AZURE_ASYNC_OPERATION_FINAL_STATE = "azure-async-operation"
+_LOCATION_FINAL_STATE = "location"
def finished(status):
if hasattr(status, 'value'):
@@ -102,9 +104,14 @@
"""LongRunningOperation
Provides default logic for interpreting operation responses
and status updates.
+
+ :param requests.Response response: The initial response.
+ :param callable deserialization_callback: The deserialization callaback.
+ :param dict lro_options: LRO options.
+ :param kwargs: Unused for now
"""
- def __init__(self, response, deserialization_callback):
+ def __init__(self, response, deserialization_callback, lro_options=None, **kwargs):
self.method = response.request.method
self.initial_response = response
self.status = ""
@@ -112,6 +119,11 @@
self.deserialization_callback = deserialization_callback
self.async_url = None
self.location_url = None
+ if lro_options is None:
+ lro_options = {
+ 'final-state-via': _AZURE_ASYNC_OPERATION_FINAL_STATE
+ }
+ self.lro_options = lro_options
def _raise_if_bad_http_status_and_method(self, response):
"""Check response status code is valid for a Put or Patch
@@ -179,8 +191,8 @@
:param requests.Response response: latest REST call response.
:rtype: bool
"""
- return (self.async_url or not self.resource) and \
- self.method in {'PUT', 'PATCH'}
+ return ((self.async_url or not self.resource) and self.method in {'PUT', 'PATCH'}) \
+ or (self.lro_options['final-state-via'] == _LOCATION_FINAL_STATE and self.location_url and self.async_url and self.method == 'POST')
def set_initial_status(self, response):
"""Process first response after initiating long running
@@ -279,7 +291,7 @@
try:
self.resource = self._deserialize(response)
except Exception:
- self.resource = None
+ self.resource = None
def set_async_url_if_present(self, response):
async_url = get_header_url(response, 'azure-asyncoperation')
@@ -302,11 +314,12 @@
class ARMPolling(PollingMethod):
- def __init__(self, timeout=30, **operation_config):
+ def __init__(self, timeout=30, lro_options=None, **operation_config):
self._timeout = timeout
self._operation = None # Will hold an instance of LongRunningOperation
self._response = None # Will hold latest received response
self._operation_config = operation_config
+ self._lro_options = lro_options
def status(self):
"""Return the current status as a string.
@@ -335,7 +348,7 @@
"""
self._client = client
self._response = initial_response
- self._operation = LongRunningOperation(initial_response, deserialization_callback)
+ self._operation = LongRunningOperation(initial_response, deserialization_callback, self._lro_options)
try:
self._operation.set_initial_status(initial_response)
except BadStatus:
@@ -380,8 +393,11 @@
raise OperationFailed("Operation failed or cancelled")
elif self._operation.should_do_final_get():
- initial_url = self._operation.initial_response.request.url
- self._response = self.request_status(initial_url)
+ if self._operation.method == 'POST' and self._operation.location_url:
+ final_get_url = self._operation.location_url
+ else:
+ final_get_url = self._operation.initial_response.request.url
+ self._response = self.request_status(final_get_url)
self._operation.get_status_from_resource(self._response)
def _delay(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure/version.py new/msrestazure-0.5.0/msrestazure/version.py
--- old/msrestazure-0.4.28/msrestazure/version.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure/version.py 2018-08-02 21:16:52.000000000 +0200
@@ -25,4 +25,4 @@
# --------------------------------------------------------------------------
#: version of the package. Use msrestazure.__version__ instead.
-msrestazure_version = "0.4.28"
+msrestazure_version = "0.5.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO new/msrestazure-0.5.0/msrestazure.egg-info/PKG-INFO
--- old/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO 2018-04-24 01:45:43.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure.egg-info/PKG-INFO 2018-08-02 21:17:34.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: msrestazure
-Version: 0.4.28
+Version: 0.5.0
Summary: AutoRest swagger generator Python client runtime. Azure-specific module.
Home-page: https://github.com/Azure/msrestazure-for-python
Author: Microsoft Corporation
@@ -28,6 +28,87 @@
Release History
---------------
+ 2018-08-02 Version 0.5.0
+ ++++++++++++++++++++++++
+
+ **Features**
+
+ - Implementation is now using ADAL and not request-oauthlib. This allows more AD scenarios (like federated) #94
+ - Add additionalInfo parsing for CloudError #102
+
+ **Breaking changes**
+
+ These breaking changes applies to ServicePrincipalCredentials, UserPassCredentials, AADTokenCredentials
+
+ - Remove "auth_uri" attribute and parameter. This was unused.
+ - Remove "state" attribute. This was unused.
+ - Remove "client" attribute. This was exposed by mistake and should have been internal. No replacement is possible.
+ - Remove "token_uri" attribute and parameter. Use "cloud_environment" and "tenant" to impact the login url now.
+ - Remove token caching based on "keyring". Token caching should be implemented using ADAL now. This implies:
+
+ - Remove the "keyring" parameter
+ - Remove the "clear_cached_token" method
+ - Remove the "retrieve_session" method
+
+ 2018-07-03 Version 0.4.35
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - MSIAuthentication regression for KeyVault since IMDS support #109
+
+ 2018-07-02 Version 0.4.34
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - MSIAuthentication should initialize the token attribute on creation #106
+
+ 2018-06-21 Version 0.4.33
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - Fixes refreshToken in UserPassCredentials and AADTokenCredentials #103
+ - Fix US government cloud definition #104
+
+ Thanks to mjcaley for his contribution
+
+ 2018-06-13 Version 0.4.32
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Implement new LRO options of Autorest #101
+
+ **Bug fixes**
+
+ - Reduce max MSI polling time for VM #100
+
+
+ 2018-05-17 Version 0.4.31
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Improve MSI for VM token polling algorithm
+
+ 2018-05-16 Version 0.4.30
+ +++++++++++++++++++++++++
+
+ **Features**
+
+ - Allow ADAL 0.5.0 to 2.0.0 excluded as valid ADAL dependency
+
+ 2018-04-30 Version 0.4.29
+ +++++++++++++++++++++++++
+
+ **Bugfixes**
+
+ - Fix refresh Token on `AADTokenCredentials` (was broken in 0.4.27)
+ - Now `UserPasswordCredentials` correctly use the refreshToken, and not user/password to refresh the session (was broken in 0.4.27)
+ - Bring back `keyring`, with minimal dependency 12.0.2 that fixes the installation problem on old Python
+
2018-04-23 Version 0.4.28
+++++++++++++++++++++++++
@@ -35,7 +116,7 @@
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`,
+ 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
@@ -76,7 +157,7 @@
**Bugfix**
- - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
+ - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) #79
2018-02-27 Version 0.4.22
+++++++++++++++++++++++++
@@ -441,5 +522,6 @@
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Software Development
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/msrestazure.egg-info/requires.txt new/msrestazure-0.5.0/msrestazure.egg-info/requires.txt
--- old/msrestazure-0.4.28/msrestazure.egg-info/requires.txt 2018-04-24 01:45:43.000000000 +0200
+++ new/msrestazure-0.5.0/msrestazure.egg-info/requires.txt 2018-08-02 21:17:34.000000000 +0200
@@ -1,2 +1,2 @@
msrest<2.0.0,>=0.4.28
-adal~=0.5.0
+adal<2.0.0,>=0.6.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/msrestazure-0.4.28/setup.py new/msrestazure-0.5.0/setup.py
--- old/msrestazure-0.4.28/setup.py 2018-04-24 01:45:02.000000000 +0200
+++ new/msrestazure-0.5.0/setup.py 2018-08-02 21:16:52.000000000 +0200
@@ -28,7 +28,7 @@
setup(
name='msrestazure',
- version='0.4.28',
+ version='0.5.0',
author='Microsoft Corporation',
author_email='azpysdkhelp@microsoft.com',
packages=find_packages(exclude=["tests", "tests.*"]),
@@ -46,10 +46,11 @@
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
'License :: OSI Approved :: MIT License',
'Topic :: Software Development'],
install_requires=[
"msrest>=0.4.28,<2.0.0",
- "adal~=0.5.0"
+ "adal>=0.6.0,<2.0.0",
],
)