Forráskód Böngészése

closes #65: introduce user authentication for GUI and API

Davide Alberani 9 éve
szülő
commit
6c89b3bdf6
2 módosított fájl, 47 hozzáadás és 16 törlés
  1. 1 1
      data/triggers-available/print_label.py
  2. 46 15
      eventman_server.py

+ 1 - 1
data/triggers-available/print_label.py

@@ -23,7 +23,7 @@ FONT_TEXT_ENCODING = 'latin-1'
 FONT_BARCODE = 'free3of9.ttf'
 
 PRINTER_NAME = None
-#PRINTER_NAME = 'DYMO_LabelWriter_450'
+PRINTER_NAME = 'DYMO_LabelWriter_450'
 
 
 def _get_resource(filename):

+ 46 - 15
eventman_server.py

@@ -44,6 +44,22 @@ re_env_key = re.compile('[^A-Z_]+')
 re_slashes = re.compile(r'//+')
 
 
+def authenticated(method):
+    """Decorator to handle authentication."""
+    original_wrapper = tornado.web.authenticated(method)
+    @tornado.web.functools.wraps(method)
+    def my_wrapper(self, *args, **kwargs):
+        # If no authentication was required from the command line or config file.
+        if not self.authentication:
+            return method(self, *args, **kwargs)
+        # un authenticated API calls gets redirected to /v1.0/[...]
+        if self.is_api() and not self.current_user:
+            self.redirect('/v%s%s' % (API_VERSION, self.get_login_url()))
+            return
+        return original_wrapper(self, *args, **kwargs)
+    return my_wrapper
+
+
 class BaseHandler(tornado.web.RequestHandler):
     """Base class for request handlers."""
     # A property to access the first value of each argument.
@@ -66,16 +82,19 @@ class BaseHandler(tornado.web.RequestHandler):
     }
 
     def is_api(self):
+        """Return True if the path is from an API call."""
         return self.request.path.startswith('/v%s' % API_VERSION)
 
     def tobool(self, obj):
+        """Convert some textual values to boolean."""
         if isinstance(obj, (list, tuple)):
             obj = obj[0]
         if isinstance(obj, (str, unicode)):
             obj = obj.lower()
         return self._bool_convert.get(obj, obj)
 
-    def _arguments_tobool(self):
+    def arguments_tobool(self):
+        """Return a dictionary of arguments, converted to booleans where possible."""
         return dict([(k, self.tobool(v)) for k, v in self.arguments.iteritems()])
 
     def initialize(self, **kwargs):
@@ -97,7 +116,7 @@ class RootHandler(BaseHandler):
     angular_app_path = os.path.join(os.path.dirname(__file__), "angular_app")
 
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def get(self, *args, **kwargs):
         # serve the ./angular_app/index.html file
         with open(self.angular_app_path + "/index.html", 'r') as fd:
@@ -173,7 +192,7 @@ class CollectionHandler(BaseHandler):
         return ret
 
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def get(self, id_=None, resource=None, resource_id=None, **kwargs):
         if resource:
             # Handle access to sub-resources.
@@ -192,7 +211,7 @@ class CollectionHandler(BaseHandler):
             self.write({self.collection: self.db.query(self.collection)})
 
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def post(self, id_=None, resource=None, resource_id=None, **kwargs):
         data = escape.json_decode(self.request.body or '{}')
         if resource:
@@ -211,7 +230,7 @@ class CollectionHandler(BaseHandler):
     put = post
 
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def delete(self, id_=None, resource=None, resource_id=None, **kwargs):
         if resource:
             # Handle access to sub-resources.
@@ -465,8 +484,9 @@ class EbCSVImportPersonsHandler(BaseHandler):
             'company', 'job_title')
 
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def post(self, **kwargs):
+        # import a CSV list of persons
         event_handler = EventsHandler(self.application, self.request)
         event_handler.db = self.db
         targetEvent = None
@@ -507,14 +527,15 @@ class EbCSVImportPersonsHandler(BaseHandler):
 class SettingsHandler(BaseHandler):
     """Handle requests for Settings."""
     @gen.coroutine
-    @tornado.web.authenticated
+    @authenticated
     def get(self, **kwds):
-        query = self._arguments_tobool()
+        query = self.arguments_tobool()
         settings = self.db.query('settings', query)
         self.write({'settings': settings})
 
 
 class WebSocketEventUpdatesHandler(tornado.websocket.WebSocketHandler):
+    """Manage websockets."""
     def _clean_url(self, url):
         return re_slashes.sub('/', url)
 
@@ -549,14 +570,17 @@ class LoginHandler(RootHandler):
 
     @gen.coroutine
     def get(self, **kwds):
+        # show the login page
         if self.is_api():
             self.set_status(401)
-            self.write({'error': 'authentication required', 'message': 'please provide username and password'})
+            self.write({'error': 'authentication required',
+                'message': 'please provide username and password'})
         else:
             with open(self.angular_app_path + "/login.html", 'r') as fd:
                 self.write(fd.read())
 
     def _authorize(self, username, password):
+        """Return True is this username/password is valid."""
         res = self.db.query('users', {'username': username})
         if not res:
             return False
@@ -574,6 +598,7 @@ class LoginHandler(RootHandler):
 
     @gen.coroutine
     def post(self):
+        # authenticate a user
         username = self.get_body_argument('username')
         password = self.get_body_argument('password')
         if self._authorize(username, password):
@@ -596,9 +621,13 @@ class LogoutHandler(RootHandler):
     """Handle user logout requests."""
     @gen.coroutine
     def get(self, **kwds):
+        # log the user out
         logging.info('logout')
         self.logout()
-        self.redirect('/login')
+        if self.is_api():
+            self.redirect('/v%s/login' % API_VERSION)
+        else:
+            self.redirect('/login')
 
 
 def run():
@@ -606,16 +635,17 @@ def run():
     # command line arguments; can also be written in a configuration file,
     # specified with the --config argument.
     define("port", default=5242, help="run on the given port", type=int)
-    define("data", default=os.path.join(os.path.dirname(__file__), "data"),
+    define("data_dir", default=os.path.join(os.path.dirname(__file__), "data"),
             help="specify the directory used to store the data")
     define("ssl_cert", default=os.path.join(os.path.dirname(__file__), 'ssl', 'eventman_cert.pem'),
             help="specify the SSL certificate to use for secure connections")
     define("ssl_key", default=os.path.join(os.path.dirname(__file__), 'ssl', 'eventman_key.pem'),
             help="specify the SSL private key to use for secure connections")
-    define("mongodbURL", default=None,
+    define("mongo_url", default=None,
             help="URL to MongoDB server", type=str)
-    define("dbName", default='eventman',
+    define("db_name", default='eventman',
             help="Name of the MongoDB database to use", type=str)
+    define("authentication", default=True, help="if set to false, no authentication is required")
     define("debug", default=False, help="run in debug mode")
     define("config", help="read configuration file",
             callback=lambda path: tornado.options.parse_config_file(path, final=False))
@@ -626,8 +656,9 @@ def run():
         logger.setLevel(logging.DEBUG)
 
     # database backend connector
-    db_connector = backend.EventManDB(url=options.mongodbURL, dbName=options.dbName)
-    init_params = dict(db=db_connector, data_dir=options.data, listen_port=options.port)
+    db_connector = backend.EventManDB(url=options.mongo_url, dbName=options.db_name)
+    init_params = dict(db=db_connector, data_dir=options.data_dir, listen_port=options.port,
+            authentication=options.authentication)
 
     # If not present, we store a user 'admin' with password 'eventman' into the database.
     if not db_connector.query('users', {'username': 'admin'}):