Hello community,

here is the log from the commit of package python-msal-extensions for 
openSUSE:Factory checked in at 2020-10-02 17:27:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msal-extensions (Old)
 and      /work/SRC/openSUSE:Factory/.python-msal-extensions.new.4249 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-msal-extensions"

Fri Oct  2 17:27:40 2020 rev:2 rq:833122 version:0.3.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-msal-extensions/python-msal-extensions.changes
    2020-02-28 15:20:01.321731970 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-msal-extensions.new.4249/python-msal-extensions.changes
  2020-10-02 17:28:28.958422321 +0200
@@ -1,0 +2,36 @@
+Tue Sep  8 19:58:59 UTC 2020 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 0.3.0
+  + New unified PersistenceNotFound exception is now raised for cases
+    where the persistence is not found. (#64, #67)
+  + Bugfix: File not found exception is now handled for Python 2.7 as a no-op 
(#69)
+  + Added performance tests for locking behavior (#58)
+  + A non-exist persistence on Linux platform would previously return a None.
+    Since this release, it will raise PersistenceNotFound exception which 
becomes
+    a consistent behavior on Windows and macOS.
+
+-------------------------------------------------------------------
+Fri Aug 28 13:40:11 UTC 2020 - John Paul Adrian Glaubitz 
<adrian.glaub...@suse.com>
+
+- Update to version 0.2.2
+  + Bugfix: Restored compatibility with upstream package portalocker version
+    < 1.4.0 when running on non-Windows platform (#50)
+  + Bugfix: Cache on Windows was not functioning in version 0.2.0 and 
0.2.1(#52)
+  + Enhancement: Improved readme providing installation and usage instructions 
(#53)
+- from version 0.2.1
+  + Functionally the same as 0.2.0, but we change the installation-time and 
import-time
+    dependency of PyGObject to run-time dependency. This would make the 
installation
+    easier for those customers who do not necessarily need to use the 
Encryption on Linux. (#47)
+  + The version 1.6.0+ of upstream package portalocker is only required on 
Windows.
+    Other platforms remain with portalocker 1.0.0+. (#49)
+- from version 0.2.0
+  + New feature: Support token cache encryption when running on Linux Desktop 
(#4, #44)
+  + Bug fix: The cache lock was not properly removed on Windows 10 (#42, #43)
+  + Change: A new set of API PersistedTokenCache is provided. Previous API is 
now deprecated
+    and will be removed in next major release which will likely come within a 
month:
+    WindowsTokenCache, OSXTokenCache, UnencryptedTokenCache, FileTokenCache 
and TokenCache.
+  + Since this release, we have a dependency on PyGObject, when running on 
Linux.
+    You may need to follow its installation steps, or follow our CI setup.
+- Update Requires from setup.py
+
+-------------------------------------------------------------------

Old:
----
  msal-extensions-0.1.3.tar.gz

New:
----
  msal-extensions-0.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-msal-extensions.spec ++++++
--- /var/tmp/diff_new_pack.VgofJw/_old  2020-10-02 17:28:31.966424122 +0200
+++ /var/tmp/diff_new_pack.VgofJw/_new  2020-10-02 17:28:31.970424124 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-msal-extensions
-Version:        0.1.3
+Version:        0.3.0
 Release:        0
 Summary:        Microsoft Authentication Library (MSAL) for Python Extensions
 License:        MIT
@@ -29,16 +29,19 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-msal >= 0.4.1
 Requires:       python-msal < 2.0.0
-Requires:       python-portalocker >= 1.0
+Requires:       python-msal >= 0.4.1
 Requires:       python-portalocker < 2.0
+Requires:       python-portalocker >= 1.0
+%ifpython2
+Requires:       python-pathlib2
+%endif
 BuildArch:      noarch
 # SECTION test requirements
-BuildRequires:  %{python_module msal >= 0.4.1}
 BuildRequires:  %{python_module msal < 2.0.0}
-BuildRequires:  %{python_module portalocker >= 1.0}
+BuildRequires:  %{python_module msal >= 0.4.1}
 BuildRequires:  %{python_module portalocker < 2.0}
+BuildRequires:  %{python_module portalocker >= 1.0}
 BuildRequires:  %{python_module pytest}
 # /SECTION
 %python_subpackages

++++++ msal-extensions-0.1.3.tar.gz -> msal-extensions-0.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/PKG-INFO 
new/msal-extensions-0.3.0/PKG-INFO
--- old/msal-extensions-0.1.3/PKG-INFO  2019-11-02 02:27:37.000000000 +0100
+++ new/msal-extensions-0.3.0/PKG-INFO  2020-09-01 22:43:13.000000000 +0200
@@ -1,11 +1,104 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: msal-extensions
-Version: 0.1.3
+Version: 0.3.0
 Summary: UNKNOWN
 Home-page: UNKNOWN
-Author: UNKNOWN
-Author-email: UNKNOWN
 License: UNKNOWN
-Description: UNKNOWN
+Description: 
+        # Microsoft Authentication Extensions for Python
+        
+        The Microsoft Authentication Extensions for Python offers secure 
mechanisms for client applications to perform cross-platform token cache 
serialization and persistence. It gives additional support to the [Microsoft 
Authentication Library for Python 
(MSAL)](https://github.com/AzureAD/microsoft-authentication-library-for-python).
 
+        
+        MSAL Python supports an in-memory cache by default and provides the 
[SerializableTokenCache](https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache)
 to perform cache serialization. You can read more about this in the MSAL 
Python 
[documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-python-token-cache-serialization).
 Developers are required to implement their own cache persistance across 
multiple platforms and Microsoft Authentication Extensions makes this simpler.
+        
+        The supported platforms are Windows, Mac and Linux. 
+        - Windows - 
[DPAPI](https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection)
 is used for encryption.
+        - MAC - The MAC KeyChain is used.
+        - Linux - [LibSecret](https://wiki.gnome.org/Projects/Libsecret) is 
used for encryption.
+        
+        > Note: It is recommended to use this library for cache persistance 
support for Public client applications such as Desktop apps only. In web 
applications, this may lead to scale and performance issues. Web applications 
are recommended to persist the cache in session. Take a look at this [webapp 
sample](https://github.com/Azure-Samples/ms-identity-python-webapp).
+        
+        ## Installation
+        
+        You can find Microsoft Authentication Extensions for Python on 
[Pypi](https://pypi.org/project/msal-extensions/).
+        1. If you haven't already, [install and/or upgrade the 
pip](https://pip.pypa.io/en/stable/installing/)
+           of your Python environment to a recent version. We tested with pip 
18.1.
+        2. Run `pip install msal-extensions`.
+        
+        ## Versions
+        
+        This library follows [Semantic Versioning](http://semver.org/).
+        
+        You can find the changes for each version under
+        
[Releases](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/releases).
+        
+        ## Usage
+        
+        The Microsoft Authentication Extensions library provides the 
`PersistedTokenCache` which accepts a platform-dependent persistence instance. 
This token cache can then be used to instantiate the `PublicClientApplication` 
in MSAL Python. 
+        
+        The token cache includes a file lock, and auto-reload behavior under 
the hood.
+        
+        
+        
+        Here is an example of this pattern for multiple platforms (taken from 
the complete [sample 
here](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/blob/dev/sample/token_cache_sample.py)):
+        
+        ```python
+        def build_persistence(location, fallback_to_plaintext=False):
+            """Build a suitable persistence instance based your current OS"""
+            if sys.platform.startswith('win'):
+                return FilePersistenceWithDataProtection(location)
+            if sys.platform.startswith('darwin'):
+                return KeychainPersistence(location, "my_service_name", 
"my_account_name")
+            if sys.platform.startswith('linux'):
+                try:
+                    return LibsecretPersistence(
+                        location,
+                        schema_name="my_schema_name",
+                        attributes={"my_attr1": "foo", "my_attr2": "bar"},
+                        )
+                except:  # pylint: disable=bare-except
+                    if not fallback_to_plaintext:
+                        raise
+                    logging.exception("Encryption unavailable. Opting in to 
plain text.")
+            return FilePersistence(location)
+        
+        persistence = build_persistence("token_cache.bin")
+        print("Is this persistence encrypted?", persistence.is_encrypted)
+        
+        cache = PersistedTokenCache(persistence)
+        ```
+        Now you can use it in an MSAL application like this:
+        ```python
+        app = msal.PublicClientApplication("my_client_id", token_cache=cache)
+        ```
+        
+        ## Community Help and Support
+        
+        We leverage Stack Overflow to work with the community on supporting 
Azure Active Directory and its SDKs, including this one!
+        We highly recommend you ask your questions on Stack Overflow (we're 
all on there!).
+        Also browse existing issues to see if someone has had your question 
before.
+        
+        We recommend you use the "msal" tag so we can see it!
+        Here is the latest Q&A on Stack Overflow for MSAL:
+        
[http://stackoverflow.com/questions/tagged/msal](http://stackoverflow.com/questions/tagged/msal)
+        
+        
+        ## Contributing
+        
+        All code is licensed under the MIT license and we triage actively on 
GitHub.
+        
+        This project welcomes contributions and suggestions.  Most 
contributions require you to agree to a
+        Contributor License Agreement (CLA) declaring that you have the right 
to, and actually do, grant us
+        the rights to use your contribution. For details, visit 
https://cla.microsoft.com.
+        
+        When you submit a pull request, a CLA-bot will automatically determine 
whether you need to provide
+        a CLA and decorate the PR appropriately (e.g., label, comment). Simply 
follow the instructions
+        provided by the bot. You will only need to do this once across all 
repos using our CLA.
+        
+        
+        ## We value and adhere to the Microsoft Open Source Code of Conduct
+        
+        This project has adopted the [Microsoft Open Source Code of 
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information 
see the [Code of Conduct 
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact 
[openc...@microsoft.com](mailto:openc...@microsoft.com) with any additional 
questions or comments.
 Platform: UNKNOWN
 Classifier: Development Status :: 2 - Pre-Alpha
+Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/README.md 
new/msal-extensions-0.3.0/README.md
--- old/msal-extensions-0.1.3/README.md 2019-11-02 02:27:01.000000000 +0100
+++ new/msal-extensions-0.3.0/README.md 2020-09-01 22:42:14.000000000 +0200
@@ -1,5 +1,85 @@
 
-# Contributing
+# Microsoft Authentication Extensions for Python
+
+The Microsoft Authentication Extensions for Python offers secure mechanisms 
for client applications to perform cross-platform token cache serialization and 
persistence. It gives additional support to the [Microsoft Authentication 
Library for Python 
(MSAL)](https://github.com/AzureAD/microsoft-authentication-library-for-python).
 
+
+MSAL Python supports an in-memory cache by default and provides the 
[SerializableTokenCache](https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache)
 to perform cache serialization. You can read more about this in the MSAL 
Python 
[documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-python-token-cache-serialization).
 Developers are required to implement their own cache persistance across 
multiple platforms and Microsoft Authentication Extensions makes this simpler.
+
+The supported platforms are Windows, Mac and Linux. 
+- Windows - 
[DPAPI](https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection)
 is used for encryption.
+- MAC - The MAC KeyChain is used.
+- Linux - [LibSecret](https://wiki.gnome.org/Projects/Libsecret) is used for 
encryption.
+
+> Note: It is recommended to use this library for cache persistance support 
for Public client applications such as Desktop apps only. In web applications, 
this may lead to scale and performance issues. Web applications are recommended 
to persist the cache in session. Take a look at this [webapp 
sample](https://github.com/Azure-Samples/ms-identity-python-webapp).
+
+## Installation
+
+You can find Microsoft Authentication Extensions for Python on 
[Pypi](https://pypi.org/project/msal-extensions/).
+1. If you haven't already, [install and/or upgrade the 
pip](https://pip.pypa.io/en/stable/installing/)
+   of your Python environment to a recent version. We tested with pip 18.1.
+2. Run `pip install msal-extensions`.
+
+## Versions
+
+This library follows [Semantic Versioning](http://semver.org/).
+
+You can find the changes for each version under
+[Releases](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/releases).
+
+## Usage
+
+The Microsoft Authentication Extensions library provides the 
`PersistedTokenCache` which accepts a platform-dependent persistence instance. 
This token cache can then be used to instantiate the `PublicClientApplication` 
in MSAL Python. 
+
+The token cache includes a file lock, and auto-reload behavior under the hood.
+
+
+
+Here is an example of this pattern for multiple platforms (taken from the 
complete [sample 
here](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/blob/dev/sample/token_cache_sample.py)):
+
+```python
+def build_persistence(location, fallback_to_plaintext=False):
+    """Build a suitable persistence instance based your current OS"""
+    if sys.platform.startswith('win'):
+        return FilePersistenceWithDataProtection(location)
+    if sys.platform.startswith('darwin'):
+        return KeychainPersistence(location, "my_service_name", 
"my_account_name")
+    if sys.platform.startswith('linux'):
+        try:
+            return LibsecretPersistence(
+                location,
+                schema_name="my_schema_name",
+                attributes={"my_attr1": "foo", "my_attr2": "bar"},
+                )
+        except:  # pylint: disable=bare-except
+            if not fallback_to_plaintext:
+                raise
+            logging.exception("Encryption unavailable. Opting in to plain 
text.")
+    return FilePersistence(location)
+
+persistence = build_persistence("token_cache.bin")
+print("Is this persistence encrypted?", persistence.is_encrypted)
+
+cache = PersistedTokenCache(persistence)
+```
+Now you can use it in an MSAL application like this:
+```python
+app = msal.PublicClientApplication("my_client_id", token_cache=cache)
+```
+
+## Community Help and Support
+
+We leverage Stack Overflow to work with the community on supporting Azure 
Active Directory and its SDKs, including this one!
+We highly recommend you ask your questions on Stack Overflow (we're all on 
there!).
+Also browse existing issues to see if someone has had your question before.
+
+We recommend you use the "msal" tag so we can see it!
+Here is the latest Q&A on Stack Overflow for MSAL:
+[http://stackoverflow.com/questions/tagged/msal](http://stackoverflow.com/questions/tagged/msal)
+
+
+## Contributing
+
+All code is licensed under the MIT license and we triage actively on GitHub.
 
 This project welcomes contributions and suggestions.  Most contributions 
require you to agree to a
 Contributor License Agreement (CLA) declaring that you have the right to, and 
actually do, grant us
@@ -9,6 +89,7 @@
 a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow 
the instructions
 provided by the bot. You will only need to do this once across all repos using 
our CLA.
 
-This project has adopted the [Microsoft Open Source Code of 
Conduct](https://opensource.microsoft.com/codeofconduct/).
-For more information see the [Code of Conduct 
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
-contact [openc...@microsoft.com](mailto:openc...@microsoft.com) with any 
additional questions or comments.
+
+## We value and adhere to the Microsoft Open Source Code of Conduct
+
+This project has adopted the [Microsoft Open Source Code of 
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information 
see the [Code of Conduct 
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact 
[openc...@microsoft.com](mailto:openc...@microsoft.com) with any additional 
questions or comments.
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/msal_extensions/__init__.py 
new/msal-extensions-0.3.0/msal_extensions/__init__.py
--- old/msal-extensions-0.1.3/msal_extensions/__init__.py       2019-11-02 
02:27:01.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions/__init__.py       2020-09-01 
22:42:14.000000000 +0200
@@ -1,11 +1,20 @@
 """Provides auxiliary functionality to the `msal` package."""
-__version__ = "0.1.3"
+__version__ = "0.3.0"
 
 import sys
 
+from .persistence import (
+    FilePersistence,
+    FilePersistenceWithDataProtection,
+    KeychainPersistence,
+    LibsecretPersistence,
+    )
+from .cache_lock import CrossPlatLock
+from .token_cache import PersistedTokenCache
+
 if sys.platform.startswith('win'):
     from .token_cache import WindowsTokenCache as TokenCache
 elif sys.platform.startswith('darwin'):
     from .token_cache import OSXTokenCache as TokenCache
 else:
-    from .token_cache import UnencryptedTokenCache as TokenCache
+    from .token_cache import FileTokenCache as TokenCache
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/msal_extensions/cache_lock.py 
new/msal-extensions-0.3.0/msal_extensions/cache_lock.py
--- old/msal-extensions-0.1.3/msal_extensions/cache_lock.py     2019-11-02 
02:27:01.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions/cache_lock.py     2020-09-01 
22:42:14.000000000 +0200
@@ -3,6 +3,7 @@
 import sys
 import errno
 import portalocker
+from distutils.version import LooseVersion
 
 
 class CrossPlatLock(object):
@@ -12,22 +13,30 @@
     """
     def __init__(self, lockfile_path):
         self._lockpath = lockfile_path
-        self._fh = None
+        # Support for passing through arguments to the open syscall was added 
in v1.4.0
+        open_kwargs = {'buffering': 0} if 
LooseVersion(portalocker.__version__) >= LooseVersion("1.4.0") else {}
+        self._lock = portalocker.Lock(
+            lockfile_path,
+            mode='wb+',
+            # In posix systems, we HAVE to use LOCK_EX(exclusive lock) bitwise 
ORed
+            # with LOCK_NB(non-blocking) to avoid blocking on lock acquisition.
+            # More information here:
+            # https://docs.python.org/3/library/fcntl.html#fcntl.lockf
+            flags=portalocker.LOCK_EX | portalocker.LOCK_NB,
+            **open_kwargs)
 
     def __enter__(self):
-        pid = os.getpid()
-
-        self._fh = open(self._lockpath, 'wb+', buffering=0)
-        portalocker.lock(self._fh, portalocker.LOCK_EX)
-        self._fh.write('{} {}'.format(pid, sys.argv[0]).encode('utf-8'))
+        file_handle = self._lock.__enter__()
+        file_handle.write('{} {}'.format(os.getpid(), 
sys.argv[0]).encode('utf-8'))
+        return file_handle
 
     def __exit__(self, *args):
-        self._fh.close()
+        self._lock.__exit__(*args)
         try:
             # Attempt to delete the lockfile. In either of the failure cases 
enumerated below, it is
             # likely that another process has raced this one and ended up 
clearing or locking the
             # file for itself.
             os.remove(self._lockpath)
-        except OSError as ex:
+        except OSError as ex:  # pylint: disable=invalid-name
             if ex.errno != errno.ENOENT and ex.errno != errno.EACCES:
                 raise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/msal_extensions/libsecret.py 
new/msal-extensions-0.3.0/msal_extensions/libsecret.py
--- old/msal-extensions-0.1.3/msal_extensions/libsecret.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions/libsecret.py      2020-09-01 
22:42:14.000000000 +0200
@@ -0,0 +1,139 @@
+"""Implements a Linux specific TokenCache, and provides auxiliary helper types.
+
+This module depends on PyGObject. But `pip install pygobject` would typically 
fail,
+until you install its dependencies first. For example, on a Debian Linux, you 
need::
+
+    sudo apt install libgirepository1.0-dev libcairo2-dev python3-dev 
gir1.2-secret-1
+    pip install pygobject
+
+Alternatively, you could skip Cairo & PyCairo, but you still need to do all 
these
+(derived from https://gitlab.gnome.org/GNOME/pygobject/-/issues/395)::
+
+    sudo apt install libgirepository1.0-dev python3-dev gir1.2-secret-1
+    pip install wheel
+    PYGOBJECT_WITHOUT_PYCAIRO=1 pip install --no-build-isolation pygobject
+"""
+import logging
+
+logger = logging.getLogger(__name__)
+
+try:
+    import gi  # 
https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux
+except ImportError:
+    logger.exception(
+        """Runtime dependency of PyGObject is missing.
+Depends on your Linux distro, you could install it system-wide by something 
like:
+    sudo apt install python3-gi python3-gi-cairo gir1.2-secret-1
+If necessary, please refer to PyGObject's doc:
+https://pygobject.readthedocs.io/en/latest/getting_started.html
+""")
+    raise
+
+try:
+    # pylint: disable=no-name-in-module
+    gi.require_version("Secret", "1")  # Would require a package 
gir1.2-secret-1
+    # pylint: disable=wrong-import-position
+    from gi.repository import Secret  # Would require a package gir1.2-secret-1
+except (ValueError, ImportError):
+    logger.exception(
+        """Require a package "gir1.2-secret-1" which could be installed by:
+        sudo apt install gir1.2-secret-1
+        """)
+    raise
+
+class LibSecretAgent(object):
+    """A loader/saver built on top of low-level libsecret"""
+    # Inspired by 
https://developer.gnome.org/libsecret/unstable/py-examples.html
+    def __init__(  # pylint: disable=too-many-arguments
+            self,
+            schema_name,
+            attributes,  # {"name": "value", ...}
+            label="",  # Helpful when visualizing secrets by other viewers
+            attribute_types=None,  # {name: SchemaAttributeType, ...}
+            collection=None,  # None means default collection
+            ):  # pylint: disable=bad-continuation
+        """This agent is built on top of lower level libsecret API.
+
+        Content stored via libsecret is associated with a bunch of attributes.
+
+        :param string schema_name:
+            Attributes would conceptually follow an existing schema.
+            But this class will do it in the other way around,
+            by automatically deriving a schema based on your attributes.
+            However, you will still need to provide a schema_name.
+            load() and save() will only operate on data with matching 
schema_name.
+
+        :param dict attributes:
+            Attributes are key-value pairs, represented as a Python dict here.
+            They will be used to filter content during load() and save().
+            Their arbitrary keys are strings.
+            Their arbitrary values can MEAN strings, integers and booleans,
+            but are always represented as strings, according to upstream 
sample:
+            https://developer.gnome.org/libsecret/0.18/py-store-example.html
+
+        :param string label:
+            It will not be used during data lookup and filtering.
+            It is only helpful when/if you visualize secrets by other viewers.
+
+        :param dict attribute_types:
+            Each key is the name of your each attribute.
+            The corresponding value will be one of the following three:
+
+            * Secret.SchemaAttributeType.STRING
+            * Secret.SchemaAttributeType.INTEGER
+            * Secret.SchemaAttributeType.BOOLEAN
+
+            But if all your attributes are Secret.SchemaAttributeType.STRING,
+            you do not need to provide this types definition at all.
+
+        :param collection:
+            The default value `None` means default collection.
+        """
+        self._collection = collection
+        self._attributes = attributes or {}
+        self._label = label
+        self._schema = Secret.Schema.new(schema_name, Secret.SchemaFlags.NONE, 
{
+            k: (attribute_types or {}).get(k, 
Secret.SchemaAttributeType.STRING)
+            for k in self._attributes})
+
+    def save(self, data):
+        """Store data. Returns a boolean of whether operation was 
successful."""
+        return Secret.password_store_sync(
+            self._schema, self._attributes, self._collection, self._label,
+            data, None)
+
+    def load(self):
+        """Load a password in the secret service, return None when found 
nothing"""
+        return Secret.password_lookup_sync(self._schema, self._attributes, 
None)
+
+    def clear(self):
+        """Returns a boolean of whether any passwords were removed"""
+        return Secret.password_clear_sync(self._schema, self._attributes, None)
+
+
+def trial_run():
+    """This trial run will raise an exception if libsecret is not functioning.
+
+    Even after you installed all the dependencies so that your script can 
start,
+    or even if your previous run was successful, your script could fail next 
time,
+    for example when it will be running inside a headless SSH session.
+
+    You do not have to do trial_run. The exception would also be raised by 
save().
+    """
+    try:
+        agent = LibSecretAgent("Test Schema", {"attr1": "foo", "attr2": "bar"})
+        payload = "Test Data"
+        agent.save(payload)  # It would fail when running inside an SSH session
+        assert agent.load() == payload  # This line is probably not reachable
+        agent.clear()
+    except (gi.repository.GLib.Error, AssertionError):
+        message = """libsecret did not perform properly.
+* If you encountered error "Remote error from secret service:
+  org.freedesktop.DBus.Error.ServiceUnknown",
+  you may need to install gnome-keyring package.
+* Headless mode (such as in an ssh session) is not supported.
+"""
+        logger.exception(message)  # This log contains trace stack for 
debugging
+        logger.warning(message)  # This is visible by default
+        raise
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/msal_extensions/persistence.py 
new/msal-extensions-0.3.0/msal_extensions/persistence.py
--- old/msal-extensions-0.1.3/msal_extensions/persistence.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions/persistence.py    2020-09-01 
22:42:14.000000000 +0200
@@ -0,0 +1,284 @@
+"""A generic persistence layer, optionally encrypted on Windows, OSX, and 
Linux.
+
+Should a certain encryption is unavailable, exception will be raised at 
run-time,
+rather than at import time.
+
+By successfully creating and using a certain persistence object,
+app developer would naturally know whether the data are protected by 
encryption.
+"""
+import abc
+import os
+import errno
+import logging
+try:
+    from pathlib import Path  # Built-in in Python 3
+except:
+    from pathlib2 import Path  # An extra lib for Python 2
+
+
+try:
+    ABC = abc.ABC
+except AttributeError:  # Python 2.7, abc exists, but not ABC
+    ABC = abc.ABCMeta("ABC", (object,), {"__slots__": ()})  # type: ignore
+
+
+logger = logging.getLogger(__name__)
+
+
+def _mkdir_p(path):
+    """Creates a directory, and any necessary parents.
+
+    This implementation based on a Stack Overflow question that can be found 
here:
+    https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python
+
+    If the path provided is an existing file, this function raises an 
exception.
+    :param path: The directory name that should be created.
+    """
+    if not path:
+        return  # NO-OP
+    try:
+        os.makedirs(path)
+    except OSError as exp:
+        if exp.errno == errno.EEXIST and os.path.isdir(path):
+            pass
+        else:
+            raise
+
+
+# We do not aim to wrap every os-specific exception.
+# Here we define only the most common one,
+# otherwise caller would need to catch os-specific persistence exceptions.
+class PersistenceNotFound(IOError):  # Use IOError rather than OSError as base,
+        # because historically an IOError was bubbled up and expected.
+        # 
https://github.com/AzureAD/microsoft-authentication-extensions-for-python/blob/0.2.2/msal_extensions/token_cache.py#L38
+        # Now we want to maintain backward compatibility even when using 
Python 2.x
+        # It makes no difference in Python 3.3+ where IOError is an alias of 
OSError.
+    def __init__(
+            self,
+            err_no=errno.ENOENT, message="Persistence not found", 
location=None):
+        super(PersistenceNotFound, self).__init__(err_no, message, location)
+
+
+class BasePersistence(ABC):
+    """An abstract persistence defining the common interface of this family"""
+
+    is_encrypted = False  # Default to False. To be overridden by sub-classes.
+
+    @abc.abstractmethod
+    def save(self, content):
+        # type: (str) -> None
+        """Save the content into this persistence"""
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def load(self):
+        # type: () -> str
+        """Load content from this persistence.
+
+        Could raise PersistenceNotFound if no save() was called before.
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def time_last_modified(self):
+        """Get the last time when this persistence has been modified.
+
+        Could raise PersistenceNotFound if no save() was called before.
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def get_location(self):
+        """Return the file path which this persistence stores (meta)data 
into"""
+        raise NotImplementedError
+
+
+class FilePersistence(BasePersistence):
+    """A generic persistence, storing data in a plain-text file"""
+
+    def __init__(self, location):
+        if not location:
+            raise ValueError("Requires a file path")
+        self._location = os.path.expanduser(location)
+        _mkdir_p(os.path.dirname(self._location))
+
+    def save(self, content):
+        # type: (str) -> None
+        """Save the content into this persistence"""
+        with open(self._location, 'w+') as handle:
+            handle.write(content)
+
+    def load(self):
+        # type: () -> str
+        """Load content from this persistence"""
+        try:
+            with open(self._location, 'r') as handle:
+                return handle.read()
+        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works 
across platform
+            if exp.errno == errno.ENOENT:
+                raise PersistenceNotFound(
+                    message=(
+                        "Persistence not initialized. "
+                        "You can recover by calling a save() first."),
+                    location=self._location,
+                    )
+            raise
+
+
+    def time_last_modified(self):
+        try:
+            return os.path.getmtime(self._location)
+        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works 
across platform
+            if exp.errno == errno.ENOENT:
+                raise PersistenceNotFound(
+                    message=(
+                        "Persistence not initialized. "
+                        "You can recover by calling a save() first."),
+                    location=self._location,
+                    )
+            raise
+
+    def touch(self):
+        """To touch this file-based persistence without writing content into 
it"""
+        Path(self._location).touch()  # For os.path.getmtime() to work
+
+    def get_location(self):
+        return self._location
+
+
+class FilePersistenceWithDataProtection(FilePersistence):
+    """A generic persistence with data stored in a file,
+    protected by Win32 encryption APIs on Windows"""
+    is_encrypted = True
+
+    def __init__(self, location, entropy=''):
+        """Initialization could fail due to unsatisfied dependency"""
+        # pylint: disable=import-outside-toplevel
+        from .windows import WindowsDataProtectionAgent
+        self._dp_agent = WindowsDataProtectionAgent(entropy=entropy)
+        super(FilePersistenceWithDataProtection, self).__init__(location)
+
+    def save(self, content):
+        # type: (str) -> None
+        data = self._dp_agent.protect(content)
+        with open(self._location, 'wb+') as handle:
+            handle.write(data)
+
+    def load(self):
+        # type: () -> str
+        try:
+            with open(self._location, 'rb') as handle:
+                data = handle.read()
+            return self._dp_agent.unprotect(data)
+        except EnvironmentError as exp:  # EnvironmentError in Py 2.7 works 
across platform
+            if exp.errno == errno.ENOENT:
+                raise PersistenceNotFound(
+                    message=(
+                        "Persistence not initialized. "
+                        "You can recover by calling a save() first."),
+                    location=self._location,
+                    )
+            logger.exception(
+                "DPAPI error likely caused by file content not previously 
encrypted. "
+                "App developer should migrate by calling save(plaintext) 
first.")
+            raise
+
+
+class KeychainPersistence(BasePersistence):
+    """A generic persistence with data stored in,
+    and protected by native Keychain libraries on OSX"""
+    is_encrypted = True
+
+    def __init__(self, signal_location, service_name, account_name):
+        """Initialization could fail due to unsatisfied dependency.
+
+        :param signal_location: See 
:func:`persistence.LibsecretPersistence.__init__`
+        """
+        if not (service_name and account_name):  # It would hang on OSX
+            raise ValueError("service_name and account_name are required")
+        from .osx import Keychain, KeychainError  # pylint: 
disable=import-outside-toplevel
+        self._file_persistence = FilePersistence(signal_location)  # Favor 
composition
+        self._Keychain = Keychain  # pylint: disable=invalid-name
+        self._KeychainError = KeychainError  # pylint: disable=invalid-name
+        self._service_name = service_name
+        self._account_name = account_name
+
+    def save(self, content):
+        with self._Keychain() as locker:
+            locker.set_generic_password(
+                self._service_name, self._account_name, content)
+        self._file_persistence.touch()  # For time_last_modified()
+
+    def load(self):
+        with self._Keychain() as locker:
+            try:
+                return locker.get_generic_password(
+                    self._service_name, self._account_name)
+            except self._KeychainError as ex:
+                if ex.exit_status == self._KeychainError.ITEM_NOT_FOUND:
+                    # This happens when a load() is called before a save().
+                    # We map it into cross-platform error for unified catching.
+                    raise PersistenceNotFound(
+                        location="Service:{} Account:{}".format(
+                            self._service_name, self._account_name),
+                        message=(
+                            "Keychain persistence not initialized. "
+                            "You can recover by call a save() first."),
+                        )
+                raise  # We do not intend to hide any other underlying 
exceptions
+
+    def time_last_modified(self):
+        return self._file_persistence.time_last_modified()
+
+    def get_location(self):
+        return self._file_persistence.get_location()
+
+
+class LibsecretPersistence(BasePersistence):
+    """A generic persistence with data stored in,
+    and protected by native libsecret libraries on Linux"""
+    is_encrypted = True
+
+    def __init__(self, signal_location, schema_name, attributes, **kwargs):
+        """Initialization could fail due to unsatisfied dependency.
+
+        :param string signal_location:
+            Besides saving the real payload into encrypted storage,
+            this class will also touch this signal file.
+            Applications may listen a FileSystemWatcher.Changed event for 
reload.
+            
https://docs.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.changed?view=netframework-4.8#remarks
+        :param string schema_name: See 
:func:`libsecret.LibSecretAgent.__init__`
+        :param dict attributes: See :func:`libsecret.LibSecretAgent.__init__`
+        """
+        # pylint: disable=import-outside-toplevel
+        from .libsecret import (  # This uncertain import is deferred till 
runtime
+            LibSecretAgent, trial_run)
+        trial_run()
+        self._agent = LibSecretAgent(schema_name, attributes, **kwargs)
+        self._file_persistence = FilePersistence(signal_location)  # Favor 
composition
+
+    def save(self, content):
+        if self._agent.save(content):
+            self._file_persistence.touch()  # For time_last_modified()
+
+    def load(self):
+        data = self._agent.load()
+        if data is None:
+            # Lower level libsecret would return None when found nothing. Here
+            # in persistence layer, we convert it to a unified error for 
consistence.
+            raise PersistenceNotFound(message=(
+                "Keyring persistence not initialized. "
+                "You can recover by call a save() first."))
+        return data
+
+    def time_last_modified(self):
+        return self._file_persistence.time_last_modified()
+
+    def get_location(self):
+        return self._file_persistence.get_location()
+
+# We could also have a KeyringPersistence() which can then be used together
+# with a FilePersistence to achieve
+#  
https://github.com/AzureAD/microsoft-authentication-extensions-for-python/issues/12
+# But this idea is not pursued at this time.
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/msal_extensions/token_cache.py 
new/msal-extensions-0.3.0/msal_extensions/token_cache.py
--- old/msal-extensions-0.1.3/msal_extensions/token_cache.py    2019-11-02 
02:27:01.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions/token_cache.py    2020-09-01 
22:42:14.000000000 +0200
@@ -1,156 +1,88 @@
 """Generic functions and types for working with a TokenCache that is not 
platform specific."""
 import os
-import sys
 import warnings
 import time
-import errno
+import logging
+
 import msal
-from .cache_lock import CrossPlatLock
 
-if sys.platform.startswith('win'):
-    from .windows import WindowsDataProtectionAgent
-elif sys.platform.startswith('darwin'):
-    from .osx import Keychain
-
-def _mkdir_p(path):
-    """Creates a directory, and any necessary parents.
-
-    This implementation based on a Stack Overflow question that can be found 
here:
-    https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python
-
-    If the path provided is an existing file, this function raises an 
exception.
-    :param path: The directory name that should be created.
-    """
-    try:
-        os.makedirs(path)
-    except OSError as exp:
-        if exp.errno == errno.EEXIST and os.path.isdir(path):
-            pass
-        else:
-            raise
+from .cache_lock import CrossPlatLock
+from .persistence import (
+    _mkdir_p, PersistenceNotFound, FilePersistence,
+    FilePersistenceWithDataProtection, KeychainPersistence)
 
 
-class FileTokenCache(msal.SerializableTokenCache):
-    """Implements basic unprotected SerializableTokenCache to a plain-text 
file."""
-    def __init__(self,
-                 cache_location,
-                 lock_location=None):
-        super(FileTokenCache, self).__init__()
-        self._cache_location = cache_location
-        self._lock_location = lock_location or self._cache_location + 
'.lockfile'
-        self._last_sync = 0  # _last_sync is a Unixtime
+logger = logging.getLogger(__name__)
 
-        self._cache_location = os.path.expanduser(self._cache_location)
-        self._lock_location = os.path.expanduser(self._lock_location)
+class PersistedTokenCache(msal.SerializableTokenCache):
+    """A token cache using given persistence layer, coordinated by a file 
lock."""
 
+    def __init__(self, persistence, lock_location=None):
+        super(PersistedTokenCache, self).__init__()
+        self._lock_location = (
+            os.path.expanduser(lock_location) if lock_location
+            else persistence.get_location() + ".lockfile")
         _mkdir_p(os.path.dirname(self._lock_location))
-        _mkdir_p(os.path.dirname(self._cache_location))
+        self._persistence = persistence
+        self._last_sync = 0  # _last_sync is a Unixtime
+        self.is_encrypted = persistence.is_encrypted
 
-    def _needs_refresh(self):
-        # type: () -> Bool
-        """
-        Inspects the file holding the encrypted TokenCache to see if a read is 
necessary.
-        :return: True if there are changes not reflected in memory, False 
otherwise.
-        """
+    def _reload_if_necessary(self):
+        # type: () -> None
+        """Reload cache from persistence layer, if necessary"""
         try:
-            updated = os.path.getmtime(self._cache_location)
-            return self._last_sync < updated
-        except IOError as exp:
-            if exp.errno != errno.ENOENT:
-                raise exp
-            return False
-
-    def _write(self, contents):
-        # type: (str) -> None
-        """Handles actually committing the serialized form of this TokenCache 
to persisted storage.
-        For types derived of this, class that will be a file, which has the 
ability to track a last
-        modified time.
-
-        :param contents: The serialized contents of a TokenCache
-        """
-        with open(self._cache_location, 'w+') as handle:
-            handle.write(contents)
-
-    def _read(self):
-        # type: () -> str
-        """Fetches the contents of a file and invokes deserialization."""
-        with open(self._cache_location, 'r') as handle:
-            return handle.read()
+            if self._last_sync < self._persistence.time_last_modified():
+                self.deserialize(self._persistence.load())
+                self._last_sync = time.time()
+        except PersistenceNotFound:
+            # From cache's perspective, a nonexistent persistence is a NO-OP.
+            pass
+        # However, existing data unable to be decrypted will still be bubbled 
up.
 
     def modify(self, credential_type, old_entry, new_key_value_pairs=None):
         with CrossPlatLock(self._lock_location):
-            if self._needs_refresh():
-                try:
-                    self.deserialize(self._read())
-                except IOError as exp:
-                    if exp.errno != errno.ENOENT:
-                        raise
-            super(FileTokenCache, self).modify(
+            self._reload_if_necessary()
+            super(PersistedTokenCache, self).modify(
                 credential_type,
                 old_entry,
                 new_key_value_pairs=new_key_value_pairs)
-            self._write(self.serialize())
-            self._last_sync = os.path.getmtime(self._cache_location)
+            self._persistence.save(self.serialize())
+            self._last_sync = time.time()
 
     def find(self, credential_type, **kwargs):  # pylint: 
disable=arguments-differ
         with CrossPlatLock(self._lock_location):
-            if self._needs_refresh():
-                try:
-                    self.deserialize(self._read())
-                except IOError as exp:
-                    if exp.errno != errno.ENOENT:
-                        raise
-                self._last_sync = time.time()
-            return super(FileTokenCache, self).find(credential_type, **kwargs)
+            self._reload_if_necessary()
+            return super(PersistedTokenCache, self).find(credential_type, 
**kwargs)
+
 
+class FileTokenCache(PersistedTokenCache):
+    """A token cache which uses plain text file to store your tokens."""
+    def __init__(self, cache_location, **ignored):  # pylint: 
disable=unused-argument
+        warnings.warn("You are using an unprotected token cache", 
RuntimeWarning)
+        warnings.warn("Use PersistedTokenCache(...) instead", 
DeprecationWarning)
+        super(FileTokenCache, self).__init__(FilePersistence(cache_location))
 
-class UnencryptedTokenCache(FileTokenCache):
-    """An unprotected token cache to default to when no-platform specific 
option is available."""
-    def __init__(self, cache_location, **kwargs):
-        warnings.warn("You are using an unprotected token cache, "
-                      "because an encrypted option is not available for 
{}".format(sys.platform),
-                      RuntimeWarning)
-        super(UnencryptedTokenCache, self).__init__(cache_location, **kwargs)
-
-
-class WindowsTokenCache(FileTokenCache):
-    """A SerializableTokenCache implementation which uses Win32 encryption 
APIs to protect your
-    tokens.
-    """
-    def __init__(self, cache_location, entropy='', **kwargs):
-        super(WindowsTokenCache, self).__init__(cache_location, **kwargs)
-        self._dp_agent = WindowsDataProtectionAgent(entropy=entropy)
-
-    def _write(self, contents):
-        with open(self._cache_location, 'wb') as handle:
-            handle.write(self._dp_agent.protect(contents))
-
-    def _read(self):
-        with open(self._cache_location, 'rb') as handle:
-            cipher_text = handle.read()
-        return self._dp_agent.unprotect(cipher_text)
-
-
-class OSXTokenCache(FileTokenCache):
-    """A SerializableTokenCache implementation which uses native Keychain 
libraries to protect your
-    tokens.
-    """
+UnencryptedTokenCache = FileTokenCache  # For backward compatibility
 
+
+class WindowsTokenCache(PersistedTokenCache):
+    """A token cache which uses Windows DPAPI to encrypt your tokens."""
+    def __init__(
+            self, cache_location, entropy='',
+            **ignored):  # pylint: disable=unused-argument
+        warnings.warn("Use PersistedTokenCache(...) instead", 
DeprecationWarning)
+        super(WindowsTokenCache, self).__init__(
+            FilePersistenceWithDataProtection(cache_location, entropy=entropy))
+
+
+class OSXTokenCache(PersistedTokenCache):
+    """A token cache which uses native Keychain libraries to encrypt your 
tokens."""
     def __init__(self,
                  cache_location,
                  service_name='Microsoft.Developer.IdentityService',
                  account_name='MSALCache',
-                 **kwargs):
-        super(OSXTokenCache, self).__init__(cache_location, **kwargs)
-        self._service_name = service_name
-        self._account_name = account_name
-
-    def _read(self):
-        with Keychain() as locker:
-            return locker.get_generic_password(self._service_name, 
self._account_name)
-
-    def _write(self, contents):
-        with Keychain() as locker:
-            locker.set_generic_password(self._service_name, 
self._account_name, contents)
-            with open(self._cache_location, "w+") as handle:
-                handle.write('{} {}'.format(os.getpid(), sys.argv[0]))
+                 **ignored):  # pylint: disable=unused-argument
+        warnings.warn("Use PersistedTokenCache(...) instead", 
DeprecationWarning)
+        super(OSXTokenCache, self).__init__(
+            KeychainPersistence(cache_location, service_name, account_name))
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msal-extensions-0.1.3/msal_extensions.egg-info/PKG-INFO 
new/msal-extensions-0.3.0/msal_extensions.egg-info/PKG-INFO
--- old/msal-extensions-0.1.3/msal_extensions.egg-info/PKG-INFO 2019-11-02 
02:27:37.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions.egg-info/PKG-INFO 2020-09-01 
22:43:13.000000000 +0200
@@ -1,11 +1,104 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: msal-extensions
-Version: 0.1.3
+Version: 0.3.0
 Summary: UNKNOWN
 Home-page: UNKNOWN
-Author: UNKNOWN
-Author-email: UNKNOWN
 License: UNKNOWN
-Description: UNKNOWN
+Description: 
+        # Microsoft Authentication Extensions for Python
+        
+        The Microsoft Authentication Extensions for Python offers secure 
mechanisms for client applications to perform cross-platform token cache 
serialization and persistence. It gives additional support to the [Microsoft 
Authentication Library for Python 
(MSAL)](https://github.com/AzureAD/microsoft-authentication-library-for-python).
 
+        
+        MSAL Python supports an in-memory cache by default and provides the 
[SerializableTokenCache](https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache)
 to perform cache serialization. You can read more about this in the MSAL 
Python 
[documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-python-token-cache-serialization).
 Developers are required to implement their own cache persistance across 
multiple platforms and Microsoft Authentication Extensions makes this simpler.
+        
+        The supported platforms are Windows, Mac and Linux. 
+        - Windows - 
[DPAPI](https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection)
 is used for encryption.
+        - MAC - The MAC KeyChain is used.
+        - Linux - [LibSecret](https://wiki.gnome.org/Projects/Libsecret) is 
used for encryption.
+        
+        > Note: It is recommended to use this library for cache persistance 
support for Public client applications such as Desktop apps only. In web 
applications, this may lead to scale and performance issues. Web applications 
are recommended to persist the cache in session. Take a look at this [webapp 
sample](https://github.com/Azure-Samples/ms-identity-python-webapp).
+        
+        ## Installation
+        
+        You can find Microsoft Authentication Extensions for Python on 
[Pypi](https://pypi.org/project/msal-extensions/).
+        1. If you haven't already, [install and/or upgrade the 
pip](https://pip.pypa.io/en/stable/installing/)
+           of your Python environment to a recent version. We tested with pip 
18.1.
+        2. Run `pip install msal-extensions`.
+        
+        ## Versions
+        
+        This library follows [Semantic Versioning](http://semver.org/).
+        
+        You can find the changes for each version under
+        
[Releases](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/releases).
+        
+        ## Usage
+        
+        The Microsoft Authentication Extensions library provides the 
`PersistedTokenCache` which accepts a platform-dependent persistence instance. 
This token cache can then be used to instantiate the `PublicClientApplication` 
in MSAL Python. 
+        
+        The token cache includes a file lock, and auto-reload behavior under 
the hood.
+        
+        
+        
+        Here is an example of this pattern for multiple platforms (taken from 
the complete [sample 
here](https://github.com/AzureAD/microsoft-authentication-extensions-for-python/blob/dev/sample/token_cache_sample.py)):
+        
+        ```python
+        def build_persistence(location, fallback_to_plaintext=False):
+            """Build a suitable persistence instance based your current OS"""
+            if sys.platform.startswith('win'):
+                return FilePersistenceWithDataProtection(location)
+            if sys.platform.startswith('darwin'):
+                return KeychainPersistence(location, "my_service_name", 
"my_account_name")
+            if sys.platform.startswith('linux'):
+                try:
+                    return LibsecretPersistence(
+                        location,
+                        schema_name="my_schema_name",
+                        attributes={"my_attr1": "foo", "my_attr2": "bar"},
+                        )
+                except:  # pylint: disable=bare-except
+                    if not fallback_to_plaintext:
+                        raise
+                    logging.exception("Encryption unavailable. Opting in to 
plain text.")
+            return FilePersistence(location)
+        
+        persistence = build_persistence("token_cache.bin")
+        print("Is this persistence encrypted?", persistence.is_encrypted)
+        
+        cache = PersistedTokenCache(persistence)
+        ```
+        Now you can use it in an MSAL application like this:
+        ```python
+        app = msal.PublicClientApplication("my_client_id", token_cache=cache)
+        ```
+        
+        ## Community Help and Support
+        
+        We leverage Stack Overflow to work with the community on supporting 
Azure Active Directory and its SDKs, including this one!
+        We highly recommend you ask your questions on Stack Overflow (we're 
all on there!).
+        Also browse existing issues to see if someone has had your question 
before.
+        
+        We recommend you use the "msal" tag so we can see it!
+        Here is the latest Q&A on Stack Overflow for MSAL:
+        
[http://stackoverflow.com/questions/tagged/msal](http://stackoverflow.com/questions/tagged/msal)
+        
+        
+        ## Contributing
+        
+        All code is licensed under the MIT license and we triage actively on 
GitHub.
+        
+        This project welcomes contributions and suggestions.  Most 
contributions require you to agree to a
+        Contributor License Agreement (CLA) declaring that you have the right 
to, and actually do, grant us
+        the rights to use your contribution. For details, visit 
https://cla.microsoft.com.
+        
+        When you submit a pull request, a CLA-bot will automatically determine 
whether you need to provide
+        a CLA and decorate the PR appropriately (e.g., label, comment). Simply 
follow the instructions
+        provided by the bot. You will only need to do this once across all 
repos using our CLA.
+        
+        
+        ## We value and adhere to the Microsoft Open Source Code of Conduct
+        
+        This project has adopted the [Microsoft Open Source Code of 
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information 
see the [Code of Conduct 
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact 
[openc...@microsoft.com](mailto:openc...@microsoft.com) with any additional 
questions or comments.
 Platform: UNKNOWN
 Classifier: Development Status :: 2 - Pre-Alpha
+Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msal-extensions-0.1.3/msal_extensions.egg-info/SOURCES.txt 
new/msal-extensions-0.3.0/msal_extensions.egg-info/SOURCES.txt
--- old/msal-extensions-0.1.3/msal_extensions.egg-info/SOURCES.txt      
2019-11-02 02:27:37.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions.egg-info/SOURCES.txt      
2020-09-01 22:43:13.000000000 +0200
@@ -3,7 +3,9 @@
 setup.py
 msal_extensions/__init__.py
 msal_extensions/cache_lock.py
+msal_extensions/libsecret.py
 msal_extensions/osx.py
+msal_extensions/persistence.py
 msal_extensions/token_cache.py
 msal_extensions/windows.py
 msal_extensions.egg-info/PKG-INFO
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msal-extensions-0.1.3/msal_extensions.egg-info/requires.txt 
new/msal-extensions-0.3.0/msal_extensions.egg-info/requires.txt
--- old/msal-extensions-0.1.3/msal_extensions.egg-info/requires.txt     
2019-11-02 02:27:37.000000000 +0100
+++ new/msal-extensions-0.3.0/msal_extensions.egg-info/requires.txt     
2020-09-01 22:43:13.000000000 +0200
@@ -1,2 +1,10 @@
 msal<2.0.0,>=0.4.1
+
+[:platform_system != "Windows"]
 portalocker~=1.0
+
+[:platform_system == "Windows"]
+portalocker~=1.6
+
+[:python_version < "3.0"]
+pathlib2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msal-extensions-0.1.3/setup.py 
new/msal-extensions-0.3.0/setup.py
--- old/msal-extensions-0.1.3/setup.py  2019-11-02 02:27:01.000000000 +0100
+++ new/msal-extensions-0.3.0/setup.py  2020-09-01 22:42:14.000000000 +0200
@@ -8,17 +8,28 @@
     io.open('msal_extensions/__init__.py', encoding='utf_8_sig').read()
     ).group(1)
 
+try:
+    long_description = open('README.md').read()
+except OSError:
+    long_description = "README.md is not accessible on TRAVIS CI's Python 3.5"
+
 setup(
     name='msal-extensions',
     version=__version__,
     packages=find_packages(),
+    long_description=long_description,
+    long_description_content_type="text/markdown",
     classifiers=[
         'Development Status :: 2 - Pre-Alpha',
     ],
     package_data={'': ['LICENSE']},
     install_requires=[
         'msal>=0.4.1,<2.0.0',
-        'portalocker~=1.0',
+        "portalocker~=1.6;platform_system=='Windows'",
+        "portalocker~=1.0;platform_system!='Windows'",
+        "pathlib2;python_version<'3.0'",
+        ## We choose to NOT define a hard dependency on this.
+        # "pygobject>=3,<4;platform_system=='Linux'",
     ],
     tests_require=['pytest'],
 )


Reply via email to