From 32a739c04d82db39a9afb21646f66a84412940c0 Mon Sep 17 00:00:00 2001 From: Arun Babu Neelicattu Date: Thu, 3 Jan 2019 11:59:29 +0100 Subject: [PATCH] Use system keyring for passwords when available This change introduces the use of system keyring to store repository passwords when one is available. Passwords are added to and removed from the keyring using the config command. Resolves: #210 --- docs/docs/repositories.md | 4 + poetry.lock | 187 ++++++++++++++++++++++++++-- poetry/console/commands/config.py | 16 ++- poetry/utils/helpers.py | 45 ++++++- pyproject.toml | 1 + tests/utils/test_helpers_keyring.py | 125 +++++++++++++++++++ 6 files changed, 365 insertions(+), 13 deletions(-) create mode 100644 tests/utils/test_helpers_keyring.py diff --git a/docs/docs/repositories.md b/docs/docs/repositories.md index 9519668f25a..a7ac1ca718f 100644 --- a/docs/docs/repositories.md +++ b/docs/docs/repositories.md @@ -49,6 +49,10 @@ If you do not specify the password you will be prompted to write it. You can also specify the username and password when using the `publish` command with the `--username` and `--password` options. +If a system keyring is available and supported, the password is stored to and retrieved from the keyring. In the above example, the credential will be stored using the name `poetry-repository-pypi`. If access to keyring fails or is unsupported, this will fall back to writing the password to the `auth.toml` file along with the username. + +Keyring support is enabled using the [keyring library](https://pypi.org/project/keyring/). For more information on supported backends refer to the [library documentation](https://keyring.readthedocs.io/en/latest/?badge=latest). + ### Install dependencies from a private repository Now that you can publish to your private repository, you need to be able to diff --git a/poetry.lock b/poetry.lock index 3c0dc9bb9cb..62184e30252 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7,6 +7,15 @@ optional = false python-versions = "*" version = "1.4.3" +[[package]] +category = "main" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\" or sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "asn1crypto" +optional = false +python-versions = "*" +version = "0.24.0" + [[package]] category = "dev" description = "A few extensions to pyyaml." @@ -106,6 +115,18 @@ optional = false python-versions = "*" version = "2018.10.15" +[[package]] +category = "main" +description = "Foreign Function Interface for Python calling C code." +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\" or sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "cffi" +optional = false +python-versions = "*" +version = "1.11.5" + +[package.dependencies] +pycparser = "*" + [[package]] category = "dev" description = "Validate configuration and produce human readable error messages." @@ -176,6 +197,15 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.4.0" +[[package]] +category = "main" +description = "This library brings the updated configparser from Python 3.5 to Python 2.6-3.5." +marker = "python_version == \"2.7\"" +name = "configparser" +optional = false +python-versions = "*" +version = "3.5.0" + [[package]] category = "dev" description = "Backports and enhancements for the contextlib module" @@ -193,10 +223,52 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" version = "4.5.2" +[[package]] +category = "main" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\" or sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "cryptography" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "2.4.2" + +[package.dependencies] +asn1crypto = ">=0.21.0" +cffi = ">=1.7,<1.11.3 || >1.11.3" +idna = ">=2.1" +six = ">=1.4.1" + +[package.dependencies.enum34] +python = "<3" +version = "*" + +[package.dependencies.ipaddress] +python = "<3" +version = "*" + +[package.extras] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "readme_renderer (>=16.0)", "sphinxcontrib-spelling (>=4.0.1)"] +pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] + +[[package]] +category = "main" +description = "Discover and load entry points from installed packages." +name = "entrypoints" +optional = false +python-versions = ">=2.7" +version = "0.2.3" + +[package.dependencies] +[package.dependencies.configparser] +python = ">=2.7,<2.8" +version = ">=3.5" + [[package]] category = "main" description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" -marker = "python_version >= \"2.7\" and python_version < \"2.8\"" +marker = "python_version >= \"2.7\" and python_version < \"2.8\" or (sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3\"" name = "enum34" optional = false python-versions = "*" @@ -298,6 +370,24 @@ version = "*" python = "<3.5" version = "*" +[[package]] +category = "main" +description = "IPv4/IPv6 manipulation library" +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3\"" +name = "ipaddress" +optional = false +python-versions = "*" +version = "1.0.22" + +[[package]] +category = "main" +description = "Low-level, pure Python DBus protocol wrapper." +marker = "sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "jeepney" +optional = false +python-versions = ">=3.5" +version = "0.4" + [[package]] category = "dev" description = "A small but fast and easy to use stand-alone template engine written in pure python." @@ -333,6 +423,30 @@ version = "*" [package.extras] format = ["jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +[[package]] +category = "main" +description = "Store and access your passwords safely." +name = "keyring" +optional = false +python-versions = ">=2.7" +version = "17.1.1" + +[package.dependencies] +entrypoints = "*" +pywin32-ctypes = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1" + +[[package.dependencies.secretstorage]] +python = "<3.5" +version = "<3" + +[[package.dependencies.secretstorage]] +python = ">=3.5" +version = "*" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs", "pytest-flake8"] + [[package]] category = "dev" description = "Python LiveReload is an awesome tool for web developers" @@ -536,6 +650,15 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.7.0" +[[package]] +category = "main" +description = "C parser in Python" +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\" or sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "pycparser" +optional = false +python-versions = "*" +version = "2.19" + [[package]] category = "dev" description = "Pygments is a syntax highlighting package written in Python." @@ -659,6 +782,15 @@ packaging = ">=14.1" pytest = ">=2.9" termcolor = ">=1.1.0" +[[package]] +category = "main" +description = "" +marker = "sys_platform == \"win32\"" +name = "pywin32-ctypes" +optional = false +python-versions = "*" +version = "0.2.0" + [[package]] category = "dev" description = "YAML parser and emitter for Python" @@ -682,8 +814,8 @@ idna = ">=2.5,<2.8" urllib3 = ">=1.21.1,<1.25" [package.extras] -security = ["idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] -socks = ["win-inet-pton", "PySocks (>=1.5.6,<1.5.7 || >1.5.7)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] category = "main" @@ -705,6 +837,34 @@ optional = false python-versions = "*" version = "1.9.0" +[[package]] +category = "main" +description = "Python bindings to FreeDesktop.org Secret Service API" +marker = "(sys_platform == \"linux2\" or sys_platform == \"linux\") and python_version < \"3.5\"" +name = "secretstorage" +optional = false +python-versions = "*" +version = "2.3.1" + +[package.dependencies] +cryptography = "*" + +[package.extras] +dbus-python = ["dbus-python"] + +[[package]] +category = "main" +description = "Python bindings to FreeDesktop.org Secret Service API" +marker = "sys_platform == \"linux\" and python_version >= \"3.5\"" +name = "secretstorage" +optional = false +python-versions = ">=3.5" +version = "3.1.0" + +[package.dependencies] +cryptography = "*" +jeepney = "*" + [[package]] category = "main" description = "Tool to Detect Surrounding Shell" @@ -785,8 +945,8 @@ toml = ">=0.9.4" virtualenv = ">=1.11.2" [package.extras] -docs = ["sphinxcontrib-autoprogram (>=0.1.5)", "pygments-github-lexers (>=0.0.5)", "towncrier (>=18.5.0)", "sphinx (>=1.8.0,<2)"] -testing = ["pytest-randomly (>=1.2.3,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-timeout (>=1.3.0,<2)", "pytest-mock (>=1.10.0,<2)", "pytest-cov (>=2.5.1,<3)", "pytest (>=3.0.0,<4)"] +docs = ["sphinx (>=1.8.0,<2)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] +testing = ["pytest (>=3.0.0,<4)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-timeout (>=1.3.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.2.3,<2)"] [[package]] category = "main" @@ -806,7 +966,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" version = "1.24.1" [package.extras] -secure = ["ipaddress", "certifi", "idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] @@ -819,7 +979,7 @@ version = "16.1.0" [package.extras] docs = ["sphinx (>=1.8.0,<2)"] -testing = ["mock", "pytest-timeout (>=1.3.0,<2)", "coverage (>=4.5.0,<5)", "pytest (>=3.0.0,<4)"] +testing = ["pytest (>=3.0.0,<4)", "coverage (>=4.5.0,<5)", "pytest-timeout (>=1.3.0,<2)", "mock"] [[package]] category = "main" @@ -830,11 +990,12 @@ python-versions = "*" version = "0.5.1" [metadata] -content-hash = "8618ec2332b366abaf70c871e9afd0f52d410e0cf17c9715525b9157c8874161" +content-hash = "a80ec0f4eae05ec131f96ce33afa8954e39430ebe9b7afbe85baed599c6c9307" python-versions = "~2.7 || ^3.4" [metadata.hashes] appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] +asn1crypto = ["2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", "9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"] "aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"] atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"] attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"] @@ -843,14 +1004,18 @@ cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff cached-property = ["3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", "9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"] cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"] certifi = ["339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", "6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a"] +cffi = ["151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", "1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", "1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", "2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", "3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", "3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", "3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", "495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", "4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", "57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", "770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", "79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", "7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", "857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", "87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", "95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", "9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", "a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", "a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", "ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", "b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", "b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", "ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", "be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", "ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", "d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", "e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", "e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", "ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", "ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", "edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", "fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"] cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"] chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] cleo = ["7a5000de616de42fd78f968192651b9174bf333a9079fc9d68ccc672ddfd3624", "a39fa806fd3f64ff0ae4e3a55bb80c8ce60ca73634e13619731d63a04a63be69"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] clikit = ["9dbdb3a0f3ddcd2e1851f11b13dc5173d8a4333d627be24e46ddbbc0d83a84e8", "b65cecedfabc2c73865454a57faeca8696408130fc5c2c74df0770335c787bec"] colorama = ["a3d89af5db9e9806a779a50296b5fdb466e281147c2c235e8225ecc6dbf7bbf3", "c9b54bebe91a6a803e0772c8561d53f2926bfeb17cd141fbabcb08424086595c"] +configparser = ["5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a"] contextlib2 = ["509f9419ee91cdd00ba34443217d5ca51f5a364a404e1dce9e8979cea969ca48", "f5260a6e679d2ff42ec91ec5252f4eeffdcf21053db9113bd0a8e4d953769c00"] coverage = ["06123b58a1410873e22134ca2d88bd36680479fe354955b3579fb8ff150e4d27", "09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", "0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", "0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", "0d34245f824cc3140150ab7848d08b7e2ba67ada959d77619c986f2062e1f0e8", "10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", "1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", "1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", "258b21c5cafb0c3768861a6df3ab0cfb4d8b495eee5ec660e16f928bf7385390", "2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", "3ad59c84c502cd134b0088ca9038d100e8fb5081bbd5ccca4863f3804d81f61d", "447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", "46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", "4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", "510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", "5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", "5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", "5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", "6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", "6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", "71afc1f5cd72ab97330126b566bbf4e8661aab7449f08895d21a5d08c6b051ff", "7349c27128334f787ae63ab49d90bf6d47c7288c63a0a5dfaa319d4b4541dd2c", "77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", "828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", "859714036274a75e6e57c7bab0c47a4602d2a8cfaaa33bbdb68c8359b2ed4f5c", "85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", "869ef4a19f6e4c6987e18b315721b8b971f7048e6eaea29c066854242b4e98d9", "8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", "977e2d9a646773cc7428cdd9a34b069d6ee254fadfb4d09b3f430e95472f3cf3", "99bd767c49c775b79fdcd2eabff405f1063d9d959039c0bdd720527a7738748a", "a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", "aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", "ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", "b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", "bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", "c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", "d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", "d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", "da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", "ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", "ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9"] +cryptography = ["05a6052c6a9f17ff78ba78f8e6eb1d777d25db3b763343a1ae89a7a8670386dd", "0eb83a24c650a36f68e31a6d0a70f7ad9c358fa2506dc7b683398b92e354a038", "0ff4a3d6ea86aa0c9e06e92a9f986de7ee8231f36c4da1b31c61a7e692ef3378", "1699f3e916981df32afdd014fb3164db28cdb61c757029f502cb0a8c29b2fdb3", "1b1f136d74f411f587b07c076149c4436a169dc19532e587460d9ced24adcc13", "21e63dd20f5e5455e8b34179ac43d95b3fb1ffa54d071fd2ed5d67da82cfe6dc", "2454ada8209bbde97065453a6ca488884bbb263e623d35ba183821317a58b46f", "3cdc5f7ca057b2214ce4569e01b0f368b3de9d8ee01887557755ccd1c15d9427", "418e7a5ec02a7056d3a4f0c0e7ea81df374205f25f4720bb0e84189aa5fd2515", "471a097076a7c4ab85561d7fa9a1239bd2ae1f9fd0047520f13d8b340bf3210b", "5ecaf9e7db3ca582c6de6229525d35db8a4e59dc3e8a40a331674ed90e658cbf", "63b064a074f8dc61be81449796e2c3f4e308b6eba04a241a5c9f2d05e882c681", "6afe324dfe6074822ccd56d80420df750e19ac30a4e56c925746c735cf22ae8b", "70596e90398574b77929cd87e1ac6e43edd0e29ba01e1365fed9c26bde295aa5", "70c2b04e905d3f72e2ba12c58a590817128dfca08949173faa19a42c824efa0b", "8908f1db90be48b060888e9c96a0dee9d842765ce9594ff6a23da61086116bb6", "af12dfc9874ac27ebe57fc28c8df0e8afa11f2a1025566476b0d50cdb8884f70", "b4fc04326b2d259ddd59ed8ea20405d2e695486ab4c5e1e49b025c484845206e", "da5b5dda4aa0d5e2b758cc8dfc67f8d4212e88ea9caad5f61ba132f948bab859"] +entrypoints = ["10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b", "d2d587dde06f99545fb13a383d2cd336a8ff1f359c5839ce3a64c917d10c029f"] enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"] filelock = ["b8d5ca5ca1c815e1574aee746650ea7301de63d87935b3463d26368b76e31633", "d610c1bb404daf85976d7a82eb2ada120f04671007266b708606565dd03b5be6"] funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"] @@ -860,8 +1025,11 @@ identify = ["5e956558a9a1e3b3891d7c6609fc9709657a11878af288ace484d1a46a93922b", idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"] importlib-metadata = ["36b02c84f9001adf65209fefdf951be8e9014a95eab9938c0779ad5670359b1c", "60b6481a72908c93ccb707abeb926fb5a15319b9e6f0b76639a718837ee12de0"] importlib-resources = ["6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b", "d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"] +ipaddress = ["64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794", "b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c"] +jeepney = ["6089412a5de162c04747f0220f6b2223b8ba660acd041e52a76426ca550e3c70", "f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74"] jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"] jsonschema = ["3ae8afd6f4ca6417f14bf43ef61341311598f14234cdb4174fe43d42b236a3c8", "dfd8426040892c8d0ef6da574085f282569f189cb24b70091a66c21c12d6705e"] +keyring = ["8f683fa6c8886da58b28c7d8e3819b1a4bf193741888e33a6e00944b673a22cf", "d71368b9a10dc096c8a1b69a46fc23f0a551f709b9a55b4d4deb7d2fbd809b68"] livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"] lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"] markdown = ["c00429bd503a47ec88d5e30a751e147dcb4c6889663cd3e2ba0afe858e009baa", "d02e0f9b04c500cde6637c11ad7c72671f359b87b9fe924b2383649d8841db7c"] @@ -879,6 +1047,7 @@ pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", " pluggy = ["447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", "bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"] pre-commit = ["7542bd8ae1c58745175ea0a9295964ee82a10f7e18c4344f5e4c02bd85d02561", "87f687da6a2651d5067cfec95b854b004e95b70143cbf2369604bb3acbce25ec"] py = ["bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", "e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"] +pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"] pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"] pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"] @@ -889,10 +1058,12 @@ pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"] pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"] pytest-sugar = ["26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283", "fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"] +pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"] pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] requests = ["65b3a120e4329e33c9889db89c80976c5272f56ea92d3e74da8a463992e3ff54", "ea881206e59f41dbd0bd445437d792e43906703fff75ca8ff43ccdb11f33f263"] requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"] scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"] +secretstorage = ["3af65c87765323e6f64c83575b05393f9e003431959c9395d1791d51497f29b6", "20196abd1a9d1310df7573d58ca6e7ed9292218c98ca3638eea07beb16080343", "29aa3cbd36dd5e54ac17d69161f9a150548f4ffba21fa8b5fdd5add854fe7d8b"] shellingham = ["c9fd71508d4363e8a3dadf405e681021461dca9ca9a2b48c9461fdfbfceaebff", "f56b5547ed84296318c21162ce345d83dd5e4755a0e4f57daee1948479f47119"] six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"] termcolor = ["1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"] diff --git a/poetry/console/commands/config.py b/poetry/console/commands/config.py index cf455b43692..882d3cc2d93 100644 --- a/poetry/console/commands/config.py +++ b/poetry/console/commands/config.py @@ -1,6 +1,10 @@ import json import re +from poetry.utils.helpers import ( + keyring_repository_password_set, + keyring_repository_password_del, +) from .command import Command @@ -190,7 +194,7 @@ def handle(self): raise ValueError( "There is no {} {} defined".format(m.group(2), m.group(1)) ) - + keyring_repository_password_del(self._auth_config, m.group(2)) self._auth_config.remove_property( "{}.{}".format(m.group(1), m.group(2)) ) @@ -211,9 +215,15 @@ def handle(self): username = values[0] password = values[1] + property_value = dict(username=username) + + try: + keyring_repository_password_set(m.group(2), username, password) + except RuntimeError: + property_value.update(password=password) + self._auth_config.add_property( - "{}.{}".format(m.group(1), m.group(2)), - {"username": username, "password": password}, + "{}.{}".format(m.group(1), m.group(2)), property_value ) return 0 diff --git a/poetry/utils/helpers.py b/poetry/utils/helpers.py index 2de7b3ddaee..8c6b0084347 100644 --- a/poetry/utils/helpers.py +++ b/poetry/utils/helpers.py @@ -5,9 +5,13 @@ import tempfile from contextlib import contextmanager +from typing import NoReturn from typing import Optional from typing import Union +from keyring import delete_password, set_password, get_password +from keyring.errors import KeyringError + from poetry.config import Config from poetry.version import Version @@ -83,13 +87,50 @@ def parse_requires(requires): # type: (str) -> Union[list, None] return requires_dist +def keyring_service_name(repository_name): # type: (str) -> str + return "{}-{}".format("poetry-repository", repository_name) + + +def keyring_repository_password_get( + repository_name, username +): # type: (str, str) -> Optional[str] + try: + return get_password(keyring_service_name(repository_name), username) + except (RuntimeError, KeyringError): + return None + + +def keyring_repository_password_set( + repository_name, username, password +): # type: (str, str, str) -> NoReturn + try: + set_password(keyring_service_name(repository_name), username, password) + except (RuntimeError, KeyringError): + raise RuntimeError("Failed to store password in keyring") + + +def keyring_repository_password_del( + config, repository_name +): # type: (Config, str) -> NoReturn + try: + repo_auth = config.setting("http-basic.{}".format(repository_name)) + if repo_auth and "username" in repo_auth: + delete_password( + keyring_service_name(repository_name), repo_auth["username"] + ) + except (RuntimeError, KeyringError): + pass + + def get_http_basic_auth( config, repository_name ): # type: (Config, str) -> Optional[tuple] repo_auth = config.setting("http-basic.{}".format(repository_name)) if repo_auth: - return repo_auth["username"], repo_auth.get("password") - + username, password = repo_auth["username"], repo_auth.get("password") + if password is None: + password = keyring_repository_password_get(repository_name, username) + return username, password return None diff --git a/pyproject.toml b/pyproject.toml index 78fe3f9e428..86539d803b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ pathlib2 = { version = "^2.3", python = "~2.7 || ~3.4" } virtualenv = { version = "^16.0", python = "~2.7" } # functools32 is needed for Python 2.7 functools32 = { version = "^3.2.3", python = "~2.7" } +keyring = "^17.1" [tool.poetry.dev-dependencies] pytest = "^3.4" diff --git a/tests/utils/test_helpers_keyring.py b/tests/utils/test_helpers_keyring.py new file mode 100644 index 00000000000..fc6f6ade21a --- /dev/null +++ b/tests/utils/test_helpers_keyring.py @@ -0,0 +1,125 @@ +from uuid import uuid4 + +import pytest +from keyring import set_keyring, get_keyring +from keyring.backend import KeyringBackend +from keyring.errors import KeyringError + +from poetry.utils.helpers import ( + keyring_service_name, + keyring_repository_password_get, + keyring_repository_password_set, + keyring_repository_password_del, +) + + +class DictKeyring(KeyringBackend): + priority = 1 + + def __init__(self): + self._storage = dict() + + def set_password(self, servicename, username, password): + if servicename not in self._storage: + self._storage[servicename] = dict() + self._storage[servicename][username] = password + + def get_password(self, servicename, username): + if servicename in self._storage: + return self._storage[servicename].get(username) + + def delete_password(self, servicename, username): + if servicename in self._storage: + if username in self._storage[servicename]: + del self._storage[servicename][username] + if not self._storage[servicename]: + del self._storage[servicename] + + +class BrokenKeyring(KeyringBackend): + priority = 1 + + def set_password(self, servicename, username, password): + raise KeyringError() + + def get_password(self, servicename, username): + raise KeyringError() + + def delete_password(self, servicename, username): + raise KeyringError() + + +@pytest.fixture +def keyring(): # type: () -> KeyringBackend + k = DictKeyring() + set_keyring(k) + return k + + +@pytest.fixture +def broken_keyring(): # type: () -> KeyringBackend + k = BrokenKeyring() + set_keyring(k) + return k + + +@pytest.fixture +def repository(): # type: () -> str + return "test" + + +@pytest.fixture +def username(): # type: () -> str + return "username" + + +@pytest.fixture +def password(): # type: () -> str + return str(uuid4()) + + +def test_keyring_repository_password_get(keyring, repository, username, password): + keyring.set_password(keyring_service_name(repository), username, password) + assert keyring_repository_password_get(repository, username) == password + + +def test_keyring_repository_password_get_not_set(keyring, repository, username): + assert keyring.get_password(keyring_service_name(repository), username) is None + assert keyring_repository_password_get(repository, username) is None + + +def test_keyring_repository_password_get_broken(broken_keyring): + assert get_keyring() == broken_keyring + assert keyring_repository_password_get("repository", "username") is None + + +def test_keyring_repository_password_set(keyring, repository, username, password): + keyring_repository_password_set(repository, username, password) + assert keyring.get_password(keyring_service_name(repository), username) == password + + +def test_keyring_repository_password_set_broken(broken_keyring): + assert get_keyring() == broken_keyring + + with pytest.raises(RuntimeError): + keyring_repository_password_set(repository, "username", "password") + + +def test_keyring_repository_password_del( + keyring, config, repository, username, password +): + keyring.set_password(keyring_service_name(repository), username, password) + config.add_property("http-basic.{}.username".format(repository), username) + keyring_repository_password_del(config, repository) + assert keyring.get_password(keyring_service_name(repository), username) is None + + +def test_keyring_repository_password_del_not_set(keyring, config, repository, username): + config.add_property("http-basic.{}.username".format(repository), username) + keyring_repository_password_del(config, repository) + assert keyring.get_password(keyring_service_name(repository), username) is None + + +def test_keyring_repository_password_del_broken(broken_keyring, config): + assert get_keyring() == broken_keyring + keyring_repository_password_del(config, "repository")