From ed513c0d6f70a4c0425cd3db5fd42121d7bf39ed Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 16 Jan 2015 17:46:43 +0100 Subject: [PATCH] DVR: Add per-user filters for all DVR entries (including timerec and autorec), fixes #2533 --- docs/html/config_access.html | 4 +++ src/access.c | 28 ++++++++++++------ src/access.h | 9 ++++-- src/dvr/dvr.h | 29 +++++++++++++++++++ src/dvr/dvr_autorec.c | 15 ++++++++++ src/dvr/dvr_db.c | 16 ++++++++++- src/dvr/dvr_timerec.c | 15 ++++++++++ src/htsp_server.c | 48 ++++++++++++++++++++++++------- src/webui/static/app/acleditor.js | 3 +- src/webui/webui.c | 7 +++++ 10 files changed, 151 insertions(+), 23 deletions(-) diff --git a/docs/html/config_access.html b/docs/html/config_access.html index e62900b7..a27e2892 100644 --- a/docs/html/config_access.html +++ b/docs/html/config_access.html @@ -93,6 +93,10 @@ The columns have the following functions:
Enables access to video recording functions for the HTSP protocol (Showtime, XBMC etc.). +
All DVR +
+ Enable to access to DVR entries created by other users (read-only). +
DVR Config Profile
If set, the user will only be able to use the DVR config profile diff --git a/src/access.c b/src/access.c index 721dd7e9..4d68338e 100644 --- a/src/access.c +++ b/src/access.c @@ -357,16 +357,17 @@ access_dump_a(access_t *a) int first; snprintf(buf, sizeof(buf), - "%s:%s [%s%s%s%s%s%s%s], conn=%u, chmin=%llu, chmax=%llu%s", + "%s:%s [%c%c%c%c%c%c%c%c], conn=%u, chmin=%llu, chmax=%llu%s", a->aa_representative ?: "", a->aa_username ?: "", - a->aa_rights & ACCESS_STREAMING ? "S" : "", - a->aa_rights & ACCESS_ADVANCED_STREAMING ? "A" : "", - a->aa_rights & ACCESS_HTSP_STREAMING ? "T" : "", - a->aa_rights & ACCESS_WEB_INTERFACE ? "W" : "", - a->aa_rights & ACCESS_RECORDER ? "R" : "", - a->aa_rights & ACCESS_HTSP_RECORDER ? "E" : "", - a->aa_rights & ACCESS_ADMIN ? "*" : "", + a->aa_rights & ACCESS_STREAMING ? 'S' : ' ', + a->aa_rights & ACCESS_ADVANCED_STREAMING ? 'A' : ' ', + a->aa_rights & ACCESS_HTSP_STREAMING ? 'T' : ' ', + a->aa_rights & ACCESS_WEB_INTERFACE ? 'W' : ' ', + a->aa_rights & ACCESS_RECORDER ? 'R' : ' ', + a->aa_rights & ACCESS_HTSP_RECORDER ? 'E' : ' ', + a->aa_rights & ACCESS_ALL_RECORDER ? 'L' : ' ', + a->aa_rights & ACCESS_ADMIN ? '*' : ' ', a->aa_conn_limit, (long long)a->aa_chmin, (long long)a->aa_chmax, a->aa_match ? ", matched" : ""); @@ -812,6 +813,8 @@ access_entry_update_rights(access_entry_t *ae) r |= ACCESS_RECORDER; if (ae->ae_htsp_dvr) r |= ACCESS_HTSP_RECORDER; + if (ae->ae_all_dvr) + r |= ACCESS_ALL_RECORDER; if (ae->ae_webui) r |= ACCESS_WEB_INTERFACE; if (ae->ae_admin) @@ -846,8 +849,10 @@ access_entry_create(const char *uuid, htsmsg_t *conf) TAILQ_INIT(&ae->ae_ipmasks); if (conf) { + /* defaults */ ae->ae_htsp_streaming = 1; ae->ae_htsp_dvr = 1; + ae->ae_all_dvr = 1; idnode_load(&ae->ae_id, conf); /* note password has PO_NOSAVE, thus it must be set manually */ if ((s = htsmsg_get_str(conf, "password")) != NULL) @@ -1297,6 +1302,12 @@ const idclass_t access_entry_class = { .name = "HTSP DVR", .off = offsetof(access_entry_t, ae_htsp_dvr), }, + { + .type = PT_BOOL, + .id = "all_dvr", + .name = "All DVR", + .off = offsetof(access_entry_t, ae_all_dvr), + }, { .type = PT_STR, .id = "dvr_config", @@ -1405,6 +1416,7 @@ access_init(int createdefault, int noacl) ae->ae_htsp_streaming = 1; ae->ae_dvr = 1; ae->ae_htsp_dvr = 1; + ae->ae_all_dvr = 1; ae->ae_webui = 1; ae->ae_admin = 1; access_entry_update_rights(ae); diff --git a/src/access.h b/src/access.h index a4aee809..22f03083 100644 --- a/src/access.h +++ b/src/access.h @@ -66,6 +66,7 @@ typedef struct access_entry { int ae_dvr; int ae_htsp_dvr; + int ae_all_dvr; struct dvr_config *ae_dvr_config; LIST_ENTRY(access_entry) ae_dvr_config_link; @@ -119,13 +120,15 @@ typedef struct access_ticket { #define ACCESS_WEB_INTERFACE (1<<3) #define ACCESS_RECORDER (1<<4) #define ACCESS_HTSP_RECORDER (1<<5) -#define ACCESS_ADMIN (1<<6) +#define ACCESS_ALL_RECORDER (1<<6) +#define ACCESS_ADMIN (1<<7) #define ACCESS_OR (1<<30) #define ACCESS_FULL \ (ACCESS_STREAMING | ACCESS_ADVANCED_STREAMING | \ - ACCESS_HTSP_STREAMING | ACCESS_HTSP_RECORDER | \ - ACCESS_WEB_INTERFACE | ACCESS_RECORDER | ACCESS_ADMIN) + ACCESS_HTSP_STREAMING | ACCESS_WEB_INTERFACE | \ + ACCESS_RECORDER | ACCESS_HTSP_RECORDER | \ + ACCESS_ALL_RECORDER | ACCESS_ADMIN) /** * Create a new ticket for the requested resource and generate a id for it diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index eb5fb0d8..be8fdfb9 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -471,6 +471,17 @@ htsmsg_t *dvr_entry_class_pri_list(void *o); htsmsg_t *dvr_entry_class_config_name_list(void *o); htsmsg_t *dvr_entry_class_duration_list(void *o, const char *not_set, int max, int step); +static inline int dvr_entry_verify(dvr_entry_t *de, access_t *a, int readonly) +{ + if (!a->aa_username || !de->de_owner) + return -1; + if (readonly && !access_verify2(a, ACCESS_ALL_RECORDER)) + return 0; + if (strcmp(de->de_owner, a->aa_username)) + return -1; + return 0; +} + /** * */ @@ -536,6 +547,15 @@ void dvr_autorec_done(void); void dvr_autorec_update(void); +static inline int dvr_autorec_entry_verify(dvr_autorec_entry_t *dae, access_t *a) +{ + if (!a->aa_username || !dae->dae_owner) + return -1; + if (strcmp(dae->dae_owner, a->aa_username)) + return -1; + return 0; +} + /** * */ @@ -571,6 +591,15 @@ void dvr_timerec_done(void); void dvr_timerec_update(void); +static inline int dvr_timerec_entry_verify(dvr_timerec_entry_t *dte, access_t *a) +{ + if (!a->aa_username || !dte->dte_owner) + return -1; + if (strcmp(dte->dte_owner, a->aa_username)) + return -1; + return 0; +} + /** * */ diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index 3ec12a77..ccbaf5a9 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -369,6 +369,20 @@ dvr_autorec_entry_class_delete(idnode_t *self) autorec_entry_destroy((dvr_autorec_entry_t *)self, 1); } +static int +dvr_autorec_entry_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write) +{ + dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)self; + + if (access_verify2(a, ACCESS_OR|ACCESS_ADMIN|ACCESS_RECORDER)) + return -1; + if (!access_verify2(a, ACCESS_ADMIN)) + return 0; + if (dvr_autorec_entry_verify(dae, a)) + return -1; + return 0; +} + static const char * dvr_autorec_entry_class_get_title (idnode_t *self) { @@ -839,6 +853,7 @@ const idclass_t dvr_autorec_entry_class = { .ic_save = dvr_autorec_entry_class_save, .ic_get_title = dvr_autorec_entry_class_get_title, .ic_delete = dvr_autorec_entry_class_delete, + .ic_perm = dvr_autorec_entry_class_perm, .ic_properties = (const property_t[]) { { .type = PT_BOOL, diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index f96c84c6..242d002c 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1108,6 +1108,20 @@ dvr_entry_class_delete(idnode_t *self) dvr_entry_cancel_delete((dvr_entry_t *)self); } +static int +dvr_entry_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write) +{ + dvr_entry_t *de = (dvr_entry_t *)self; + + if (access_verify2(a, ACCESS_OR|ACCESS_ADMIN|ACCESS_RECORDER)) + return -1; + if (!access_verify2(a, ACCESS_ADMIN)) + return 0; + if (dvr_entry_verify(de, a, msg_to_write == NULL ? 1 : 0)) + return -1; + return 0; +} + static const char * dvr_entry_class_get_title (idnode_t *self) { @@ -1680,10 +1694,10 @@ const idclass_t dvr_entry_class = { .ic_class = "dvrentry", .ic_caption = "DVR Entry", .ic_event = "dvrentry", - .ic_perm_def = ACCESS_RECORDER, .ic_save = dvr_entry_class_save, .ic_get_title = dvr_entry_class_get_title, .ic_delete = dvr_entry_class_delete, + .ic_perm = dvr_entry_class_perm, .ic_properties = (const property_t[]) { { .type = PT_TIME, diff --git a/src/dvr/dvr_timerec.c b/src/dvr/dvr_timerec.c index 5d295776..f52cac41 100644 --- a/src/dvr/dvr_timerec.c +++ b/src/dvr/dvr_timerec.c @@ -310,6 +310,20 @@ dvr_timerec_entry_class_delete(idnode_t *self) timerec_entry_destroy((dvr_timerec_entry_t *)self, 1); } +static int +dvr_timerec_entry_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write) +{ + dvr_timerec_entry_t *dte = (dvr_timerec_entry_t *)self; + + if (access_verify2(a, ACCESS_OR|ACCESS_ADMIN|ACCESS_RECORDER)) + return -1; + if (!access_verify2(a, ACCESS_ADMIN)) + return 0; + if (dvr_timerec_entry_verify(dte, a)) + return -1; + return 0; +} + static const char * dvr_timerec_entry_class_get_title (idnode_t *self) { @@ -521,6 +535,7 @@ const idclass_t dvr_timerec_entry_class = { .ic_save = dvr_timerec_entry_class_save, .ic_get_title = dvr_timerec_entry_class_get_title, .ic_delete = dvr_timerec_entry_class_delete, + .ic_perm = dvr_timerec_entry_class_perm, .ic_properties = (const property_t[]) { { .type = PT_BOOL, diff --git a/src/htsp_server.c b/src/htsp_server.c index 2206d484..fea8e6fb 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -910,7 +910,8 @@ htsp_build_event htsmsg_add_str(out, "image", ee->image); } - if((de = dvr_entry_find_by_event(e)) != NULL) { + if((de = dvr_entry_find_by_event(e)) != NULL && + !dvr_entry_verify(de, htsp->htsp_granted_access, 1)) { htsmsg_add_u32(out, "dvrId", idnode_get_short_uuid(&de->de_id)); } @@ -1070,15 +1071,18 @@ htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in) /* Send all autorecs */ TAILQ_FOREACH(dae, &autorec_entries, dae_link) - htsp_send_message(htsp, htsp_build_autorecentry(dae, "autorecEntryAdd"), NULL); + if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access)) + htsp_send_message(htsp, htsp_build_autorecentry(dae, "autorecEntryAdd"), NULL); /* Send all timerecs */ TAILQ_FOREACH(dte, &timerec_entries, dte_link) - htsp_send_message(htsp, htsp_build_timerecentry(dte, "timerecEntryAdd"), NULL); + if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access)) + htsp_send_message(htsp, htsp_build_timerecentry(dte, "timerecEntryAdd"), NULL); /* Send all DVR entries */ LIST_FOREACH(de, &dvrentries, de_global_link) - if (htsp_user_access_channel(htsp,de->de_channel)) + if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1) && + htsp_user_access_channel(htsp,de->de_channel)) htsp_send_message(htsp, htsp_build_dvrentry(de, "dvrEntryAdd"), NULL); /* Send EPG updates */ @@ -1498,9 +1502,12 @@ htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) if(htsmsg_get_u32(in, "id", &dvrEntryId)) return htsp_error("Missing argument 'id'"); - if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) return htsp_error("id not found"); + if(dvr_entry_verify(de, htsp->htsp_granted_access, 1)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, de->de_channel)) return htsp_error("User does not have access"); @@ -1539,9 +1546,12 @@ htsp_method_cancelDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) if(htsmsg_get_u32(in, "id", &dvrEntryId)) return htsp_error("Missing argument 'id'"); - if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) return htsp_error("id not found"); + if(dvr_entry_verify(de, htsp->htsp_granted_access, 0)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, de->de_channel)) return htsp_error("User does not have access"); @@ -1568,9 +1578,12 @@ htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in) if(htsmsg_get_u32(in, "id", &dvrEntryId)) return htsp_error("Missing argument 'id'"); - if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) return htsp_error("id not found"); + if(dvr_entry_verify(de, htsp->htsp_granted_access, 0)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, de->de_channel)) return htsp_error("User does not have access"); @@ -1682,6 +1695,9 @@ htsp_method_deleteAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in) if((dae = dvr_autorec_find_by_uuid(daeId)) == NULL) return htsp_error("id not found"); + if(dvr_autorec_entry_verify(dae, htsp->htsp_granted_access)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, dae->dae_channel)) return htsp_error("User does not have access"); @@ -1775,6 +1791,9 @@ htsp_method_deleteTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in) if((dte = dvr_timerec_find_by_uuid(dteId)) == NULL) return htsp_error("id not found"); + if(dvr_timerec_entry_verify(dte, htsp->htsp_granted_access)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, dte->dte_channel)) return htsp_error("User does not have access"); @@ -1813,9 +1832,12 @@ htsp_method_getDvrCutpoints(htsp_connection_t *htsp, htsmsg_t *in) if (htsmsg_get_u32(in, "id", &dvrEntryId)) return htsp_error("Missing argument 'id'"); - if( (de = dvr_entry_find_by_id(dvrEntryId)) == NULL) + if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) return htsp_error("id not found"); + if(dvr_entry_verify(de, htsp->htsp_granted_access, 1)) + return htsp_error("User does not have access"); + /* Check access */ if (!htsp_user_access_channel(htsp, de->de_channel)) return htsp_error("User does not have access"); @@ -2206,6 +2228,9 @@ htsp_method_file_open(htsp_connection_t *htsp, htsmsg_t *in) if(de == NULL) return htsp_error("DVR entry does not exist"); + if (dvr_entry_verify(de, htsp->htsp_granted_access, 1)) + return htsp_error("User does not have access"); + if (!htsp_user_access_channel(htsp, de->de_channel)) return htsp_error("User does not have access"); @@ -2987,6 +3012,7 @@ _htsp_dvr_entry_update(dvr_entry_t *de, const char *method, htsmsg_t *msg) htsp_connection_t *htsp; LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) { if (htsp->htsp_async_mode & HTSP_ASYNC_ON && + !dvr_entry_verify(de, htsp->htsp_granted_access, 1) && htsp_user_access_channel(htsp, de->de_channel)) { htsmsg_t *m = msg ? htsmsg_copy(msg) : htsp_build_dvrentry(de, method); @@ -3037,7 +3063,8 @@ _htsp_autorec_entry_update(dvr_autorec_entry_t *dae, const char *method, htsmsg_ htsp_connection_t *htsp; LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) { if (htsp->htsp_async_mode & HTSP_ASYNC_ON) { - if (dae->dae_channel == NULL || htsp_user_access_channel(htsp, dae->dae_channel)) { + if ((dae->dae_channel == NULL || htsp_user_access_channel(htsp, dae->dae_channel)) && + !dvr_autorec_entry_verify(dae, htsp->htsp_granted_access)) { htsmsg_t *m = msg ? htsmsg_copy(msg) : htsp_build_autorecentry(dae, method); htsp_send_message(htsp, m, NULL); @@ -3089,7 +3116,8 @@ _htsp_timerec_entry_update(dvr_timerec_entry_t *dte, const char *method, htsmsg_ htsp_connection_t *htsp; LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) { if (htsp->htsp_async_mode & HTSP_ASYNC_ON) { - if (dte->dte_channel == NULL || htsp_user_access_channel(htsp, dte->dte_channel)) { + if ((dte->dte_channel == NULL || htsp_user_access_channel(htsp, dte->dte_channel)) && + !dvr_timerec_entry_verify(dte, htsp->htsp_granted_access)) { htsmsg_t *m = msg ? htsmsg_copy(msg) : htsp_build_timerecentry(dte, method); htsp_send_message(htsp, m, NULL); diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js index 97ca4299..dd826d39 100644 --- a/src/webui/static/app/acleditor.js +++ b/src/webui/static/app/acleditor.js @@ -7,7 +7,7 @@ tvheadend.acleditor = function(panel, index) var list = 'enabled,username,password,prefix,' + 'webui,admin,' + 'streaming,adv_streaming,htsp_streaming,' + - 'profile,conn_limit,dvr,htsp_dvr,dvr_config,' + + 'profile,conn_limit,dvr,htsp_dvr,all_dvr,dvr_config,' + 'channel_min,channel_max,channel_tag,comment'; tvheadend.idnode_grid(panel, { @@ -25,6 +25,7 @@ tvheadend.acleditor = function(panel, index) htsp_streaming: { width: 200 }, dvr: { width: 150 }, htsp_dvr: { width: 150 }, + all_dvr: { width: 150 }, webui: { width: 140 }, admin: { width: 100 }, conn_limit: { width: 160 }, diff --git a/src/webui/webui.c b/src/webui/webui.c index 4dff23e9..b559e850 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -631,6 +631,9 @@ http_dvr_playlist(http_connection_t *hc, dvr_entry_t *de) if(http_access_verify(hc, ACCESS_RECORDER)) return HTTP_STATUS_UNAUTHORIZED; + if(dvr_entry_verify(de, hc->hc_access, 1)) + return HTTP_STATUS_NOT_FOUND; + hostpath = http_get_hostpath(hc); durration = dvr_entry_get_stop_time(de) - dvr_entry_get_start_time(de); fsize = dvr_get_filesize(de); @@ -1134,6 +1137,10 @@ page_dvrfile(http_connection_t *hc, const char *remain, void *opaque) pthread_mutex_unlock(&global_lock); return HTTP_STATUS_NOT_FOUND; } + if(dvr_entry_verify(de, hc->hc_access, 1)) { + pthread_mutex_unlock(&global_lock); + return HTTP_STATUS_NOT_FOUND; + } fname = tvh_strdupa(de->de_filename); content = muxer_container_type2mime(de->de_mc, 1);