Skip to content

Commit 8404023

Browse files
authored
Merge pull request #105 from AzureAD/release-0.3.1
Release 0.3.1
2 parents b90d20e + ea91eda commit 8404023

File tree

16 files changed

+319
-60
lines changed

16 files changed

+319
-60
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: CI
5+
6+
on:
7+
push:
8+
pull_request:
9+
branches: [ dev ]
10+
# This guards against unknown PR until a community member vet it and label it.
11+
types: [ labeled ]
12+
13+
jobs:
14+
ci:
15+
16+
runs-on: ${{ matrix.os }}
17+
strategy:
18+
matrix:
19+
python-version: [3.7, 3.8, 3.9, 2.7]
20+
os: [ubuntu-latest, windows-latest, macos-latest]
21+
include:
22+
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-environment-variables-in-a-matrix
23+
- python-version: 3.7
24+
toxenv: "py37"
25+
- python-version: 3.8
26+
toxenv: "py38"
27+
- python-version: 3.9
28+
toxenv: "py39"
29+
- python-version: 2.7
30+
toxenv: "py27"
31+
- python-version: 3.9
32+
os: ubuntu-latest
33+
lint: "true"
34+
steps:
35+
- uses: actions/checkout@v2
36+
- name: Set up Python ${{ matrix.python-version }}
37+
uses: actions/setup-python@v2
38+
with:
39+
python-version: ${{ matrix.python-version }}
40+
- name: Install Linux dependencies for Python 2
41+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '2.7' }}
42+
run: |
43+
sudo apt update
44+
sudo apt install python-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1 gnome-keyring
45+
- name: Install Linux dependencies for Python 3
46+
if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version != '2.7' }}
47+
run: |
48+
sudo apt update
49+
sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1 gnome-keyring
50+
- name: Install PyGObject on Linux
51+
if: ${{ matrix.os == 'ubuntu-latest' }}
52+
run: |
53+
python -m pip install --upgrade pip
54+
python -m pip install pygobject
55+
- name: Install Python dependencies
56+
run: |
57+
python -m pip install --upgrade pip
58+
python -m pip install pylint tox pytest
59+
pip install .
60+
- name: Lint
61+
if: ${{ matrix.lint == 'true' }}
62+
run: |
63+
pylint msal_extensions
64+
# stop the build if there are Python syntax errors or undefined names
65+
#flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
66+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
67+
#flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
68+
- name: Test on Linux with encryption
69+
if: ${{ matrix.os == 'ubuntu-latest' }}
70+
run: |
71+
# Don't know why, but the pytest and "." have to be re-installed again for them to be used
72+
echo "echo secret_placeholder | gnome-keyring-daemon --unlock; pip install pytest .; pytest" > linux_test.sh
73+
chmod +x linux_test.sh
74+
sudo dbus-run-session -- ./linux_test.sh
75+
- name: Test on other platforms without encryption
76+
if: ${{ matrix.os != 'ubuntu-latest' }}
77+
env:
78+
TOXENV: ${{ matrix.toxenv }}
79+
run: |
80+
tox
81+
82+
cd:
83+
needs: ci
84+
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags') || github.ref == 'refs/heads/master')
85+
runs-on: ubuntu-latest
86+
steps:
87+
- uses: actions/checkout@v2
88+
- name: Set up Python 3.9
89+
uses: actions/setup-python@v2
90+
with:
91+
python-version: 3.9
92+
- name: Build a package for release
93+
run: |
94+
python -m pip install build --user
95+
python -m build --sdist --wheel --outdir dist/ .
96+
- name: Publish to TestPyPI
97+
uses: pypa/[email protected]
98+
if: github.ref == 'refs/heads/master'
99+
with:
100+
user: __token__
101+
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
102+
repository_url: https://test.pypi.org/legacy/
103+
- name: Publish to PyPI
104+
if: startsWith(github.ref, 'refs/tags')
105+
uses: pypa/[email protected]
106+
with:
107+
user: __token__
108+
password: ${{ secrets.PYPI_API_TOKEN }}
109+

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,5 @@ ASALocalRun/
332332

333333
# MFractors (Xamarin productivity tool) working folder
334334
.mfractor/
335+
336+
.eggs/

.pylintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
good-names=
33
logger
44
disable=
5+
super-with-arguments, # For Python 2.x
6+
raise-missing-from, # For Python 2.x
57
trailing-newlines,
68
useless-object-inheritance

.travis.yml

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,42 @@ matrix:
99
before_install:
1010
- sudo apt update
1111
- sudo apt install python-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
12+
- pip install --upgrade pip
1213
- python: "3.5"
1314
env: TOXENV=py35
1415
os: linux
1516
before_install:
1617
- sudo apt update
1718
- sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
18-
- python: "3.6"
19-
env: TOXENV=py36
20-
os: linux
21-
before_install:
22-
- sudo apt update
23-
- sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
19+
- pip install --upgrade pip
20+
21+
## Somehow cryptography is not able to be compiled and installed on Python 3.6
22+
#- python: "3.6"
23+
# env:
24+
# - TOXENV=py36
25+
# - CRYPTOGRAPHY_DONT_BUILD_RUST=1
26+
# os: linux
27+
# before_install:
28+
# - sudo apt update
29+
# - sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
30+
# - pip install --upgrade pip
31+
2432
- python: "3.7"
2533
env: TOXENV=py37
2634
os: linux
2735
dist: xenial
2836
before_install:
2937
- sudo apt update
3038
- sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
39+
- pip install --upgrade pip
3140
- python: "3.8"
3241
env: TOXENV=py38
3342
os: linux
3443
dist: xenial
3544
before_install:
3645
- sudo apt update
3746
- sudo apt install python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-secret-1
47+
- pip install --upgrade pip
3848
- name: "Python 3.7 on macOS"
3949
env: TOXENV=py37
4050
os: osx
@@ -43,17 +53,23 @@ matrix:
4353
- name: "Python 2.7 on Windows"
4454
env: TOXENV=py27 PATH=/c/Python27:/c/Python27/Scripts:$PATH
4555
os: windows
46-
before_install: choco install python2
56+
before_install:
57+
- choco install python2
58+
- pip install --upgrade --user pip
4759
language: shell
4860
- name: "Python 3.5 on Windows"
4961
env: TOXENV=py35 PATH=/c/Python35:/c/Python35/Scripts:$PATH
5062
os: windows
51-
before_install: choco install python3 --version 3.5.4
63+
before_install:
64+
- choco install python3 --version 3.5.4
65+
- pip install --upgrade --user pip
5266
language: shell
5367
- name: "Python 3.7 on Windows"
5468
env: TOXENV=py37 PATH=/c/Python37:/c/Python37/Scripts:$PATH
5569
os: windows
56-
before_install: choco install python3 --version 3.7.3
70+
before_install:
71+
- choco install python3 --version 3.7.3
72+
- pip install --upgrade --user pip
5773
language: shell
5874

5975
install:

Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# TODO: Can this Dockerfile use multi-stage build?
2+
# Final size 690MB. (It would be 1.16 GB if started with python:3 as base)
3+
FROM python:3-slim
4+
5+
# Install Generic PyGObject (sans GTK)
6+
#The following somehow won't work:
7+
#RUN apt-get update && apt-get install -y python3-gi python3-gi-cairo
8+
RUN apt-get update && apt-get install -y \
9+
libcairo2-dev \
10+
libgirepository1.0-dev \
11+
python3-dev
12+
RUN pip install "pygobject>=3,<4"
13+
14+
# Install MSAL Extensions dependencies
15+
# Don't know how to get container talk to dbus on host,
16+
# so we choose to create a self-contained image by installing gnome-keyring
17+
RUN apt-get install -y \
18+
gir1.2-secret-1 \
19+
gnome-keyring
20+
21+
# Not strictly necessary, but we include a pytest (which is only 3MB) to facilitate testing.
22+
RUN pip install "pytest>=6,<7"
23+
24+
# Install MSAL Extensions. Upgrade the pinned version number to trigger a new image build.
25+
RUN pip install "msal-extensions==0.3"
26+
27+
# This setup is inspired from https://github.com/jaraco/keyring#using-keyring-on-headless-linux-systems-in-a-docker-container
28+
ENTRYPOINT ["dbus-run-session", "--"]
29+
# Note: gnome-keyring-daemon needs previleged mode, therefore can not be run by a RUN command.
30+
CMD ["sh", "-c", "echo default_secret | gnome-keyring-daemon --unlock; bash"]

docker_run.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/bash
2+
IMAGE_NAME=msal-extensions:latest
3+
4+
docker build -t $IMAGE_NAME - < Dockerfile
5+
6+
echo "==== Integration Test for Persistence on Linux (libsecret) ===="
7+
echo "After seeing the bash prompt, run the following to test encryption on Linux:"
8+
echo " pip install -e ."
9+
echo " pytest"
10+
docker run --rm -it \
11+
--privileged \
12+
-w /home -v $PWD:/home \
13+
$IMAGE_NAME \
14+
$1
15+

msal_extensions/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Provides auxiliary functionality to the `msal` package."""
2-
__version__ = "0.3.0"
2+
__version__ = "0.3.1"
33

44
import sys
55

msal_extensions/cache_lock.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
import os
33
import sys
44
import errno
5-
import portalocker
5+
import time
6+
import logging
67
from distutils.version import LooseVersion
78

9+
import portalocker
10+
11+
12+
logger = logging.getLogger(__name__)
13+
814

915
class CrossPlatLock(object):
1016
"""Offers a mechanism for waiting until another process is finished interacting with a shared
@@ -14,7 +20,8 @@ class CrossPlatLock(object):
1420
def __init__(self, lockfile_path):
1521
self._lockpath = lockfile_path
1622
# Support for passing through arguments to the open syscall was added in v1.4.0
17-
open_kwargs = {'buffering': 0} if LooseVersion(portalocker.__version__) >= LooseVersion("1.4.0") else {}
23+
open_kwargs = ({'buffering': 0}
24+
if LooseVersion(portalocker.__version__) >= LooseVersion("1.4.0") else {})
1825
self._lock = portalocker.Lock(
1926
lockfile_path,
2027
mode='wb+',
@@ -25,9 +32,32 @@ def __init__(self, lockfile_path):
2532
flags=portalocker.LOCK_EX | portalocker.LOCK_NB,
2633
**open_kwargs)
2734

35+
def _try_to_create_lock_file(self):
36+
timeout = 5
37+
check_interval = 0.25
38+
current_time = getattr(time, "monotonic", time.time)
39+
timeout_end = current_time() + timeout
40+
pid = os.getpid()
41+
while timeout_end > current_time():
42+
try:
43+
with open(self._lockpath, 'x'): # pylint: disable=unspecified-encoding
44+
return True
45+
except ValueError: # This needs to be the first clause, for Python 2 to hit it
46+
logger.warning("Python 2 does not support atomic creation of file")
47+
return False
48+
except FileExistsError: # Only Python 3 will reach this clause
49+
logger.debug(
50+
"Process %d found existing lock file, will retry after %f second",
51+
pid, check_interval)
52+
time.sleep(check_interval)
53+
return False
54+
2855
def __enter__(self):
56+
pid = os.getpid()
57+
if not self._try_to_create_lock_file():
58+
logger.warning("Process %d failed to create lock file", pid)
2959
file_handle = self._lock.__enter__()
30-
file_handle.write('{} {}'.format(os.getpid(), sys.argv[0]).encode('utf-8'))
60+
file_handle.write('{} {}'.format(pid, sys.argv[0]).encode('utf-8')) # pylint: disable=consider-using-f-string
3161
return file_handle
3262

3363
def __exit__(self, *args):
@@ -38,5 +68,5 @@ def __exit__(self, *args):
3868
# file for itself.
3969
os.remove(self._lockpath)
4070
except OSError as ex: # pylint: disable=invalid-name
41-
if ex.errno != errno.ENOENT and ex.errno != errno.EACCES:
71+
if ex.errno not in (errno.ENOENT, errno.EACCES):
4272
raise

msal_extensions/libsecret.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,29 @@
1313
pip install wheel
1414
PYGOBJECT_WITHOUT_PYCAIRO=1 pip install --no-build-isolation pygobject
1515
"""
16-
import logging
17-
18-
logger = logging.getLogger(__name__)
1916

2017
try:
21-
import gi # https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux
18+
import gi # https://github.com/AzureAD/microsoft-authentication-extensions-for-python/wiki/Encryption-on-Linux # pylint: disable=line-too-long
2219
except ImportError:
23-
logger.exception(
24-
"""Runtime dependency of PyGObject is missing.
20+
raise ImportError("""Unable to import module 'gi'
21+
Runtime dependency of PyGObject is missing.
2522
Depends on your Linux distro, you could install it system-wide by something like:
2623
sudo apt install python3-gi python3-gi-cairo gir1.2-secret-1
2724
If necessary, please refer to PyGObject's doc:
2825
https://pygobject.readthedocs.io/en/latest/getting_started.html
29-
""")
30-
raise
26+
""") # Message via exception rather than log
3127

3228
try:
3329
# pylint: disable=no-name-in-module
3430
gi.require_version("Secret", "1") # Would require a package gir1.2-secret-1
3531
# pylint: disable=wrong-import-position
3632
from gi.repository import Secret # Would require a package gir1.2-secret-1
37-
except (ValueError, ImportError):
38-
logger.exception(
33+
except (ValueError, ImportError) as ex:
34+
raise type(ex)(
3935
"""Require a package "gir1.2-secret-1" which could be installed by:
4036
sudo apt install gir1.2-secret-1
41-
""")
42-
raise
37+
""") # Message via exception rather than log
38+
4339

4440
class LibSecretAgent(object):
4541
"""A loader/saver built on top of low-level libsecret"""
@@ -51,7 +47,7 @@ def __init__( # pylint: disable=too-many-arguments
5147
label="", # Helpful when visualizing secrets by other viewers
5248
attribute_types=None, # {name: SchemaAttributeType, ...}
5349
collection=None, # None means default collection
54-
): # pylint: disable=bad-continuation
50+
):
5551
"""This agent is built on top of lower level libsecret API.
5652
5753
Content stored via libsecret is associated with a bunch of attributes.
@@ -126,14 +122,13 @@ def trial_run():
126122
agent.save(payload) # It would fail when running inside an SSH session
127123
assert agent.load() == payload # This line is probably not reachable
128124
agent.clear()
129-
except (gi.repository.GLib.Error, AssertionError):
125+
except (gi.repository.GLib.Error, AssertionError): # pylint: disable=no-member
126+
# https://pygobject.readthedocs.io/en/latest/guide/api/error_handling.html#examples
130127
message = """libsecret did not perform properly.
131128
* If you encountered error "Remote error from secret service:
132129
org.freedesktop.DBus.Error.ServiceUnknown",
133130
you may need to install gnome-keyring package.
134131
* Headless mode (such as in an ssh session) is not supported.
135132
"""
136-
logger.exception(message) # This log contains trace stack for debugging
137-
logger.warning(message) # This is visible by default
138-
raise
133+
raise RuntimeError(message) # Message via exception rather than log
139134

0 commit comments

Comments
 (0)