diff --git a/data/triggers-available/print_label.py b/data/triggers-available/print_label.py index b407f5a..82cf81f 100755 --- a/data/triggers-available/print_label.py +++ b/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): diff --git a/eventman_server.py b/eventman_server.py index dda7c95..1313610 100755 --- a/eventman_server.py +++ b/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'}):