]> www.vanbest.org Git - motioneye-debian/commitdiff
added local http authentication
authorCalin Crisan <ccrisan@gmail.com>
Sat, 12 Oct 2013 15:13:41 +0000 (18:13 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sat, 12 Oct 2013 15:13:41 +0000 (18:13 +0300)
doc/todo.txt
src/handlers.py
static/js/main.js
templates/base.html
templates/main.html

index d101fbeed2e05b9d88f832b214a18c130c50ba3c..0ca7dee6b2b9d98301b839f14aaba753995e7d80 100644 (file)
@@ -1,5 +1,6 @@
 
 -> authentication
+-> cache configuration
 
 -> prevent Request closed errors by stopping mjpg clients before stopping motion
 
index 2e0ee5f635f1986c3610c5b2b68474a157e02eb7..51b0bea53b636b98233ba6503d0f7f07e30bd0ab 100644 (file)
@@ -1,4 +1,5 @@
 
+import base64
 import json
 import logging
 
@@ -33,6 +34,8 @@ class BaseHandler(RequestHandler):
     def render(self, template_name, content_type='text/html', **context):
         self.set_header('Content-Type', content_type)
         
+        context['USER'] = self.current_user
+        
         content = template.render(template_name, **context)
         self.finish(content)
     
@@ -40,13 +43,52 @@ class BaseHandler(RequestHandler):
         self.set_header('Content-Type', 'application/json')
         self.finish(json.dumps(data))
 
+    def get_current_user(self):
+        try:
+            scheme, token = self.request.headers.get('Authorization', '').split()
+            if scheme.lower() == 'basic':
+                user, pwd = base64.decodestring(token).split(':')
+                main_config = config.get_main()
+                
+                if user == main_config.get('@admin_username') and pwd == main_config.get('@admin_password'):
+                    return 'admin'
+                
+                elif user == main_config.get('@normal_username') and pwd == main_config.get('@normal_password'):
+                    return 'normal'
+                
+        except:
+            pass
+
+        return None
+    
+    @staticmethod
+    def auth(admin=False):
+        def decorator(func):
+            def wrapper(self, *args, **kwargs):
+                user = self.current_user
+                if (user is None) or (user != 'admin' and admin):
+                    realm = 'motionEye admin authentication' if admin else 'motionEye authentication'
+                    
+                    self.set_status(401)
+                    self.set_header('WWW-Authenticate', 'basic realm="%(realm)s"' % {
+                            'realm': realm})
+                    return self.finish('Authentication required.')
+                
+                return func(self, *args, **kwargs)
+            
+            return wrapper
+        
+        return decorator
+
 
 class MainHandler(BaseHandler):
+    @BaseHandler.auth()
     def get(self):
         self.render('main.html')
 
 
 class ConfigHandler(BaseHandler):
+    @asynchronous
     def get(self, camera_id=None, op=None):
         if camera_id is not None:
             camera_id = int(camera_id)
@@ -63,6 +105,7 @@ class ConfigHandler(BaseHandler):
         else:
             raise HTTPError(400, 'unknown operation')
     
+    @asynchronous
     def post(self, camera_id=None, op=None):
         if camera_id is not None:
             camera_id = int(camera_id)
@@ -82,7 +125,7 @@ class ConfigHandler(BaseHandler):
         else:
             raise HTTPError(400, 'unknown operation')
     
-    @asynchronous
+    @BaseHandler.auth(admin=True)
     def get_config(self, camera_id):
         if camera_id:
             logging.debug('getting config for camera %(id)s' % {'id': camera_id})
@@ -125,7 +168,8 @@ class ConfigHandler(BaseHandler):
             ui_config = self._main_dict_to_ui(config.get_main())
             self.finish_json(ui_config)
     
-    def set_config(self, camera_id, ui_config=None):
+    @BaseHandler.auth(admin=True)
+    def set_config(self, camera_id, ui_config=None, no_finish=False):
         if ui_config is None:
             try:
                 ui_config = json.loads(self.request.body)
@@ -141,12 +185,12 @@ class ConfigHandler(BaseHandler):
                 
                 for key, cfg in ui_config.items():
                     if key == 'main':
-                        self.set_config(None, cfg)
+                        self.set_config(None, cfg, no_finish=True)
                         
                     else:
-                        self.set_config(int(key), cfg)
+                        self.set_config(int(key), cfg, no_finish=True)
 
-                return
+                return self.finish_json()
                  
             logging.debug('setting config for camera %(id)s' % {'id': camera_id})
             
@@ -192,8 +236,10 @@ class ConfigHandler(BaseHandler):
             config.set_main(main_config)
 
         motionctl.restart()
+        
+        self.finish_json()
 
-    @asynchronous
+    @BaseHandler.auth(admin=True)
     def set_preview(self, camera_id):
         try:
             controls = json.loads(self.request.body)
@@ -239,7 +285,7 @@ class ConfigHandler(BaseHandler):
                     self.finish_json({'error': True})
                     
                 else:
-                    self.finish_json({})
+                    self.finish_json()
             
             remote.set_preview(
                     camera_config['@host'],
@@ -249,7 +295,7 @@ class ConfigHandler(BaseHandler):
                     camera_config['@remote_camera_id'],
                     controls, on_response)
 
-    @asynchronous
+    @BaseHandler.auth()
     def list_cameras(self):
         logging.debug('listing cameras')
 
@@ -270,8 +316,11 @@ class ConfigHandler(BaseHandler):
                 
         else:  # local listing
             cameras = []
-            
-            length = [len(config.get_camera_ids())]
+            camera_ids = config.get_camera_ids()
+            if not config.get_main().get('@enabled'):
+                camera_ids = []
+                
+            length = [len(camera_ids)]
             def check_finished():
                 if len(cameras) == length[0]:
                     self.finish_json({'cameras': cameras})
@@ -289,8 +338,8 @@ class ConfigHandler(BaseHandler):
                     check_finished()
                     
                 return on_response
-                    
-            for camera_id in config.get_camera_ids():
+            
+            for camera_id in camera_ids:
                 camera_config = config.get_camera(camera_id)
                 if camera_config['@proto'] == 'v4l2':
                     ui_config = self._camera_dict_to_ui(camera_config)
@@ -308,6 +357,7 @@ class ConfigHandler(BaseHandler):
             if length[0] == 0:        
                 self.finish_json({'cameras': []})
 
+    @BaseHandler.auth(admin=True)
     def list_devices(self):
         logging.debug('listing devices')
         
@@ -322,7 +372,7 @@ class ConfigHandler(BaseHandler):
         
         self.finish_json({'devices': devices})
     
-    @asynchronous
+    @BaseHandler.auth(admin=True)
     def add_camera(self):
         logging.debug('adding new camera')
         
@@ -374,7 +424,8 @@ class ConfigHandler(BaseHandler):
                     device_details.get('username'),
                     device_details.get('password'),
                     device_details.get('remote_camera_id'), on_response)
-        
+    
+    @BaseHandler.auth(admin=True)
     def rem_camera(self, camera_id):
         logging.debug('removing camera %(id)s' % {'id': camera_id})
         
@@ -383,6 +434,8 @@ class ConfigHandler(BaseHandler):
         
         if local:
             motionctl.restart()
+            
+        self.finish_json()
         
     def _main_ui_to_dict(self, ui):
         return {
@@ -687,9 +740,10 @@ class ConfigHandler(BaseHandler):
             ui['working_schedule'] = True
         
         return ui
-        
+
 
 class SnapshotHandler(BaseHandler):
+    @asynchronous
     def get(self, camera_id, op, filename=None):
         if camera_id is not None:
             camera_id = int(camera_id)
@@ -708,7 +762,7 @@ class SnapshotHandler(BaseHandler):
         else:
             raise HTTPError(400, 'unknown operation')
     
-    @asynchronous
+    @BaseHandler.auth()
     def current(self, camera_id):
         camera_config = config.get_camera(camera_id)
         if camera_config['@proto'] == 'v4l2':
@@ -735,19 +789,26 @@ class SnapshotHandler(BaseHandler):
                     camera_config['@password'],
                     camera_config['@remote_camera_id'], on_response)
                 
+    @BaseHandler.auth()
     def list(self, camera_id):
         logging.debug('listing snapshots for camera %(id)s' % {'id': camera_id})
         
         # TODO implement me
+        
+        self.finish_json()
     
+    @BaseHandler.auth()
     def download(self, camera_id, filename):
         logging.debug('downloading snapshot %(filename)s of camera %(id)s' % {
                 'filename': filename, 'id': camera_id})
         
         # TODO implement me
+        
+        self.finish_json()
 
 
 class MovieHandler(BaseHandler):
+    @asynchronous
     def get(self, camera_id, op, filename=None):
         if op == 'list':
             self.list(camera_id)
@@ -757,10 +818,20 @@ class MovieHandler(BaseHandler):
         
         else:
             raise HTTPError(400, 'unknown operation')
-    
+
+    @BaseHandler.auth()    
     def list(self, camera_id):
         logging.debug('listing movies for camera %(id)s' % {'id': camera_id})
+
+        # TODO implement me
+        
+        self.finish_json()
     
+    @BaseHandler.auth()
     def download(self, camera_id, filename):
         logging.debug('downloading movie %(filename)s of camera %(id)s' % {
                 'filename': filename, 'id': camera_id})
+
+        # TODO implement me
+        
+        self.finish_json()
index a9f927033f6b6a2d034b80de78de632d549ca955..8d61223d65f35162ba5f835db1ebf4b641f51de5 100644 (file)
@@ -760,15 +760,7 @@ function doRemCamera() {
     /* fetch & push */
 
 function fetchCurrentConfig() {
-    /* fetch the main configuration */
-    ajax('GET', '/config/main/get/', null, function (data) {
-        if (data == null || data.error) {
-            showErrorMessage(data && data.error);
-            return;
-        }
-        
-        dict2MainUi(data);
-
+    function fetchCameraList() {
         /* fetch the camera list */
         ajax('GET', '/config/list/', null, function (data) {
             if (data == null || data.error) {
@@ -777,26 +769,46 @@ function fetchCurrentConfig() {
             }
             
             var i, cameras = data.cameras;
-            var videoDeviceSelect = $('#videoDeviceSelect');
-            videoDeviceSelect.html('');
-            for (i = 0; i < cameras.length; i++) {
-                var camera = cameras[i];
-                videoDeviceSelect.append('<option value="' + camera['id'] + '">' + camera['name'] + '</option>');
-            }
-            videoDeviceSelect.append('<option value="add">add camera...</option>');
             
-            if (cameras.length > 0) {
-                videoDeviceSelect[0].selectedIndex = 0;
-                fetchCurrentCameraConfig();
-            }
-            else {
-                videoDeviceSelect[0].selectedIndex = -1;
+            if (user === 'admin') {
+                var videoDeviceSelect = $('#videoDeviceSelect');
+                videoDeviceSelect.html('');
+                for (i = 0; i < cameras.length; i++) {
+                    var camera = cameras[i];
+                    videoDeviceSelect.append('<option value="' + camera['id'] + '">' + camera['name'] + '</option>');
+                }
+                videoDeviceSelect.append('<option value="add">add camera...</option>');
+                
+                if (cameras.length > 0) {
+                    videoDeviceSelect[0].selectedIndex = 0;
+                    fetchCurrentCameraConfig();
+                }
+                else {
+                    videoDeviceSelect[0].selectedIndex = -1;
+                }
+            
+                updateConfigUi();
             }
             
             recreateCameraFrames(cameras);
-            updateConfigUi();
         });
-    });
+    }
+    
+    if (user === 'admin') {
+        /* fetch the main configuration */
+        ajax('GET', '/config/main/get/', null, function (data) {
+            if (data == null || data.error) {
+                showErrorMessage(data && data.error);
+                return;
+            }
+            
+            dict2MainUi(data);
+            fetchCameraList();
+        });
+    }
+    else {
+        fetchCameraList();
+    }
 }
 
 function fetchCurrentCameraConfig() {
@@ -1096,6 +1108,12 @@ function addCameraFrameUi(cameraId, cameraName, framerate) {
     var cameraImg = cameraFrameDiv.find('img.camera');
     var progressImg = cameraFrameDiv.find('img.camera-progress');
     
+    /* no camera buttons if not admin */
+    if (user !== 'admin') {
+        configureButton.hide();
+        closeButton.hide();
+    }
+    
     cameraFrameDiv.attr('id', 'camera' + cameraId);
     cameraFrameDiv[0].framerate = framerate;
     cameraFrameDiv[0].refreshDivider = 0;
@@ -1157,11 +1175,6 @@ function remCameraFrameUi(cameraId) {
 }
 
 function recreateCameraFrames(cameras) {
-    /* if motioneye is globally disabled, we remove all the camera frames */;
-    if (!$('#motionEyeSwitch')[0].checked) {
-        cameras = [];
-    }
-    
     var pageContainer = $('div.page-container');
     
     function updateCameras(cameras) {
@@ -1192,7 +1205,7 @@ function recreateCameraFrames(cameras) {
             }
         }
         
-        if ($('#videoDeviceSelect').find('option').length < 2) {
+        if ($('#videoDeviceSelect').find('option').length < 2 && user === 'admin') {
             /* invite the user to add a camera */
             var addCameraLink = $('<div style="text-align: center; margin-top: 30px;">' + 
                     '<a href="javascript:runAddCameraDialog()">You have not configured any camera yet. Click here to add one...</a></div>');
index 81a1e90f82ccde18499bef5eb84939c9f673161f..76d7b00de2830dda01a54cb1bd9f05ea012424fd 100644 (file)
@@ -20,6 +20,7 @@
             
             <script type="text/javascript">
                 var staticUrl = '{{STATIC_URL}}';
+                var user = '{{USER}}';
             </script>
         {% endblock %}
     </head>
index 045e653187fd0ff3d509bf1390a1e647fb3091ac..e8e6894835fec5e0afc766e49dcabca335a3569e 100644 (file)
 {% block body %}
     <div class="header">
         <div class="header-container">
+            {% if USER == 'admin' %}
             <div class="settings-top-bar">
                 <div class="button settings-button mouse-effect" title="settings"></div>
                 <select class="video-device styled" id="videoDeviceSelect"></select>
                 <div class="button rem-camera-button mouse-effect" id="remCameraButton" title="remove camera"></div>
                 <div class="button apply-button" id="applyButton">Apply</div>
             </div>
+            {% endif %}
             <div class="logo">
                 <a href="/">
                     <span class="logo">motionEye</span>