aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhalcy <halcy@ARARAGI-KUN>2022-11-13 22:32:04 +0200
committerhalcy <halcy@ARARAGI-KUN>2022-11-13 22:32:04 +0200
commit24c686f6b274e04082b13f159ea10d995c2ca281 (patch)
tree57b0095298f0564f6a5730a9b6a3ac9cdacb85b9
parent5b328d479c17e44a6dde96408d6c8680b6c11ee0 (diff)
downloadmastodon.py-24c686f6b274e04082b13f159ea10d995c2ca281.tar.gz
Improve auth support
-rw-r--r--mastodon/Mastodon.py31
-rw-r--r--tests/cassettes/test_revoke.yaml253
-rw-r--r--tests/test_auth.py27
3 files changed, 304 insertions, 7 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index e84df6d..48d850b 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -486,8 +486,7 @@ class Mastodon:
486 """ 486 """
487 return Mastodon.__SUPPORTED_MASTODON_VERSION 487 return Mastodon.__SUPPORTED_MASTODON_VERSION
488 488
489 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", 489 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):
490 scopes=__DEFAULT_SCOPES, force_login=False):
491 """ 490 """
492 Returns the url that a client needs to request an oauth grant from the server. 491 Returns the url that a client needs to request an oauth grant from the server.
493 492
@@ -501,6 +500,10 @@ class Mastodon:
501 500
502 Pass force_login if you want the user to always log in even when already logged 501 Pass force_login if you want the user to always log in even when already logged
503 into web mastodon (i.e. when registering multiple different accounts in an app). 502 into web mastodon (i.e. when registering multiple different accounts in an app).
503
504 State is the oauth `state`parameter to pass to the server. It is strongly suggested
505 to use a random, nonguessable value (i.e. nothing meaningful and no incrementing ID)
506 to preserve security guarantees. It can be left out for non-web login flows.
504 """ 507 """
505 if client_id is None: 508 if client_id is None:
506 client_id = self.client_id 509 client_id = self.client_id
@@ -515,12 +518,11 @@ class Mastodon:
515 params['redirect_uri'] = redirect_uris 518 params['redirect_uri'] = redirect_uris
516 params['scope'] = " ".join(scopes) 519 params['scope'] = " ".join(scopes)
517 params['force_login'] = force_login 520 params['force_login'] = force_login
521 params['state'] = state
518 formatted_params = urlencode(params) 522 formatted_params = urlencode(params)
519 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) 523 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params])
520 524
521 def log_in(self, username=None, password=None, 525 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):
522 code=None, redirect_uri="urn:ietf:wg:oauth:2.0:oob", refresh_token=None,
523 scopes=__DEFAULT_SCOPES, to_file=None):
524 """ 526 """
525 Get the access token for a user. 527 Get the access token for a user.
526 528
@@ -588,6 +590,25 @@ class Mastodon:
588 590
589 return response['access_token'] 591 return response['access_token']
590 592
593 def revoke_access_token(self):
594 """
595 Revoke the oauth token the user is currently authenticated with, effectively removing
596 the apps access and requiring the user to log in again.
597 """
598 if self.access_token is None:
599 raise MastodonIllegalArgumentError("Not logged in, do not have a token to revoke.")
600 if self.client_id is None or self.client_secret is None:
601 raise MastodonIllegalArgumentError("Client authentication (id + secret) is required to revoke tokens.")
602 params = collections.OrderedDict([])
603 params['client_id'] = self.client_id
604 params['client_secret'] = self.client_secret
605 params['token'] = self.access_token
606 self.__api_request('POST', '/oauth/revoke', params)
607
608 # We are now logged out, clear token and logged in id
609 self.access_token = None
610 self.__logged_in_id = None
611
591 @api_version("2.7.0", "2.7.0", "2.7.0") 612 @api_version("2.7.0", "2.7.0", "2.7.0")
592 def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None): 613 def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None):
593 """ 614 """
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)