Skip to content

Commit 3634876

Browse files
authored
expired token handle (#40)
* expired token handle (relates #108 mergin plugin issue)
1 parent 039ddfc commit 3634876

File tree

4 files changed

+45
-10
lines changed

4 files changed

+45
-10
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ name = "pypi"
66
[packages]
77
python-dateutil = "==2.6.0"
88
pygeodiff = "==0.7.4"
9+
pytz = "==2019.3"
910

1011

1112
[dev-packages]

mergin/client.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ class ClientError(Exception):
5353
pass
5454

5555

56+
class LoginError(Exception):
57+
pass
58+
59+
5660
class SyncError(Exception):
5761
def __init__(self, msg, detail=""):
5862
super().__init__(msg)
@@ -581,8 +585,21 @@ def __init__(self, url, auth_token=None, login=None, password=None, plugin_versi
581585
urllib.request.install_opener(self.opener)
582586

583587
if login and password:
584-
self.login(login, password)
588+
self._auth_params = {
589+
"login": login,
590+
"password": password
591+
}
592+
if not self._auth_session:
593+
self.login(login, password)
594+
595+
def _check_token(f):
596+
def wrapper(self, *args):
597+
if (not self._auth_session or self._auth_session['expire'] < datetime.now(timezone.utc)) and self._auth_params:
598+
self.login(self._auth_params['login'], self._auth_params['password'])
599+
return f(self, *args)
600+
return wrapper
585601

602+
@_check_token
586603
def _do_request(self, request):
587604
if self._auth_session:
588605
delta = self._auth_session["expire"] - datetime.now(timezone.utc)
@@ -686,10 +703,19 @@ def login(self, login, password):
686703
"login": login,
687704
"password": password
688705
}
689-
self._auth_params = params
690-
resp = self.post("/v1/auth/login", params, {"Content-Type": "application/json"})
691-
data = json.load(resp)
692-
session = data["session"]
706+
try:
707+
self._auth_params = params
708+
url = urllib.parse.urljoin(self.url, urllib.parse.quote("/v1/auth/login"))
709+
data = json.dumps(self._auth_params, cls=DateTimeEncoder).encode("utf-8")
710+
request = urllib.request.Request(url, data, {"Content-Type": "application/json"}, method="POST")
711+
resp = self.opener.open(request)
712+
data = json.load(resp)
713+
session = data["session"]
714+
except urllib.error.HTTPError as e:
715+
if e.headers.get("Content-Type", "") == "application/problem+json":
716+
info = json.load(e)
717+
raise LoginError(info.get("detail"))
718+
raise LoginError(e.read().decode("utf-8"))
693719
self._auth_session = {
694720
"token": "Bearer %s" % session["token"],
695721
"expire": dateutil.parser.parse(session["expire"])
@@ -1154,7 +1180,6 @@ def upload_chunk(chunk_id, data):
11541180
for chunk in file_meta["chunks"]:
11551181
data = file.read(UPLOAD_CHUNK_SIZE)
11561182
upload_chunk(chunk, data)
1157-
11581183
def project_status(self, directory):
11591184
"""
11601185
Get project status, e.g. server and local changes.

mergin/test/test_client.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import os
22
import tempfile
33
import shutil
4+
from datetime import datetime, timedelta
45
import pytest
5-
from ..client import MerginClient, ClientError, MerginProject, SyncError
6+
import pytz
7+
8+
from ..client import MerginClient, ClientError, MerginProject, SyncError, LoginError
69
from ..utils import generate_checksum
710

811
SERVER_URL = os.environ.get('TEST_MERGIN_URL')
@@ -41,7 +44,7 @@ def test_login(mc):
4144
with pytest.raises(ValueError, match='Invalid token data'):
4245
MerginClient(mc.url, auth_token='Bearer .jas646kgfa')
4346

44-
with pytest.raises(ClientError, match='Invalid username or password'):
47+
with pytest.raises(LoginError, match='Invalid username or password'):
4548
mc.login('foo', 'bar')
4649

4750

@@ -329,8 +332,13 @@ def test_list_of_push_changes(mc):
329332
mp = MerginProject(project_dir)
330333

331334
shutil.copy(mp.fpath('inserted_1_A.gpkg'), mp.fpath(f_updated))
332-
335+
mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1)
333336
pull_changes, push_changes, push_changes_summary = mc.project_status(project_dir)
334337
assert str(push_changes_summary) == PUSH_CHANGES_SUMMARY
335338

339+
mc._auth_session["expire"] = datetime.now().replace(tzinfo=pytz.utc) - timedelta(days=1)
340+
mc._auth_params = None
341+
with pytest.raises(ClientError, match="You don't have the permission to access the requested resource. It is either read-protected or not readable by the server."):
342+
mc.project_status(project_dir)
343+
336344

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
platforms='any',
1919
install_requires=[
2020
'python-dateutil==2.6.0',
21-
'pygeodiff==0.7.4'
21+
'pygeodiff==0.7.4',
22+
'pytz==2019.3'
2223
],
2324

2425
test_suite='nose.collector',

0 commit comments

Comments
 (0)