diff --git a/couchpotato/__init__.py b/couchpotato/__init__.py index 089ecd4..930e465 100644 --- a/couchpotato/__init__.py +++ b/couchpotato/__init__.py @@ -1,11 +1,10 @@ from couchpotato.api import api_docs, api_docs_missing, api -from couchpotato.core.auth import requires_auth from couchpotato.core.event import fireEvent -from couchpotato.core.helpers.variable import md5 +from couchpotato.core.helpers.variable import md5, tryInt from couchpotato.core.logger import CPLog from couchpotato.environment import Env from tornado import template -from tornado.web import RequestHandler +from tornado.web import RequestHandler, authenticated import os import time import traceback @@ -16,9 +15,23 @@ log = CPLog(__name__) views = {} template_loader = template.Loader(os.path.join(os.path.dirname(__file__), 'templates')) + +class BaseHandler(RequestHandler): + + def get_current_user(self): + username = Env.setting('username') + password = Env.setting('password') + + if username or password: + print self.get_secure_cookie('user') + return self.get_secure_cookie('user') + else: # Login when no username or password are set + return True + # Main web handler -@requires_auth -class WebHandler(RequestHandler): +class WebHandler(BaseHandler): + + @authenticated def get(self, route, *args, **kwargs): route = route.strip('/') if not views.get(route): @@ -28,7 +41,7 @@ class WebHandler(RequestHandler): try: self.write(views[route]()) except: - log.error('Failed doing web request "%s": %s', (route, traceback.format_exc())) + log.error("Failed doing web request '%s': %s", (route, traceback.format_exc())) self.write({'success': False, 'error': 'Failed returning results'}) def addView(route, func, static = False): @@ -79,6 +92,38 @@ class KeyHandler(RequestHandler): self.write({'success': False, 'error': 'Failed returning results'}) +class LoginHandler(BaseHandler): + + def get(self, *args, **kwargs): + + if self.get_current_user(): + self.redirect('/') + else: + self.write(template_loader.load('login.html').generate()) + + def post(self, *args, **kwargs): + + api = None + + username = Env.setting('username') + password = Env.setting('password') + + if (self.get_argument('username') == username or not username) and (md5(self.get_argument('password')) == password or not password): + api = Env.setting('api_key') + + if api: + remember_me = tryInt(self.get_argument('remember_me', default = 0)) + self.set_secure_cookie('user', api, expires_days = 30 if remember_me else 0) + + self.redirect('/') + +class LogoutHandler(BaseHandler): + + def get(self, *args, **kwargs): + self.clear_cookie('user') + self.redirect('/login') + + def page_not_found(rh): index_url = Env.get('web_base') url = rh.request.uri[len(index_url):] diff --git a/couchpotato/core/auth.py b/couchpotato/core/auth.py deleted file mode 100644 index e877860..0000000 --- a/couchpotato/core/auth.py +++ /dev/null @@ -1,45 +0,0 @@ -from couchpotato.core.helpers.variable import md5 -from couchpotato.environment import Env -import base64 - -def check_auth(username, password): - return username == Env.setting('username') and password == Env.setting('password') - -def requires_auth(handler_class): - - def wrap_execute(handler_execute): - - def require_basic_auth(handler, kwargs): - - if Env.setting('username') and Env.setting('password'): - - auth_header = handler.request.headers.get('Authorization') - auth_decoded = base64.decodestring(auth_header[6:]) if auth_header else None - - username = '' - password = '' - - if auth_decoded: - username, password = auth_decoded.split(':', 2) - - if auth_header is None or not auth_header.startswith('Basic ') or (not check_auth(username.decode('latin'), md5(password.decode('latin')))): - handler.set_status(401) - handler.set_header('WWW-Authenticate', 'Basic realm="CouchPotato Login"') - handler._transforms = [] - handler.finish() - - return False - - return True - - def _execute(self, transforms, *args, **kwargs): - - if not require_basic_auth(self, kwargs): - return False - return handler_execute(self, transforms, *args, **kwargs) - - return _execute - - handler_class._execute = wrap_execute(handler_class._execute) - - return handler_class diff --git a/couchpotato/runner.py b/couchpotato/runner.py index ab92919..19ccf4e 100644 --- a/couchpotato/runner.py +++ b/couchpotato/runner.py @@ -1,6 +1,6 @@ from argparse import ArgumentParser from cache import FileSystemCache -from couchpotato import KeyHandler +from couchpotato import KeyHandler, LoginHandler, LogoutHandler from couchpotato.api import NonBlockHandler, ApiHandler from couchpotato.core.event import fireEventAsync, fireEvent from couchpotato.core.helpers.encoding import toUnicode @@ -233,10 +233,11 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En log_function = lambda x : None, debug = config['use_reloader'], gzip = True, + cookie_secret = api_key, + login_url = "/login", ) Env.set('app', application) - # Request handlers application.add_handlers(".*$", [ (r'%snonblock/(.*)(/?)' % api_base, NonBlockHandler), @@ -245,6 +246,10 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En (r'%s(.*)(/?)' % api_base, ApiHandler), # Main API handler (r'%sgetkey(/?)' % web_base, KeyHandler), # Get API key (r'%s' % api_base, RedirectHandler, {"url": web_base + 'docs/'}), # API docs + + # Login handlers + (r'%slogin(/?)' % web_base, LoginHandler), + (r'%slogout(/?)' % web_base, LogoutHandler), # Catch all webhandlers (r'%s(.*)(/?)' % web_base, WebHandler), diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html new file mode 100644 index 0000000..d06440c --- /dev/null +++ b/couchpotato/templates/login.html @@ -0,0 +1,18 @@ +{% autoescape None %} + + + + + + + Login + + +
+
+
+
+
+
+ + \ No newline at end of file