From d2822ccf8522a2dab9f451d801536a177d9fd38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 28 Aug 2015 20:28:58 +0200 Subject: [PATCH 01/11] Make OAuth compatible with Python 2 --- rtv/__main__.py | 5 +++-- rtv/oauth.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/rtv/__main__.py b/rtv/__main__.py index 881feff..9970e4a 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -79,7 +79,8 @@ def load_oauth_config(): defaults = dict(config.items('oauth')) else: # Populate OAuth section - config['oauth'] = {'auto_login': False} + config.add_section('oauth') + config.set('oauth', 'auto_login', 'false') with open(get_config_fp(), 'w') as cfg: config.write(cfg) defaults = dict(config.items('oauth')) @@ -154,7 +155,7 @@ def main(): reddit.config.decode_html_entities = False with curses_session() as stdscr: oauth = OAuthTool(reddit, stdscr, LoadScreen(stdscr)) - if args.auto_login == 'True': # Ew! + if args.auto_login == 'true': # Ew! oauth.authorize() if args.link: page = SubmissionPage(stdscr, reddit, oauth, url=args.link) diff --git a/rtv/oauth.py b/rtv/oauth.py index 0d3029c..f7d64cf 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -1,4 +1,3 @@ -from six.moves import configparser import curses import logging import os @@ -7,6 +6,7 @@ import uuid import webbrowser import praw +from six.moves import configparser from . import config from .curses_helpers import show_notification, prompt_input @@ -111,7 +111,7 @@ class OAuthTool(object): try: with self.loader(message='Refreshing token'): new_access_info = self.reddit.refresh_access_information( - self.config['oauth']['refresh_token']) + self.config.get('oauth', 'refresh_token')) self.access_info = new_access_info self.reddit.set_access_credentials(scope=set(self.access_info['scope']), access_token=self.access_info['access_token'], @@ -121,8 +121,8 @@ class OAuthTool(object): praw.errors.HTTPException) as e: show_notification(self.stdscr, ['Invalid OAuth data']) else: - self.config['oauth']['access_token'] = self.access_info['access_token'] - self.config['oauth']['refresh_token'] = self.access_info['refresh_token'] + self.config.set('oauth', 'access_token', self.access_info['access_token']) + self.config.set('oauth', 'refresh_token', self.access_info['refresh_token']) self.save_config() def authorize(self): @@ -132,7 +132,7 @@ class OAuthTool(object): self.open_config(update=True) # If no previous OAuth data found, starting from scratch - if 'oauth' not in self.config or 'access_token' not in self.config['oauth']: + if not self.config.has_section('oauth') or not self.config.has_option('oauth', 'access_token'): # Generate a random UUID hex_uuid = uuid.uuid4().hex @@ -178,11 +178,11 @@ class OAuthTool(object): except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken) as e: show_notification(self.stdscr, ['Invalid OAuth data']) else: - if 'oauth' not in self.config: - self.config['oauth'] = {} + if not self.config.has_section('oauth'): + self.config.add_section('oauth') - self.config['oauth']['access_token'] = self.access_info['access_token'] - self.config['oauth']['refresh_token'] = self.access_info['refresh_token'] + self.config.set('oauth', 'access_token', self.access_info['access_token']) + self.config.set('oauth', 'refresh_token', self.access_info['refresh_token']) self.save_config() # Otherwise, fetch new access token else: From c579aa928dad7cefc07027668b005a4c9eb35ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 28 Aug 2015 20:40:40 +0200 Subject: [PATCH 02/11] Additional Python 2 instructions --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index f2394a7..fd14260 100644 --- a/README.rst +++ b/README.rst @@ -46,6 +46,13 @@ The installation will place a script in the system path $ rtv $ rtv --help +If you're having issues running RTV with Python 2, run RTV as module : + +.. code-block:: bash + + $ cd /path/to/rtv + $ python2 -m rtv + ===== Usage ===== From d24c81bce6ef2353e7a408377c4a5362d8799d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 28 Aug 2015 21:13:57 +0200 Subject: [PATCH 03/11] External OAuth configuration file --- README.rst | 19 +++++++++++++------ rtv/__main__.py | 50 ++++++++++++++++++++++++------------------------- rtv/oauth.py | 4 ++-- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/README.rst b/README.rst index fd14260..a32e6b1 100644 --- a/README.rst +++ b/README.rst @@ -155,20 +155,21 @@ If you prefer to stay in the terminal, use ``$BROWSER`` to specify a console-bas Config File ----------- -RTV will read a configuration placed at ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME``). -Each line in the file will replace the corresponding default argument in the launch script. +RTV will read two configuration files: +* ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME/.rtv``) +* ``~/.config/rtv/oauth.cfg`` (or ``$XDG_CONFIG_HOME/.rtv-oauth``) +Each line in the files will replace the corresponding default argument in the launch script. This can be used to avoid having to re-enter login credentials every time the program is launched. -The OAuth section contains a boolean to trigger auto-login (defaults to False). +The OAuth section contains a boolean to trigger auto-login (defaults to false). When authenticated, two additional fields are written : **access_token** and **refresh_token**. Those are basically like username and password : they are used to authenticate you on Reddit servers. Example initial config: -.. code-block:: ini +**rtv.cfg** - [oauth] - auto_login=False +.. code-block:: ini [rtv] # Log file location @@ -184,6 +185,12 @@ Example initial config: # This may be necessary for compatibility with some terminal browsers # ascii=True +**oauth.cfg** + +.. code-block:: ini + + [oauth] + auto_login=false ========= Changelog diff --git a/rtv/__main__.py b/rtv/__main__.py index 9970e4a..8d3c1f8 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -22,7 +22,13 @@ from tornado import ioloop __all__ = [] -def get_config_fp(): +def load_rtv_config(): + """ + Attempt to load saved settings for things like the username and password. + """ + + config = configparser.ConfigParser() + HOME = os.path.expanduser('~') XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) @@ -35,30 +41,9 @@ def get_config_fp(): # get the first existing config file for config_path in config_paths: if os.path.exists(config_path): + config.read(config_path) break - return config_path - -def open_config(): - """ - Search for a configuration file at the location ~/.rtv and attempt to load - saved settings for things like the username and password. - """ - - config = configparser.ConfigParser() - - config_path = get_config_fp() - config.read(config_path) - - return config - -def load_rtv_config(): - """ - Attempt to load saved settings for things like the username and password. - """ - - config = open_config() - defaults = {} if config.has_section('rtv'): defaults = dict(config.items('rtv')) @@ -73,7 +58,22 @@ def load_oauth_config(): Attempt to load saved OAuth settings """ - config = open_config() + config = configparser.ConfigParser() + + HOME = os.path.expanduser('~') + XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', + os.path.join(HOME, '.config')) + + config_paths = [ + os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg'), + os.path.join(HOME, '.rtv-oauth') + ] + + # get the first existing config file + for config_path in config_paths: + if os.path.exists(config_path): + config.read(config_path) + break if config.has_section('oauth'): defaults = dict(config.items('oauth')) @@ -81,7 +81,7 @@ def load_oauth_config(): # Populate OAuth section config.add_section('oauth') config.set('oauth', 'auto_login', 'false') - with open(get_config_fp(), 'w') as cfg: + with open(config_path, 'w') as cfg: config.write(cfg) defaults = dict(config.items('oauth')) diff --git a/rtv/oauth.py b/rtv/oauth.py index f7d64cf..8fcd118 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -77,8 +77,8 @@ class OAuthTool(object): os.path.join(HOME, '.config')) config_paths = [ - os.path.join(XDG_CONFIG_HOME, 'rtv', 'rtv.cfg'), - os.path.join(HOME, '.rtv') + os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg'), + os.path.join(HOME, '.rtv-oauth') ] # get the first existing config file From 3fb4dc66bb1efa92acc416852268848d263a5acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 28 Aug 2015 21:15:49 +0200 Subject: [PATCH 04/11] README --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index a32e6b1..17628a3 100644 --- a/README.rst +++ b/README.rst @@ -156,8 +156,10 @@ Config File ----------- RTV will read two configuration files: + * ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME/.rtv``) * ``~/.config/rtv/oauth.cfg`` (or ``$XDG_CONFIG_HOME/.rtv-oauth``) + Each line in the files will replace the corresponding default argument in the launch script. This can be used to avoid having to re-enter login credentials every time the program is launched. From 314d2dbf2643bf51d7ec5110531ea69a5d8374df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Mon, 31 Aug 2015 20:37:02 +0200 Subject: [PATCH 05/11] OAuth config file improvements --- rtv/__main__.py | 14 +++++--------- rtv/oauth.py | 15 +++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/rtv/__main__.py b/rtv/__main__.py index 8d3c1f8..ebd8d51 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -64,16 +64,12 @@ def load_oauth_config(): XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) - config_paths = [ - os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg'), - os.path.join(HOME, '.rtv-oauth') - ] + if os.path.exists(os.path.join(XDG_CONFIG_HOME, 'rtv')): + config_path = os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg') + else: + config_path = os.path.join(HOME, '.rtv-oauth') - # get the first existing config file - for config_path in config_paths: - if os.path.exists(config_path): - config.read(config_path) - break + config.read(config_path) if config.has_section('oauth'): defaults = dict(config.items('oauth')) diff --git a/rtv/oauth.py b/rtv/oauth.py index 8fcd118..8203de6 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -76,17 +76,12 @@ class OAuthTool(object): XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) - config_paths = [ - os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg'), - os.path.join(HOME, '.rtv-oauth') - ] + if os.path.exists(os.path.join(XDG_CONFIG_HOME, 'rtv')): + file_path = os.path.join(XDG_CONFIG_HOME, 'rtv', 'oauth.cfg') + else: + file_path = os.path.join(HOME, '.rtv-oauth') - # get the first existing config file - for config_path in config_paths: - if os.path.exists(config_path): - break - - return config_path + return file_path def open_config(self, update=False): if self.config_fp is None: From f6546aaf75592d05d85a14e62f270781f3113818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Tue, 1 Sep 2015 22:32:56 +0200 Subject: [PATCH 06/11] Let PRAW manage authentication --- rtv/__main__.py | 1 - rtv/oauth.py | 41 ++--------------------- rtv/page.py | 15 --------- rtv/submission.py | 6 ---- rtv/subreddit.py | 11 +----- rtv/{subscriptions.py => subscription.py} | 3 -- 6 files changed, 4 insertions(+), 73 deletions(-) rename rtv/{subscriptions.py => subscription.py} (97%) diff --git a/rtv/__main__.py b/rtv/__main__.py index ebd8d51..0096031 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -103,7 +103,6 @@ def command_line(): oauth_group = parser.add_argument_group('OAuth data (optional)', OAUTH) oauth_group.add_argument('--auto-login', dest='auto_login', help='OAuth auto-login setting') - oauth_group.add_argument('--auth-token', dest='access_token', help='OAuth authorization token') oauth_group.add_argument('--refresh-token', dest='refresh_token', help='OAuth refresh token') args = parser.parse_args() diff --git a/rtv/oauth.py b/rtv/oauth.py index 8203de6..e6f0168 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -16,8 +16,6 @@ from tornado import ioloop, web __all__ = ['token_validity', 'OAuthTool'] _logger = logging.getLogger(__name__) -token_validity = 3540 - oauth_state = None oauth_code = None oauth_error = None @@ -62,8 +60,6 @@ class OAuthTool(object): self.access_info = {} - self.token_expiration = 0 - # Initialize Tornado webapp and listen on port 65000 self.callback_app = web.Application([ (r'/', HomeHandler), @@ -95,31 +91,6 @@ class OAuthTool(object): with open(self.config_fp, 'w') as cfg: self.config.write(cfg) - def set_token_expiration(self): - self.token_expiration = time.time() + token_validity - - def token_expired(self): - return time.time() > self.token_expiration - - def refresh(self, force=False): - if self.token_expired() or force: - try: - with self.loader(message='Refreshing token'): - new_access_info = self.reddit.refresh_access_information( - self.config.get('oauth', 'refresh_token')) - self.access_info = new_access_info - self.reddit.set_access_credentials(scope=set(self.access_info['scope']), - access_token=self.access_info['access_token'], - refresh_token=self.access_info['refresh_token']) - self.set_token_expiration() - except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken, - praw.errors.HTTPException) as e: - show_notification(self.stdscr, ['Invalid OAuth data']) - else: - self.config.set('oauth', 'access_token', self.access_info['access_token']) - self.config.set('oauth', 'refresh_token', self.access_info['refresh_token']) - self.save_config() - def authorize(self): self.reddit.set_oauth_app_info(self.client_id, self.client_secret, @@ -127,7 +98,7 @@ class OAuthTool(object): self.open_config(update=True) # If no previous OAuth data found, starting from scratch - if not self.config.has_section('oauth') or not self.config.has_option('oauth', 'access_token'): + if not self.config.has_section('oauth') or not self.config.has_option('oauth', 'refresh_token'): # Generate a random UUID hex_uuid = uuid.uuid4().hex @@ -164,21 +135,15 @@ class OAuthTool(object): with self.loader(message='Logging in'): # Get access information (tokens and scopes) self.access_info = self.reddit.get_access_information(self.final_code) - - self.reddit.set_access_credentials( - scope=set(self.access_info['scope']), - access_token=self.access_info['access_token'], - refresh_token=self.access_info['refresh_token']) - self.set_token_expiration() except (praw.errors.OAuthAppRequired, praw.errors.OAuthInvalidToken) as e: show_notification(self.stdscr, ['Invalid OAuth data']) else: if not self.config.has_section('oauth'): self.config.add_section('oauth') - self.config.set('oauth', 'access_token', self.access_info['access_token']) self.config.set('oauth', 'refresh_token', self.access_info['refresh_token']) self.save_config() # Otherwise, fetch new access token else: - self.refresh(force=True) + with self.loader(message='Logging in'): + self.reddit.refresh_access_information(self.config.get('oauth', 'refresh_token')) diff --git a/rtv/page.py b/rtv/page.py index 8f13565..9eb9025 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -314,9 +314,6 @@ class BasePage(object): @BaseController.register('a') def upvote(self): - # Refresh access token if expired - self.oauth.refresh() - data = self.content.get(self.nav.absolute_index) try: if 'likes' not in data: @@ -332,9 +329,6 @@ class BasePage(object): @BaseController.register('z') def downvote(self): - # Refresh access token if expired - self.oauth.refresh() - data = self.content.get(self.nav.absolute_index) try: if 'likes' not in data: @@ -372,9 +366,6 @@ class BasePage(object): show_notification(self.stdscr, ['Not logged in']) return - # Refresh access token if expired - self.oauth.refresh() - data = self.content.get(self.nav.absolute_index) if data.get('author') != self.reddit.user.name: curses.flash() @@ -403,9 +394,6 @@ class BasePage(object): show_notification(self.stdscr, ['Not logged in']) return - # Refresh access token if expired - self.oauth.refresh() - data = self.content.get(self.nav.absolute_index) if data.get('author') != self.reddit.user.name: curses.flash() @@ -440,9 +428,6 @@ class BasePage(object): Checks the inbox for unread messages and displays a notification. """ - # Refresh access token if expired - self.oauth.refresh() - inbox = len(list(self.reddit.get_unread(limit=1))) try: if inbox > 0: diff --git a/rtv/submission.py b/rtv/submission.py index 1086ce6..f5aba23 100644 --- a/rtv/submission.py +++ b/rtv/submission.py @@ -93,9 +93,6 @@ class SubmissionPage(BasePage): show_notification(self.stdscr, ['Not logged in']) return - # Refresh access token if expired - self.oauth.refresh() - data = self.content.get(self.nav.absolute_index) if data['type'] == 'Submission': content = data['text'] @@ -131,9 +128,6 @@ class SubmissionPage(BasePage): def delete_comment(self): "Delete a comment as long as it is not the current submission" - # Refresh access token if expired - self.oauth.refresh() - if self.nav.absolute_index != -1: self.delete() else: diff --git a/rtv/subreddit.py b/rtv/subreddit.py index fcd1f07..9359d1e 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -8,7 +8,7 @@ import requests from .exceptions import SubredditError, AccountError from .page import BasePage, Navigator, BaseController from .submission import SubmissionPage -from .subscriptions import SubscriptionPage +from .subscription import SubscriptionPage from .content import SubredditContent from .helpers import open_browser, open_editor, strip_subreddit_url from .docs import SUBMISSION_FILE @@ -54,9 +54,6 @@ class SubredditPage(BasePage): def refresh_content(self, name=None, order=None): "Re-download all submissions and reset the page index" - # Refresh access token if expired - self.oauth.refresh() - name = name or self.content.name order = order or self.content.order @@ -136,9 +133,6 @@ class SubredditPage(BasePage): show_notification(self.stdscr, ['Not logged in']) return - # Refresh access token if expired - self.oauth.refresh() - # Strips the subreddit to just the name # Make sure it is a valid subreddit for submission subreddit = self.reddit.get_subreddit(self.content.name) @@ -180,9 +174,6 @@ class SubredditPage(BasePage): show_notification(self.stdscr, ['Not logged in']) return - # Refresh access token if expired - self.oauth.refresh() - # Open subscriptions page page = SubscriptionPage(self.stdscr, self.reddit, self.oauth) page.loop() diff --git a/rtv/subscriptions.py b/rtv/subscription.py similarity index 97% rename from rtv/subscriptions.py rename to rtv/subscription.py index 64e3a2a..0361427 100644 --- a/rtv/subscriptions.py +++ b/rtv/subscription.py @@ -38,9 +38,6 @@ class SubscriptionPage(BasePage): def refresh_content(self): "Re-download all subscriptions and reset the page index" - # Refresh access token if expired - self.oauth.refresh() - self.content = SubscriptionContent.from_user(self.reddit, self.loader) self.nav = Navigator(self.content.get) From 6933b35240586732f7160897aa03091922c35bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 4 Sep 2015 18:18:31 +0200 Subject: [PATCH 07/11] Avoid infinite loop if server crashes --- rtv/oauth.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rtv/oauth.py b/rtv/oauth.py index e6f0168..b81ddd1 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -28,17 +28,18 @@ class HomeHandler(web.RequestHandler): class AuthHandler(web.RequestHandler): def get(self): - global oauth_state - global oauth_code - global oauth_error + try: + global oauth_state + global oauth_code + global oauth_error - oauth_state = self.get_argument('state', default='state_placeholder') - oauth_code = self.get_argument('code', default='code_placeholder') - oauth_error = self.get_argument('error', default='error_placeholder') + oauth_state = self.get_argument('state', default='state_placeholder') + oauth_code = self.get_argument('code', default='code_placeholder') + oauth_error = self.get_argument('error', default='error_placeholder') - self.render('auth.html', state=oauth_state, code=oauth_code, error=oauth_error) - - ioloop.IOLoop.current().stop() + self.render('auth.html', state=oauth_state, code=oauth_code, error=oauth_error) + finally: + ioloop.IOLoop.current().stop() class OAuthTool(object): From eaed5142080736a561051c656c4bdc3673c3abfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 4 Sep 2015 18:22:01 +0200 Subject: [PATCH 08/11] OAuth flow terminal web browser support --- rtv/oauth.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/rtv/oauth.py b/rtv/oauth.py index b81ddd1..afa0c32 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -58,9 +58,11 @@ class OAuthTool(object): self.redirect_uri = redirect_uri or config.oauth_redirect_uri self.scope = scope or config.oauth_scope.split('-') - self.access_info = {} + # Terminal web browser + self.compact = os.environ.get('BROWSER') in ['w3m', 'links', 'elinks', 'lynx'] + # Initialize Tornado webapp and listen on port 65000 self.callback_app = web.Application([ (r'/', HomeHandler), @@ -93,6 +95,9 @@ class OAuthTool(object): self.config.write(cfg) def authorize(self): + if self.compact and not '.compact' in self.reddit.config.API_PATHS['authorize']: + self.reddit.config.API_PATHS['authorize'] += '.compact' + self.reddit.set_oauth_app_info(self.client_id, self.client_secret, self.redirect_uri) @@ -106,9 +111,16 @@ class OAuthTool(object): permission_ask_page_link = self.reddit.get_authorize_url(str(hex_uuid), scope=self.scope, refreshable=True) - with self.loader(message='Waiting for authorization'): - webbrowser.open(permission_ask_page_link) + if self.compact: + show_notification(self.stdscr, ['Opening ' + os.environ.get('BROWSER')]) + curses.endwin() + webbrowser.open_new_tab(permission_ask_page_link) ioloop.IOLoop.current().start() + curses.doupdate() + else: + with self.loader(message='Waiting for authorization'): + webbrowser.open(permission_ask_page_link) + ioloop.IOLoop.current().start() global oauth_state global oauth_code From b643ce955909d248be6ee87665ee1f6d7e1799dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 4 Sep 2015 18:23:10 +0200 Subject: [PATCH 09/11] Complete logout --- rtv/oauth.py | 6 ++++++ rtv/page.py | 1 + 2 files changed, 7 insertions(+) diff --git a/rtv/oauth.py b/rtv/oauth.py index afa0c32..7edf7b5 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -94,6 +94,12 @@ class OAuthTool(object): with open(self.config_fp, 'w') as cfg: self.config.write(cfg) + def clear_oauth_data(self): + self.open_config(update=True) + if self.config.has_section('oauth') and self.config.has_option('oauth', 'refresh_token'): + self.config.remove_option('oauth', 'refresh_token') + self.save_config() + def authorize(self): if self.compact and not '.compact' in self.reddit.config.API_PATHS['authorize']: self.reddit.config.API_PATHS['authorize'] += '.compact' diff --git a/rtv/page.py b/rtv/page.py index 9eb9025..34a5467 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -352,6 +352,7 @@ class BasePage(object): if self.reddit.is_oauth_session(): self.reddit.clear_authentication() + self.oauth.clear_oauth_data() return self.oauth.authorize() From 1f0ca4d59264bd1e924198d1cfe1ecececa8ef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 4 Sep 2015 18:39:22 +0200 Subject: [PATCH 10/11] Update README.rst and docs --- README.rst | 24 ++++++++++++++---------- rtv/docs.py | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 17628a3..7de1152 100644 --- a/README.rst +++ b/README.rst @@ -155,18 +155,10 @@ If you prefer to stay in the terminal, use ``$BROWSER`` to specify a console-bas Config File ----------- -RTV will read two configuration files: - -* ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME/.rtv``) -* ``~/.config/rtv/oauth.cfg`` (or ``$XDG_CONFIG_HOME/.rtv-oauth``) - +RTV will read a configuration placed at ``~/.config/rtv/rtv.cfg`` (or ``$XDG_CONFIG_HOME``). Each line in the files will replace the corresponding default argument in the launch script. This can be used to avoid having to re-enter login credentials every time the program is launched. -The OAuth section contains a boolean to trigger auto-login (defaults to false). -When authenticated, two additional fields are written : **access_token** and **refresh_token**. -Those are basically like username and password : they are used to authenticate you on Reddit servers. - Example initial config: **rtv.cfg** @@ -187,7 +179,19 @@ Example initial config: # This may be necessary for compatibility with some terminal browsers # ascii=True -**oauth.cfg** +----- +OAuth +----- + +OAuth is an authentication standard, that replaces authentication with login and password. + +RTV implements OAuth. It stores OAuth configuration at ``~/.config/rtv/oauth.cfg``(or ``$XDG_CONFIG_HOME``). +**The OAuth configuration file must be writable, and is created automatically if it doesn't exist.** +It contains a boolean to trigger auto-login (defaults to false). +When authenticated, an additional field is written : **refresh_token**. +This acts as a replacement to username and password : it is used to authenticate you on Reddit servers. + +Example **oauth.cfg**: .. code-block:: ini diff --git a/rtv/docs.py b/rtv/docs.py index cefd7b0..4f427c8 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -18,8 +18,8 @@ given, the program will display a secure prompt to enter a password. """ OAUTH = """\ -Authentication is now done by OAuth, since PRAW will stop supporting login with -username and password soon. +Authentication is now done by OAuth, since PRAW will drop +password authentication soon. """ CONTROLS = """ From 0134e157d083e18f7e1dcbbe663fdd8eb22fdc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Piboub=C3=A8s?= Date: Fri, 4 Sep 2015 20:09:58 +0200 Subject: [PATCH 11/11] Listen on port only when web server is needed --- rtv/oauth.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rtv/oauth.py b/rtv/oauth.py index 7edf7b5..307cf4a 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -63,12 +63,11 @@ class OAuthTool(object): # Terminal web browser self.compact = os.environ.get('BROWSER') in ['w3m', 'links', 'elinks', 'lynx'] - # Initialize Tornado webapp and listen on port 65000 + # Initialize Tornado webapp self.callback_app = web.Application([ (r'/', HomeHandler), (r'/auth', AuthHandler), ], template_path='rtv/templates') - self.callback_app.listen(65000) def get_config_fp(self): HOME = os.path.expanduser('~') @@ -111,6 +110,9 @@ class OAuthTool(object): self.open_config(update=True) # If no previous OAuth data found, starting from scratch if not self.config.has_section('oauth') or not self.config.has_option('oauth', 'refresh_token'): + # Start HTTP server and listen on port 65000 + self.callback_app.listen(65000) + # Generate a random UUID hex_uuid = uuid.uuid4().hex