-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from AzureAD/release-0.1.0
Release 0.1.0
- Loading branch information
Showing
16 changed files
with
942 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[MESSAGES CONTROL] | ||
disable= | ||
useless-object-inheritance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
language: python | ||
|
||
matrix: | ||
fast_finish: true | ||
include: | ||
- python: "2.7" | ||
env: TOXENV=py27 PYPI=true | ||
os: linux | ||
- python: "3.5" | ||
env: TOXENV=py35 | ||
os: linux | ||
- python: "3.6" | ||
env: TOXENV=py36 | ||
os: linux | ||
- python: "3.7" | ||
env: TOXENV=py37 | ||
os: linux | ||
dist: xenial | ||
- name: "Python 3.7 on macOS" | ||
env: TOXENV=py37 | ||
os: osx | ||
osx_image: xcode10.2 | ||
language: shell | ||
- name: "Python 2.7 on Windows" | ||
env: TOXENV=py27 PATH=/c/Python27:/c/Python27/Scripts:$PATH | ||
os: windows | ||
before_install: choco install python2 | ||
language: shell | ||
- name: "Python 3.5 on Windows" | ||
env: TOXENV=py35 PATH=/c/Python35:/c/Python35/Scripts:$PATH | ||
os: windows | ||
before_install: choco install python3 --version 3.5.4 | ||
language: shell | ||
- name: "Python 3.7 on Windows" | ||
env: TOXENV=py37 PATH=/c/Python37:/c/Python37/Scripts:$PATH | ||
os: windows | ||
before_install: choco install python3 --version 3.7.3 | ||
language: shell | ||
|
||
install: | ||
- pip install tox pylint | ||
- pip install . | ||
|
||
script: | ||
- pylint msal_extensions | ||
- tox | ||
|
||
deploy: | ||
- # test pypi | ||
provider: pypi | ||
distributions: "sdist bdist_wheel" | ||
server: https://test.pypi.org/legacy/ | ||
user: "nugetaad" | ||
password: | ||
secure: dpNi6BsZyiAx/gkxJ5Mz6m2yDz2dRGWsSgS5pF+ywNzgHJ6+0e234GyLbSUY5bFeeA7WtOr4is3bxSLB/6tTWDVWdw3TL4FGlDM/54MSLWg8R5bR9PRwO+VU1kvQ03yz+B9mTpzuiwL2e+OSwcwo97jForADzmSRA5OpEq5Z7zAs7WR8J2tyhl+288NwLtKJMVy39UmPl9oifu6/5RfBn7EWLmC7MrMFhHTb2Gj7fJWw4u+5vx9bsQ7ubfiwPbRAtYXLz6wDMtwtFzwme4zZPg5HwWCn0WWlX4b6x7xXirZ7yKsy9iACLgTrLMeAkferrex7f03NFeIDobasML+fLbZufATaL3M97kNGZwulEYNp2+RWyLu/NW6FoZCbS+cSL8HuFnkIDHzEoO56ItMiD9EH47q/NeKgwrrzKjfY+KzaMQOYLlVgCa4WrIeFh5CkwJ4RHrfanMIV2vbEvMxsnHc/mZ+yvgBOFoBNXYN1HEDzEv1NxDPcyt7MBlPUVinEreQaHba7w6qH9Rf0eWgfW2ypBXe+nHaZxQgaGC6J+WGUkzalYQspmHVU4CcuwJa55kuchJs/gbyZKkyK6P8uD5IP6VZiavwZcjWcfvwbZaLeOqzSDVCDMg8M2zYZHoa+6ZR4EgDVW7RvaRvjvvhPTPj5twmLf3YYVJtHIyJSLug= | ||
on: | ||
branch: master | ||
tags: false | ||
condition: $PYPI = "true" | ||
|
||
- # production pypi | ||
provider: pypi | ||
distributions: "sdist bdist_wheel" | ||
user: "nugetaad" | ||
password: | ||
secure: dpNi6BsZyiAx/gkxJ5Mz6m2yDz2dRGWsSgS5pF+ywNzgHJ6+0e234GyLbSUY5bFeeA7WtOr4is3bxSLB/6tTWDVWdw3TL4FGlDM/54MSLWg8R5bR9PRwO+VU1kvQ03yz+B9mTpzuiwL2e+OSwcwo97jForADzmSRA5OpEq5Z7zAs7WR8J2tyhl+288NwLtKJMVy39UmPl9oifu6/5RfBn7EWLmC7MrMFhHTb2Gj7fJWw4u+5vx9bsQ7ubfiwPbRAtYXLz6wDMtwtFzwme4zZPg5HwWCn0WWlX4b6x7xXirZ7yKsy9iACLgTrLMeAkferrex7f03NFeIDobasML+fLbZufATaL3M97kNGZwulEYNp2+RWyLu/NW6FoZCbS+cSL8HuFnkIDHzEoO56ItMiD9EH47q/NeKgwrrzKjfY+KzaMQOYLlVgCa4WrIeFh5CkwJ4RHrfanMIV2vbEvMxsnHc/mZ+yvgBOFoBNXYN1HEDzEv1NxDPcyt7MBlPUVinEreQaHba7w6qH9Rf0eWgfW2ypBXe+nHaZxQgaGC6J+WGUkzalYQspmHVU4CcuwJa55kuchJs/gbyZKkyK6P8uD5IP6VZiavwZcjWcfvwbZaLeOqzSDVCDMg8M2zYZHoa+6ZR4EgDVW7RvaRvjvvhPTPj5twmLf3YYVJtHIyJSLug= | ||
on: | ||
branch: master | ||
tags: true | ||
condition: $PYPI = "true" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
resources: | ||
- repo: self | ||
|
||
trigger: | ||
batch: true | ||
branches: | ||
include: | ||
- '*' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
"""Provides auxiliary functionality to the `msal` package.""" | ||
__version__ = "0.1.0" | ||
|
||
import sys | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"""Provides a mechanism for not competing with other processes interacting with an MSAL cache.""" | ||
import os | ||
import sys | ||
import errno | ||
import portalocker | ||
|
||
|
||
class CrossPlatLock(object): | ||
"""Offers a mechanism for waiting until another process is finished interacting with a shared | ||
resource. This is specifically written to interact with a class of the same name in the .NET | ||
extensions library. | ||
""" | ||
def __init__(self, lockfile_path): | ||
self._lockpath = lockfile_path | ||
self._fh = None | ||
|
||
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')) | ||
|
||
def __exit__(self, *args): | ||
self._fh.close() | ||
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: | ||
if ex.errno != errno.ENOENT and ex.errno != errno.EACCES: | ||
raise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
# pylint: disable=duplicate-code | ||
|
||
"""Implements a macOS specific TokenCache, and provides auxiliary helper types.""" | ||
|
||
import os | ||
import ctypes as _ctypes | ||
|
||
OS_RESULT = _ctypes.c_int32 | ||
|
||
|
||
class KeychainError(OSError): | ||
"""The RuntimeError that will be run when a function interacting with Keychain fails.""" | ||
|
||
ACCESS_DENIED = -128 | ||
NO_SUCH_KEYCHAIN = -25294 | ||
NO_DEFAULT = -25307 | ||
ITEM_NOT_FOUND = -25300 | ||
|
||
def __init__(self, exit_status): | ||
super(KeychainError, self).__init__() | ||
self.exit_status = exit_status | ||
# TODO: pylint: disable=fixme | ||
# use SecCopyErrorMessageString to fetch the appropriate message here. | ||
self.message = \ | ||
'{} ' \ | ||
'see https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/MacErrors.h'\ | ||
.format(self.exit_status) | ||
|
||
def _get_native_location(name): | ||
# type: (str) -> str | ||
""" | ||
Fetches the location of a native MacOS library. | ||
:param name: The name of the library to be loaded. | ||
:return: The location of the library on a MacOS filesystem. | ||
""" | ||
return '/System/Library/Frameworks/{0}.framework/{0}'.format(name) | ||
|
||
|
||
# Load native MacOS libraries | ||
_SECURITY = _ctypes.CDLL(_get_native_location('Security')) | ||
_CORE = _ctypes.CDLL(_get_native_location('CoreFoundation')) | ||
|
||
|
||
# Bind CFRelease from native MacOS libraries. | ||
_CORE_RELEASE = _CORE.CFRelease | ||
_CORE_RELEASE.argtypes = ( | ||
_ctypes.c_void_p, | ||
) | ||
|
||
# Bind SecCopyErrorMessageString from native MacOS libraries. | ||
# https://developer.apple.com/documentation/security/1394686-seccopyerrormessagestring?language=objc | ||
_SECURITY_COPY_ERROR_MESSAGE_STRING = _SECURITY.SecCopyErrorMessageString | ||
_SECURITY_COPY_ERROR_MESSAGE_STRING.argtypes = ( | ||
OS_RESULT, | ||
_ctypes.c_void_p | ||
) | ||
_SECURITY_COPY_ERROR_MESSAGE_STRING.restype = _ctypes.c_char_p | ||
|
||
# Bind SecKeychainOpen from native MacOS libraries. | ||
# https://developer.apple.com/documentation/security/1396431-seckeychainopen | ||
_SECURITY_KEYCHAIN_OPEN = _SECURITY.SecKeychainOpen | ||
_SECURITY_KEYCHAIN_OPEN.argtypes = ( | ||
_ctypes.c_char_p, | ||
_ctypes.POINTER(_ctypes.c_void_p) | ||
) | ||
_SECURITY_KEYCHAIN_OPEN.restype = OS_RESULT | ||
|
||
# Bind SecKeychainCopyDefault from native MacOS libraries. | ||
# https://developer.apple.com/documentation/security/1400743-seckeychaincopydefault?language=objc | ||
_SECURITY_KEYCHAIN_COPY_DEFAULT = _SECURITY.SecKeychainCopyDefault | ||
_SECURITY_KEYCHAIN_COPY_DEFAULT.argtypes = ( | ||
_ctypes.POINTER(_ctypes.c_void_p), | ||
) | ||
_SECURITY_KEYCHAIN_COPY_DEFAULT.restype = OS_RESULT | ||
|
||
|
||
# Bind SecKeychainItemFreeContent from native MacOS libraries. | ||
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT = _SECURITY.SecKeychainItemFreeContent | ||
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT.argtypes = ( | ||
_ctypes.c_void_p, | ||
_ctypes.c_void_p, | ||
) | ||
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT.restype = OS_RESULT | ||
|
||
# Bind SecKeychainItemModifyAttributesAndData from native MacOS libraries. | ||
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA = \ | ||
_SECURITY.SecKeychainItemModifyAttributesAndData | ||
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA.argtypes = ( | ||
_ctypes.c_void_p, | ||
_ctypes.c_void_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_void_p, | ||
) | ||
_SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA.restype = OS_RESULT | ||
|
||
# Bind SecKeychainFindGenericPassword from native MacOS libraries. | ||
# https://developer.apple.com/documentation/security/1397301-seckeychainfindgenericpassword?language=objc | ||
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD = _SECURITY.SecKeychainFindGenericPassword | ||
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD.argtypes = ( | ||
_ctypes.c_void_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_char_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_char_p, | ||
_ctypes.POINTER(_ctypes.c_uint32), | ||
_ctypes.POINTER(_ctypes.c_void_p), | ||
_ctypes.POINTER(_ctypes.c_void_p), | ||
) | ||
_SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD.restype = OS_RESULT | ||
# Bind SecKeychainAddGenericPassword from native MacOS | ||
# https://developer.apple.com/documentation/security/1398366-seckeychainaddgenericpassword?language=objc | ||
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD = _SECURITY.SecKeychainAddGenericPassword | ||
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD.argtypes = ( | ||
_ctypes.c_void_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_char_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_char_p, | ||
_ctypes.c_uint32, | ||
_ctypes.c_char_p, | ||
_ctypes.POINTER(_ctypes.c_void_p), | ||
) | ||
_SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD.restype = OS_RESULT | ||
|
||
|
||
class Keychain(object): | ||
"""Encapsulates the interactions with a particular MacOS Keychain.""" | ||
def __init__(self, filename=None): | ||
# type: (str) -> None | ||
self._ref = _ctypes.c_void_p() | ||
|
||
if filename: | ||
filename = os.path.expanduser(filename) | ||
self._filename = filename.encode('utf-8') | ||
else: | ||
self._filename = None | ||
|
||
def __enter__(self): | ||
if self._filename: | ||
status = _SECURITY_KEYCHAIN_OPEN(self._filename, self._ref) | ||
else: | ||
status = _SECURITY_KEYCHAIN_COPY_DEFAULT(self._ref) | ||
|
||
if status: | ||
raise OSError(status) | ||
return self | ||
|
||
def __exit__(self, *args): | ||
if self._ref: | ||
_CORE_RELEASE(self._ref) | ||
|
||
def get_generic_password(self, service, account_name): | ||
# type: (str, str) -> str | ||
"""Fetch the password associated with a particular service and account. | ||
:param service: The service that this password is associated with. | ||
:param account_name: The account that this password is associated with. | ||
:return: The value of the password associated with the specified service and account. | ||
""" | ||
service = service.encode('utf-8') | ||
account_name = account_name.encode('utf-8') | ||
|
||
length = _ctypes.c_uint32() | ||
contents = _ctypes.c_void_p() | ||
exit_status = _SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD( | ||
self._ref, | ||
len(service), | ||
service, | ||
len(account_name), | ||
account_name, | ||
length, | ||
contents, | ||
None, | ||
) | ||
|
||
if exit_status: | ||
raise KeychainError(exit_status=exit_status) | ||
|
||
value = _ctypes.create_string_buffer(length.value) | ||
_ctypes.memmove(value, contents.value, length.value) | ||
_SECURITY_KEYCHAIN_ITEM_FREE_CONTENT(None, contents) | ||
return value.raw.decode('utf-8') | ||
|
||
def set_generic_password(self, service, account_name, value): | ||
# type: (str, str, str) -> None | ||
"""Associate a password with a given service and account. | ||
:param service: The service to associate this password with. | ||
:param account_name: The account to associate this password with. | ||
:param value: The string that should be used as the password. | ||
""" | ||
service = service.encode('utf-8') | ||
account_name = account_name.encode('utf-8') | ||
value = value.encode('utf-8') | ||
|
||
entry = _ctypes.c_void_p() | ||
find_exit_status = _SECURITY_KEYCHAIN_FIND_GENERIC_PASSWORD( | ||
self._ref, | ||
len(service), | ||
service, | ||
len(account_name), | ||
account_name, | ||
None, | ||
None, | ||
entry, | ||
) | ||
|
||
if not find_exit_status: | ||
modify_exit_status = _SECURITY_KEYCHAIN_ITEM_MODIFY_ATTRIBUTES_AND_DATA( | ||
entry, | ||
None, | ||
len(value), | ||
value, | ||
) | ||
if modify_exit_status: | ||
raise KeychainError(exit_status=modify_exit_status) | ||
|
||
elif find_exit_status == KeychainError.ITEM_NOT_FOUND: | ||
add_exit_status = _SECURITY_KEYCHAIN_ADD_GENERIC_PASSWORD( | ||
self._ref, | ||
len(service), | ||
service, | ||
len(account_name), | ||
account_name, | ||
len(value), | ||
value, | ||
None | ||
) | ||
|
||
if add_exit_status: | ||
raise KeychainError(exit_status=add_exit_status) | ||
else: | ||
raise KeychainError(exit_status=find_exit_status) | ||
|
||
def get_internet_password(self, service, username): | ||
# type: (str, str) -> str | ||
""" Fetches a password associated with a domain and username. | ||
NOTE: THIS IS NOT YET IMPLEMENTED | ||
:param service: The website/service that this password is associated with. | ||
:param username: The account that this password is associated with. | ||
:return: The password that was associated with the given service and username. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def set_internet_password(self, service, username, value): | ||
# type: (str, str, str) -> None | ||
"""Sets a password associated with a domain and a username. | ||
NOTE: THIS IS NOT YET IMPLEMENTED | ||
:param service: The website/service that this password is associated with. | ||
:param username: The account that this password is associated with. | ||
:param value: The password that should be associated with the given service and username. | ||
""" | ||
raise NotImplementedError() |
Oops, something went wrong.