Making real progress this time.
This commit is contained in:
@@ -9,7 +9,6 @@ import warnings
|
||||
|
||||
import six
|
||||
import praw
|
||||
import tornado
|
||||
import requests
|
||||
|
||||
from . import docs
|
||||
@@ -158,7 +157,5 @@ def main():
|
||||
# Ensure sockets are closed to prevent a ResourceWarning
|
||||
if 'reddit' in locals():
|
||||
reddit.handler.http.close()
|
||||
# Explicitly close file descriptors opened by Tornado's IOLoop
|
||||
tornado.ioloop.IOLoop.current().close(all_fds=True)
|
||||
|
||||
sys.exit(main())
|
||||
|
||||
@@ -15,9 +15,9 @@ from .objects import KeyMap
|
||||
|
||||
PACKAGE = os.path.dirname(__file__)
|
||||
HOME = os.path.expanduser('~')
|
||||
TEMPLATE = os.path.join(PACKAGE, 'templates')
|
||||
DEFAULT_CONFIG = os.path.join(TEMPLATE, 'rtv.cfg')
|
||||
DEFAULT_MAILCAP = os.path.join(TEMPLATE, 'mailcap')
|
||||
TEMPLATES = os.path.join(PACKAGE, 'templates')
|
||||
DEFAULT_CONFIG = os.path.join(TEMPLATES, 'rtv.cfg')
|
||||
DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap')
|
||||
XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
|
||||
CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg')
|
||||
MAILCAP = os.path.join(HOME, '.mailcap')
|
||||
|
||||
24
rtv/docs.py
24
rtv/docs.py
@@ -103,3 +103,27 @@ SUBMISSION_EDIT_FILE = """{content}
|
||||
#
|
||||
# Editing {name}
|
||||
"""
|
||||
|
||||
OAUTH_ACCESS_DENIED = """\
|
||||
<h1 style="color: red">Access Denied</h1><hr>
|
||||
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> was
|
||||
denied access and will continue to operate in unauthenticated mode
|
||||
you can close this window.</p>
|
||||
"""
|
||||
|
||||
OAUTH_ERROR = """\
|
||||
<h1 style="color: red">Error</h1><hr>
|
||||
<p>{error}</p>
|
||||
"""
|
||||
|
||||
OAUTH_INVALID = """\
|
||||
<h1>Wait...</h1><hr>
|
||||
<p>This page is supposed to be a Reddit OAuth callback.
|
||||
You can't just come here hands in your pocket!</p>
|
||||
"""
|
||||
|
||||
OAUTH_SUCCESS = """\
|
||||
<h1 style="color: green">Access Granted</h1><hr>
|
||||
<p><span style="font-weight: bold">Reddit Terminal Viewer</span>
|
||||
will now log in, you can close this window.</p>
|
||||
"""
|
||||
117
rtv/oauth.py
117
rtv/oauth.py
@@ -1,62 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
import string
|
||||
import codecs
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from tornado import gen, ioloop, web, httpserver
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
from .config import TEMPLATE
|
||||
from . import docs
|
||||
from .config import TEMPLATES
|
||||
|
||||
|
||||
class OAuthHandler(web.RequestHandler):
|
||||
"""
|
||||
Intercepts the redirect that Reddit sends the user to after they verify or
|
||||
deny the application access.
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
The GET should supply 3 request params:
|
||||
state: Unique id that was supplied by us at the beginning of the
|
||||
process to verify that the session matches.
|
||||
code: Code that we can use to generate the refresh token.
|
||||
error: If an error occurred, it will be placed here.
|
||||
"""
|
||||
INDEX = os.path.join(TEMPLATES, 'index.html')
|
||||
|
||||
def initialize(self, display=None, params=None):
|
||||
self.display = display
|
||||
self.params = params
|
||||
|
||||
def get(self):
|
||||
self.params['state'] = self.get_argument('state', default=None)
|
||||
self.params['code'] = self.get_argument('code', default=None)
|
||||
self.params['error'] = self.get_argument('error', default=None)
|
||||
class OAuthHandler(BaseHTTPRequestHandler):
|
||||
|
||||
self.render('index.html', **self.params)
|
||||
params = {'state': None, 'code': None, 'error': None}
|
||||
|
||||
complete = self.params['state'] and self.params['code']
|
||||
if complete or self.params['error']:
|
||||
# Stop IOLoop if using a background browser such as firefox
|
||||
if self.display:
|
||||
ioloop.IOLoop.current().stop()
|
||||
def do_GET(self):
|
||||
|
||||
parsed_path = urlparse(self.path)
|
||||
if parsed_path.path != '/':
|
||||
self.send_error(404)
|
||||
|
||||
qs = parse_qs(parsed_path.query)
|
||||
self.params['state'] = qs['state'][0] if 'state' in qs else None
|
||||
self.params['code'] = qs['code'][0] if 'code' in qs else None
|
||||
self.params['error'] = qs['error'][0] if 'error' in qs else None
|
||||
|
||||
body = self.build_body()
|
||||
|
||||
# send_response also sets the Server and Date headers
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=UTF-8')
|
||||
self.send_header('Content-Length', len(body))
|
||||
self.end_headers()
|
||||
|
||||
self.wfile.write(body)
|
||||
|
||||
# Shutdown the server after serving the request
|
||||
# http://stackoverflow.com/a/22533929
|
||||
thread = threading.Thread(target=self.server.shutdown)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def build_body(self, template_file=INDEX):
|
||||
|
||||
if self.params['error'] == 'access_denied':
|
||||
message = docs.OAUTH_ACCESS_DENIED
|
||||
elif self.params['error'] is not None:
|
||||
message = docs.OAUTH_ERROR.format(error=self.params['error'])
|
||||
elif self.params['state'] is None or self.params['code'] is None:
|
||||
message = docs.OAUTH_INVALID
|
||||
else:
|
||||
message = docs.OAUTH_SUCCESS
|
||||
|
||||
with codecs.open(template_file, 'r', 'utf-8') as fp:
|
||||
index_text = fp.read()
|
||||
|
||||
body = string.Template(index_text).substitute(message=message)
|
||||
body = codecs.encode(body, 'utf-8')
|
||||
return body
|
||||
|
||||
def log_message(self, format, *args):
|
||||
_logger.debug(format, *args)
|
||||
|
||||
|
||||
class OAuthHelper(object):
|
||||
|
||||
params = OAuthHandler.params
|
||||
|
||||
def __init__(self, reddit, term, config):
|
||||
|
||||
self.term = term
|
||||
self.reddit = reddit
|
||||
self.config = config
|
||||
|
||||
self.http_server = None
|
||||
self.params = {'state': None, 'code': None, 'error': None}
|
||||
|
||||
# Initialize Tornado webapp
|
||||
# Pass a mutable params object so the request handler can modify it
|
||||
kwargs = {'display': self.term.display, 'params': self.params}
|
||||
routes = [('/', OAuthHandler, kwargs)]
|
||||
self.callback_app = web.Application(
|
||||
routes, template_path=TEMPLATE)
|
||||
address = ('', self.config['oauth_redirect_port'])
|
||||
self.server = HTTPServer(address, OAuthHandler)
|
||||
|
||||
self.reddit.set_oauth_app_info(
|
||||
self.config['oauth_client_id'],
|
||||
@@ -79,14 +108,6 @@ class OAuthHelper(object):
|
||||
self.config.refresh_token)
|
||||
return
|
||||
|
||||
# https://github.com/tornadoweb/tornado/issues/1420
|
||||
io = ioloop.IOLoop.current()
|
||||
|
||||
# Start the authorization callback server
|
||||
if self.http_server is None:
|
||||
self.http_server = httpserver.HTTPServer(self.callback_app)
|
||||
self.http_server.listen(self.config['oauth_redirect_port'])
|
||||
|
||||
state = uuid.uuid4().hex
|
||||
authorize_url = self.reddit.get_authorize_url(
|
||||
state, scope=self.config['oauth_scope'], refreshable=True)
|
||||
@@ -97,7 +118,7 @@ class OAuthHelper(object):
|
||||
# point we continue and check the callback params.
|
||||
with self.term.loader('Opening browser for authorization'):
|
||||
self.term.open_browser(authorize_url)
|
||||
io.start()
|
||||
self.server.serve_forever()
|
||||
if self.term.loader.exception:
|
||||
return
|
||||
else:
|
||||
@@ -138,10 +159,4 @@ class OAuthHelper(object):
|
||||
|
||||
def clear_oauth_data(self):
|
||||
self.reddit.clear_authentication()
|
||||
self.config.delete_refresh_token()
|
||||
|
||||
@gen.coroutine
|
||||
def _async_open_browser(self, url):
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
yield executor.submit(self.term.open_browser, url)
|
||||
ioloop.IOLoop.current().stop()
|
||||
self.config.delete_refresh_token()
|
||||
@@ -25,20 +25,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% if error == 'access_denied' %}
|
||||
<h1 style="color: red">Access Denied</h1><hr>
|
||||
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> was denied access and will continue to operate in unauthenticated mode, you can close this window.
|
||||
{% elif error is not None %}
|
||||
<h1 style="color: red">Error</h1><hr>
|
||||
<p>{{ error }}</p>
|
||||
{% elif (state is None or code is None) %}
|
||||
<h1>Wait...</h1><hr>
|
||||
<p>This page is supposed to be a Reddit OAuth callback. You can't just come here hands in your pocket!</p>
|
||||
{% else %}
|
||||
<h1 style="color: green">Access Granted</h1><hr>
|
||||
<p><span style="font-weight: bold">Reddit Terminal Viewer</span> will now log in, you can close this window.</p>
|
||||
{% end %}
|
||||
|
||||
${message}
|
||||
<div id="footer">View the <a href="http://www.github.com/michael-lazar/rtv">Documentation</a></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,12 +3,9 @@ universal = 1
|
||||
|
||||
[metadata]
|
||||
requires-dist =
|
||||
tornado
|
||||
praw>=3.5,<4
|
||||
six
|
||||
requests
|
||||
kitchen
|
||||
beautifulsoup4
|
||||
mailcap-fix
|
||||
futures; python_version=="2.6" or python_version=="2.7"
|
||||
|
||||
|
||||
10
setup.py
10
setup.py
@@ -3,12 +3,8 @@ import setuptools
|
||||
|
||||
from version import __version__ as version
|
||||
|
||||
requirements = ['tornado', 'praw==3.5.0', 'six', 'requests', 'kitchen',
|
||||
'beautifulsoup4', 'mailcap-fix']
|
||||
|
||||
# Python 2: add required concurrent.futures backport from Python 3.2
|
||||
if sys.version_info.major <= 2:
|
||||
requirements.append('futures')
|
||||
requirements = ['praw==3.5.0', 'six', 'requests', 'kitchen', 'beautifulsoup4',
|
||||
'mailcap-fix']
|
||||
|
||||
setuptools.setup(
|
||||
name='rtv',
|
||||
@@ -23,8 +19,6 @@ setuptools.setup(
|
||||
packages=['rtv'],
|
||||
package_data={'rtv': ['templates/*']},
|
||||
data_files=[("share/man/man1", ["rtv.1"])],
|
||||
extras_require={
|
||||
':python_version=="2.6" or python_version=="2.7"': ['futures']},
|
||||
install_requires=requirements,
|
||||
entry_points={'console_scripts': ['rtv=rtv.__main__:main']},
|
||||
classifiers=[
|
||||
|
||||
Reference in New Issue
Block a user