|
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> <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
|
|