From 682d95470d73e6154c25971839122d457dab5577 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Fri, 17 May 2019 11:47:20 -0400 Subject: [PATCH 1/6] spaces --- distros/ubuntu1204/control | 6 ++++-- distros/ubuntu1604/control | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index fdf9c186e..d80cbc67a 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -24,7 +24,8 @@ Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh ,libwww-perl ,libdata-uuid-perl ,libssl-dev - ,libcrypt-eksblowfish-perl, libdata-entropy-perl + ,libcrypt-eksblowfish-perl + ,libdata-entropy-perl # Unbundled (dh_linktree): ,libjs-jquery ,libjs-mootools @@ -69,7 +70,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libio-socket-multicast-perl, libdigest-sha-perl ,libsys-cpu-perl, libsys-meminfo-perl ,libssl - ,libcrypt-eksblowfish-perl, libdata-entropy-perl + ,libcrypt-eksblowfish-perl + ,libdata-entropy-perl Recommends: ${misc:Recommends} ,libapache2-mod-php5 | php5-fpm diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index bfabd3ad3..d07104100 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -31,7 +31,8 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libwww-perl ,libdata-uuid-perl ,libssl-dev - ,libcrypt-eksblowfish-perl, libdata-entropy-perl + ,libcrypt-eksblowfish-perl + ,libdata-entropy-perl # Unbundled (dh_linktree): ,libjs-jquery ,libjs-mootools @@ -79,7 +80,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,zip ,libpcre3 ,libssl - ,libcrypt-eksblowfish-perl, libdata-entropy-perl + ,libcrypt-eksblowfish-perl + ,libdata-entropy-perl Recommends: ${misc:Recommends} ,libapache2-mod-php5 | libapache2-mod-php | php5-fpm | php-fpm ,mysql-server | mariadb-server | virtual-mysql-server From 304192472d74bfb2c280a7e340d2f0a07290b21a Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Fri, 17 May 2019 12:02:24 -0400 Subject: [PATCH 2/6] removed extra line --- distros/ubuntu1204/control | 1 - 1 file changed, 1 deletion(-) diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index d80cbc67a..1dc8a2b38 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -72,7 +72,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libssl ,libcrypt-eksblowfish-perl ,libdata-entropy-perl - Recommends: ${misc:Recommends} ,libapache2-mod-php5 | php5-fpm ,mysql-server | virtual-mysql-server From 8e1037458ae675579c6e0b7efc6b0b1c9611530e Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Sat, 18 May 2019 11:23:16 -0400 Subject: [PATCH 3/6] when regenerating using refresh tokens, username needs to be derived from the refresh token, as no session would exist --- web/api/app/Controller/HostController.php | 19 +++++++++++++++---- web/includes/auth.php | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index 9ff4e7c76..d6827c491 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -49,7 +49,7 @@ class HostController extends AppController { $cred = $this->_getCredentials(true); // generate refresh } else { - $cred = $this->_getCredentials(false); // don't generate refresh + $cred = $this->_getCredentials(false, $mToken); // don't generate refresh } $login_array = array ( @@ -114,7 +114,7 @@ class HostController extends AppController { } } - private function _getCredentials($generate_refresh_token=false) { + private function _getCredentials($generate_refresh_token=false, $mToken='') { $credentials = ''; $this->loadModel('Config'); @@ -127,6 +127,17 @@ class HostController extends AppController { throw new ForbiddenException(__('Please create a valid AUTH_HASH_SECRET in ZoneMinder')); } + if ($mToken) { + // If we have a token, we need to derive username from there + $ret = validateToken($mToken, 'refresh'); + $mUser = $ret[0]['Username']; + + } else { + $mUser = $_SESSION['username']; + } + + ZM\Info("Creating token for \"$mUser\""); + /* we won't support AUTH_HASH_IPS in token mode reasons: a) counter-intuitive for mobile consumers @@ -149,7 +160,7 @@ class HostController extends AppController { "iss" => "ZoneMinder", "iat" => $access_issued_at, "exp" => $access_expire_at, - "user" => $_SESSION['username'], + "user" => $mUser, "type" => "access" ); @@ -167,7 +178,7 @@ class HostController extends AppController { "iss" => "ZoneMinder", "iat" => $refresh_issued_at, "exp" => $refresh_expire_at, - "user" => $_SESSION['username'], + "user" => $mUser, "type" => "refresh" ); $jwt_refresh_token = \Firebase\JWT\JWT::encode($refresh_token, $key, 'HS256'); diff --git a/web/includes/auth.php b/web/includes/auth.php index 33d2b3fb6..3b823004c 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -244,7 +244,7 @@ function validateToken ($token, $allowed_token_type='access') { $minIssuedAt = $saved_user_details['TokenMinExpiry']; if ($issuedAt < $minIssuedAt) { - ZM\Error ("Token revoked for $username. Please generate a new token"); + ZM\Error ("Token revoked for \"$username\". Please generate a new token"); $_SESSION['loginFailed'] = true; unset($user); return array(false, "Token revoked. Please re-generate"); @@ -253,7 +253,7 @@ function validateToken ($token, $allowed_token_type='access') { $user = $saved_user_details; return array($user, "OK"); } else { - ZM\Error ("Could not retrieve user $username details"); + ZM\Error ("Could not retrieve user \"$username\" details"); $_SESSION['loginFailed'] = true; unset($user); return array(false, "No such user/credentials"); From 33db7e1e35944668f3a5b9db9b424d6f4fd2967a Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Sat, 18 May 2019 13:47:31 -0400 Subject: [PATCH 4/6] add libssl1.0.0 for ubuntu 16/12 --- distros/ubuntu1204/control | 2 +- distros/ubuntu1604/control | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index 1dc8a2b38..9e54e2aa3 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -69,7 +69,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl ,libio-socket-multicast-perl, libdigest-sha-perl ,libsys-cpu-perl, libsys-meminfo-perl - ,libssl + ,libssl | libssl1.0.0 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl Recommends: ${misc:Recommends} diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index d07104100..30451f7e1 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -79,7 +79,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,rsyslog | system-log-daemon ,zip ,libpcre3 - ,libssl + ,libssl | libssl1.0.0 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl Recommends: ${misc:Recommends} From 036d47545f6b22fb1c0139ff55f97080fd3d039f Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Sat, 18 May 2019 19:35:13 -0400 Subject: [PATCH 5/6] small API fixes --- docs/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d60847d92..2b96bb397 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -85,7 +85,7 @@ Or for API 2.0: Using these keys with subsequent requests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Once you have the keys (a.k.a credentials (v1.0, v2.0) or token (v2.0)) you should now supply that credential to subsequent API calls like this: +Once you have the keys (a.k.a credentials (v1.0, v2.0) or token (v2.0)) you should now supply that key to subsequent API calls like this: :: @@ -108,7 +108,7 @@ Once you have the keys (a.k.a credentials (v1.0, v2.0) or token (v2.0)) you shou Key lifetime (v1.0) ^^^^^^^^^^^^^^^^^^^^^ -If you are using the old ``auth_hash`` mechanism present in v1.0, then the credentials will time out based on PHP session timeout. This is often confusing and sometime causes additional issues due to the fact that the old method also includes timestamps in its hash. +If you are using the old credentials mechanism present in v1.0, then the credentials will time out based on PHP session timeout (if you are using cookies), or the value of ``AUTH_HASH_TTL`` which defaults to 2 hours. Note that there is no way to look at the hash and decipher how much time is remaining. So it is your responsibility to record the time you got the hash and assume it was generated at the time you got it and re-login before that time expires. Key lifetime (v2.0) ^^^^^^^^^^^^^^^^^^^^^^ From ddd02ec10df4b91905d8785262cf3897104b72ce Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Sun, 19 May 2019 07:29:27 -0400 Subject: [PATCH 6/6] clean up of API, remove redundant sections --- docs/api.rst | 162 ++++++++++++++++----------------------------------- 1 file changed, 49 insertions(+), 113 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 2b96bb397..177678977 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -40,14 +40,14 @@ For v2.0 APIs, you have an additional option right below it - ``OPT_USE_LEGACY_A Enabling secret key ^^^^^^^^^^^^^^^^^^^ -* It is important that you create a "Secret Key". This needs to be a set of hard to guess characters, that only you know. ZoneMinder does not create a key for you. It is your responsibility to create it. If you haven't created one already, please do so by going to ``Options->Systems`` and populating ``AUTH_HASH_SECRET``. Don't forget to save. +* It is **important** that you create a "Secret Key". This needs to be a set of hard to guess characters, that only you know. ZoneMinder does not create a key for you. It is your responsibility to create it. If you haven't created one already, please do so by going to ``Options->Systems`` and populating ``AUTH_HASH_SECRET``. Don't forget to save. * If you plan on using V2.0 token based security, **it is mandatory to populate this secret key**, as it is used to sign the token. If you don't, token authentication will fail. V1.0 did not mandate this requirement. -Getting API key +Getting an API key ^^^^^^^^^^^^^^^^^^^^^^^ -To get API key: +To get an API key: :: @@ -91,29 +91,34 @@ Once you have the keys (a.k.a credentials (v1.0, v2.0) or token (v2.0)) you shou # RECOMMENDED: v2.0 token based curl -XPOST https://yourserver/zm/api/monitors.json&token= - # or + + # or # v1.0 or 2.0 based API access (will only work if AUTH_HASH_LOGINS is enabled) curl -XPOST -d "auth=" https://yourserver/zm/api/monitors.json + # or + curl -XGET https://yourserver/zm/api/monitors.json&auth= + # or, if you specified -c cookies.txt in the original login request + curl -b cookies.txt -XGET https://yourserver/zm/api/monitors.json .. NOTE:: - ZoneMinder's API layer allows API keys to be encoded either as a querty parameter or as a data payload. If you don't pass keys, you could use cookies (not recommended as a general approach) + ZoneMinder's API layer allows API keys to be encoded either as a query parameter or as a data payload. If you don't pass keys, you could use cookies (not recommended as a general approach) Key lifetime (v1.0) ^^^^^^^^^^^^^^^^^^^^^ -If you are using the old credentials mechanism present in v1.0, then the credentials will time out based on PHP session timeout (if you are using cookies), or the value of ``AUTH_HASH_TTL`` which defaults to 2 hours. Note that there is no way to look at the hash and decipher how much time is remaining. So it is your responsibility to record the time you got the hash and assume it was generated at the time you got it and re-login before that time expires. +If you are using the old credentials mechanism present in v1.0, then the credentials will time out based on PHP session timeout (if you are using cookies), or the value of ``AUTH_HASH_TTL`` (if you are using ``auth=`` and have enabled ``AUTH_HASH_LOGINS``) which defaults to 2 hours. Note that there is no way to look at the hash and decipher how much time is remaining. So it is your responsibility to record the time you got the hash and assume it was generated at the time you got it and re-login before that time expires. Key lifetime (v2.0) ^^^^^^^^^^^^^^^^^^^^^^ -In version 2.0, it is very easy to know when a key will expire. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. +In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. Understanding access/refresh tokens (v2.0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -128,7 +133,7 @@ If you are using V2.0, then you need to know how to use these tokens effectively **To Summarize:** * Pass your ``username`` and ``password`` to ``login.json`` only once in 24 hours to renew your tokens -* Pass your "refresh token" to ``login.json`` once an hour to renew your ``access token`` +* Pass your "refresh token" to ``login.json`` once in two hours (or whatever you have set the value of ``AUTH_HASH_TTL`` to) to renew your ``access token`` * Use your ``access token`` for all API invocations. In fact, V2.0 will reject your request (if it is not to ``login.json``) if it comes with a refresh token instead of an access token to discourage usage of this token when it should not be used. @@ -138,7 +143,7 @@ This minimizes the amount of sensitive data that is sent over the wire and the l Understanding key security ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Version 1.0 uses an MD5 hash to generate the credentials. The hash is computed over your secret key (if avaiable), username, password and some time parameters (along with remote IP if enabled). This is not a secure/recommended hashing mechanism. If your auth hash is compromised, an attacker will be able to use your hash till it expires. To avoid this, you could disable the user in ZoneMinder. +* Version 1.0 uses an MD5 hash to generate the credentials. The hash is computed over your secret key (if available), username, password and some time parameters (along with remote IP if enabled). This is not a secure/recommended hashing mechanism. If your auth hash is compromised, an attacker will be able to use your hash till it expires. To avoid this, you could disable the user in ZoneMinder. Furthermore, enabling remote IP (``AUTH_HASH_REMOTE_IP``) requires that you issue future requests from the same IP that generated the tokens. While this may be considered an additional layer for security, this can cause issues with mobile devices. * Version 2.0 uses a different approach. The hash is a simple base64 encoded form of "claims", but signed with your secret key. Consider for example, the following access key: @@ -160,10 +165,10 @@ If you were to use any `JWT token verifier `__ it can easily dec Invalid Signature -Don't be surprised. JWT tokens are not meant to be encrypted. It is just an assertion of a claim. It states that the issuer of this token was ZoneMinder, +Don't be surprised. JWT tokens, by default, are `not meant to be encrypted `__. It is just an assertion of a claim. It states that the issuer of this token was ZoneMinder, It was issued at (iat) Wednesday, 2019-05-15 17:19:12 UTC and will expire on (exp) Wednesday, 2019-05-15 18:19:12 UTC. This token claims to be owned by an admin and is an access token. If your token were to be stolen, this information is available to the person who stole it. Note that there are no sensitive details like passwords in this claim. -However, that person will **not** have your secret key as part of this token and therefore, will NOT be able to create a new JWT token to get, say, a refresh token. They will however, be able to use your access token to access resources just like the auth hash above, till the access token expires (1hr). To revoke this token, you don't need to disable the user. Go to ``Options->API`` and tap on "Revoke All Access Tokens". This will invalidate the token immediately (this option will invalidate all tokens for all users, and new ones will need to be generated). +However, that person will **not** have your secret key as part of this token and therefore, will NOT be able to create a new JWT token to get, say, a refresh token. They will however, be able to use your access token to access resources just like the auth hash above, till the access token expires (2 hrs). To revoke this token, you don't need to disable the user. Go to ``Options->API`` and tap on "Revoke All Access Tokens". This will invalidate the token immediately (this option will invalidate all tokens for all users, and new ones will need to be generated). Over time, we will provide you with more fine grained access to these options. @@ -175,105 +180,12 @@ Over time, we will provide you with more fine grained access to these options. * If you believe your tokens are compromised, revoke them, but also check if your attacker has compromised more than you think (example, they may also have your username/password or access to your system via other exploits, in which case they can regenerate as many tokens/credentials as they want). - .. NOTE:: Subsequent sections don't explicitly callout the key addition to APIs. We assume that you will append the correct keys as per our explanation above. - -Logout APIs -^^^^^^^^^^^^^^ -The APIs tie into ZoneMinder's existing security model. This means if you have -OPT_AUTH enabled, you need to log into ZoneMinder using the same browser you plan to -use the APIs from. If you are developing an app that relies on the API, you need -to do a POST login from the app into ZoneMinder before you can access the API. - -Then, you need to re-use the authentication information of the login (returned as cookie states) -with subsequent APIs for the authentication information to flow through to the APIs. - -This means if you plan to use cuRL to experiment with these APIs, you first need to login: - -**Login process for ZoneMinder v1.32.0 and above** - -:: - - curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/host/login.json - -Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this: - -:: - - curl -b cookies.txt http://yourzmip/zm/api/host/logout.json - - -**Login process for older versions of ZoneMinder** - -:: - - curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php - -The equivalent logout process for older versions of ZoneMinder is: - -:: - - curl -XPOST -d "username=XXXX&password=YYYY&action=logout&view=console" -b cookies.txt http://yourzmip/zm/index.php - -replacing *XXXX* and *YYYY* with your username and password, respectively. - -Please make sure you do this in a directory where you have write permissions, otherwise cookies.txt will not be created -and the command will silently fail. - - -What the "-c cookies.txt" does is store a cookie state reflecting that you have logged into ZM. You now need -to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are -using CuRL like so: - -:: - - curl -b cookies.txt http://yourzmip/zm/api/monitors.json - -This would return a list of monitors and pass on the authentication information to the ZM API layer. - -A deeper dive into the login process -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -As you might have seen above, there are two ways to login, one that uses the `login.json` API and the other that logs in using the ZM portal. If you are running ZoneMinder 1.32.0 and above, it is *strongly* recommended you use the `login.json` approach. The "old" approach will still work but is not as powerful as the API based login. Here are the reasons why: - - * The "old" approach basically uses the same login webpage (`index.php`) that a user would log into when viewing the ZM console. This is not really using an API and more importantly, if you have additional components like reCAPTCHA enabled, this will not work. Using the API approach is much cleaner and will work irrespective of reCAPTCHA - - * The new login API returns important information that you can use to stream videos as well, right after login. Consider for example, a typical response to the login API (`/login.json`): - -:: - - { - "credentials": "auth=f5b9cf48693fe8552503c8ABCD5", - "append_password": 0, - "version": "1.31.44", - "apiversion": "1.0" - } - -In this example I have `OPT_AUTH` enabled in ZoneMinder and it returns my credential key. You can then use this key to stream images like so: - -:: - - - -Where `authval` is the credentials returned to start streaming videos. - -The `append_password` field will contain 1 when it is necessary for you to append your ZM password. This is the case when you set `AUTH_RELAY` in ZM options to "plain", for example. In that case, the `credentials` field may contain something like `&user=admin&pass=` and you have to add your password to that string. - - -.. NOTE:: It is recommended you invoke the `login` API once every 60 minutes to make sure the session stays alive. The same is true if you use the old login method too. - - - -Examples (please read security notice above) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Please remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using -CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests -in your app. - +Examples +^^^^^^^^^ (In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running) @@ -500,6 +412,15 @@ This returns number of events per monitor that were recorded in the last day whe +Return sorted events +^^^^^^^^^^^^^^^^^^^^^^ + +This returns a list of events within a time range and also sorts it by descending order + +:: + + curl -XGET "http://server/zm/api/events/index/StartTime%20>=:2015-05-15%2018:43:56/EndTime%20<=:208:43:56.json?sort=StartTime&direction=desc" + Configuration Apis ^^^^^^^^^^^^^^^^^^^ @@ -674,6 +595,9 @@ Returns: This only works if you have a multiserver setup in place. If you don't it will return an empty array. +Other APIs +^^^^^^^^^^ +This is not a complete list. ZM supports more parameters/APIs. A good way to dive in is to look at the `API code `__ directly. Streaming Interface ^^^^^^^^^^^^^^^^^^^ @@ -690,9 +614,13 @@ For example: :: + + + # or + - #or - + + will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px. @@ -728,10 +656,13 @@ Similar to live playback, if you have chosen to store events in JPEG mode, you c :: - - #or + # or + + + + * This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system * This will playback event 293820, starting from frame 1 as an MJPEG stream @@ -741,12 +672,16 @@ Similar to live playback, if you have chosen to store events in JPEG mode, you c If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file: :: + - - #or -* This will play back the video recording for event 294690 + # or + + + + +This above will play back the video recording for event 294690 What other parameters are supported? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -757,6 +692,7 @@ are generated. Change and observe. Further Reading ^^^^^^^^^^^^^^^^ + As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces. There are several details that haven't yet been documented. Till they are, here are some resources: