diff options
author | Lorenz Diener <[email protected]> | 2019-10-12 18:58:46 +0200 |
---|---|---|
committer | Lorenz Diener <[email protected]> | 2019-10-12 18:58:46 +0200 |
commit | ca45cd65aa1e404164d318f1b5453de6e8b5cc6d (patch) | |
tree | 606a65e4b5585a778d9d1a4df9118dc02b403f11 | |
parent | 5c4916bd813e205d7fc5cd7f44324b0cecdc4940 (diff) | |
download | mastodon.py-ca45cd65aa1e404164d318f1b5453de6e8b5cc6d.tar.gz |
Add ability to persist base urls with clientid/secret/token (fixes #200)
-rw-r--r-- | mastodon/Mastodon.py | 52 | ||||
-rw-r--r-- | tests/test_auth.py | 30 | ||||
-rw-r--r-- | tests/test_create_app.py | 2 |
3 files changed, 65 insertions, 19 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 465beb7..c84ac6a 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py | |||
@@ -267,16 +267,17 @@ class Mastodon: | |||
267 | 267 | ||
268 | if to_file is not None: | 268 | if to_file is not None: |
269 | with open(to_file, 'w') as secret_file: | 269 | with open(to_file, 'w') as secret_file: |
270 | secret_file.write(response['client_id'] + '\n') | 270 | secret_file.write(response['client_id'] + "\n") |
271 | secret_file.write(response['client_secret'] + '\n') | 271 | secret_file.write(response['client_secret'] + "\n") |
272 | 272 | secret_file.write(api_base_url + "\n") | |
273 | |||
273 | return (response['client_id'], response['client_secret']) | 274 | return (response['client_id'], response['client_secret']) |
274 | 275 | ||
275 | ### | 276 | ### |
276 | # Authentication, including constructor | 277 | # Authentication, including constructor |
277 | ### | 278 | ### |
278 | def __init__(self, client_id=None, client_secret=None, access_token=None, | 279 | def __init__(self, client_id=None, client_secret=None, access_token=None, |
279 | api_base_url=__DEFAULT_BASE_URL, debug_requests=False, | 280 | api_base_url=None, debug_requests=False, |
280 | ratelimit_method="wait", ratelimit_pacefactor=1.1, | 281 | ratelimit_method="wait", ratelimit_pacefactor=1.1, |
281 | request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, | 282 | request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, |
282 | version_check_mode = "created", session=None): | 283 | version_check_mode = "created", session=None): |
@@ -285,9 +286,12 @@ class Mastodon: | |||
285 | give a `client_id` and it is not a file, you must also give a secret. If you specify an | 286 | give a `client_id` and it is not a file, you must also give a secret. If you specify an |
286 | `access_token` then you don't need to specify a `client_id`. It is allowed to specify | 287 | `access_token` then you don't need to specify a `client_id`. It is allowed to specify |
287 | neither - in this case, you will be restricted to only using endpoints that do not | 288 | neither - in this case, you will be restricted to only using endpoints that do not |
288 | require authentication. | 289 | require authentication. If a file is given as `client_id`, client ID, secret and |
290 | base url are read from that file. | ||
289 | 291 | ||
290 | You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). | 292 | You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). If |
293 | a file is given, Mastodon.py also tries to load the base URL from this file, if present. A | ||
294 | client id and secret are not required in this case. | ||
291 | 295 | ||
292 | Mastodon.py can try to respect rate limits in several ways, controlled by `ratelimit_method`. | 296 | Mastodon.py can try to respect rate limits in several ways, controlled by `ratelimit_method`. |
293 | "throw" makes functions throw a `MastodonRatelimitError` when the rate | 297 | "throw" makes functions throw a `MastodonRatelimitError` when the rate |
@@ -298,8 +302,9 @@ class Mastodon: | |||
298 | even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also | 302 | even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also |
299 | note that "pace" and "wait" are NOT thread safe. | 303 | note that "pace" and "wait" are NOT thread safe. |
300 | 304 | ||
301 | Specify `api_base_url` if you wish to talk to an instance other than the flagship one. | 305 | Specify `api_base_url` if you wish to talk to an instance other than the flagship one. When |
302 | If a file is given as `client_id`, client ID and secret are read from that file. | 306 | reading from client id or access token files as written by Mastodon.py 1.5.0 or larger, |
307 | this can be omitted. | ||
303 | 308 | ||
304 | By default, a timeout of 300 seconds is used for all requests. If you wish to change this, | 309 | By default, a timeout of 300 seconds is used for all requests. If you wish to change this, |
305 | pass the desired timeout (in seconds) as `request_timeout`. | 310 | pass the desired timeout (in seconds) as `request_timeout`. |
@@ -317,7 +322,10 @@ class Mastodon: | |||
317 | changed after the version of Mastodon that is connected has been released. If it is set to "none", | 322 | changed after the version of Mastodon that is connected has been released. If it is set to "none", |
318 | version checking is disabled. | 323 | version checking is disabled. |
319 | """ | 324 | """ |
320 | self.api_base_url = Mastodon.__protocolize(api_base_url) | 325 | self.api_base_url = None |
326 | if not api_base_url is None: | ||
327 | self.api_base_url = Mastodon.__protocolize(api_base_url) | ||
328 | |||
321 | self.client_id = client_id | 329 | self.client_id = client_id |
322 | self.client_secret = client_secret | 330 | self.client_secret = client_secret |
323 | self.access_token = access_token | 331 | self.access_token = access_token |
@@ -364,6 +372,13 @@ class Mastodon: | |||
364 | with open(self.client_id, 'r') as secret_file: | 372 | with open(self.client_id, 'r') as secret_file: |
365 | self.client_id = secret_file.readline().rstrip() | 373 | self.client_id = secret_file.readline().rstrip() |
366 | self.client_secret = secret_file.readline().rstrip() | 374 | self.client_secret = secret_file.readline().rstrip() |
375 | |||
376 | try_base_url = secret_file.readline().rstrip() | ||
377 | if (not try_base_url is None) and len(try_base_url) != 0: | ||
378 | try_base_url = Mastodon.__protocolize(try_base_url) | ||
379 | if not (self.api_base_url is None or try_base_url == self.api_base_url): | ||
380 | raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified') | ||
381 | self.api_base_url = try_base_url | ||
367 | else: | 382 | else: |
368 | if self.client_secret is None: | 383 | if self.client_secret is None: |
369 | raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret') | 384 | raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret') |
@@ -371,7 +386,14 @@ class Mastodon: | |||
371 | if self.access_token is not None and os.path.isfile(self.access_token): | 386 | if self.access_token is not None and os.path.isfile(self.access_token): |
372 | with open(self.access_token, 'r') as token_file: | 387 | with open(self.access_token, 'r') as token_file: |
373 | self.access_token = token_file.readline().rstrip() | 388 | self.access_token = token_file.readline().rstrip() |
374 | 389 | ||
390 | try_base_url = token_file.readline().rstrip() | ||
391 | if (not try_base_url is None) and len(try_base_url) != 0: | ||
392 | try_base_url = Mastodon.__protocolize(try_base_url) | ||
393 | if not (self.api_base_url is None or try_base_url == self.api_base_url): | ||
394 | raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified') | ||
395 | self.api_base_url = try_base_url | ||
396 | |||
375 | def retrieve_mastodon_version(self): | 397 | def retrieve_mastodon_version(self): |
376 | """ | 398 | """ |
377 | Determine installed mastodon version and set major, minor and patch (not including RC info) accordingly. | 399 | Determine installed mastodon version and set major, minor and patch (not including RC info) accordingly. |
@@ -508,8 +530,9 @@ class Mastodon: | |||
508 | 530 | ||
509 | if to_file is not None: | 531 | if to_file is not None: |
510 | with open(to_file, 'w') as token_file: | 532 | with open(to_file, 'w') as token_file: |
511 | token_file.write(response['access_token'] + '\n') | 533 | token_file.write(response['access_token'] + "\n") |
512 | 534 | token_file.write(self.api_base_url + "\n") | |
535 | |||
513 | self.__logged_in_id = None | 536 | self.__logged_in_id = None |
514 | 537 | ||
515 | return response['access_token'] | 538 | return response['access_token'] |
@@ -572,8 +595,9 @@ class Mastodon: | |||
572 | 595 | ||
573 | if to_file is not None: | 596 | if to_file is not None: |
574 | with open(to_file, 'w') as token_file: | 597 | with open(to_file, 'w') as token_file: |
575 | token_file.write(response['access_token'] + '\n') | 598 | token_file.write(response['access_token'] + "\n") |
576 | 599 | token_file.write(self.api_base_url + "\n") | |
600 | |||
577 | self.__logged_in_id = None | 601 | self.__logged_in_id = None |
578 | 602 | ||
579 | return response['access_token'] | 603 | return response['access_token'] |
diff --git a/tests/test_auth.py b/tests/test_auth.py index b4a004e..fbf8974 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py | |||
@@ -30,7 +30,6 @@ def test_log_in_password(api_anonymous): | |||
30 | password='mastodonadmin') | 30 | password='mastodonadmin') |
31 | assert token | 31 | assert token |
32 | 32 | ||
33 | |||
34 | @pytest.mark.vcr() | 33 | @pytest.mark.vcr() |
35 | def test_log_in_password_incorrect(api_anonymous): | 34 | def test_log_in_password_incorrect(api_anonymous): |
36 | with pytest.raises(MastodonIllegalArgumentError): | 35 | with pytest.raises(MastodonIllegalArgumentError): |
@@ -38,7 +37,6 @@ def test_log_in_password_incorrect(api_anonymous): | |||
38 | username='admin@localhost', | 37 | username='admin@localhost', |
39 | password='hunter2') | 38 | password='hunter2') |
40 | 39 | ||
41 | |||
42 | @pytest.mark.vcr() | 40 | @pytest.mark.vcr() |
43 | def test_log_in_password_to_file(api_anonymous, tmpdir): | 41 | def test_log_in_password_to_file(api_anonymous, tmpdir): |
44 | filepath = tmpdir.join('token') | 42 | filepath = tmpdir.join('token') |
@@ -46,18 +44,42 @@ def test_log_in_password_to_file(api_anonymous, tmpdir): | |||
46 | username='admin@localhost', | 44 | username='admin@localhost', |
47 | password='mastodonadmin', | 45 | password='mastodonadmin', |
48 | to_file=str(filepath)) | 46 | to_file=str(filepath)) |
49 | token = filepath.read_text('UTF-8').rstrip() | 47 | token = filepath.read_text('UTF-8').rstrip().split("\n")[0] |
50 | assert token | 48 | assert token |
51 | api = api_anonymous | 49 | api = api_anonymous |
52 | api.access_token = token | 50 | api.access_token = token |
53 | assert api.account_verify_credentials() | 51 | assert api.account_verify_credentials() |
54 | 52 | ||
53 | @pytest.mark.vcr() | ||
54 | def test_url_errors(tmpdir): | ||
55 | clientid_good = tmpdir.join("clientid") | ||
56 | token_good = tmpdir.join("token") | ||
57 | clientid_bad = tmpdir.join("clientid_bad") | ||
58 | token_bad = tmpdir.join("token_bad") | ||
59 | |||
60 | clientid_good.write_text("foo\nbar\nhttps://zombo.com\n", "UTF-8") | ||
61 | token_good.write_text("foo\nhttps://zombo.com\n", "UTF-8") | ||
62 | clientid_bad.write_text("foo\nbar\nhttps://evil.org\n", "UTF-8") | ||
63 | token_bad.write_text("foo\nhttps://evil.org\n", "UTF-8") | ||
64 | |||
65 | api = Mastodon(client_id = clientid_good, access_token = token_good) | ||
66 | assert api | ||
67 | assert api.api_base_url == "https://zombo.com" | ||
68 | assert Mastodon(client_id = clientid_good, access_token = token_good, api_base_url = "zombo.com") | ||
69 | |||
70 | with pytest.raises(MastodonIllegalArgumentError): | ||
71 | Mastodon(client_id = clientid_good, access_token = token_bad, api_base_url = "zombo.com") | ||
72 | |||
73 | with pytest.raises(MastodonIllegalArgumentError): | ||
74 | Mastodon(client_id = clientid_bad, access_token = token_good, api_base_url = "zombo.com") | ||
75 | |||
76 | with pytest.raises(MastodonIllegalArgumentError): | ||
77 | Mastodon(client_id = clientid_bad, access_token = token_bad, api_base_url = "zombo.com") | ||
55 | 78 | ||
56 | @pytest.mark.skip(reason="Not sure how to test this without setting up selenium or a similar browser automation suite to click on the allow button") | 79 | @pytest.mark.skip(reason="Not sure how to test this without setting up selenium or a similar browser automation suite to click on the allow button") |
57 | def test_log_in_code(api_anonymous): | 80 | def test_log_in_code(api_anonymous): |
58 | pass | 81 | pass |
59 | 82 | ||
60 | |||
61 | @pytest.mark.skip(reason="Not supported by Mastodon >:@ (yet?)") | 83 | @pytest.mark.skip(reason="Not supported by Mastodon >:@ (yet?)") |
62 | def test_log_in_refresh(api_anonymous): | 84 | def test_log_in_refresh(api_anonymous): |
63 | pass | 85 | pass |
diff --git a/tests/test_create_app.py b/tests/test_create_app.py index b9de298..8260751 100644 --- a/tests/test_create_app.py +++ b/tests/test_create_app.py | |||
@@ -31,7 +31,7 @@ def test_create_app(mocker, to_file=None, redirect_uris=None, website=None): | |||
31 | def test_create_app_to_file(mocker, tmpdir): | 31 | def test_create_app_to_file(mocker, tmpdir): |
32 | filepath = tmpdir.join('credentials') | 32 | filepath = tmpdir.join('credentials') |
33 | test_create_app(mocker, to_file=str(filepath)) | 33 | test_create_app(mocker, to_file=str(filepath)) |
34 | assert filepath.read_text('UTF-8') == "foo\nbar\n" | 34 | assert filepath.read_text('UTF-8') == "foo\nbar\nhttps://example.com\n" |
35 | 35 | ||
36 | def test_create_app_redirect_uris(mocker): | 36 | def test_create_app_redirect_uris(mocker): |
37 | test_create_app(mocker, redirect_uris='http://example.net') | 37 | test_create_app(mocker, redirect_uris='http://example.net') |