Merge branch 'master' into doc-updates

This commit is contained in:
Andy Piper 2022-11-13 22:04:13 +00:00
commit 3d10b13f32
No known key found for this signature in database
GPG Key ID: 8FBF7373AC449A7F
4 changed files with 334 additions and 27 deletions

View File

@ -5,38 +5,44 @@ version number. Breaking changes will be indicated by a change in the minor
v1.5.3 (in progress) v1.5.3 (in progress)
-------------------- --------------------
* 3.1.3 support * 3.1.3 support
* Add v2 media_post api * Added v2 media_post api
* 3.1.4 support * 3.1.4 support
* Add "remote", "local" and "only_media" parameter for timelines more broadly * Added "remote", "local" and "only_media" parameter for timelines more broadly
* Document updates to instance information api return value * Documented updates to instance information api return value
* 3.2.0 support * 3.2.0 support
* Add account notes API * Added account notes API
* Add thumbnail support to media_post / media_update * Added thumbnail support to media_post / media_update
* Document new keys in media API * Documented new keys in media API
* 3.3.0 support * 3.3.0 support
* Add "notify" parameter for following. * Added "notify" parameter for following.
* Add support for timed mutes * Added support for timed mutes
* Add support for getting an accounts features tags via account_featured_tags * Added support for getting an accounts features tags via account_featured_tags
* Miscelaneous additions
* Added support for paginating by date via converting dates to snowflake IDs (on Mastodon only - thanks to edent for the suggestion)
* Added a method to revoke oauth tokens (thanks fluffy-critter)
* Fixes * Fixes
* Various small and big fixes, improving reliablity and test coverage * Various small and big fixes, improving reliablity and test coverage
* Changed URLs from "tootsuite" to "mastodon" in several places (thanks andypiper)
* Fixed some fields not converting to datetimes (thanks SouthFox-D)
* Improved oauth web flow support
v1.5.2 v1.5.2
------ ------
* BREAKING CHANGE (but to a representation that was intended to be internal): Greatly improve how pagination info is stored (arittner) * BREAKING CHANGE (but to a representation that was intended to be internal): Greatly improve how pagination info is stored (arittner)
* Add "unknown event" handler for streaming (arittner) * Added "unknown event" handler for streaming (arittner)
* Add support for exclude_types in notifications api (MicroCheapFx) * Added support for exclude_types in notifications api (MicroCheapFx)
* Add pagination to bookmarks (arittner) * Added pagination to bookmarks (arittner)
* Make connecting for streaming more resilient (arittner) * Made connecting for streaming more resilient (arittner)
* Allow specifying a user agent header (arittner) * Allowed specifying a user agent header (arittner)
* Add support for tagged and exclude_reblogs on account_statuses api (arittner) * Addeded support for tagged and exclude_reblogs on account_statuses api (arittner)
* Add support for reports without attached statuses (arittner) * Added support for reports without attached statuses (arittner)
* General fixes * General fixes
* Fix a typo in __json_fruefalse_parse (zen-tools) * Fixed a typo in __json_fruefalse_parse (zen-tools)
* Some non-mastodon related fixes * Some non-mastodon related fixes
* Fix a typo in error message for content_type (rinpatch * Fixed a typo in error message for content_type (rinpatch
* Add support for specifying file name when uploading (animeavi) * Added support for specifying file name when uploading (animeavi)
* Fix several crashes related to gotosocials version string (fwaggle) * Fixed several crashes related to gotosocials version string (fwaggle)
* Fix an issue related to hometowns version string * Fixed an issue related to hometowns version string
v1.5.1 v1.5.1
------ ------

View File

@ -513,6 +513,8 @@ class Mastodon:
def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob",
scopes=__DEFAULT_SCOPES, force_login=False): scopes=__DEFAULT_SCOPES, force_login=False):
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):
""" """
Returns the URL that a client needs to request an OAuth grant from the server. Returns the URL that a client needs to request an OAuth grant from the server.
@ -526,6 +528,10 @@ class Mastodon:
Pass force_login if you want the user to always log in even when already logged Pass force_login if you want the user to always log in even when already logged
into web Mastodon (i.e. when registering multiple different accounts in an app). into web Mastodon (i.e. when registering multiple different accounts in an app).
State is the oauth `state`parameter to pass to the server. It is strongly suggested
to use a random, nonguessable value (i.e. nothing meaningful and no incrementing ID)
to preserve security guarantees. It can be left out for non-web login flows.
""" """
if client_id is None: if client_id is None:
client_id = self.client_id client_id = self.client_id
@ -540,12 +546,11 @@ class Mastodon:
params['redirect_uri'] = redirect_uris params['redirect_uri'] = redirect_uris
params['scope'] = " ".join(scopes) params['scope'] = " ".join(scopes)
params['force_login'] = force_login params['force_login'] = force_login
params['state'] = state
formatted_params = urlencode(params) formatted_params = urlencode(params)
return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) return "".join([self.api_base_url, "/oauth/authorize?", formatted_params])
def log_in(self, username=None, password=None, 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):
code=None, redirect_uri="urn:ietf:wg:oauth:2.0:oob", refresh_token=None,
scopes=__DEFAULT_SCOPES, to_file=None):
""" """
Get the access token for a user. Get the access token for a user.
@ -620,6 +625,26 @@ class Mastodon:
return response['access_token'] return response['access_token']
def revoke_access_token(self):
"""
Revoke the oauth token the user is currently authenticated with, effectively removing
the apps access and requiring the user to log in again.
"""
if self.access_token is None:
raise MastodonIllegalArgumentError("Not logged in, do not have a token to revoke.")
if self.client_id is None or self.client_secret is None:
raise MastodonIllegalArgumentError("Client authentication (id + secret) is required to revoke tokens.")
params = collections.OrderedDict([])
params['client_id'] = self.client_id
params['client_secret'] = self.client_secret
params['token'] = self.access_token
self.__api_request('POST', '/oauth/revoke', params)
# We are now logged out, clear token and logged in id
self.access_token = None
self.__logged_in_id = None
@api_version("2.7.0", "2.7.0", "2.7.0") @api_version("2.7.0", "2.7.0", "2.7.0")
def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None): def create_account(self, username, password, email, agreement=False, reason=None, locale="en", scopes=__DEFAULT_SCOPES, to_file=None):
""" """

View File

@ -0,0 +1,253 @@
interactions:
- request:
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
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '271'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/oauth/token
response:
body:
string: '{"access_token":"s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk","token_type":"Bearer","scope":"read
write follow push","created_at":1668370881}'
headers:
Cache-Control:
- no-store
Content-Security-Policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
style-src ''self'' http://localhost:3000 ''nonce-pCi2AQ9aKYXwS29cp2OHAg=='';
media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
worker-src ''self'' blob: http://localhost:3000'
Content-Type:
- application/json; charset=utf-8
ETag:
- W/"b4c5259bc2edbe94aab5df0f3b8ba79a"
Pragma:
- no-cache
Referrer-Policy:
- strict-origin-when-cross-origin
Transfer-Encoding:
- chunked
Vary:
- Accept, Origin
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-Request-Id:
- 086350f4-00fc-4d82-a5ce-2b557df59682
X-Runtime:
- '0.040539'
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
- request:
body: client_id=__MASTODON_PY_TEST_CLIENT_ID&client_secret=__MASTODON_PY_TEST_CLIENT_SECRET&token=s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer s3ZSxpaa2Uhe9EcHankvkfaQZQGiWpdEWIhX7GuhDlk
Connection:
- keep-alive
Content-Length:
- '135'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/oauth/revoke
response:
body:
string: '{}'
headers:
Cache-Control:
- max-age=0, private, must-revalidate
Content-Security-Policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
style-src ''self'' http://localhost:3000 ''nonce-PqM4ChK427oGZ5jIKprkYQ=='';
media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
worker-src ''self'' blob: http://localhost:3000'
Content-Type:
- application/json; charset=utf-8
ETag:
- W/"44136fa355b3678a1146ad16f7e8649e"
Referrer-Policy:
- strict-origin-when-cross-origin
Transfer-Encoding:
- chunked
Vary:
- Accept
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-Request-Id:
- 36ec7e63-b15b-487a-aa4a-c396db945794
X-Runtime:
- '0.010431'
X-XSS-Protection:
- 1; mode=block
status:
code: 200
message: OK
- request:
body: status=illegal+access+detected
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '30'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/api/v1/statuses
response:
body:
string: '{"error":"The access token is invalid"}'
headers:
Cache-Control:
- no-store
Content-Security-Policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
style-src ''self'' http://localhost:3000 ''nonce-UClOh6+Y0zf3a4O/ysqT/w=='';
media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
worker-src ''self'' blob: http://localhost:3000'
Content-Type:
- application/json; charset=utf-8
Pragma:
- no-cache
Referrer-Policy:
- strict-origin-when-cross-origin
Transfer-Encoding:
- chunked
Vary:
- Accept, Origin
WWW-Authenticate:
- Bearer realm="Doorkeeper", error="invalid_token", error_description="The access
token is invalid"
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-Request-Id:
- ab1f9d04-149b-431a-b2b0-79921d45f3bc
X-Runtime:
- '0.005292'
X-XSS-Protection:
- 1; mode=block
status:
code: 401
message: Unauthorized
- request:
body: status=illegal+access+detected
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '30'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/api/v1/statuses
response:
body:
string: '{"error":"The access token is invalid"}'
headers:
Cache-Control:
- no-store
Content-Security-Policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000;
style-src ''self'' http://localhost:3000 ''nonce-ZTmQUUs9q7lX74Aa3bTgzA=='';
media-src ''self'' https: data: http://localhost:3000; frame-src ''self''
https:; manifest-src ''self'' http://localhost:3000; connect-src ''self''
data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000
ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline''
''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000;
worker-src ''self'' blob: http://localhost:3000'
Content-Type:
- application/json; charset=utf-8
Pragma:
- no-cache
Referrer-Policy:
- strict-origin-when-cross-origin
Transfer-Encoding:
- chunked
Vary:
- Accept, Origin
WWW-Authenticate:
- Bearer realm="Doorkeeper", error="invalid_token", error_description="The access
token is invalid"
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-Request-Id:
- 9a7ad262-0d94-4df6-92a5-a670d6515979
X-Runtime:
- '0.004613'
X-XSS-Protection:
- 1; mode=block
status:
code: 401
message: Unauthorized
version: 1

View File

@ -22,14 +22,37 @@ def test_log_in_none(api_anonymous):
with pytest.raises(MastodonIllegalArgumentError): with pytest.raises(MastodonIllegalArgumentError):
api_anonymous.log_in() api_anonymous.log_in()
@pytest.mark.vcr() @pytest.mark.vcr()
def test_log_in_password(api_anonymous): def test_log_in_password(api_anonymous):
token = api_anonymous.log_in( token = api_anonymous.log_in(
username='mastodonpy_test_2@localhost:3000', username='mastodonpy_test_2@localhost:3000',
password='5fc638e0e53eafd9c4145b6bb852667d') password='5fc638e0e53eafd9c4145b6bb852667d'
)
assert token assert token
@pytest.mark.vcr()
def test_revoke(api_anonymous):
token = api_anonymous.log_in(
username='mastodonpy_test_2@localhost:3000',
password='5fc638e0e53eafd9c4145b6bb852667d'
)
api_anonymous.revoke_access_token()
try:
api_anonymous.toot("illegal access detected")
assert False
except Exception as e:
print(e)
pass
api_revoked_token = Mastodon(access_token = token)
try:
api_anonymous.toot("illegal access detected")
assert False
except Exception as e:
print(e)
pass
@pytest.mark.vcr() @pytest.mark.vcr()
def test_log_in_password_incorrect(api_anonymous): def test_log_in_password_incorrect(api_anonymous):
with pytest.raises(MastodonIllegalArgumentError): with pytest.raises(MastodonIllegalArgumentError):