]> www.vanbest.org Git - motioneye-debian/commitdiff
streaming authentication mode is now configurable
authorCalin Crisan <ccrisan@gmail.com>
Sun, 29 Mar 2015 14:39:51 +0000 (17:39 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 29 Mar 2015 14:39:51 +0000 (17:39 +0300)
src/config.py
src/handlers.py
src/mjpgclient.py
src/utils.py
static/js/main.js
templates/main.html

index 818240f465ec7bfc71efbbcd1865b24ec30651df..9dd9a4110a76e48d7563ed13204e18ef4f524677 100644 (file)
@@ -110,7 +110,7 @@ def get_main(as_lines=False):
             no_convert=['@admin_username', '@admin_password', '@normal_username', '@normal_password'])
     
     _get_additional_config(main_config, camera=False)
-    _set_default_motion(main_config, old_motion=_is_old_motion())
+    _set_default_motion(main_config, old_motion=is_old_motion())
     
     _main_config_cache = main_config
     
@@ -121,7 +121,7 @@ def set_main(main_config):
     global _main_config_cache
     
     main_config = dict(main_config)
-    _set_default_motion(main_config, old_motion=_is_old_motion())
+    _set_default_motion(main_config, old_motion=is_old_motion())
     _main_config_cache = main_config
     
     main_config = dict(main_config)
@@ -277,7 +277,7 @@ def get_camera(camera_id, as_lines=False):
         camera_config['@enabled'] = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id} in threads
         camera_config['@id'] = camera_id
         
-        old_motion = _is_old_motion()
+        old_motion = is_old_motion()
         
         # adapt directives from old configuration, if needed
         if old_motion:
@@ -331,7 +331,7 @@ def set_camera(camera_id, camera_config):
     camera_config = dict(camera_config)
     
     if utils.local_camera(camera_config):
-        old_motion = _is_old_motion()
+        old_motion = is_old_motion()
         
         # adapt directives to old configuration, if needed
         if old_motion:
@@ -571,8 +571,8 @@ def camera_ui_to_dict(ui):
         '@webcam_resolution': max(1, int(ui['streaming_resolution'])),
         '@webcam_server_resize': ui['streaming_server_resize'],
         'stream_motion': ui['streaming_motion'],
-        'stream_auth_method': 2 if main_config['@normal_password'] else 0,
-        'stream_authentication': (main_config['@normal_username'] + ':' + main_config['@normal_password']) if main_config['@normal_password'] else '',
+        'stream_auth_method': {'disabled': 0, 'basic': 1, 'digest': 2}.get(ui['streaming_auth_mode'], 0),
+        'stream_authentication': main_config['@normal_username'] + ':' + main_config['@normal_password'],
 
         # still images
         'output_pictures': False,
@@ -850,6 +850,7 @@ def camera_dict_to_ui(data):
         'streaming_resolution': int(data['@webcam_resolution']),
         'streaming_server_resize': data['@webcam_server_resize'],
         'streaming_port': int(data['stream_port']),
+        'streaming_auth_mode': {0: 'disabled', 1: 'basic', 2: 'digest'}.get(data.get('stream_auth_method'), 'disabled'),
         'streaming_motion': int(data['stream_motion']),
         
         # still images
@@ -1227,6 +1228,21 @@ def restore(content):
         return None
 
 
+def is_old_motion():
+    try:
+        binary, version = motionctl.find_motion()  # @UnusedVariable
+        
+        if version.startswith('trunkREV'): # e.g. trunkREV599
+            version = int(version[8:])
+            return version < _LAST_OLD_CONFIG_VERSIONS[0]
+        
+        else: # stable release, should be in the format x.y.z
+            return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
+
+    except:
+        return False
+
+
 def _value_to_python(value):
     value_lower = value.lower()
     if value_lower == 'off':
@@ -1402,21 +1418,6 @@ def _dict_to_conf(lines, data, list_names=[]):
     return lines
 
 
-def _is_old_motion():
-    try:
-        binary, version = motionctl.find_motion()  # @UnusedVariable
-        
-        if version.startswith('trunkREV'): # e.g. trunkREV599
-            version = int(version[8:])
-            return version < _LAST_OLD_CONFIG_VERSIONS[0]
-        
-        else: # stable release, should be in the format x.y.z
-            return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
-
-    except:
-        return False
-
-
 def _set_default_motion(data, old_motion):
     data.setdefault('@enabled', True)
 
index eeecc8a837d74e6f5954aa6a59a1608e4fcafbe0..696787b5849ee0bcd54698237fc5eea78716f3d5 100644 (file)
@@ -154,7 +154,8 @@ class MainHandler(BaseHandler):
                 main_sections=main_sections,
                 camera_sections=camera_sections,
                 hostname=socket.gethostname(),
-                admin_username=config.get_main().get('@admin_username'))
+                admin_username=config.get_main().get('@admin_username'),
+                old_motion=config.is_old_motion())
 
 
 class ConfigHandler(BaseHandler):
index 69cc86fc7ae9ecd3b9436ce570bbf37c8b18e9e6..7e01b9b7ec765e87522aea422ea7a763243b6d3d 100644 (file)
@@ -108,9 +108,15 @@ class MjpgClient(iostream.IOStream):
         logging.debug('mjpg client for camera %(camera_id)s connected on port %(port)s' % {
                 'port': self._port, 'camera_id': self._camera_id})
         
-        self.write('GET / HTTP/1.0\r\n\r\n')
+        if self._username:
+            auth_header = utils.build_basic_header(self._username, self._password)
+            self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+            
+        else:
+            self.write('GET / HTTP/1.0\r\n\r\n')
+
         self._seek_http()
-    
+
     def _seek_http(self):
         if self._check_error():
             return
@@ -140,18 +146,32 @@ class MjpgClient(iostream.IOStream):
         if self._check_error():
             return
         
+        m = re.match('Basic\s*realm="([a-zA-Z0-9\-\s]+)"', data.strip())
+        if m:
+            logging.debug('mjpgclient: using basic authentication')
+            
+            auth_header = utils.build_basic_header(self._username, self._password)
+            self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+            self._seek_http()
+
+            return
+
         m = re.match('Digest\s*realm="([a-zA-Z0-9\-\s]+)",\s*nonce="([a-zA-Z0-9]+)"', data.strip())
-        if not m:
-            logging.error('mjpgclient: unknown authentication header: "%s"' % data)
-            return self._seek_content_length()
+        if m:
+            logging.debug('mjpgclient: using digest authentication')
 
-        realm, nonce = m.groups()
-        self._auth_digest_state['realm'] = realm
-        self._auth_digest_state['nonce'] = nonce
+            realm, nonce = m.groups()
+            self._auth_digest_state['realm'] = realm
+            self._auth_digest_state['nonce'] = nonce
+    
+            auth_header = utils.build_digest_header('GET', '/', self._username, self._password, self._auth_digest_state)
+            self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+            self._seek_http()
+            
+            return
 
-        auth_header = utils.build_digest_header('GET', '/', self._username, self._password, self._auth_digest_state)
-        self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
-        self._seek_http()
+        logging.error('mjpgclient: unknown authentication header: "%s"' % data)
+        self._seek_content_length()
 
     def _seek_content_length(self):
         if self._check_error():
@@ -255,7 +275,7 @@ def get_jpg(camera_id):
         
         port = camera_config['stream_port']
         username, password = None, None
-        if camera_config.get('stream_auth_method') == 2:
+        if camera_config.get('stream_auth_method') > 0:
             username, password = camera_config.get('stream_authentication', ':').split(':')
 
         client = MjpgClient(camera_id, port, username, password)
index 73aacd34ac1527d69bfac72f930be768032e6b82..101a88127e0e6c22222f8412edfaa864e82fd0e2 100644 (file)
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 
+import base64
 import datetime
 import hashlib
 import logging
@@ -348,6 +349,10 @@ def compute_signature(method, uri, body, key):
     return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
 
 
+def build_basic_header(username, password):
+    return 'Basic ' + base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
+
+
 def build_digest_header(method, url, username, password, state):
     realm = state['realm']
     nonce = state['nonce']
index c2b9926cf4fa183c9d56ee5bfafa85bba0050308..cf1a074ac229ec63564ca767ae739849b5f8dd70 100644 (file)
@@ -1202,6 +1202,7 @@ function cameraUi2Dict() {
         'streaming_resolution': $('#streamingResolutionSlider').val(),
         'streaming_server_resize': $('#streamingServerResizeSwitch')[0].checked,
         'streaming_port': $('#streamingPortEntry').val(),
+        'streaming_auth_mode': $('#streamingAuthModeSelect').val() || 'disabled', /* compatibility with old motion */
         'streaming_motion': $('#streamingMotion')[0].checked,
         
         /* still images */
@@ -1456,6 +1457,7 @@ function dict2CameraUi(dict) {
     $('#streamingResolutionSlider').val(dict['streaming_resolution']);
     $('#streamingServerResizeSwitch')[0].checked = dict['streaming_server_resize'];
     $('#streamingPortEntry').val(dict['streaming_port']);
+    $('#streamingAuthModeSelect').val(dict['streaming_auth_mode']);
     $('#streamingMotion')[0].checked = dict['streaming_motion'];
     
     var cameraUrl = location.protocol + '//' + location.host + '/picture/' + dict.id + '/';
index 291f66c48e10fb5c8ff9c0cb7c5aa8be822f8a42..f3eed0ab3ee0a8f75998ea6899b708f336e86ca7 100644 (file)
                         <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingPortEntry"></td>
                         <td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
                     </tr>
+                    {% if not old_motion %}
+                    <tr class="settings-item advanced-setting">
+                        <td class="settings-item-label"><span class="settings-item-label">Authentication Mode</span></td>
+                        <td class="settings-item-value">
+                            <select class="styled streaming camera-config" id="streamingAuthModeSelect">
+                                <option value="disabled">Disabled</option>
+                                <option value="basic">Basic</option>
+                                <option value="digest">Digest</option>
+                            </select>
+                        </td>
+                        <td><span class="help-mark" title="the authentication mode to use when accessing the stream (use Basic instead of Digest if you encounter issues with third party apps)">?</span></td>
+                    </tr>
+                    {% endif %}
                     <tr class="settings-item advanced-setting">
                         <td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
                         <td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingMotion"></td>
                         <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
                     </tr>
+                    <tr class="settings-item advanced-setting">
+                        <td colspan="100"><div class="settings-item-separator"></div></td>
+                    </tr>
                     <tr class="settings-item advanced-setting local-streaming">
                         <td class="settings-item-label"><span class="settings-item-label">Snapshot URL</span></td>
                         <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingSnapshotUrlEntry" readonly="readonly"></td>