Fossil SCM

fossil-scm / src / capabilities.c
Blame History Raw 480 lines
1
/*
2
** Copyright (c) 2018 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used managing user capability strings.
19
*/
20
#include "config.h"
21
#include "capabilities.h"
22
#include <assert.h>
23
24
#if INTERFACE
25
/*
26
** A capability string object holds all defined capabilities in a
27
** vector format that is subject to boolean operations.
28
*/
29
struct CapabilityString {
30
unsigned char x[128];
31
};
32
#endif
33
34
/*
35
** Add capabilities to a CapabilityString. If pIn is NULL, then create
36
** a new capability string.
37
**
38
** Call capability_free() on the allocated CapabilityString object to
39
** deallocate.
40
*/
41
CapabilityString *capability_add(CapabilityString *pIn, const char *zCap){
42
int c;
43
int i;
44
if( pIn==0 ){
45
pIn = fossil_malloc( sizeof(*pIn) );
46
memset(pIn, 0, sizeof(*pIn));
47
}
48
if( zCap ){
49
for(i=0; (c = zCap[i])!=0; i++){
50
if( c>='0' && c<='z' ) pIn->x[c] = 1;
51
}
52
}
53
return pIn;
54
}
55
56
/*
57
** Remove capabilities from a CapabilityString.
58
*/
59
CapabilityString *capability_remove(CapabilityString *pIn, const char *zCap){
60
int c;
61
int i;
62
if( pIn==0 ){
63
pIn = fossil_malloc( sizeof(*pIn) );
64
memset(pIn, 0, sizeof(*pIn));
65
}
66
if( zCap ){
67
for(i=0; (c = zCap[i])!=0; i++){
68
if( c>='0' && c<='z' ) pIn->x[c] = 0;
69
}
70
}
71
return pIn;
72
}
73
74
/*
75
** Return true if any of the capabilities in zNeeded are found in pCap
76
*/
77
int capability_has_any(CapabilityString *p, const char *zNeeded){
78
if( p==0 ) return 0;
79
if( zNeeded==0 ) return 0;
80
while( zNeeded[0] ){
81
int c = zNeeded[0];
82
if( fossil_isalnum(c) && p->x[c] ) return 1;
83
zNeeded++;
84
}
85
return 0;
86
}
87
88
/*
89
** Delete a CapabilityString object.
90
*/
91
void capability_free(CapabilityString *p){
92
fossil_free(p);
93
}
94
95
/*
96
** Expand the capability string by including all capabilities for
97
** special users "nobody" and "anonymous". Also include "reader"
98
** if "u" is present and "developer" if "v" is present.
99
*/
100
void capability_expand(CapabilityString *pIn){
101
static char *zNobody = 0;
102
static char *zAnon = 0;
103
static char *zReader = 0;
104
static char *zDev = 0;
105
static char *zAdmin = "bcdefghijklmnopqrtwz234567ACD";
106
int doneV = 0;
107
108
if( pIn==0 ){
109
fossil_free(zNobody); zNobody = 0;
110
fossil_free(zAnon); zAnon = 0;
111
fossil_free(zReader); zReader = 0;
112
fossil_free(zDev); zDev = 0;
113
return;
114
}
115
if( zNobody==0 ){
116
zNobody = db_text(0, "SELECT cap FROM user WHERE login='nobody'");
117
zAnon = db_text(0, "SELECT cap FROM user WHERE login='anonymous'");
118
zReader = db_text(0, "SELECT cap FROM user WHERE login='reader'");
119
zDev = db_text(0, "SELECT cap FROM user WHERE login='developer'");
120
}
121
pIn = capability_add(pIn, zAnon);
122
pIn = capability_add(pIn, zNobody);
123
if( pIn->x['a'] || pIn->x['s'] ){
124
pIn = capability_add(pIn, zAdmin);
125
}
126
if( pIn->x['v'] ){
127
pIn = capability_add(pIn, zDev);
128
doneV = 1;
129
}
130
if( pIn->x['u'] ){
131
pIn = capability_add(pIn, zReader);
132
if( pIn->x['v'] && !doneV ){
133
pIn = capability_add(pIn, zDev);
134
}
135
}
136
}
137
138
/*
139
** Render a capability string in canonical string format. Space to hold
140
** the returned string is obtained from fossil_malloc() can should be freed
141
** by the caller.
142
*/
143
char *capability_string(CapabilityString *p){
144
Blob out;
145
int i;
146
int j = 0;
147
char buf[100];
148
blob_init(&out, 0, 0);
149
for(i='a'; i<='z'; i++){
150
if( p->x[i] ) buf[j++] = i;
151
}
152
for(i='0'; i<='9'; i++){
153
if( p->x[i] ) buf[j++] = i;
154
}
155
for(i='A'; i<='Z'; i++){
156
if( p->x[i] ) buf[j++] = i;
157
}
158
buf[j] = 0;
159
return fossil_strdup(buf);
160
}
161
162
/*
163
** The next two routines implement an aggregate SQL function that
164
** takes multiple capability strings and in the end returns their
165
** union. Example usage:
166
**
167
** SELECT capunion(cap) FROM user WHERE login IN ('nobody','anonymous');
168
*/
169
void capability_union_step(
170
sqlite3_context *context,
171
int argc,
172
sqlite3_value **argv
173
){
174
CapabilityString *p;
175
const char *zIn;
176
177
zIn = (const char*)sqlite3_value_text(argv[0]);
178
if( zIn==0 ) return;
179
p = (CapabilityString*)sqlite3_aggregate_context(context, sizeof(*p));
180
p = capability_add(p, zIn);
181
}
182
void capability_union_finalize(sqlite3_context *context){
183
CapabilityString *p;
184
p = sqlite3_aggregate_context(context, 0);
185
if( p ){
186
char *zOut = capability_string(p);
187
sqlite3_result_text(context, zOut, -1, fossil_free);
188
}
189
}
190
191
/*
192
** The next routines takes the raw USER.CAP field and expands it with
193
** capabilities from special users. Example:
194
**
195
** SELECT fullcap(cap) FROM user WHERE login=?1
196
*/
197
void capability_fullcap(
198
sqlite3_context *context,
199
int argc,
200
sqlite3_value **argv
201
){
202
CapabilityString *p;
203
const char *zIn;
204
char *zOut;
205
206
zIn = (const char*)sqlite3_value_text(argv[0]);
207
if( zIn==0 ) zIn = "";
208
p = capability_add(0, zIn);
209
capability_expand(p);
210
zOut = capability_string(p);
211
sqlite3_result_text(context, zOut, -1, fossil_free);
212
capability_free(p);
213
}
214
215
#if INTERFACE
216
/*
217
** Capabilities are grouped into "classes" as follows:
218
*/
219
#define CAPCLASS_CODE 0x0001
220
#define CAPCLASS_WIKI 0x0002
221
#define CAPCLASS_TKT 0x0004
222
#define CAPCLASS_FORUM 0x0008
223
#define CAPCLASS_DATA 0x0010
224
#define CAPCLASS_ALERT 0x0020
225
#define CAPCLASS_OTHER 0x0040
226
#define CAPCLASS_SUPER 0x0080
227
#define CAPCLASS_ALL 0xffff
228
#endif /* INTERFACE */
229
230
231
/*
232
** The following structure holds descriptions of the various capabilities.
233
*/
234
static struct Caps {
235
char cCap; /* The capability letter */
236
unsigned short eClass; /* The "class" for this capability */
237
unsigned nUser; /* Number of users with this capability */
238
char *zAbbrev; /* Abbreviated mnemonic name */
239
char *zOneLiner; /* One-line summary */
240
} aCap[] = {
241
{ 'a', CAPCLASS_SUPER, 0,
242
"Admin", "Create and delete users" },
243
{ 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0,
244
"Attach", "Add attachments to wiki or tickets" },
245
{ 'c', CAPCLASS_TKT, 0,
246
"Append-Tkt", "Append to existing tickets" },
247
/*
248
** d unused since fork from CVSTrac;
249
** see https://fossil-scm.org/forum/forumpost/43c78f4bef
250
*/
251
{ 'e', CAPCLASS_DATA, 0,
252
"View-PII", "View sensitive info such as email addresses" },
253
{ 'f', CAPCLASS_WIKI, 0,
254
"New-Wiki", "Create new wiki pages" },
255
{ 'g', CAPCLASS_DATA, 0,
256
"Clone", "Clone the repository" },
257
{ 'h', CAPCLASS_OTHER, 0,
258
"Hyperlinks", "Show hyperlinks to detailed repository history" },
259
{ 'i', CAPCLASS_CODE, 0,
260
"Check-In", "Check-in code changes" },
261
{ 'j', CAPCLASS_WIKI, 0,
262
"Read-Wiki", "View wiki pages" },
263
{ 'k', CAPCLASS_WIKI, 0,
264
"Write-Wiki", "Edit wiki pages" },
265
{ 'l', CAPCLASS_WIKI|CAPCLASS_SUPER, 0,
266
"Mod-Wiki", "Moderator for wiki pages" },
267
{ 'm', CAPCLASS_WIKI, 0,
268
"Append-Wiki", "Append to wiki pages" },
269
{ 'n', CAPCLASS_TKT, 0,
270
"New-Tkt", "Create new tickets" },
271
{ 'o', CAPCLASS_CODE, 0,
272
"Check-Out", "Check out code" },
273
{ 'p', CAPCLASS_OTHER, 0,
274
"Password", "Change your own password" },
275
{ 'q', CAPCLASS_TKT|CAPCLASS_SUPER, 0,
276
"Mod-Tkt", "Moderate tickets" },
277
{ 'r', CAPCLASS_TKT, 0,
278
"Read-Tkt", "View tickets" },
279
{ 's', CAPCLASS_SUPER, 0,
280
"Superuser", "Setup and configure the repository" },
281
{ 't', CAPCLASS_TKT, 0,
282
"Reports", "Create new ticket report formats" },
283
{ 'u', CAPCLASS_OTHER, 0,
284
"Reader", "Inherit all the capabilities of the \"reader\" user" },
285
{ 'v', CAPCLASS_OTHER, 0,
286
"Developer", "Inherit all capabilities of the \"developer\" user" },
287
{ 'w', CAPCLASS_TKT, 0,
288
"Write-Tkt", "Edit tickets" },
289
{ 'x', CAPCLASS_DATA, 0,
290
"Private", "Push and/or pull private branches" },
291
{ 'y', CAPCLASS_SUPER, 0,
292
"Write-UV", "Push unversioned content" },
293
{ 'z', CAPCLASS_CODE, 0,
294
"Zip-Download", "Download a ZIP archive, tarball, or SQL archive" },
295
{ '2', CAPCLASS_FORUM, 0,
296
"Forum-Read", "Read forum posts by others" },
297
{ '3', CAPCLASS_FORUM, 0,
298
"Forum-Write", "Create new forum messages" },
299
{ '4', CAPCLASS_FORUM, 0,
300
"Forum-Trusted", "Create forum messages that bypass moderation" },
301
{ '5', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
302
"Forum-Mod", "Moderator for forum messages" },
303
{ '6', CAPCLASS_FORUM|CAPCLASS_SUPER, 0,
304
"Forum-Admin", "Grant capability '4' to other users" },
305
{ '7', CAPCLASS_ALERT, 0,
306
"Alerts", "Sign up for email alerts" },
307
{ 'A', CAPCLASS_ALERT|CAPCLASS_SUPER, 0,
308
"Announce", "Send announcements to all subscribers" },
309
{ 'C', CAPCLASS_FORUM, 0,
310
"Chat", "Read and/or writes messages in the chatroom" },
311
{ 'D', CAPCLASS_OTHER, 0,
312
"Debug", "Enable debugging features" },
313
};
314
315
/*
316
** Populate the aCap[].nUser values based on the current content
317
** of the USER table.
318
*/
319
void capabilities_count(void){
320
int i;
321
static int done = 0;
322
Stmt q;
323
if( done ) return;
324
db_prepare(&q, "SELECT fullcap(cap) FROM user");
325
while( db_step(&q)==SQLITE_ROW ){
326
const char *zCap = db_column_text(&q, 0);
327
if( zCap==0 || zCap[0]==0 ) continue;
328
for(i=0; i<(int)(sizeof(aCap)/sizeof(aCap[0])); i++){
329
if( strchr(zCap, aCap[i].cCap) ) aCap[i].nUser++;
330
}
331
}
332
db_finalize(&q);
333
done = 1;
334
}
335
336
337
/*
338
** Generate HTML that lists all of the capability letters together with
339
** a brief summary of what each letter means.
340
*/
341
void capabilities_table(unsigned mClass){
342
int i;
343
if( g.perm.Admin ) capabilities_count();
344
@ <table>
345
@ <tbody>
346
for(i=0; i<(int)(sizeof(aCap)/sizeof(aCap[0])); i++){
347
int n;
348
if( (aCap[i].eClass & mClass)==0 ) continue;
349
@ <tr><th valign="top">%c(aCap[i].cCap)</th>
350
@ <td>%h(aCap[i].zAbbrev)</td><td>%h(aCap[i].zOneLiner)</td>\
351
n = aCap[i].nUser;
352
if( n && g.perm.Admin ){
353
@ <td><a href="%R/setup_ulist?with=%c(aCap[i].cCap)">\
354
@ %d(n) user%s(n>1?"s":"")</a></td>\
355
}
356
@ </tr>
357
}
358
@ </tbody>
359
@ </table>
360
}
361
362
/*
363
** Generate a "capability summary table" that shows the major capabilities
364
** against the various user categories.
365
*/
366
void capability_summary(void){
367
Stmt q;
368
CapabilityString *pCap;
369
char *zSelfCap;
370
char *zPubPages = db_get("public-pages",0);
371
int hasPubPages = zPubPages && zPubPages[0];
372
373
pCap = capability_add(0, db_get("default-perms","u"));
374
capability_expand(pCap);
375
zSelfCap = capability_string(pCap);
376
capability_free(pCap);
377
378
db_prepare(&q,
379
"WITH t(id,seq) AS (VALUES('nobody',1),('anonymous',2),('reader',3),"
380
"('developer',4))"
381
" SELECT id, CASE WHEN user.login='nobody' THEN user.cap"
382
" ELSE fullcap(user.cap) END,seq,1"
383
" FROM t LEFT JOIN user ON t.id=user.login"
384
" UNION ALL"
385
" SELECT 'Public Pages', %Q, 100, %d"
386
" UNION ALL"
387
" SELECT 'New User Default', %Q, 110, 1"
388
" UNION ALL"
389
" SELECT 'Regular User', fullcap(capunion(cap)), 200, count(*) FROM user"
390
" WHERE cap NOT GLOB '*[as]*' AND login NOT IN (SELECT id FROM t)"
391
" UNION ALL"
392
" SELECT 'Administrator', fullcap(capunion(cap)), 300, count(*) FROM user"
393
" WHERE cap GLOB '*[as]*'"
394
" ORDER BY 3 ASC",
395
zSelfCap, hasPubPages, zSelfCap
396
);
397
@ <table id='capabilitySummary' cellpadding="0" cellspacing="0" border="1">
398
@ <tr><th>&nbsp;<th>Code<th>Forum<th>Tickets<th>Wiki<th>Chat\
399
@ <th>Unversioned Content</th></tr>
400
while( db_step(&q)==SQLITE_ROW ){
401
const char *zId = db_column_text(&q, 0);
402
const char *zCap = db_column_text(&q, 1);
403
int n = db_column_int(&q, 3);
404
int eType;
405
static const char *const azType[] = { "off", "read", "write" };
406
static const char *const azClass[] =
407
{ "capsumOff", "capsumRead", "capsumWrite" };
408
409
if( n==0 ) continue;
410
411
/* Code */
412
if( db_column_int(&q,2)<10 ){
413
@ <tr><th align="right"><tt>"%h(zId)"</tt></th>
414
}else if( n>1 ){
415
@ <tr><th align="right">%d(n) %h(zId)s</th>
416
}else{
417
@ <tr><th align="right">%h(zId)</th>
418
}
419
if( sqlite3_strglob("*[asi]*",zCap)==0 ){
420
eType = 2;
421
}else if( sqlite3_strglob("*[oz]*",zCap)==0 ){
422
eType = 1;
423
}else{
424
eType = 0;
425
}
426
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
427
428
/* Forum */
429
if( sqlite3_strglob("*[as3456]*",zCap)==0 ){
430
eType = 2;
431
}else if( sqlite3_strglob("*2*",zCap)==0 ){
432
eType = 1;
433
}else{
434
eType = 0;
435
}
436
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
437
438
/* Ticket */
439
if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){
440
eType = 2;
441
}else if( sqlite3_strglob("*r*",zCap)==0 ){
442
eType = 1;
443
}else{
444
eType = 0;
445
}
446
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
447
448
/* Wiki */
449
if( sqlite3_strglob("*[asdfklm]*",zCap)==0 ){
450
eType = 2;
451
}else if( sqlite3_strglob("*j*",zCap)==0 ){
452
eType = 1;
453
}else{
454
eType = 0;
455
}
456
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
457
458
/* Chat */
459
if( sqlite3_strglob("*C*",zCap)==0 ){
460
eType = 2;
461
}else{
462
eType = 0;
463
}
464
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
465
466
/* Unversioned */
467
if( sqlite3_strglob("*y*",zCap)==0 ){
468
eType = 2;
469
}else if( sqlite3_strglob("*[ioas]*",zCap)==0 ){
470
eType = 1;
471
}else{
472
eType = 0;
473
}
474
@ <td class="%s(azClass[eType])">%s(azType[eType])</td>
475
476
}
477
db_finalize(&q);
478
@ </table>
479
}
480

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button