From 938d815790a53407a3113b183319b985135e1d1d Mon Sep 17 00:00:00 2001 Message-Id: <938d815790a53407a3113b183319b985135e1d1d.1715009853.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sun, 23 Dec 2007 18:16:43 +0000 Subject: [PATCH] Web interface starts to reflect user rights properly: Organization: Straylight/Edgeware From: rjk@greenend.org.uk <> - scratch/move/remove rights check is moved to lib/queue-rights.c which is shared with the web interface - new @rights@ and @movable@ expansions (not used yet) - @removable@ and @scratchable@ rewritten for the new world --- doc/disorder_config.5.in | 20 ++++++++ lib/Makefile.am | 2 +- lib/queue-rights.c | 105 +++++++++++++++++++++++++++++++++++++++ lib/rights.h | 8 +++ server/cgimain.c | 11 +--- server/dcgi.c | 96 +++++++++++++++++++++++++---------- server/dcgi.h | 3 ++ server/server.c | 34 ++----------- templates/playing.html | 5 +- 9 files changed, 218 insertions(+), 66 deletions(-) create mode 100644 lib/queue-rights.c diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index 269abe8..eeef352 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -911,6 +911,10 @@ file for full documentation of the labels used by the standard templates. .B @length@ Expands to the length of the current track. .TP +.B @movable@ +Expands to \fBtrue\fR if the current track is movable, otherwise to +\fBfalse\fR. +.TP .B @navigate{\fIDIRECTORY\fB}{\fITEMPLATE\fB} Expands \fITEMPLATE\fR for each component of \fIDIRECTORY\fR in turn. Use \fB@dirname\fR and \fB@basename@\fR to get the components of the path to @@ -992,9 +996,25 @@ Expands to \fBtrue\fR if random play is currently enabled, otherwise to Expands \fITEMPLATE\fR repeatedly using the each recently played track in turn as the current track. The most recently played track comes first. .TP +.B @removable@ +Expands to \fBtrue\fR if the current track is removable, otherwise to +\fBfalse\fR. +.TP .B @resolve{\fITRACK\fB}@ Resolve aliases for \fITRACK\fR and expands to the result. .TP +.B @right{\fIRIGHT\fB}@ +Exapnds to \fBtrue\fR if the user has right \fIRIGHT\fR, otherwise to +\fBfalse\fR. +.TP +.B @right{\fIRIGHT\fB}{\fITRUEPART\fB}{\fIFALSEPART\fB}@ +Expands to \fITRUEPART\fR if the user right \fIRIGHT\fR, otherwise to +\fIFALSEPART\fR (which may be omitted). +.TP +.B @scratchable@ +Expands to \fBtrue\fR if the currently playing track is scratchable, otherwise +to \fBfalse\fR. +.TP .B @search{\fIPART\fB}\fR[\fB{\fICONTEXT\fB}\fR]\fB{\fITEMPLATE\fB}@ Expands \fITEMPLATE\fR once for each group of search results that have a common value of track part \fIPART\fR. diff --git a/lib/Makefile.am b/lib/Makefile.am index a7ddf48..7b4ce0a 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -53,7 +53,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ asprintf.c fprintf.c snprintf.c \ queue.c queue.h \ regsub.c regsub.h \ - rights.c rights.h \ + rights.c queue-rights.c rights.h \ rtp.h \ selection.c selection.h \ signame.c signame.h \ diff --git a/lib/queue-rights.c b/lib/queue-rights.c new file mode 100644 index 0000000..523a4ad --- /dev/null +++ b/lib/queue-rights.c @@ -0,0 +1,105 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2007 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file lib/queue-rights.c + * @brief Various rights-checking operations + */ + +#include +#include "types.h" + +#include + +#include "queue.h" +#include "rights.h" + +/** @brief Test for scratchability + * @param rights User rights + * @param who Username + * @param q Queue entry or NULL + * @return non-0 if scratchable, else 0 + */ +int right_scratchable(rights_type rights, const char *who, + const struct queue_entry *q) { + rights_type r; + + if(!q) + return 0; + if(q->submitter) + if(!strcmp(q->submitter, who)) + r = RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_ANY; + else + r = RIGHT_SCRATCH_ANY; + else + r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY; + return !!(rights & r); +} + +/** @brief Test for movability + * @param rights User rights + * @param who Username + * @param q Queue entry or NULL + * @return non-0 if movable, else 0 + */ +int right_movable(rights_type rights, const char *who, + const struct queue_entry *q) { + rights_type r; + + if(!q) + return 0; + if(q->submitter) + if(!strcmp(q->submitter, who)) + r = RIGHT_MOVE_MINE|RIGHT_MOVE_ANY; + else + r = RIGHT_MOVE_ANY; + else + r = RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY; + return !!(rights & r); +} + +/** @brief Test for removability + * @param rights User rights + * @param who Username + * @param q Queue entry or NULL + * @return non-0 if removable, else 0 + */ +int right_removable(rights_type rights, const char *who, + const struct queue_entry *q) { + rights_type r; + + if(!q) + return 0; + if(q->submitter) + if(!strcmp(q->submitter, who)) + r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY; + else + r = RIGHT_REMOVE_ANY; + else + r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY; + return !!(rights & r); +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/rights.h b/lib/rights.h index 794213c..c51ca5c 100644 --- a/lib/rights.h +++ b/lib/rights.h @@ -24,6 +24,8 @@ #ifndef RIGHTS_H #define RIGHTS_H +struct queue_entry; + /** @brief User can perform read-only operations */ #define RIGHT_READ 0x00000001 @@ -102,6 +104,12 @@ typedef uint32_t rights_type; char *rights_string(rights_type r); int parse_rights(const char *s, rights_type *rp, int report); +int right_scratchable(rights_type rights, const char *who, + const struct queue_entry *q); +int right_movable(rights_type rights, const char *who, + const struct queue_entry *q); +int right_removable(rights_type rights, const char *who, + const struct queue_entry *q); #endif /* RIGHTS_H */ diff --git a/server/cgimain.c b/server/cgimain.c index ec63bf8..53f7a9b 100644 --- a/server/cgimain.c +++ b/server/cgimain.c @@ -33,7 +33,6 @@ #include "client.h" #include "sink.h" #include "cgi.h" -#include "dcgi.h" #include "mem.h" #include "log.h" #include "configuration.h" @@ -41,6 +40,7 @@ #include "api-client.h" #include "mime.h" #include "printf.h" +#include "dcgi.h" /** @brief Infer the base URL for the web interface if it's not set * @@ -111,14 +111,7 @@ int main(int argc, char **argv) { login_cookie = cd.cookies[n].value; } } - /* Log in with the cookie if possible otherwise as guest */ - if(disorder_connect_cookie(g.client, login_cookie)) { - disorder_cgi_error(&output, &s, "connect"); - return 0; - } - /* If there was a cookie but it went bad, we forget it */ - if(login_cookie && !strcmp(disorder_user(g.client), "guest")) - login_cookie = 0; + disorder_cgi_login(&s, &output); /* TODO RFC 3875 s8.2 recommendations e.g. concerning PATH_INFO */ disorder_cgi(&output, &s); if(fclose(stdout) < 0) fatal(errno, "error closing stdout"); diff --git a/server/dcgi.c b/server/dcgi.c index e81aac6..08203f8 100644 --- a/server/dcgi.c +++ b/server/dcgi.c @@ -39,7 +39,6 @@ #include "vector.h" #include "sink.h" #include "cgi.h" -#include "dcgi.h" #include "log.h" #include "configuration.h" #include "table.h" @@ -54,6 +53,7 @@ #include "defs.h" #include "trackname.h" #include "charset.h" +#include "dcgi.h" char *login_cookie; @@ -141,6 +141,7 @@ static void lookups(dcgi_state *ds, unsigned want) { unsigned need; struct queue_entry *r, *rnext; const char *dir, *re; + char *rights; if(ds->g->client && (need = want ^ (ds->g->flags & want)) != 0) { if(need & DC_QUEUE) @@ -175,6 +176,12 @@ static void lookups(dcgi_state *ds, unsigned want) { &ds->g->files, &ds->g->nfiles)) ds->g->nfiles = 0; } + if(need & DC_RIGHTS) { + ds->g->rights = RIGHT_READ; /* fail-safe */ + if(!disorder_userinfo(ds->g->client, disorder_user(ds->g->client), + "rights", &rights)) + parse_rights(rights, &ds->g->rights, 1); + } ds->g->flags |= need; } } @@ -473,11 +480,7 @@ static void act_logout(cgi_sink *output, disorder_revoke(ds->g->client); login_cookie = 0; /* Reconnect as guest */ - ds->g->client = disorder_new(0); - if(disorder_connect_cookie(ds->g->client, 0)) { - disorder_cgi_error(output, ds, "connect"); - exit(0); - } + disorder_cgi_login(ds, output); /* Back to the login page */ expand_template(ds, output, "login"); } @@ -1236,17 +1239,12 @@ static void exp_scratchable(int attribute((unused)) nargs, cgi_sink *output, void attribute((unused)) *u) { dcgi_state *ds = u; - int result; - - if(config->restrictions & RESTRICT_SCRATCH) { - lookups(ds, DC_PLAYING); - result = (ds->g->playing - && (!ds->g->playing->submitter - || !strcmp(ds->g->playing->submitter, - disorder_user(ds->g->client)))); - } else - result = 1; - sink_printf(output->sink, "%s", bool2str(result)); + + lookups(ds, DC_PLAYING|DC_RIGHTS); + sink_printf(output->sink, "%s", + bool2str(right_scratchable(ds->g->rights, + disorder_user(ds->g->client), + ds->g->playing))); } static void exp_removable(int attribute((unused)) nargs, @@ -1254,16 +1252,25 @@ static void exp_removable(int attribute((unused)) nargs, cgi_sink *output, void attribute((unused)) *u) { dcgi_state *ds = u; - int result; - if(config->restrictions & RESTRICT_REMOVE) - result = (ds->track - && ds->track->submitter - && !strcmp(ds->track->submitter, - disorder_user(ds->g->client))); - else - result = 1; - sink_printf(output->sink, "%s", bool2str(result)); + lookups(ds, DC_RIGHTS); + sink_printf(output->sink, "%s", + bool2str(right_removable(ds->g->rights, + disorder_user(ds->g->client), + ds->track))); +} + +static void exp_movable(int attribute((unused)) nargs, + char attribute((unused)) **args, + cgi_sink *output, + void attribute((unused)) *u) { + dcgi_state *ds = u; + + lookups(ds, DC_RIGHTS); + sink_printf(output->sink, "%s", + bool2str(right_movable(ds->g->rights, + disorder_user(ds->g->client), + ds->track))); } static void exp_navigate(int attribute((unused)) nargs, @@ -1536,6 +1543,25 @@ static void exp_user(int attribute((unused)) nargs, cgi_output(output, "%s", disorder_user(ds->g->client)); } +static void exp_right(int attribute((unused)) nargs, + char **args, + cgi_sink *output, + void *u) { + dcgi_state *const ds = u; + const char *right = expandarg(args[0], ds); + rights_type r; + + lookups(ds, DC_RIGHTS); + if(parse_rights(right, &r, 1/*report*/)) + r = 0; + if(args[1] == 0) + cgi_output(output, "%s", bool2str(!!(r & ds->g->rights))); + else if(r & ds->g->rights) + expandstring(output, args[1], ds); + else if(args[2]) + expandstring(output, args[2], ds); +} + static const struct cgi_expansion expansions[] = { { "#", 0, INT_MAX, EXP_MAGIC, exp_comment }, { "action", 0, 0, 0, exp_action }, @@ -1563,6 +1589,7 @@ static const struct cgi_expansion expansions[] = { { "isrecent", 0, 0, 0, exp_isrecent }, { "label", 1, 1, 0, exp_label }, { "length", 0, 0, 0, exp_length }, + { "movable", 0, 0, 0, exp_movable }, { "navigate", 2, 2, EXP_MAGIC, exp_navigate }, { "ne", 2, 2, 0, exp_ne }, { "new", 1, 1, EXP_MAGIC, exp_new }, @@ -1583,6 +1610,7 @@ static const struct cgi_expansion expansions[] = { { "recent", 1, 1, EXP_MAGIC, exp_recent }, { "removable", 0, 0, 0, exp_removable }, { "resolve", 1, 1, 0, exp_resolve }, + { "right", 1, 3, EXP_MAGIC, exp_right }, { "scratchable", 0, 0, 0, exp_scratchable }, { "search", 2, 3, EXP_MAGIC, exp_search }, { "server-version", 0, 0, 0, exp_server_version }, @@ -1646,6 +1674,22 @@ void disorder_cgi_error(cgi_sink *output, dcgi_state *ds, perform_action(output, ds, "error"); } +/** @brief Log in as the current user or guest if none */ +void disorder_cgi_login(dcgi_state *ds, cgi_sink *output) { + /* Create a new connection */ + ds->g->client = disorder_new(0); + /* Forget everything we knew */ + ds->g->flags = 0; + /* Reconnect */ + if(disorder_connect_cookie(ds->g->client, login_cookie)) { + disorder_cgi_error(output, ds, "connect"); + exit(0); + } + /* If there was a cookie but it went bad, we forget it */ + if(login_cookie && !strcmp(disorder_user(ds->g->client), "guest")) + login_cookie = 0; +} + /* Local Variables: c-basic-offset:2 diff --git a/server/dcgi.h b/server/dcgi.h index 0cf5a89..ac110d8 100644 --- a/server/dcgi.h +++ b/server/dcgi.h @@ -31,12 +31,14 @@ typedef struct dcgi_global { #define DC_DIRS 0x0010 #define DC_FILES 0x0020 #define DC_NEW 0x0040 +#define DC_RIGHTS 0x0080 struct queue_entry *queue, *playing, *recent; int volume_left, volume_right; char **files, **dirs; int nfiles, ndirs; char **new; int nnew; + rights_type rights; } dcgi_global; typedef struct dcgi_state { @@ -57,6 +59,7 @@ typedef struct dcgi_state { void disorder_cgi(cgi_sink *output, dcgi_state *ds); void disorder_cgi_error(cgi_sink *output, dcgi_state *ds, const char *msg); +void disorder_cgi_login(dcgi_state *ds, cgi_sink *output); extern char *login_cookie; diff --git a/server/server.c b/server/server.c index e6ba7f8..b680f2d 100644 --- a/server/server.c +++ b/server/server.c @@ -233,20 +233,12 @@ static int c_play(struct conn *c, char **vec, static int c_remove(struct conn *c, char **vec, int attribute((unused)) nvec) { struct queue_entry *q; - rights_type r; if(!(q = queue_find(vec[0]))) { sink_writes(ev_writer_sink(c->w), "550 no such track on the queue\n"); return 1; } - if(q->submitter) - if(!strcmp(q->submitter, c->who)) - r = RIGHT_REMOVE_MINE|RIGHT_REMOVE_ANY; - else - r = RIGHT_REMOVE_ANY; - else - r = RIGHT_REMOVE_RANDOM|RIGHT_REMOVE_ANY; - if(!(c->rights & r)) { + if(!right_removable(c->rights, c->who, q)) { error(0, "%s attempted remove but lacks required rights", c->who); sink_writes(ev_writer_sink(c->w), "510 Not authorized to remove that track\n"); @@ -269,8 +261,6 @@ static int c_remove(struct conn *c, char **vec, static int c_scratch(struct conn *c, char **vec, int nvec) { - rights_type r; - if(!playing) { sink_writes(ev_writer_sink(c->w), "250 nothing is playing\n"); return 1; /* completed */ @@ -278,14 +268,7 @@ static int c_scratch(struct conn *c, /* TODO there is a bug here: if we specify an ID but it's not the currently * playing track then you will get 550 if you weren't authorized to scratch * the currently playing track. */ - if(playing->submitter) - if(!strcmp(playing->submitter, c->who)) - r = RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_ANY; - else - r = RIGHT_SCRATCH_ANY; - else - r = RIGHT_SCRATCH_RANDOM|RIGHT_SCRATCH_ANY; - if(!(c->rights & r)) { + if(!right_scratchable(c->rights, c->who, playing)) { error(0, "%s attempted scratch but lacks required rights", c->who); sink_writes(ev_writer_sink(c->w), "510 Not authorized to scratch that track\n"); @@ -852,20 +835,13 @@ static int c_log(struct conn *c, * @return 0 if move is prohibited, non-0 if it is allowed */ static int has_move_rights(struct conn *c, struct queue_entry **qs, int nqs) { - rights_type r = 0; - for(; nqs > 0; ++qs, --nqs) { struct queue_entry *const q = *qs; - if(q->submitter) - if(!strcmp(q->submitter, c->who)) - r |= RIGHT_MOVE_MINE|RIGHT_MOVE_ANY; - else - r |= RIGHT_MOVE_ANY; - else - r |= RIGHT_MOVE_RANDOM|RIGHT_MOVE_ANY; + if(!right_movable(c->rights, c->who, q)) + return 0; } - return !!(c->rights & r); + return 1; } static int c_move(struct conn *c, diff --git a/templates/playing.html b/templates/playing.html index 2f1f42f..31eadfd 100644 --- a/templates/playing.html +++ b/templates/playing.html @@ -186,7 +186,10 @@ USA href="@url@?action=remove&nonce=@nonce@&id=@id@&mgmt=@arg:mgmt@">@label:playing.remove@}{ }@ + alt="@label:playing.remove@">}{@label:playing.remove@}@ @if{@arg:mgmt@}{ @if{@isfirst@} { -- [mdw]