Skip to content

Commit

Permalink
Merge 0b3e789 into 9140f35
Browse files Browse the repository at this point in the history
  • Loading branch information
LilSpazJoekp committed Jul 27, 2021
2 parents 9140f35 + 0b3e789 commit 3544fff
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Expand Up @@ -8,6 +8,8 @@ Unreleased

- 301 redirects result in a ``Redirect`` exception.
- ``Requestor`` is now initialzed with a ``timeout`` parameter.
- ``ScriptAuthorizer``, ``ReadOnlyAuthorizer``, and ``DeviceIDAuthorizer`` have a
new parameter, ``scopes``, which determines the scope of access requests.

2.2.1 (2021/07/06)
------------------
Expand Down
50 changes: 45 additions & 5 deletions asyncprawcore/auth.py
Expand Up @@ -285,22 +285,35 @@ class DeviceIDAuthorizer(BaseAuthorizer):

AUTHENTICATOR_CLASS = UntrustedAuthenticator

def __init__(self, authenticator, device_id="DO_NOT_TRACK_THIS_DEVICE"):
def __init__(
self, authenticator, device_id="DO_NOT_TRACK_THIS_DEVICE", scopes=None
):
"""Represent an app-only OAuth2 authorization for 'installed' apps.
:param authenticator: An instance of :class:`UntrustedAuthenticator`.
:param device_id: (optional) A unique ID (20-30 character ASCII string) (default
DO_NOT_TRACK_THIS_DEVICE). For more information about this parameter, see:
https://github.com/reddit/reddit/wiki/OAuth2#application-only-oauth
:param scopes: (Optional) A list of OAuth scopes to request authorization for
(default: None). The scope ``*`` is requested when the default argument is
used.
"""
super(DeviceIDAuthorizer, self).__init__(authenticator)
self._device_id = device_id
self._scopes = scopes

async def refresh(self):
"""Obtain a new access token."""
additional_kwargs = {}
if self._scopes:
additional_kwargs["scope"] = " ".join(self._scopes)
grant_type = "https://oauth.reddit.com/grants/installed_client"
await self._request_token(grant_type=grant_type, device_id=self._device_id)
await self._request_token(
grant_type=grant_type,
device_id=self._device_id,
**additional_kwargs,
)


class ImplicitAuthorizer(BaseAuthorizer):
Expand Down Expand Up @@ -339,9 +352,23 @@ class ReadOnlyAuthorizer(Authorizer):

AUTHENTICATOR_CLASS = TrustedAuthenticator

def __init__(self, authenticator, scopes=None):
"""Represent a ReadOnly authorization to Reddit's API.
:param scopes: (Optional) A list of OAuth scopes to request authorization for
(default: None). The scope ``*`` is requested when the default argument is
used.
"""
super().__init__(authenticator)
self._scopes = scopes

async def refresh(self):
"""Obtain a new ReadOnly access token."""
await self._request_token(grant_type="client_credentials")
additional_kwargs = {}
if self._scopes:
additional_kwargs["scope"] = " ".join(self._scopes)
await self._request_token(grant_type="client_credentials", **additional_kwargs)


class ScriptAuthorizer(Authorizer):
Expand All @@ -354,7 +381,14 @@ class ScriptAuthorizer(Authorizer):

AUTHENTICATOR_CLASS = TrustedAuthenticator

def __init__(self, authenticator, username, password, two_factor_callback=None):
def __init__(
self,
authenticator,
username,
password,
two_factor_callback=None,
scopes=None,
):
"""Represent a single personal-use authorization to Reddit's API.
:param authenticator: An instance of :class:`TrustedAuthenticator`.
Expand All @@ -363,16 +397,22 @@ def __init__(self, authenticator, username, password, two_factor_callback=None):
:param two_factor_callback: A synchronous or asynchronous function that returns
OTPs (One-Time Passcodes), also known as 2FA auth codes. If this function is
provided, prawcore will call it when authenticating.
:param scopes: (Optional) A list of OAuth scopes to request authorization for
(default: None). The scope ``*`` is requested when the default argument is
used.
"""
super(ScriptAuthorizer, self).__init__(authenticator)
self._username = username
self._password = password
self._scopes = scopes
self._two_factor_callback = two_factor_callback
self._username = username

async def refresh(self):
"""Obtain a new personal-use script type access token."""
additional_kwargs = {}
if self._scopes:
additional_kwargs["scope"] = " ".join(self._scopes)
if self._two_factor_callback:
if inspect.iscoroutinefunction(self._two_factor_callback):
otp = await self._two_factor_callback()
Expand Down
2 changes: 1 addition & 1 deletion asyncprawcore/exceptions.py
Expand Up @@ -152,7 +152,7 @@ def __init__(self, response):
"""Initialize a TooManyRequests exception instance.
:param response: A requests.response instance that may contain a retry-after
header and a message.
header and a message.
"""
self.response = response
Expand Down
68 changes: 68 additions & 0 deletions tests/cassettes/DeviceIDAuthorizer_refresh__with_scopes.json
@@ -0,0 +1,68 @@
{
"interactions": [
{
"request": {
"body": [
[
"device_id",
"DO_NOT_TRACK_THIS_DEVICE"
],
[
"grant_type",
"https://oauth.reddit.com/grants/installed_client"
],
[
"scope",
"adsedit adsread creddits history"
]
],
"headers": {
"AUTHORIZATION": [
"Basic UGdwOF91NndWWThFS0E6"
],
"Connection": [
"close"
],
"User-Agent": [
"asyncprawcore:test (by /u/Lil_SpazJoekp) asyncprawcore/2.2.2.dev0"
]
},
"method": "POST",
"uri": "https://www.reddit.com/api/v1/access_token"
},
"response": {
"body": {
"string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"device_id\": \"DO_NOT_TRACK_THIS_DEVICE\", \"expires_in\": 3600, \"scope\": \"adsedit adsread creddits history\"}"
},
"headers": {
"Accept-Ranges": "bytes",
"Cache-Control": "max-age=0, must-revalidate",
"Connection": "close",
"Content-Length": "181",
"Content-Type": "application/json; charset=UTF-8",
"Date": "Tue, 27 Jul 2021 17:46:26 GMT",
"Server": "snooserv",
"Set-Cookie": "edgebucket=OzV3zDfwVNAWIt1kbn; Domain=reddit.com; Max-Age=63071999; Path=/; secure",
"Strict-Transport-Security": "max-age=15552000; includeSubDomains; preload",
"Via": "1.1 varnish",
"X-Clacks-Overhead": "GNU Terry Pratchett",
"X-Moose": "majestic",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-ratelimit-remaining": "295",
"x-ratelimit-reset": "214",
"x-ratelimit-used": "5",
"x-reddit-loid": "0000000000dk3rx6vi.2.1627407986872.Z0FBQUFBQmhBRVp5cUNzTnc4d2RxSE1oUUhIcGJTdzl5UmhLak1mQ2djelhoRmtNTGs3Q01tVHFOT2staEhidjJDUzVkWXhTaWVURDltMWkwdFJWbDJLaUppMUVIUEstUlREWnpIOHZWM0pkbi1rdG5tZnY0Qkh2clByaF9YVHNHYTI5b2JkN09qQS0",
"x-xss-protection": "1; mode=block"
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://www.reddit.com/api/v1/access_token"
}
}
],
"recorded_at": "2021-07-27T12:46:30",
"version": 1
}
64 changes: 64 additions & 0 deletions tests/cassettes/ReadOnlyAuthorizer_refresh__with_scopes.json
@@ -0,0 +1,64 @@
{
"interactions": [
{
"request": {
"body": [
[
"grant_type",
"client_credentials"
],
[
"scope",
"adsedit adsread creddits history"
]
],
"headers": {
"AUTHORIZATION": [
"Basic <BASIC_AUTH>"
],
"Connection": [
"close"
],
"User-Agent": [
"asyncprawcore:test (by /u/Lil_SpazJoekp) asyncprawcore/2.2.2.dev0"
]
},
"method": "POST",
"uri": "https://www.reddit.com/api/v1/access_token"
},
"response": {
"body": {
"string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 3600, \"scope\": \"adsedit adsread creddits history\"}"
},
"headers": {
"Accept-Ranges": "bytes",
"Cache-Control": "max-age=0, must-revalidate",
"Connection": "close",
"Content-Length": "140",
"Content-Type": "application/json; charset=UTF-8",
"Date": "Tue, 27 Jul 2021 17:45:18 GMT",
"Server": "snooserv",
"Set-Cookie": "edgebucket=62aM61ZBcJl9OkULjg; Domain=reddit.com; Max-Age=63071999; Path=/; secure",
"Strict-Transport-Security": "max-age=15552000; includeSubDomains; preload",
"Via": "1.1 varnish",
"X-Clacks-Overhead": "GNU Terry Pratchett",
"X-Moose": "majestic",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-ratelimit-remaining": "297",
"x-ratelimit-reset": "282",
"x-ratelimit-used": "3",
"x-reddit-loid": "0000000000dk3r63pz.2.1627407918395.Z0FBQUFBQmhBRVl1Q2pjdUc1RFkzLV9pby1RekYwVFVKbndjUHJ1OTBaWENtRk9LT21XSmJfaHlmNm9xNnlNWkdfRG1QSWdUWUdZa1dReUZSVEgzTWlmU2VGZ0ZJWFFVRlBNUmJXRVVfc1VVZ0xuQ2lSUGJQdFdpcDZ0eWRnZkFpNGxRNHFkWEZOakw",
"x-xss-protection": "1; mode=block"
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://www.reddit.com/api/v1/access_token"
}
}
],
"recorded_at": "2021-07-27T12:45:21",
"version": 1
}
71 changes: 71 additions & 0 deletions tests/cassettes/ScriptAuthorizer_refresh__with_scopes.json
@@ -0,0 +1,71 @@
{
"interactions": [
{
"request": {
"body": [
[
"grant_type",
"password"
],
[
"password",
"<PASSWORD>"
],
[
"scope",
"adsedit adsread creddits history"
],
[
"username",
"<USERNAME>"
]
],
"headers": {
"AUTHORIZATION": [
"Basic <BASIC_AUTH>"
],
"Connection": [
"close"
],
"User-Agent": [
"asyncprawcore:test (by /u/Lil_SpazJoekp) asyncprawcore/2.2.2.dev0"
]
},
"method": "POST",
"uri": "https://www.reddit.com/api/v1/access_token"
},
"response": {
"body": {
"string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 3600, \"scope\": \"adsedit adsread creddits history\"}"
},
"headers": {
"Accept-Ranges": "bytes",
"Cache-Control": "max-age=0, must-revalidate",
"Connection": "close",
"Content-Length": "152",
"Content-Type": "application/json; charset=UTF-8",
"Date": "Tue, 27 Jul 2021 17:45:01 GMT",
"Server": "snooserv",
"Set-Cookie": "edgebucket=cXXi2UwmJwuG6dsoFM; Domain=reddit.com; Max-Age=63071999; Path=/; secure",
"Strict-Transport-Security": "max-age=15552000; includeSubDomains; preload",
"Via": "1.1 varnish",
"X-Clacks-Overhead": "GNU Terry Pratchett",
"X-Moose": "majestic",
"x-content-type-options": "nosniff",
"x-frame-options": "SAMEORIGIN",
"x-ratelimit-remaining": "298",
"x-ratelimit-reset": "300",
"x-ratelimit-used": "2",
"x-xss-protection": "1; mode=block"
},
"status": {
"code": 200,
"message": "OK"
},
"url": "https://www.reddit.com/api/v1/access_token"
}
}
],
"recorded_at": "2021-07-27T12:45:03",
"version": 1
}

0 comments on commit 3544fff

Please sign in to comment.