-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add the `reddit` package - Add the `OAuthHelper` class - providing the methods for the Reddit authorization - Add the `http_server` configuration to the `config` - Adjust the `isort` configuration to indent multi-line imports correctly
- Loading branch information
1 parent
d545829
commit 70740ab
Showing
9 changed files
with
185 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# -*- coding: utf-8 -*- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import uuid | ||
import webbrowser | ||
|
||
import click | ||
from praw import Reddit | ||
from prawcore.exceptions import PrawcoreException | ||
from structlog import get_logger | ||
|
||
from slow_start_rewatch.config import Config | ||
from slow_start_rewatch.exceptions import AuthorizationError, RedditError | ||
from slow_start_rewatch.http_server import http_server | ||
|
||
BAD_REQUEST_ERROR = 400 | ||
|
||
log = get_logger() | ||
|
||
|
||
class OAuthHelper(object): | ||
"""Provides methods for Reddit authorization.""" | ||
|
||
def __init__(self, config: Config, reddit: Reddit) -> None: | ||
"""Initialize OAuthHelper.""" | ||
self.config = config | ||
self.reddit = reddit | ||
|
||
def authorize(self) -> None: | ||
""" | ||
Authorize via OAuth. | ||
Open a background browser (e.g. firefox) which is non-blocking. | ||
The server will block until it responds to its first request. Then the | ||
callback params are checked. | ||
""" | ||
state = uuid.uuid4().hex | ||
authorize_url = self.reddit.auth.url( | ||
scopes=self.config["reddit.oauth_scope"], | ||
state=state, | ||
) | ||
|
||
click.echo( | ||
"Opening a web browser for authorization:\n" + | ||
"- You will be asked to allow the Slow Start Rewatch app to " + | ||
"connect with your Reddit account so that it can submit and " + | ||
"edit posts on your behalf.\n" + | ||
"- The app cannot access your password.\n" + | ||
"- Press Ctrl+C if you would like to quit before completing " + | ||
"the authorization.", | ||
) | ||
log.debug("webbrowser_open", url=authorize_url) | ||
webbrowser.open_new(authorize_url) | ||
|
||
code = http_server.run( | ||
state=state, | ||
hostname=self.config["http_server.hostname"], | ||
port=self.config["http_server.port"], | ||
) | ||
|
||
log.info("oauth_authorize", code=code) | ||
try: | ||
refresh_token = self.reddit.auth.authorize(code) | ||
except PrawcoreException as exception: | ||
log.exception("oauth_authorize_failed") | ||
raise RedditError( | ||
"Failed to retrieve the Refresh Token.", | ||
) from exception | ||
|
||
if not refresh_token: | ||
log.error("oauth_authorize_missing_token") | ||
raise AuthorizationError( | ||
"Reddit hasn't provided the Refresh Token.", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
from unittest.mock import call, patch | ||
|
||
import pytest | ||
from prawcore.exceptions import PrawcoreException | ||
|
||
from slow_start_rewatch.exceptions import AuthorizationError, RedditError | ||
from slow_start_rewatch.http_server import http_server | ||
from slow_start_rewatch.reddit.oauth_helper import OAuthHelper | ||
from tests.conftest import ( | ||
HTTP_SERVER_HOSTNAME, | ||
HTTP_SERVER_PORT, | ||
OAUTH_CODE, | ||
MockConfig, | ||
) | ||
|
||
|
||
@patch.object(http_server, "run") | ||
@patch("webbrowser.open_new") | ||
def test_successful_authorization( | ||
webbrowser_open_new, | ||
http_server_run, | ||
oauth_helper_config, | ||
reddit, | ||
): | ||
"""Test successful OAuth authorization.""" | ||
http_server_run.return_value = OAUTH_CODE | ||
|
||
oauth_helper = OAuthHelper(oauth_helper_config, reddit) | ||
|
||
oauth_helper.authorize() | ||
|
||
assert webbrowser_open_new.call_count == 1 | ||
assert reddit.auth.authorize.call_args == call(OAUTH_CODE) | ||
|
||
|
||
@patch.object(http_server, "run") | ||
@patch("webbrowser.open_new") | ||
def test_failed_authorization( | ||
webbrowser_open_new, | ||
http_server_run, | ||
oauth_helper_config, | ||
reddit, | ||
): | ||
"""Test an error during OAuth authorization.""" | ||
http_server_run.side_effect = AuthorizationError("Tsundere response") | ||
|
||
oauth_helper = OAuthHelper(oauth_helper_config, reddit) | ||
|
||
with pytest.raises(AuthorizationError): | ||
oauth_helper.authorize() | ||
|
||
|
||
@patch.object(http_server, "run") | ||
@patch("webbrowser.open_new") | ||
def test_failed_token_retrieval( | ||
webbrowser_open_new, | ||
http_server_run, | ||
oauth_helper_config, | ||
reddit, | ||
): | ||
"""Test errors during the refresh token retrieval.""" | ||
http_server_run.return_value = OAUTH_CODE | ||
reddit.auth.authorize.side_effect = [ | ||
PrawcoreException, | ||
None, | ||
] | ||
|
||
oauth_helper = OAuthHelper(oauth_helper_config, reddit) | ||
|
||
with pytest.raises(RedditError): | ||
oauth_helper.authorize() | ||
|
||
with pytest.raises(AuthorizationError): | ||
oauth_helper.authorize() | ||
|
||
|
||
@pytest.fixture() | ||
def oauth_helper_config(): | ||
"""Return mock Config for testing OAuthHelper.""" | ||
return MockConfig({ | ||
"reddit": {"oauth_scope": ["headpat", "hug"]}, | ||
"http_server": { | ||
"hostname": HTTP_SERVER_HOSTNAME, | ||
"port": HTTP_SERVER_PORT, | ||
}, | ||
}) |