Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ and ``password`` from that file as login credentials.
{ "username": "myusername", "password": "mypassword" }


You can also provide multiple username and password tuples in a file as login credentials.
The tuple to be used can be chosen by providing its username when calling pycaching.login(),
e.g. pycaching.login("myusername2"). The first username and password tuple specified will be
used as default if pycaching.login() is called without providing a username.

.. code-block:: json

# sample .gc_credentials JSON file with mutiple users
[ { "username": "myusername1", "password": "mypassword1" },
{ "username": "myusername2", "password": "mypassword2" } ]


.. code-block:: python

import pycaching
Expand Down
27 changes: 24 additions & 3 deletions pycaching/geocaching.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def login(self, username=None, password=None):

if not username or not password:
try:
username, password = self._load_credentials()
username, password = self._load_credentials(username=username)
except FileNotFoundError as e:
raise LoginFailedException("Credentials file not found and "
"no username and password is given.") from e
Expand Down Expand Up @@ -136,7 +136,7 @@ def login(self, username=None, password=None):
raise LoginFailedException("Cannot login to the site "
"(probably wrong username or password).")

def _load_credentials(self):
def _load_credentials(self, username=None):
"""Load credentials from file.

Find credentials file in either current directory or user's home directory. If exists, load
Expand Down Expand Up @@ -164,7 +164,28 @@ def _load_credentials(self):

# load contents
with open(credentials_file, "r") as f:
credentials = json.load(f)
cred = json.load(f)
if isinstance(cred, dict):
if username is None:
credentials = cred
else:
if "username" in cred and cred["username"] == username:
credentials = cred
else:
raise KeyError("User {} requested but not found in credential.".format(username))
elif isinstance(cred, list):
if username is None and len(cred) > 0:
credentials = cred[0]
else:
for c in cred:
if "username" in c and c["username"] == username:
credentials = c
break
else:
raise KeyError("User {} requested but not found in credentials.".format(username))
else:
raise KeyError("Credential data type is unexpected {}".format(type(cred)))

if "password" in credentials and "password_cmd" in credentials:
raise KeyError("Ambiguous keys. Choose either \"password\" or \"password_cmd\".")
elif "password" in credentials:
Expand Down
85 changes: 85 additions & 0 deletions test/test_geocaching.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ def test_logout(self):
def test_load_credentials(self):
filename_backup = self.gc._credentials_file
credentials = {"username": _username, "password": _password}
multi_credentials = [{"username": _username+"1", "password": _password+"1"},
{"username": _username+"2", "password": _password+"2"}]
empty_valid_json = {}
nonsense_str = b"ss{}ef"
password_cmd = "echo {}".format(_password)
Expand All @@ -213,6 +215,59 @@ def test_load_credentials(self):
finally:
os.remove(valid.name)

with self.subTest("Try to load valid credentials with username from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps(credentials).encode())
self.gc._credentials_file = os.path.basename(valid.name)
username, password = self.gc._load_credentials(_username)
self.assertEqual(_username, username)
self.assertEqual(_password, password)
finally:
os.remove(valid.name)

with self.subTest("Try to load valid credentials with not existing username from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps(credentials).encode())
self.gc._credentials_file = os.path.basename(valid.name)
with self.assertRaises(KeyError):
username, password = self.gc._load_credentials(_username+"3")
finally:
os.remove(valid.name)

with self.subTest("Try to load valid multi credentials with default user from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps(multi_credentials).encode())
self.gc._credentials_file = os.path.basename(valid.name)
username, password = self.gc._load_credentials()
self.assertEqual(_username+"1", username)
self.assertEqual(_password+"1", password)
finally:
os.remove(valid.name)

with self.subTest("Try to load valid multi credentials with user from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps(multi_credentials).encode())
self.gc._credentials_file = os.path.basename(valid.name)
username, password = self.gc._load_credentials(_username+"2")
self.assertEqual(_username+"2", username)
self.assertEqual(_password+"2", password)
finally:
os.remove(valid.name)

with self.subTest("Try to load valid multi credentials with not existing user from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps(multi_credentials).encode())
self.gc._credentials_file = os.path.basename(valid.name)
with self.assertRaises(KeyError):
username, password = self.gc._load_credentials(_username+"3")
finally:
os.remove(valid.name)

with self.subTest("Try to load empty file from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as empty:
Expand All @@ -223,6 +278,36 @@ def test_load_credentials(self):
finally:
os.remove(empty.name)

with self.subTest("Try to load very empty multi credential file from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps([]).encode())
self.gc._credentials_file = os.path.basename(valid.name)
with self.assertRaises(KeyError):
username, password = self.gc._load_credentials()
finally:
os.remove(valid.name)

with self.subTest("Try to load empty multi credential file from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps([empty_valid_json]).encode())
self.gc._credentials_file = os.path.basename(valid.name)
with self.assertRaises(KeyError):
username, password = self.gc._load_credentials()
finally:
os.remove(valid.name)

with self.subTest("Try to load multi credential file with invalid format from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as valid:
valid.write(json.dumps("username").encode())
self.gc._credentials_file = os.path.basename(valid.name)
with self.assertRaises(KeyError):
username, password = self.gc._load_credentials()
finally:
os.remove(valid.name)

with self.subTest("Try to load nonsense file from current directory"):
try:
with NamedTemporaryFile(dir=".", delete=False) as nonsense:
Expand Down