Skip to content

Commit 013fa17

Browse files
asfaltboyssbarnea
authored andcommitted
feat: use keyring to get/set basic auth password
1 parent f0708f9 commit 013fa17

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

jira/jirashell.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import webbrowser
1515
from getpass import getpass
1616

17+
import keyring
1718
import requests
1819
from oauthlib.oauth1 import SIGNATURE_RSA
1920
from requests_oauthlib import OAuth1
@@ -258,6 +259,20 @@ def get_config():
258259
return options, basic_auth, oauth, kerberos_auth
259260

260261

262+
def handle_basic_auth(auth, server):
263+
if auth.get('password'):
264+
password = auth['password']
265+
if input(
266+
'Would you like to remember password in OS keyring? (y/n)'
267+
) == 'y':
268+
keyring.set_password(server, auth['username'], password)
269+
else:
270+
print('Getting password from keyring...')
271+
password = keyring.get_password(server, auth['username'])
272+
assert password, 'No password provided!'
273+
return (auth['username'], password)
274+
275+
261276
def main():
262277
# workaround for avoiding UnicodeEncodeError when printing exceptions
263278
# containing unicode on py2
@@ -281,7 +296,7 @@ def main():
281296
options, basic_auth, oauth, kerberos_auth = get_config()
282297

283298
if basic_auth:
284-
basic_auth = (basic_auth['username'], basic_auth['password'])
299+
basic_auth = handle_basic_auth(auth=basic_auth, server=options['server'])
285300

286301
if oauth.get('oauth_dance') is True:
287302
oauth = oauth_dance(

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
argparse; python_version<'2.7'
22
defusedxml
33
functools32; python_version<'3.2' # PSF license
4+
keyring<19 # to support python 2.7 and 3.4
45
pbr>=3.0.0
56
requests-oauthlib>=1.1.0
67
requests>=2.10.0

tests/test_shell.py

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import unicode_literals
33
import pytest # noqa
4+
import io
45
import requests # noqa
56
import sys
67

78
try:
89
# python 3.4+ should use builtin unittest.mock not mock package
9-
from unittest.mock import patch
10+
from unittest.mock import patch, MagicMock
1011
except ImportError:
11-
from mock import patch
12+
from mock import patch, MagicMock
1213

1314
from jira import Role, Issue, JIRA, JIRAError, Project # noqa
1415
import jira.jirashell as jirashell
1516

1617

17-
def test_unicode(requests_mock, capsys):
18+
@pytest.fixture
19+
def testargs():
20+
return ["jirashell", "-s", "http://localhost"]
21+
22+
23+
def test_unicode(requests_mock, capsys, testargs):
1824
"""This functions tests that CLI tool does not throw an UnicodeDecodeError
1925
when it attempts to display some Unicode error message, which can happen
2026
when printing exceptions received from the remote HTTP server.
@@ -23,9 +29,77 @@ def test_unicode(requests_mock, capsys):
2329
Likely not needed for Py3 versions.
2430
"""
2531
requests_mock.register_uri('GET', 'http://localhost/rest/api/2/serverInfo', text='Δεν βρέθηκε', status_code=404)
26-
testargs = ["jirashell", "-s", "http://localhost"]
32+
2733
with patch.object(sys, 'argv', testargs):
2834
jirashell.main()
2935
captured = capsys.readouterr()
3036
assert captured.err.startswith("JiraError HTTP 404")
3137
assert captured.out == ""
38+
39+
40+
@pytest.fixture
41+
def mock_keyring():
42+
_keyring = {}
43+
44+
def mock_set_password(server, username, password):
45+
_keyring[(server, username)] = password
46+
47+
def mock_get_password(server, username):
48+
return _keyring.get((server, username), '')
49+
50+
mock_kr = MagicMock(
51+
set_password=MagicMock(side_effect=mock_set_password),
52+
get_password=MagicMock(side_effect=mock_get_password),
53+
_keyring=_keyring,
54+
)
55+
mocked_module = patch.object(jirashell, 'keyring', new=mock_kr)
56+
yield mocked_module.start()
57+
mocked_module.stop()
58+
59+
60+
@pytest.mark.timeout(4)
61+
def test_no_password_try_keyring(requests_mock, capsys, testargs, mock_keyring, monkeypatch):
62+
requests_mock.register_uri('GET', 'http://localhost/rest/api/2/serverInfo', status_code=200)
63+
64+
# no password provided
65+
args = testargs + ['-u', 'test@user']
66+
with patch.object(sys, 'argv', args):
67+
jirashell.main()
68+
69+
assert len(requests_mock.request_history) == 0
70+
captured = capsys.readouterr()
71+
assert captured.err == "No password provided!\nassert ''\n"
72+
assert "Getting password from keyring..." == captured.out.strip()
73+
assert mock_keyring._keyring == {}
74+
75+
# password provided, don't save
76+
monkeypatch.setattr('sys.stdin', io.StringIO('n'))
77+
args = args + ['-p', 'pass123']
78+
with patch.object(sys, 'argv', args):
79+
jirashell.main()
80+
81+
assert len(requests_mock.request_history) == 4
82+
captured = capsys.readouterr()
83+
assert captured.out.strip() == "Would you like to remember password in OS keyring? (y/n)"
84+
assert mock_keyring._keyring == {}
85+
86+
# password provided, save
87+
monkeypatch.setattr('sys.stdin', io.StringIO('y'))
88+
args = args + ['-p', 'pass123']
89+
with patch.object(sys, 'argv', args):
90+
jirashell.main()
91+
92+
assert len(requests_mock.request_history) == 8
93+
captured = capsys.readouterr()
94+
assert captured.out.strip() == "Would you like to remember password in OS keyring? (y/n)"
95+
assert mock_keyring._keyring == {('http://localhost', 'test@user'): 'pass123'}
96+
97+
# user stored password
98+
args = testargs + ['-u', 'test@user']
99+
with patch.object(sys, 'argv', args):
100+
jirashell.main()
101+
102+
assert len(requests_mock.request_history) == 12
103+
captured = capsys.readouterr()
104+
assert "Getting password from keyring..." == captured.out.strip()
105+
assert mock_keyring._keyring == {('http://localhost', 'test@user'): 'pass123'}

0 commit comments

Comments
 (0)