Add oauth_userinfo endpoint

pull/419/head
halcy 2025-08-17 19:13:17 +03:00
rodzic c21ba153f7
commit 93a96af966
11 zmienionych plików z 355 dodań i 4 usunięć

Wyświetl plik

@ -20,6 +20,7 @@ v2.1.0 (IN PROGRESS)
* Fix `notifications` returning nonsense when passing a single `id` (Thanks @chinchalinchin for the report) * Fix `notifications` returning nonsense when passing a single `id` (Thanks @chinchalinchin for the report)
* Fix `moved` accidentally being named `moved_to_account` (Thanks @unusualevent for the report) * Fix `moved` accidentally being named `moved_to_account` (Thanks @unusualevent for the report)
* Added a warning for deprecated endpoints if the "deprecation" header is present * Added a warning for deprecated endpoints if the "deprecation" header is present
* Added `oauth_userinfo` endpoint.
v2.0.1 v2.0.1
------ ------

Wyświetl plik

@ -8,7 +8,7 @@ Refer to mastodon changelog and API docs for details when implementing, add or m
* [ ] New endpoints for endorsements, replacing "pin" api, which is now deprecated: accounts_endorsements(id), account_endorse(id), account_unendorse(id) * [ ] New endpoints for endorsements, replacing "pin" api, which is now deprecated: accounts_endorsements(id), account_endorse(id), account_unendorse(id)
* [ ] New endpoints for featured tags: tag_feature(name), tag_unfeature(name) * [ ] New endpoints for featured tags: tag_feature(name), tag_unfeature(name)
* [ ] New endpoint: instance_terms, with or without date (format?) * [ ] New endpoint: instance_terms, with or without date (format?)
* [ ] Some oauth stuff (userinfo? capability discovery? see issue for that) * [x] Some oauth stuff (userinfo? capability discovery? see issue for that)
* [ ] status_delete now has a media delete param * [ ] status_delete now has a media delete param
* [ ] push_subscribe now has a "standard" parameter to switch between two versions. may also need to update crypto impls? * [ ] push_subscribe now has a "standard" parameter to switch between two versions. may also need to update crypto impls?
* [ ] account_register now has a date of birth param (as above: format?) * [ ] account_register now has a date of birth param (as above: format?)

Wyświetl plik

@ -395,6 +395,9 @@ Return types
.. autoclass:: mastodon.return_types.OAuthServerInfo .. autoclass:: mastodon.return_types.OAuthServerInfo
:members: :members:
.. autoclass:: mastodon.return_types.OAuthUserInfo
:members:
Deprecated types Deprecated types
================ ================
.. autoclass:: mastodon.return_types.Filter .. autoclass:: mastodon.return_types.Filter

Wyświetl plik

@ -39,6 +39,11 @@ Authentication
.. automethod:: Mastodon.create_account .. automethod:: Mastodon.create_account
.. automethod:: Mastodon.email_resend_confirmation .. automethod:: Mastodon.email_resend_confirmation
OAuth information
-----------------
.. automethod:: Mastodon.oauth_authorization_server_info
.. automethod:: Mastodon.oauth_userinfo
User preferences User preferences
---------------- ----------------
.. automethod:: Mastodon.preferences .. automethod:: Mastodon.preferences

Wyświetl plik

@ -14,7 +14,7 @@ from mastodon.utility import parse_version_string, api_version
from mastodon.internals import Mastodon as Internals from mastodon.internals import Mastodon as Internals
from mastodon.utility import Mastodon as Utility from mastodon.utility import Mastodon as Utility
from typing import List, Optional, Union, Tuple from typing import List, Optional, Union, Tuple
from mastodon.return_types import Application, AttribAccessDict, OAuthServerInfo from mastodon.return_types import Application, AttribAccessDict, OAuthServerInfo, OAuthUserInfo
from mastodon.compat import PurePath from mastodon.compat import PurePath
class Mastodon(Internals): class Mastodon(Internals):
@ -358,6 +358,21 @@ class Mastodon(Internals):
response = AttribAccessDict() response = AttribAccessDict()
return response return response
@api_version("4.3.0", "4.3.0")
def oauth_userinfo(self) -> OAuthUserInfo:
"""
Returns information about the authenticated user.
Intended for something called "OpenID Connect", which you can find information about here:
https://openid.net/developers/how-connect-works/
"""
oauth_url = "".join([self.api_base_url, "/oauth/userinfo"])
oauth_info = self.oauth_authorization_server_info()
if "userinfo_endpoint" in oauth_info:
oauth_url = Mastodon.__protocolize(oauth_info["userinfo_endpoint"])
Mastodon.__oauth_url_check(oauth_url)
return self.__api_request('GET', oauth_url, do_ratelimiting=False, base_url_override="")
def log_in(self, username: Optional[str] = None, password: Optional[str] = None, code: Optional[str] = None, def log_in(self, username: Optional[str] = None, password: Optional[str] = None, code: Optional[str] = None,
redirect_uri: str = "urn:ietf:wg:oauth:2.0:oob", refresh_token: Optional[str] = None, scopes: List[str] = _DEFAULT_SCOPES, redirect_uri: str = "urn:ietf:wg:oauth:2.0:oob", refresh_token: Optional[str] = None, scopes: List[str] = _DEFAULT_SCOPES,
to_file: Optional[Union[str, PurePath]] = None, allow_http: bool = False) -> str: to_file: Optional[Union[str, PurePath]] = None, allow_http: bool = False) -> str:

Wyświetl plik

@ -59,7 +59,8 @@ _SCOPE_SETS = {
'admin:write:email_domain_blocks', 'admin:write:email_domain_blocks',
'admin:write:canonical_email_blocks', 'admin:write:canonical_email_blocks',
], ],
'profile': []
} }
_VALID_SCOPES = ['read', 'write', 'follow', 'push', 'admin:read', 'admin:write'] + \ _VALID_SCOPES = ['read', 'write', 'follow', 'push', 'admin:read', 'admin:write', 'profile'] + \
_SCOPE_SETS['read'] + _SCOPE_SETS['write'] + \ _SCOPE_SETS['read'] + _SCOPE_SETS['write'] + \
_SCOPE_SETS['admin:read'] + _SCOPE_SETS['admin:write'] _SCOPE_SETS['admin:read'] + _SCOPE_SETS['admin:write']

Wyświetl plik

@ -7110,6 +7110,74 @@ class OAuthServerInfo(AttribAccessDict):
_version = "4.4.0" _version = "4.4.0"
class OAuthUserInfo(AttribAccessDict):
"""
Information about the currently logged in user, returned by the OAuth userinfo endpoint.
Example:
.. code-block:: python
# Returns a OAuthUserInfo object
mastodon.oauth_userinfo()
See also (Mastodon API documentation): https://docs.joinmastodon.org/methods/oauth/#userinfo
"""
iss: "str"
"""
The issuer of the OAuth server. Can be used to avoid accidentally getting replies from a wrong server by comparing it against the `issuer` field in OAuthServerInfo.
Should contain (as text): URL
Version history:
* 4.4.0: added
"""
sub: "str"
"""
The subject identifier of the user. For Mastodon, the URI of the ActivityPub Actor document.
Should contain (as text): URL
Version history:
* 4.4.0: added
"""
name: "str"
"""
The display name of the user.
Version history:
* 4.4.0: added
"""
preferred_username: "str"
"""
The preferred username of the user, i.e. the part after the first and before the second @ in their account name.
Version history:
* 4.4.0: added
"""
profile : "str"
"""
The URL of the users profile page.
Should contain (as text): URL
Version history:
* 4.4.0: added
"""
picture: "str"
"""
The URL of the users profile picture.
Should contain (as text): URL
Version history:
* 4.4.0: added
"""
_version = "4.4.0"
ENTITY_NAME_MAP = { ENTITY_NAME_MAP = {
"Account": Account, "Account": Account,
"AccountField": AccountField, "AccountField": AccountField,
@ -7226,6 +7294,7 @@ ENTITY_NAME_MAP = {
"NotificationRequest": NotificationRequest, "NotificationRequest": NotificationRequest,
"SupportedLocale": SupportedLocale, "SupportedLocale": SupportedLocale,
"OAuthServerInfo": OAuthServerInfo, "OAuthServerInfo": OAuthServerInfo,
"OAuthUserInfo": OAuthUserInfo,
} }
__all__ = [ __all__ = [
"Account", "Account",
@ -7343,5 +7412,6 @@ __all__ = [
"NotificationRequest", "NotificationRequest",
"SupportedLocale", "SupportedLocale",
"OAuthServerInfo", "OAuthServerInfo",
"OAuthUserInfo",
] ]

Wyświetl plik

@ -209,7 +209,7 @@ if sys.version_info < (3, 9):
FilterKeyword, FilterStatus, IdentityProof, StatusSource, Suggestion, Translation, \ FilterKeyword, FilterStatus, IdentityProof, StatusSource, Suggestion, Translation, \
AccountCreationError, AccountCreationErrorDetails, AccountCreationErrorDetailsField, NotificationPolicy, NotificationPolicySummary, RelationshipSeveranceEvent, \ AccountCreationError, AccountCreationErrorDetails, AccountCreationErrorDetailsField, NotificationPolicy, NotificationPolicySummary, RelationshipSeveranceEvent, \
GroupedNotificationsResults, PartialAccountWithAvatar, NotificationGroup, AccountWarning, UnreadNotificationsCount, Appeal, \ GroupedNotificationsResults, PartialAccountWithAvatar, NotificationGroup, AccountWarning, UnreadNotificationsCount, Appeal, \
NotificationRequest, SupportedLocale, OAuthServerInfo NotificationRequest, SupportedLocale, OAuthServerInfo, OAuthUserInfo
if isinstance(t, ForwardRef): if isinstance(t, ForwardRef):
try: try:
t = t._evaluate(globals(), locals(), frozenset()) t = t._evaluate(globals(), locals(), frozenset())

Wyświetl plik

@ -10479,5 +10479,78 @@
"is_nullable": false "is_nullable": false
} }
} }
},
{
"name": "OAuth User Info",
"python_name": "OAuthUserInfo",
"func_call": "mastodon.oauth_userinfo()",
"func_call_real": null,
"func_call_additional": null,
"func_alternate_acc": null,
"manual_update": false,
"masto_doc_link": "https://docs.joinmastodon.org/methods/oauth/#userinfo",
"description": "Information about the currently logged in user, returned by the OAuth userinfo endpoint.",
"fields": {
"iss": {
"description": "The issuer of the OAuth server. Can be used to avoid accidentally getting replies from a wrong server by comparing it against the `issuer` field in OAuthServerInfo.",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": "URL",
"is_optional": false,
"is_nullable": false
},
"sub": {
"description": "The subject identifier of the user. For Mastodon, the URI of the ActivityPub Actor document",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": "URL",
"is_optional": false,
"is_nullable": false
},
"name": {
"description": "The display name of the user.",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": null,
"is_optional": false,
"is_nullable": false
},
"preferred_username": {
"description": "The preferred username of the user, i.e. the part after the first and before the second @ in their account name.",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": null,
"is_optional": false,
"is_nullable": false
},
"profile ": {
"description": "The URL of the users profile page.",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": "URL",
"is_optional": false,
"is_nullable": false
},
"picture": {
"description": "The URL of the users profile picture.",
"enum": null,
"version_history": [["4.4.0", "added"]],
"field_type": "str",
"field_subtype": null,
"field_structuretype": "URL",
"is_optional": false,
"is_nullable": false
}
}
} }
] ]

Wyświetl plik

@ -0,0 +1,166 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br
Authorization:
- DUMMY
Connection:
- keep-alive
User-Agent:
- mastodonpy
method: GET
uri: https://mastodon.social/.well-known/oauth-authorization-server
response:
body:
string: '{"issuer": "https://mastodon.social/", "authorization_endpoint": "https://mastodon.social/oauth/authorize",
"token_endpoint": "https://mastodon.social/oauth/token", "revocation_endpoint":
"https://mastodon.social/oauth/revoke", "userinfo_endpoint": "https://mastodon.social/oauth/userinfo",
"scopes_supported": ["read", "profile", "write", "write:accounts", "write:blocks",
"write:bookmarks", "write:conversations", "write:favourites", "write:filters",
"write:follows", "write:lists", "write:media", "write:mutes", "write:notifications",
"write:reports", "write:statuses", "read:accounts", "read:blocks", "read:bookmarks",
"read:favourites", "read:filters", "read:follows", "read:lists", "read:mutes",
"read:notifications", "read:search", "read:statuses", "follow", "push", "admin:read",
"admin:read:accounts", "admin:read:reports", "admin:read:domain_allows", "admin:read:domain_blocks",
"admin:read:ip_blocks", "admin:read:email_domain_blocks", "admin:read:canonical_email_blocks",
"admin:write", "admin:write:accounts", "admin:write:reports", "admin:write:domain_allows",
"admin:write:domain_blocks", "admin:write:ip_blocks", "admin:write:email_domain_blocks",
"admin:write:canonical_email_blocks"], "response_types_supported": ["code"],
"response_modes_supported": ["query", "fragment", "form_post"], "grant_types_supported":
["authorization_code", "client_credentials"], "token_endpoint_auth_methods_supported":
["client_secret_basic", "client_secret_post"], "code_challenge_methods_supported":
["S256"], "service_documentation": "https://docs.joinmastodon.org/", "app_registration_endpoint":
"https://mastodon.social/api/v1/apps"}'
headers:
Connection:
- keep-alive
Date:
- Sun, 17 Aug 2025 16:11:31 GMT
Strict-Transport-Security:
- max-age=31557600
Vary:
- Origin, Accept-Encoding
X-Cache:
- MISS, MISS, MISS
X-Cache-Hits:
- 0, 0, 0
X-Served-By:
- cache-fra-eddf8230103-FRA, cache-fra-eddf8230103-FRA, cache-hel1410031-HEL
X-Timer:
- S1755447092.718686,VS0,VE76
accept-ranges:
- none
alt-svc:
- h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
cache-control:
- max-age=0, private, must-revalidate
content-length:
- '1563'
content-security-policy:
- 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src
''self'' https://mastodon.social; img-src ''self'' data: blob: https://mastodon.social
https://files.mastodon.social; media-src ''self'' data: https://mastodon.social
https://files.mastodon.social; manifest-src ''self'' https://mastodon.social;
form-action ''self''; child-src ''self'' blob: https://mastodon.social; worker-src
''self'' blob: https://mastodon.social; connect-src ''self'' data: blob: https://mastodon.social
https://files.mastodon.social wss://streaming.mastodon.social; script-src
''self'' https://mastodon.social ''wasm-unsafe-eval''; frame-src ''self''
https:; style-src ''self'' https://mastodon.social ''nonce-xuGiALvgOfrc1xtVTIoYFw=='''
content-type:
- application/json; charset=utf-8
etag:
- W/"6039ea331b50e2b000b155f90aaead96"
referrer-policy:
- same-origin
transfer-encoding:
- chunked
via:
- 1.1 varnish, 1.1 varnish, 1.1 varnish
x-content-type-options:
- nosniff
x-frame-options:
- DENY
x-request-id:
- 046bcf0334eb061f18d41d273d11bdc7
x-runtime:
- '0.009508'
x-xss-protection:
- '0'
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, br
Authorization:
- DUMMY
Connection:
- keep-alive
User-Agent:
- mastodonpy
method: GET
uri: https://mastodon.social/oauth/userinfo
response:
body:
string: '{"iss": "https://mastodon.social/", "sub": "https://mastodon.social/users/halcy",
"name": "autumnal halcy", "preferred_username": "halcy", "profile": "https://mastodon.social/@halcy",
"picture": "https://mastodon.social/avatars/original/missing.png"}'
headers:
Connection:
- keep-alive
Date:
- Sun, 17 Aug 2025 16:11:31 GMT
Strict-Transport-Security:
- max-age=31557600
Vary:
- Authorization, Origin, Accept-Encoding
X-Cache:
- MISS, MISS, MISS
X-Cache-Hits:
- 0, 0, 0
X-Served-By:
- cache-fra-eddf8230036-FRA, cache-fra-eddf8230036-FRA, cache-hel1410030-HEL
X-Timer:
- S1755447092.866749,VS0,VE110
accept-ranges:
- none
alt-svc:
- h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
cache-control:
- private, no-store
content-length:
- '239'
content-security-policy:
- default-src 'none'; frame-ancestors 'none'; form-action 'none'
content-type:
- application/json; charset=utf-8
etag:
- W/"2d63fbd9fa70b7e1d88f1de0be7f462c"
referrer-policy:
- same-origin
transfer-encoding:
- chunked
via:
- 1.1 varnish, 1.1 varnish, 1.1 varnish
x-content-type-options:
- nosniff
x-frame-options:
- DENY
x-request-id:
- 3d94a304cd78c66ccf962b7282cf9f24
x-runtime:
- '0.016761'
x-xss-protection:
- '0'
status:
code: 200
message: OK
version: 1

Wyświetl plik

@ -1801,3 +1801,20 @@ def test_entity_oauthserverinfo(mastodon_base, mastodon_admin):
if sys.version_info >= (3, 9): if sys.version_info >= (3, 9):
assert real_issubclass(type(result), OAuthServerInfo), str(type(result)) + ' is not a subclass of OAuthServerInfo after to_json/from_json' assert real_issubclass(type(result), OAuthServerInfo), str(type(result)) + ' is not a subclass of OAuthServerInfo after to_json/from_json'
@pytest.mark.vcr(
filter_query_parameters=[('access_token', 'DUMMY'), ('client_id', 'DUMMY'), ('client_secret', 'DUMMY')],
filter_post_data_parameters=[('access_token', 'DUMMY'), ('client_id', 'DUMMY'), ('client_secret', 'DUMMY')],
filter_headers=[('Authorization', 'DUMMY')],
before_record_request=vcr_filter,
before_record_response=token_scrubber,
match_on=['method', 'uri'],
cassette_library_dir='tests/cassettes_entity_tests'
)
def test_entity_oauthuserinfo(mastodon_base, mastodon_admin):
mastodon = mastodon_base
result = mastodon.oauth_userinfo()
assert real_issubclass(type(result), OAuthUserInfo), str(type(result)) + ' is not a subclass of OAuthUserInfo'
result = Entity.from_json(result.to_json())
if sys.version_info >= (3, 9):
assert real_issubclass(type(result), OAuthUserInfo), str(type(result)) + ' is not a subclass of OAuthUserInfo after to_json/from_json'