aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.rst50
-rw-r--r--mastodon/Mastodon.py31
-rw-r--r--tests/cassettes/test_revoke.yaml253
-rw-r--r--tests/test_auth.py27
4 files changed, 334 insertions, 27 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1ce779a..c027b91 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,38 +5,44 @@ version number. Breaking changes will be indicated by a change in the minor
5v1.5.3 (in progress) 5v1.5.3 (in progress)
6-------------------- 6--------------------
7* 3.1.3 support 7* 3.1.3 support
8 * Add v2 media_post api 8 * Added v2 media_post api
9* 3.1.4 support 9* 3.1.4 support
10 * Add "remote", "local" and "only_media" parameter for timelines more broadly 10 * Added "remote", "local" and "only_media" parameter for timelines more broadly
11 * Document updates to instance information api return value 11 * Documented updates to instance information api return value
12* 3.2.0 support 12* 3.2.0 support
13 * Add account notes API 13 * Added account notes API
14 * Add thumbnail support to media_post / media_update 14 * Added thumbnail support to media_post / media_update
15 * Document new keys in media API 15 * Documented new keys in media API
16* 3.3.0 support 16* 3.3.0 support
17 * Add "notify" parameter for following. 17 * Added "notify" parameter for following.
18 * Add support for timed mutes 18 * Added support for timed mutes
19 * Add support for getting an accounts features tags via account_featured_tags 19 * Added support for getting an accounts features tags via account_featured_tags
20* Miscelaneous additions
21 * Added support for paginating by date via converting dates to snowflake IDs (on Mastodon only - thanks to edent for the suggestion)
22 * Added a method to revoke oauth tokens (thanks fluffy-critter)
20* Fixes 23* Fixes
21 * Various small and big fixes, improving reliablity and test coverage 24 * Various small and big fixes, improving reliablity and test coverage
22 25 * Changed URLs from "tootsuite" to "mastodon" in several places (thanks andypiper)
26 * Fixed some fields not converting to datetimes (thanks SouthFox-D)
27 * Improved oauth web flow support
28
23v1.5.2 29v1.5.2
24------ 30------
25* BREAKING CHANGE (but to a representation that was intended to be internal): Greatly improve how pagination info is stored (arittner) 31* BREAKING CHANGE (but to a representation that was intended to be internal): Greatly improve how pagination info is stored (arittner)
26* Add "unknown event" handler for streaming (arittner) 32* Added "unknown event" handler for streaming (arittner)
27* Add support for exclude_types in notifications api (MicroCheapFx) 33* Added support for exclude_types in notifications api (MicroCheapFx)
28* Add pagination to bookmarks (arittner) 34* Added pagination to bookmarks (arittner)
29* Make connecting for streaming more resilient (arittner) 35* Made connecting for streaming more resilient (arittner)
30* Allow specifying a user agent header (arittner) 36* Allowed specifying a user agent header (arittner)
31* Add support for tagged and exclude_reblogs on account_statuses api (arittner) 37* Addeded support for tagged and exclude_reblogs on account_statuses api (arittner)
32* Add support for reports without attached statuses (arittner) 38* Added support for reports without attached statuses (arittner)
33* General fixes 39* General fixes
34 * Fix a typo in __json_fruefalse_parse (zen-tools) 40 * Fixed a typo in __json_fruefalse_parse (zen-tools)
35* Some non-mastodon related fixes 41* Some non-mastodon related fixes
36 * Fix a typo in error message for content_type (rinpatch 42 * Fixed a typo in error message for content_type (rinpatch
37 * Add support for specifying file name when uploading (animeavi) 43 * Added support for specifying file name when uploading (animeavi)
38 * Fix several crashes related to gotosocials version string (fwaggle) 44 * Fixed several crashes related to gotosocials version string (fwaggle)
39 * Fix an issue related to hometowns version string 45 * Fixed an issue related to hometowns version string
40 46
41v1.5.1 47v1.5.1
42------ 48------
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index 95a33b8..98578fb 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -513,6 +513,8 @@ class Mastodon:
513 513
514 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", 514 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob",
515 scopes=__DEFAULT_SCOPES, force_login=False): 515 scopes=__DEFAULT_SCOPES, force_login=False):
516
517 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", scopes=__DEFAULT_SCOPES, force_login=False, state=None):
516 """ 518 """
517 Returns the URL that a client needs to request an OAuth grant from the server. 519 Returns the URL that a client needs to request an OAuth grant from the server.
518 520
@@ -526,6 +528,10 @@ class Mastodon:
526 528
527 Pass force_login if you want the user to always log in even when already logged 529 Pass force_login if you want the user to always log in even when already logged
528 into web Mastodon (i.e. when registering multiple different accounts in an app). 530 into web Mastodon (i.e. when registering multiple different accounts in an app).
531
532 State is the oauth `state`parameter to pass to the server. It is strongly suggested
533 to use a random, nonguessable value (i.e. nothing meaningful and no incrementing ID)
534 to preserve security guarantees. It can be left out for non-web login flows.
529 """ 535 """
530 if client_id is None: 536 if client_id is None:
531 client_id = self.client_id 537 client_id = self.client_id
@@ -540,12 +546,11 @@ class Mastodon:
540 params['redirect_uri'] = redirect_uris 546 params['redirect_uri'] = redirect_uris
541 params['scope'] = " ".join(scopes) 547 params['scope'] = " ".join(scopes)
542 params['force_login'] = force_login 548 params['force_login'] = force_login
549 params['state'] = state
543 formatted_params = urlencode(params) 550 formatted_params = urlencode(params)
544 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) 551 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params])
545 552
546 def log_in(self, username=None, password=None, 553 def log_in(self, username=None, password=None, code=None, redirect_uri="urn:ietf:wg:oauth:2.0:oob", refresh_token=None, scopes=__DEFAULT_SCOPES, to_file=None):
547 code=None, redirect_uri="urn:ietf:wg:oauth:2.0:oob", refresh_token=None,
548 scopes=__DEFAULT_SCOPES, to_file=None):
549 """ 554 """
550 Get the access token for a user. 555 Get the access token for a user.
551 556
@@ -620,6 +625,26 @@ class Mastodon:
620 625
621 return response['access_token'] 626 return response['access_token']
622 627
628
629 def revoke_access_token(self):
630 """
631 Revoke the oauth token the user is currently authenticated with, effectively removing
632 the apps access and requiring the user to log in again.
633 """
634 if self.access_token is None:
635 raise MastodonIllegalArgumentError("Not logged in, do not have a token to revoke.")
636 if self.client_id is None or self.client_secret is None:
637 raise MastodonIllegalArgumentError("Client authentication (id + secret) is required to revoke tokens.")
638 params = collections.OrderedDict([])
639 params['client_id'] = self.client_id
640 params['client_secret'] = self.client_secret
641 params['token'] = self.access_token
642 self.__api_request('POST', '/oauth/revoke', params)
643
644 # We are now logged out, clear token and logged in id
645 self.access_token = None
646 self.__logged_in_id = None
647
623 @api_version("2.7.0", "2.7.0", "2.7.0") 648 @api_version("2.7.0", "2.7.0", "2.7.0")
624 def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None): 649 def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None):
625 """ 650 """
diff --git a/tests/cassettes/test_revoke.yaml b/tests/cassettes/test_revoke.yaml
new file mode 100644
index 0000000..1bbc487
--- /dev/null
+++ b/tests/cassettes/test_revoke.yaml
@@ -0,0 +1,253 @@
1interactions:
2- request:
3 body: username=mastodonpy_test_2%40localhost%3A3000&password=5fc638e0e53eafd9c4145b6bb852667d&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=password&client_id=__MASTODON_PY_TEST_CLIENT_ID&client_secret=__MASTODON_PY_TEST_CLIENT_SECRET&scope=read+write+follow+push
4 headers:
5 Accept:
6 - '*/*'
7 Accept-Encoding:
8 - gzip, deflate
9 Connection:
10 - keep-alive
11 Content-Length:
12 - '271'
13 Content-Type:
14 - application/x-www-form-urlencoded
15 User-Agent:
16 - tests/v311
17 method: POST
18 uri: http://localhost:3000/oauth/token
19 response:
20 body:
21 string: '{"access_token":"s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk","token_type":"Bearer","scope":"read
22 write follow push","created_at":1668370881}'
23 headers:
24 Cache-Control:
25 - no-store
26 Content-Security-Policy:
27 - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
28 ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
29 style-src ''self'' http://localhost:3000 ''nonce-pCi2AQ9aKYXwS29cp2OHAg=='';
30 media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
31 https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
32 data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
33 ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
34 ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
35 worker-src ''self'' blob: http://localhost:3000'
36 Content-Type:
37 - application/json; charset=utf-8
38 ETag:
39 - W/"b4c5259bc2edbe94aab5df0f3b8ba79a"
40 Pragma:
41 - no-cache
42 Referrer-Policy:
43 - strict-origin-when-cross-origin
44 Transfer-Encoding:
45 - chunked
46 Vary:
47 - Accept, Origin
48 X-Content-Type-Options:
49 - nosniff
50 X-Download-Options:
51 - noopen
52 X-Frame-Options:
53 - SAMEORIGIN
54 X-Permitted-Cross-Domain-Policies:
55 - none
56 X-Request-Id:
57 - 086350f4-00fc-4d82-a5ce-2b557df59682
58 X-Runtime:
59 - '0.040539'
60 X-XSS-Protection:
61 - 1; mode=block
62 status:
63 code: 200
64 message: OK
65- request:
66 body: client_id=__MASTODON_PY_TEST_CLIENT_ID&client_secret=__MASTODON_PY_TEST_CLIENT_SECRET&token=s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk
67 headers:
68 Accept:
69 - '*/*'
70 Accept-Encoding:
71 - gzip, deflate
72 Authorization:
73 - Bearer s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk
74 Connection:
75 - keep-alive
76 Content-Length:
77 - '135'
78 Content-Type:
79 - application/x-www-form-urlencoded
80 User-Agent:
81 - tests/v311
82 method: POST
83 uri: http://localhost:3000/oauth/revoke
84 response:
85 body:
86 string: '{}'
87 headers:
88 Cache-Control:
89 - max-age=0, private, must-revalidate
90 Content-Security-Policy:
91 - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
92 ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
93 style-src ''self'' http://localhost:3000 ''nonce-PqM4ChK427oGZ5jIKprkYQ=='';
94 media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
95 https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
96 data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
97 ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
98 ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
99 worker-src ''self'' blob: http://localhost:3000'
100 Content-Type:
101 - application/json; charset=utf-8
102 ETag:
103 - W/"44136fa355b3678a1146ad16f7e8649e"
104 Referrer-Policy:
105 - strict-origin-when-cross-origin
106 Transfer-Encoding:
107 - chunked
108 Vary:
109 - Accept
110 X-Content-Type-Options:
111 - nosniff
112 X-Download-Options:
113 - noopen
114 X-Frame-Options:
115 - SAMEORIGIN
116 X-Permitted-Cross-Domain-Policies:
117 - none
118 X-Request-Id:
119 - 36ec7e63-b15b-487a-aa4a-c396db945794
120 X-Runtime:
121 - '0.010431'
122 X-XSS-Protection:
123 - 1; mode=block
124 status:
125 code: 200
126 message: OK
127- request:
128 body: status=illegal+access+detected
129 headers:
130 Accept:
131 - '*/*'
132 Accept-Encoding:
133 - gzip, deflate
134 Connection:
135 - keep-alive
136 Content-Length:
137 - '30'
138 Content-Type:
139 - application/x-www-form-urlencoded
140 User-Agent:
141 - tests/v311
142 method: POST
143 uri: http://localhost:3000/api/v1/statuses
144 response:
145 body:
146 string: '{"error":"The access token is invalid"}'
147 headers:
148 Cache-Control:
149 - no-store
150 Content-Security-Policy:
151 - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
152 ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
153 style-src ''self'' http://localhost:3000 ''nonce-UClOh6+Y0zf3a4O/ysqT/w=='';
154 media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
155 https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
156 data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
157 ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
158 ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
159 worker-src ''self'' blob: http://localhost:3000'
160 Content-Type:
161 - application/json; charset=utf-8
162 Pragma:
163 - no-cache
164 Referrer-Policy:
165 - strict-origin-when-cross-origin
166 Transfer-Encoding:
167 - chunked
168 Vary:
169 - Accept, Origin
170 WWW-Authenticate:
171 - Bearer realm="Doorkeeper", error="invalid_token", error_description="The access
172 token is invalid"
173 X-Content-Type-Options:
174 - nosniff
175 X-Download-Options:
176 - noopen
177 X-Frame-Options:
178 - SAMEORIGIN
179 X-Permitted-Cross-Domain-Policies:
180 - none
181 X-Request-Id:
182 - ab1f9d04-149b-431a-b2b0-79921d45f3bc
183 X-Runtime:
184 - '0.005292'
185 X-XSS-Protection:
186 - 1; mode=block
187 status:
188 code: 401
189 message: Unauthorized
190- request:
191 body: status=illegal+access+detected
192 headers:
193 Accept:
194 - '*/*'
195 Accept-Encoding:
196 - gzip, deflate
197 Connection:
198 - keep-alive
199 Content-Length:
200 - '30'
201 Content-Type:
202 - application/x-www-form-urlencoded
203 User-Agent:
204 - tests/v311
205 method: POST
206 uri: http://localhost:3000/api/v1/statuses
207 response:
208 body:
209 string: '{"error":"The access token is invalid"}'
210 headers:
211 Cache-Control:
212 - no-store
213 Content-Security-Policy:
214 - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
215 ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
216 style-src ''self'' http://localhost:3000 ''nonce-ZTmQUUs9q7lX74Aa3bTgzA=='';
217 media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
218 https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
219 data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
220 ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
221 ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
222 worker-src ''self'' blob: http://localhost:3000'
223 Content-Type:
224 - application/json; charset=utf-8
225 Pragma:
226 - no-cache
227 Referrer-Policy:
228 - strict-origin-when-cross-origin
229 Transfer-Encoding:
230 - chunked
231 Vary:
232 - Accept, Origin
233 WWW-Authenticate:
234 - Bearer realm="Doorkeeper", error="invalid_token", error_description="The access
235 token is invalid"
236 X-Content-Type-Options:
237 - nosniff
238 X-Download-Options:
239 - noopen
240 X-Frame-Options:
241 - SAMEORIGIN
242 X-Permitted-Cross-Domain-Policies:
243 - none
244 X-Request-Id:
245 - 9a7ad262-0d94-4df6-92a5-a670d6515979
246 X-Runtime:
247 - '0.004613'
248 X-XSS-Protection:
249 - 1; mode=block
250 status:
251 code: 401
252 message: Unauthorized
253version: 1
diff --git a/tests/test_auth.py b/tests/test_auth.py
index c3acb66..a14b4e7 100644
--- a/tests/test_auth.py
+++ b/tests/test_auth.py
@@ -22,15 +22,38 @@ def test_log_in_none(api_anonymous):
22 with pytest.raises(MastodonIllegalArgumentError): 22 with pytest.raises(MastodonIllegalArgumentError):
23 api_anonymous.log_in() 23 api_anonymous.log_in()
24 24
25
26@pytest.mark.vcr() 25@pytest.mark.vcr()
27def test_log_in_password(api_anonymous): 26def test_log_in_password(api_anonymous):
28 token = api_anonymous.log_in( 27 token = api_anonymous.log_in(
29 username='mastodonpy_test_2@localhost:3000', 28 username='mastodonpy_test_2@localhost:3000',
30 password='5fc638e0e53eafd9c4145b6bb852667d') 29 password='5fc638e0e53eafd9c4145b6bb852667d'
30 )
31 assert token 31 assert token
32 32
33@pytest.mark.vcr() 33@pytest.mark.vcr()
34def test_revoke(api_anonymous):
35 token = api_anonymous.log_in(
36 username='mastodonpy_test_2@localhost:3000',
37 password='5fc638e0e53eafd9c4145b6bb852667d'
38 )
39 api_anonymous.revoke_access_token()
40
41 try:
42 api_anonymous.toot("illegal access detected")
43 assert False
44 except Exception as e:
45 print(e)
46 pass
47
48 api_revoked_token = Mastodon(access_token = token)
49 try:
50 api_anonymous.toot("illegal access detected")
51 assert False
52 except Exception as e:
53 print(e)
54 pass
55
56@pytest.mark.vcr()
34def test_log_in_password_incorrect(api_anonymous): 57def test_log_in_password_incorrect(api_anonymous):
35 with pytest.raises(MastodonIllegalArgumentError): 58 with pytest.raises(MastodonIllegalArgumentError):
36 api_anonymous.log_in( 59 api_anonymous.log_in(
Powered by cgit v1.2.3 (git 2.41.0)