|
1
|
/* |
|
2
|
** Copyright (c) 2007 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
|
** Setup pages associated with user management. The code in this |
|
19
|
** file was formerly part of the "setup.c" module, but has been broken |
|
20
|
** out into its own module to improve maintainability. |
|
21
|
** |
|
22
|
** Note: Do not confuse "Users" with "Subscribers". Code to deal with |
|
23
|
** subscribers is over in the "alerts.c" source file. |
|
24
|
*/ |
|
25
|
#include "config.h" |
|
26
|
#include <assert.h> |
|
27
|
#include "setupuser.h" |
|
28
|
|
|
29
|
/* |
|
30
|
** WEBPAGE: setup_ulist |
|
31
|
** |
|
32
|
** Show a list of users. Clicking on any user jumps to the edit |
|
33
|
** screen for that user. Requires Admin privileges. |
|
34
|
** |
|
35
|
** Query parameters: |
|
36
|
** |
|
37
|
** with=CAP Only show users that have one or more capabilities in CAP. |
|
38
|
** ubg Color backgrounds by username hash |
|
39
|
*/ |
|
40
|
void setup_ulist(void){ |
|
41
|
Stmt s; |
|
42
|
double rNow; |
|
43
|
const char *zWith = P("with"); |
|
44
|
int bUnusedOnly = P("unused")!=0; |
|
45
|
int bUbg = P("ubg")!=0; |
|
46
|
int bHaveAlerts; |
|
47
|
|
|
48
|
login_check_credentials(); |
|
49
|
if( !g.perm.Admin ){ |
|
50
|
login_needed(0); |
|
51
|
return; |
|
52
|
} |
|
53
|
bHaveAlerts = alert_tables_exist(); |
|
54
|
style_submenu_element("Add", "setup_uedit"); |
|
55
|
style_submenu_element("Log", "access_log"); |
|
56
|
style_submenu_element("Help", "setup_ulist_notes"); |
|
57
|
if( bHaveAlerts ){ |
|
58
|
style_submenu_element("Subscribers", "subscribers"); |
|
59
|
} |
|
60
|
style_set_current_feature("setup"); |
|
61
|
style_header("User List"); |
|
62
|
if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ |
|
63
|
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable'> |
|
64
|
@ <thead><tr> |
|
65
|
@ <th>Category |
|
66
|
@ <th>Capabilities (<a href='%R/setup_ucap_list'>key</a>) |
|
67
|
@ <th>Info <th>Last Change</tr></thead> |
|
68
|
@ <tbody> |
|
69
|
db_prepare(&s, |
|
70
|
"SELECT uid, login, cap, date(mtime,'unixepoch')" |
|
71
|
" FROM user" |
|
72
|
" WHERE login IN ('anonymous','nobody','developer','reader')" |
|
73
|
" ORDER BY login" |
|
74
|
); |
|
75
|
while( db_step(&s)==SQLITE_ROW ){ |
|
76
|
int uid = db_column_int(&s, 0); |
|
77
|
const char *zLogin = db_column_text(&s, 1); |
|
78
|
const char *zCap = db_column_text(&s, 2); |
|
79
|
const char *zDate = db_column_text(&s, 4); |
|
80
|
@ <tr> |
|
81
|
@ <td><a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a> |
|
82
|
@ <td>%h(zCap) |
|
83
|
|
|
84
|
if( fossil_strcmp(zLogin,"anonymous")==0 ){ |
|
85
|
@ <td>All logged-in users |
|
86
|
}else if( fossil_strcmp(zLogin,"developer")==0 ){ |
|
87
|
@ <td>Users with '<b>v</b>' capability |
|
88
|
}else if( fossil_strcmp(zLogin,"nobody")==0 ){ |
|
89
|
@ <td>All users without login |
|
90
|
}else if( fossil_strcmp(zLogin,"reader")==0 ){ |
|
91
|
@ <td>Users with '<b>u</b>' capability |
|
92
|
}else{ |
|
93
|
@ <td> |
|
94
|
} |
|
95
|
if( zDate && zDate[0] ){ |
|
96
|
@ <td>%h(zDate) |
|
97
|
}else{ |
|
98
|
@ <td> |
|
99
|
} |
|
100
|
@ </tr> |
|
101
|
} |
|
102
|
db_finalize(&s); |
|
103
|
@ </tbody></table> |
|
104
|
@ <div class='section'>Users</div> |
|
105
|
}else{ |
|
106
|
style_submenu_element("All Users", "setup_ulist"); |
|
107
|
if( bUnusedOnly ){ |
|
108
|
@ <div class='section'>Unused logins</div> |
|
109
|
}else if( zWith ){ |
|
110
|
if( zWith[1]==0 ){ |
|
111
|
@ <div class='section'>Users with capability "%h(zWith)"</div> |
|
112
|
}else{ |
|
113
|
@ <div class='section'>Users with any capability in "%h(zWith)"</div> |
|
114
|
} |
|
115
|
} |
|
116
|
} |
|
117
|
if( !bUnusedOnly ){ |
|
118
|
style_submenu_element("Unused", "setup_ulist?unused"); |
|
119
|
} |
|
120
|
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \ |
|
121
|
@ data-column-types='ktxKTKt' data-init-sort='4'> |
|
122
|
@ <thead><tr> |
|
123
|
@ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\ |
|
124
|
@ <th>Alerts</tr></thead> |
|
125
|
@ <tbody> |
|
126
|
db_multi_exec( |
|
127
|
"CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)" |
|
128
|
"WITHOUT ROWID;" |
|
129
|
); |
|
130
|
if( db_table_exists("repository","accesslog") ){ |
|
131
|
db_multi_exec( |
|
132
|
"INSERT INTO lastAccess(uname, atime)" |
|
133
|
" SELECT uname, max(mtime) FROM (" |
|
134
|
" SELECT uname, mtime FROM accesslog WHERE success" |
|
135
|
" UNION ALL" |
|
136
|
" SELECT login AS uname, rcvfrom.mtime AS mtime" |
|
137
|
" FROM rcvfrom JOIN user USING(uid))" |
|
138
|
" GROUP BY 1;" |
|
139
|
); |
|
140
|
} |
|
141
|
if( !db_table_exists("repository","subscriber") ){ |
|
142
|
db_multi_exec( |
|
143
|
"CREATE TEMP TABLE subscriber(suname PRIMARY KEY, ssub, subscriberId)" |
|
144
|
"WITHOUT ROWID;" |
|
145
|
); |
|
146
|
} |
|
147
|
if( bUnusedOnly ){ |
|
148
|
zWith = mprintf( |
|
149
|
" AND login NOT IN (" |
|
150
|
"SELECT user FROM event WHERE user NOT NULL " |
|
151
|
"UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)" |
|
152
|
" AND uid NOT IN (SELECT uid FROM rcvfrom)", |
|
153
|
bHaveAlerts ? |
|
154
|
" UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":""); |
|
155
|
}else if( zWith && zWith[0] ){ |
|
156
|
zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); |
|
157
|
}else{ |
|
158
|
zWith = ""; |
|
159
|
} |
|
160
|
db_prepare(&s, |
|
161
|
/*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," |
|
162
|
/* 5 */"lower(login) AS sortkey, " |
|
163
|
/* 6 */"CASE WHEN info LIKE '%%expires 20%%'" |
|
164
|
" THEN substr(info,instr(lower(info),'expires')+8,10)" |
|
165
|
" END AS exp," |
|
166
|
/* 7 */"atime," |
|
167
|
/* 8 */"user.mtime AS sorttime," |
|
168
|
/*9-11*/"%s" |
|
169
|
" FROM user LEFT JOIN lastAccess ON login=uname" |
|
170
|
" LEFT JOIN subscriber ON login=suname" |
|
171
|
" WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" |
|
172
|
" ORDER BY sorttime DESC", |
|
173
|
bHaveAlerts |
|
174
|
? "subscriber.ssub, subscriber.subscriberId, subscriber.semail" |
|
175
|
: "null, null, null", |
|
176
|
zWith/*safe-for-%s*/ |
|
177
|
); |
|
178
|
rNow = db_double(0.0, "SELECT julianday('now');"); |
|
179
|
while( db_step(&s)==SQLITE_ROW ){ |
|
180
|
int uid = db_column_int(&s, 0); |
|
181
|
const char *zLogin = db_column_text(&s, 1); |
|
182
|
const char *zCap = db_column_text(&s, 2); |
|
183
|
const char *zInfo = db_column_text(&s, 3); |
|
184
|
const char *zDate = db_column_text(&s, 4); |
|
185
|
const char *zSortKey = db_column_text(&s,5); |
|
186
|
const char *zExp = db_column_text(&s,6); |
|
187
|
double rATime = db_column_double(&s,7); |
|
188
|
char *zAge = 0; |
|
189
|
const char *zSub; |
|
190
|
int sid = db_column_int(&s,10); |
|
191
|
sqlite3_int64 sorttime = db_column_int64(&s, 8); |
|
192
|
if( rATime>0.0 ){ |
|
193
|
zAge = human_readable_age(rNow - rATime); |
|
194
|
} |
|
195
|
if( bUbg ){ |
|
196
|
@ <tr style='background-color: %h(user_color(zLogin));'> |
|
197
|
}else{ |
|
198
|
@ <tr> |
|
199
|
} |
|
200
|
@ <td data-sortkey='%h(zSortKey)'>\ |
|
201
|
@ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a> |
|
202
|
@ <td>%h(zCap) |
|
203
|
@ <td>%h(zInfo) |
|
204
|
@ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"") |
|
205
|
@ <td>%h(zExp?zExp:"") |
|
206
|
@ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"") |
|
207
|
if( db_column_type(&s,9)==SQLITE_NULL ){ |
|
208
|
@ <td> |
|
209
|
}else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){ |
|
210
|
@ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a> |
|
211
|
}else{ |
|
212
|
const char *zEmail = db_column_text(&s, 11); |
|
213
|
char * zAt = zEmail ? mprintf(" → %h", zEmail) : mprintf(""); |
|
214
|
@ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a> %z(zAt) |
|
215
|
} |
|
216
|
|
|
217
|
@ </tr> |
|
218
|
fossil_free(zAge); |
|
219
|
} |
|
220
|
@ </tbody></table> |
|
221
|
db_finalize(&s); |
|
222
|
style_table_sorter(); |
|
223
|
style_finish_page(); |
|
224
|
} |
|
225
|
|
|
226
|
/* |
|
227
|
** WEBPAGE: setup_ulist_notes |
|
228
|
** |
|
229
|
** A documentation page showing notes about user configuration. This |
|
230
|
** information used to be a side-bar on the user list page, but has been |
|
231
|
** factored out for improved presentation. |
|
232
|
*/ |
|
233
|
void setup_ulist_notes(void){ |
|
234
|
style_set_current_feature("setup"); |
|
235
|
style_header("User Configuration Notes"); |
|
236
|
@ <h1>User Configuration Notes:</h1> |
|
237
|
@ <ol> |
|
238
|
@ <li><p> |
|
239
|
@ Every user, logged in or not, inherits the privileges of |
|
240
|
@ <span class="usertype">nobody</span>. |
|
241
|
@ </p></li> |
|
242
|
@ |
|
243
|
@ <li><p> |
|
244
|
@ Any human can login as <span class="usertype">anonymous</span> since the |
|
245
|
@ password is clearly displayed on the login page for them to type. The |
|
246
|
@ purpose of requiring anonymous to log in is to prevent access by spiders. |
|
247
|
@ Every logged-in user inherits the combined privileges of |
|
248
|
@ <span class="usertype">anonymous</span> and |
|
249
|
@ <span class="usertype">nobody</span>. |
|
250
|
@ </p></li> |
|
251
|
@ |
|
252
|
@ <li><p> |
|
253
|
@ Users with privilege <span class="capability">u</span> inherit the combined |
|
254
|
@ privileges of <span class="usertype">reader</span>, |
|
255
|
@ <span class="usertype">anonymous</span>, and |
|
256
|
@ <span class="usertype">nobody</span>. |
|
257
|
@ </p></li> |
|
258
|
@ |
|
259
|
@ <li><p> |
|
260
|
@ Users with privilege <span class="capability">v</span> inherit the combined |
|
261
|
@ privileges of <span class="usertype">developer</span>, |
|
262
|
@ <span class="usertype">anonymous</span>, and |
|
263
|
@ <span class="usertype">nobody</span>. |
|
264
|
@ </p></li> |
|
265
|
@ |
|
266
|
@ <li><p>The permission flags are as follows:</p> |
|
267
|
capabilities_table(CAPCLASS_ALL); |
|
268
|
@ </li> |
|
269
|
@ </ol> |
|
270
|
style_finish_page(); |
|
271
|
} |
|
272
|
|
|
273
|
/* |
|
274
|
** WEBPAGE: setup_ucap_list |
|
275
|
** |
|
276
|
** A documentation page showing the meaning of the various user capabilities |
|
277
|
** code letters. |
|
278
|
*/ |
|
279
|
void setup_ucap_list(void){ |
|
280
|
style_set_current_feature("setup"); |
|
281
|
style_header("User Capability Codes"); |
|
282
|
@ <h1>All capabilities</h1> |
|
283
|
capabilities_table(CAPCLASS_ALL); |
|
284
|
@ <h1>Capabilities associated with checked-in content</h1> |
|
285
|
capabilities_table(CAPCLASS_CODE); |
|
286
|
@ <h1>Capabilities associated with data transfer and sync</h1> |
|
287
|
capabilities_table(CAPCLASS_DATA); |
|
288
|
@ <h1>Capabilities associated with the forum</h1> |
|
289
|
capabilities_table(CAPCLASS_FORUM); |
|
290
|
@ <h1>Capabilities associated with tickets</h1> |
|
291
|
capabilities_table(CAPCLASS_TKT); |
|
292
|
@ <h1>Capabilities associated with wiki</h1> |
|
293
|
capabilities_table(CAPCLASS_WIKI); |
|
294
|
@ <h1>Administrative capabilities</h1> |
|
295
|
capabilities_table(CAPCLASS_SUPER); |
|
296
|
@ <h1>Miscellaneous capabilities</h1> |
|
297
|
capabilities_table(CAPCLASS_OTHER); |
|
298
|
style_finish_page(); |
|
299
|
} |
|
300
|
|
|
301
|
/* |
|
302
|
** Return true if zPw is a valid password string. A valid |
|
303
|
** password string is: |
|
304
|
** |
|
305
|
** (1) A zero-length string, or |
|
306
|
** (2) a string that contains a character other than '*'. |
|
307
|
*/ |
|
308
|
static int isValidPwString(const char *zPw){ |
|
309
|
if( zPw==0 ) return 0; |
|
310
|
if( zPw[0]==0 ) return 1; |
|
311
|
while( zPw[0]=='*' ){ zPw++; } |
|
312
|
return zPw[0]!=0; |
|
313
|
} |
|
314
|
|
|
315
|
/* |
|
316
|
** Return true if user capability strings zOrig and zNew materially |
|
317
|
** differ, taking into account that they may be sorted in an arbitary |
|
318
|
** order. This does not take inherited permissions into |
|
319
|
** account. Either argument may be NULL. A NULL and an empty string |
|
320
|
** are considered equivalent here. e.g. "abc" and "cab" are equivalent |
|
321
|
** for this purpose, but "aCb" and "acb" are not. |
|
322
|
*/ |
|
323
|
static int userCapsChanged(const char *zOrig, const char *zNew){ |
|
324
|
if( !zOrig ){ |
|
325
|
return zNew ? (0!=*zNew) : 0; |
|
326
|
}else if( !zNew ){ |
|
327
|
return 0!=*zOrig; |
|
328
|
}else if( 0==fossil_strcmp(zOrig, zNew) ){ |
|
329
|
return 0; |
|
330
|
}else{ |
|
331
|
/* We don't know that zOrig and zNew are sorted equivalently. The |
|
332
|
** following steps will compare strings which contain all the same |
|
333
|
** capabilities letters as equivalent, regardless of the letters' |
|
334
|
** order in their strings. */ |
|
335
|
char aOrig[128]; /* table of zOrig bytes */ |
|
336
|
int nOrig = 0, nNew = 0; |
|
337
|
|
|
338
|
memset( &aOrig[0], 0, sizeof(aOrig) ); |
|
339
|
for( ; *zOrig; ++zOrig, ++nOrig ){ |
|
340
|
if( 0==(*zOrig & 0x80) ){ |
|
341
|
aOrig[(int)*zOrig] = 1; |
|
342
|
} |
|
343
|
} |
|
344
|
for( ; *zNew; ++zNew, ++nNew ){ |
|
345
|
if( 0==(*zNew & 0x80) && !aOrig[(int)*zNew] ){ |
|
346
|
return 1; |
|
347
|
} |
|
348
|
} |
|
349
|
return nOrig!=nNew; |
|
350
|
} |
|
351
|
} |
|
352
|
|
|
353
|
/* |
|
354
|
** COMMAND: test-user-caps-changed |
|
355
|
** |
|
356
|
** Usage: %fossil test-user-caps-changed caps1 caps2 |
|
357
|
** |
|
358
|
*/ |
|
359
|
void test_user_caps_changed(void){ |
|
360
|
|
|
361
|
char const * zOld = g.argc>2 ? g.argv[2] : NULL; |
|
362
|
char const * zNew = g.argc>3 ? g.argv[3] : NULL; |
|
363
|
fossil_print("Has changes? = %d\n", |
|
364
|
userCapsChanged( zOld, zNew )); |
|
365
|
} |
|
366
|
|
|
367
|
/* |
|
368
|
** Sends notification of user permission elevation changes to all |
|
369
|
** subscribers with a "u" subscription. This is a no-op if alerts are |
|
370
|
** not enabled. |
|
371
|
** |
|
372
|
** These subscriptions differ from most, in that: |
|
373
|
** |
|
374
|
** - They currently lack an "unsubscribe" link. |
|
375
|
** |
|
376
|
** - Only an admin can assign this subscription, but if a non-admin |
|
377
|
** edits their subscriptions after an admin assigns them this one, |
|
378
|
** this particular one will be lost. "Feature or bug?" is unclear, |
|
379
|
** but it would be odd for a non-admin to be assigned this |
|
380
|
** capability. |
|
381
|
*/ |
|
382
|
static void alert_user_cap_change(const char *zLogin, /*Affected user*/ |
|
383
|
int uid, /*[user].uid*/ |
|
384
|
int bIsNew, /*true if new user*/ |
|
385
|
const char *zOrigCaps,/*Old caps*/ |
|
386
|
const char *zNewCaps /*New caps*/){ |
|
387
|
Blob hdr, body; |
|
388
|
Stmt q; |
|
389
|
int nBody; |
|
390
|
AlertSender *pSender; |
|
391
|
char *zSubname; |
|
392
|
char *zURL; |
|
393
|
char * zSubject; |
|
394
|
|
|
395
|
if( !alert_enabled() ) return; |
|
396
|
zSubject = bIsNew |
|
397
|
? mprintf("New user created: [%q]", zLogin) |
|
398
|
: mprintf("User [%q] capabilities changed", zLogin); |
|
399
|
zURL = db_get("email-url",0); |
|
400
|
zSubname = db_get("email-subname", "[Fossil Repo]"); |
|
401
|
blob_init(&body, 0, 0); |
|
402
|
blob_init(&hdr, 0, 0); |
|
403
|
if( bIsNew ){ |
|
404
|
blob_appendf(&body, "User [%q] was created with " |
|
405
|
"permissions [%q] by user [%q].\n", |
|
406
|
zLogin, zNewCaps, g.zLogin); |
|
407
|
} else { |
|
408
|
blob_appendf(&body, "Permissions for user [%q] were changed " |
|
409
|
"from [%q] to [%q] by user [%q].\n", |
|
410
|
zLogin, zOrigCaps, zNewCaps, g.zLogin); |
|
411
|
} |
|
412
|
if( zURL ){ |
|
413
|
blob_appendf(&body, "\nUser editor: %s/setup_uedit?id=%d\n", zURL, uid); |
|
414
|
} |
|
415
|
nBody = blob_size(&body); |
|
416
|
pSender = alert_sender_new(0, 0); |
|
417
|
db_prepare(&q, |
|
418
|
"SELECT semail, hex(subscriberCode)" |
|
419
|
" FROM subscriber, user " |
|
420
|
" WHERE sverified AND NOT sdonotcall" |
|
421
|
" AND suname=login" |
|
422
|
" AND ssub GLOB '*u*'"); |
|
423
|
while( !pSender->zErr && db_step(&q)==SQLITE_ROW ){ |
|
424
|
const char *zTo = db_column_text(&q, 0); |
|
425
|
blob_truncate(&hdr, 0); |
|
426
|
blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", |
|
427
|
zTo, zSubname, zSubject); |
|
428
|
if( zURL ){ |
|
429
|
const char *zCode = db_column_text(&q, 1); |
|
430
|
blob_truncate(&body, nBody); |
|
431
|
blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
|
432
|
zURL, zCode); |
|
433
|
} |
|
434
|
alert_send(pSender, &hdr, &body, 0); |
|
435
|
} |
|
436
|
db_finalize(&q); |
|
437
|
alert_sender_free(pSender); |
|
438
|
fossil_free(zURL); |
|
439
|
fossil_free(zSubname); |
|
440
|
fossil_free(zSubject); |
|
441
|
} |
|
442
|
|
|
443
|
/* |
|
444
|
** WEBPAGE: setup_uedit |
|
445
|
** |
|
446
|
** Edit information about a user or create a new user. |
|
447
|
** Requires Admin privileges. |
|
448
|
*/ |
|
449
|
void user_edit(void){ |
|
450
|
const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
|
451
|
const char *zGroup; |
|
452
|
const char *zOldLogin; |
|
453
|
int uid, i; |
|
454
|
char *zOldCaps = 0; /* Capabilities before edit */ |
|
455
|
char *zDeleteVerify = 0; /* Delete user verification text */ |
|
456
|
int higherUser = 0; /* True if user being edited is SETUP and the */ |
|
457
|
/* user doing the editing is ADMIN. Disallow editing */ |
|
458
|
const char *inherit[128]; |
|
459
|
int a[128]; |
|
460
|
const char *oa[128]; |
|
461
|
|
|
462
|
/* Must have ADMIN privileges to access this page |
|
463
|
*/ |
|
464
|
login_check_credentials(); |
|
465
|
if( !g.perm.Admin ){ login_needed(0); return; } |
|
466
|
|
|
467
|
/* Check to see if an ADMIN user is trying to edit a SETUP account. |
|
468
|
** Don't allow that. |
|
469
|
*/ |
|
470
|
zId = PD("id", "0"); |
|
471
|
uid = atoi(zId); |
|
472
|
if( uid>0 ){ |
|
473
|
zOldCaps = db_text("", "SELECT cap FROM user WHERE uid=%d",uid); |
|
474
|
if( zId && !g.perm.Setup ){ |
|
475
|
higherUser = zOldCaps && strchr(zOldCaps,'s'); |
|
476
|
} |
|
477
|
} |
|
478
|
|
|
479
|
if( P("can") ){ |
|
480
|
/* User pressed the cancel button */ |
|
481
|
cgi_redirect(cgi_referer("setup_ulist")); |
|
482
|
return; |
|
483
|
} |
|
484
|
|
|
485
|
/* Check for requests to delete the user */ |
|
486
|
if( P("delete") && cgi_csrf_safe(2) ){ |
|
487
|
int n; |
|
488
|
if( P("verifydelete") ){ |
|
489
|
/* Verified delete user request */ |
|
490
|
db_unprotect(PROTECT_USER); |
|
491
|
if( alert_tables_exist() ){ |
|
492
|
/* Also delete any subscriptions associated with this user */ |
|
493
|
db_multi_exec("DELETE FROM subscriber WHERE suname=" |
|
494
|
"(SELECT login FROM user WHERE uid=%d)", uid); |
|
495
|
} |
|
496
|
db_multi_exec("DELETE FROM user WHERE uid=%d", uid); |
|
497
|
db_protect_pop(); |
|
498
|
moderation_disapprove_for_missing_users(); |
|
499
|
admin_log("Deleted user [%s] (uid %d).", |
|
500
|
PD("login","???")/*safe-for-%s*/, uid); |
|
501
|
cgi_redirect(cgi_referer("setup_ulist")); |
|
502
|
return; |
|
503
|
} |
|
504
|
n = db_int(0, "SELECT count(*) FROM event" |
|
505
|
" WHERE user=%Q AND objid NOT IN private", |
|
506
|
P("login")); |
|
507
|
if( n==0 ){ |
|
508
|
zDeleteVerify = mprintf("Check this box and press \"Delete User\" again"); |
|
509
|
}else{ |
|
510
|
zDeleteVerify = mprintf( |
|
511
|
"User \"%s\" has %d or more artifacts in the block-chain. " |
|
512
|
"Delete anyhow?", |
|
513
|
P("login")/*safe-for-%s*/, n); |
|
514
|
} |
|
515
|
} |
|
516
|
|
|
517
|
style_set_current_feature("setup"); |
|
518
|
|
|
519
|
/* If we have all the necessary information, write the new or |
|
520
|
** modified user record. After writing the user record, redirect |
|
521
|
** to the page that displays a list of users. |
|
522
|
*/ |
|
523
|
if( !cgi_all("login","info","pw","apply") ){ |
|
524
|
/* need all of the above properties to make a change. Since one or |
|
525
|
** more are missing, no-op */ |
|
526
|
}else if( higherUser ){ |
|
527
|
/* An Admin (a) user cannot edit a Superuser (s) */ |
|
528
|
}else if( zDeleteVerify!=0 ){ |
|
529
|
/* Need to verify a delete request */ |
|
530
|
}else if( !cgi_csrf_safe(2) ){ |
|
531
|
/* This might be a cross-site request forgery, so ignore it */ |
|
532
|
}else{ |
|
533
|
/* We have all the information we need to make the change to the user */ |
|
534
|
char c; |
|
535
|
int bCapsChanged = 0 /* 1 if user's permissions changed */; |
|
536
|
const int bIsNew = uid<=0; |
|
537
|
char aCap[70], zNm[4]; |
|
538
|
zNm[0] = 'a'; |
|
539
|
zNm[2] = 0; |
|
540
|
for(i=0, c='a'; c<='z'; c++){ |
|
541
|
zNm[1] = c; |
|
542
|
a[c&0x7f] = ((c!='s' && c!='y') || g.perm.Setup) && P(zNm)!=0; |
|
543
|
if( a[c&0x7f] ) aCap[i++] = c; |
|
544
|
} |
|
545
|
for(c='0'; c<='9'; c++){ |
|
546
|
zNm[1] = c; |
|
547
|
a[c&0x7f] = P(zNm)!=0; |
|
548
|
if( a[c&0x7f] ) aCap[i++] = c; |
|
549
|
} |
|
550
|
for(c='A'; c<='Z'; c++){ |
|
551
|
zNm[1] = c; |
|
552
|
a[c&0x7f] = P(zNm)!=0; |
|
553
|
if( a[c&0x7f] ) aCap[i++] = c; |
|
554
|
} |
|
555
|
|
|
556
|
aCap[i] = 0; |
|
557
|
bCapsChanged = bIsNew || userCapsChanged(zOldCaps, &aCap[0]); |
|
558
|
zPw = P("pw"); |
|
559
|
zLogin = P("login"); |
|
560
|
if( strlen(zLogin)==0 ){ |
|
561
|
const char *zRef = cgi_referer("setup_ulist"); |
|
562
|
style_header("User Creation Error"); |
|
563
|
@ <span class="loginError">Empty login not allowed.</span> |
|
564
|
@ |
|
565
|
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)"> |
|
566
|
@ [Bummer]</a></p> |
|
567
|
style_finish_page(); |
|
568
|
return; |
|
569
|
} |
|
570
|
if( isValidPwString(zPw) ){ |
|
571
|
zPw = sha1_shared_secret(zPw, zLogin, 0); |
|
572
|
}else{ |
|
573
|
zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); |
|
574
|
} |
|
575
|
zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); |
|
576
|
if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d",zLogin,uid) ){ |
|
577
|
const char *zRef = cgi_referer("setup_ulist"); |
|
578
|
style_header("User Creation Error"); |
|
579
|
@ <span class="loginError">Login "%h(zLogin)" is already used by |
|
580
|
@ a different user.</span> |
|
581
|
@ |
|
582
|
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)"> |
|
583
|
@ [Bummer]</a></p> |
|
584
|
style_finish_page(); |
|
585
|
return; |
|
586
|
} |
|
587
|
cgi_csrf_verify(); |
|
588
|
db_unprotect(PROTECT_USER); |
|
589
|
uid = db_int(0, |
|
590
|
"REPLACE INTO user(uid,login,info,pw,cap,mtime) " |
|
591
|
"VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now()) " |
|
592
|
"RETURNING uid", |
|
593
|
uid, zLogin, P("info"), zPw, &aCap[0]); |
|
594
|
assert( uid>0 ); |
|
595
|
if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){ |
|
596
|
if( alert_tables_exist() ){ |
|
597
|
/* Rename matching subscriber entry, else the user cannot |
|
598
|
re-subscribe with their same email address. */ |
|
599
|
db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q", |
|
600
|
zLogin, zOldLogin); |
|
601
|
} |
|
602
|
admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin ); |
|
603
|
} |
|
604
|
db_protect_pop(); |
|
605
|
setup_incr_cfgcnt(); |
|
606
|
admin_log( "%s user [%q] with capabilities [%q].", |
|
607
|
bIsNew ? "Added" : "Updated", |
|
608
|
zLogin, &aCap[0] ); |
|
609
|
if( atoi(PD("all","0"))>0 ){ |
|
610
|
Blob sql; |
|
611
|
char *zErr = 0; |
|
612
|
blob_zero(&sql); |
|
613
|
if( zOldLogin==0 ){ |
|
614
|
blob_appendf(&sql, |
|
615
|
"INSERT INTO user(login)" |
|
616
|
" SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", |
|
617
|
zLogin, zLogin |
|
618
|
); |
|
619
|
zOldLogin = zLogin; |
|
620
|
} |
|
621
|
#if 0 |
|
622
|
/* Problem: when renaming a user we need to update the subscriber |
|
623
|
** names to match but we cannot know from here if each member of |
|
624
|
** the login group has the subscriber tables, so we cannot blindly |
|
625
|
** include this SQL. */ |
|
626
|
else if( fossil_strcmp(zLogin, zOldLogin)!=0 |
|
627
|
&& alert_tables_exist() ){ |
|
628
|
/* Rename matching subscriber entry, else the user cannot |
|
629
|
re-subscribe with their same email address. */ |
|
630
|
blob_appendf(&sql, |
|
631
|
"UPDATE subscriber SET suname=%Q WHERE suname=%Q;", |
|
632
|
zLogin, zOldLogin); |
|
633
|
} |
|
634
|
#endif |
|
635
|
blob_appendf(&sql, |
|
636
|
"UPDATE user SET login=%Q," |
|
637
|
" pw=coalesce(shared_secret(%Q,%Q," |
|
638
|
"(SELECT value FROM config WHERE name='project-code')),pw)," |
|
639
|
" info=%Q," |
|
640
|
" cap=%Q," |
|
641
|
" mtime=now()" |
|
642
|
" WHERE login=%Q;", |
|
643
|
zLogin, P("pw"), zLogin, P("info"), &aCap[0], |
|
644
|
zOldLogin |
|
645
|
); |
|
646
|
db_unprotect(PROTECT_USER); |
|
647
|
login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr); |
|
648
|
db_protect_pop(); |
|
649
|
blob_reset(&sql); |
|
650
|
admin_log( "Updated user [%q] in all login groups " |
|
651
|
"with capabilities [%q].", |
|
652
|
zLogin, &aCap[0] ); |
|
653
|
if( zErr ){ |
|
654
|
const char *zRef = cgi_referer("setup_ulist"); |
|
655
|
style_header("User Change Error"); |
|
656
|
admin_log( "Error updating user '%q': %s'.", zLogin, zErr ); |
|
657
|
@ <span class="loginError">%h(zErr)</span> |
|
658
|
@ |
|
659
|
@ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)"> |
|
660
|
@ [Bummer]</a></p> |
|
661
|
style_finish_page(); |
|
662
|
if( bCapsChanged ){ |
|
663
|
/* It's possible that caps were updated locally even if |
|
664
|
** login group updates failed. */ |
|
665
|
alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]); |
|
666
|
} |
|
667
|
return; |
|
668
|
} |
|
669
|
} |
|
670
|
if( bCapsChanged ){ |
|
671
|
alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]); |
|
672
|
} |
|
673
|
cgi_redirect(cgi_referer("setup_ulist")); |
|
674
|
return; |
|
675
|
} |
|
676
|
|
|
677
|
/* Load the existing information about the user, if any |
|
678
|
*/ |
|
679
|
zLogin = ""; |
|
680
|
zInfo = ""; |
|
681
|
zCap = zOldCaps; |
|
682
|
zPw = ""; |
|
683
|
for(i='a'; i<='z'; i++) oa[i] = ""; |
|
684
|
for(i='0'; i<='9'; i++) oa[i] = ""; |
|
685
|
for(i='A'; i<='Z'; i++) oa[i] = ""; |
|
686
|
if( uid ){ |
|
687
|
assert( zCap ); |
|
688
|
zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
|
689
|
zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
|
690
|
zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
|
691
|
for(i=0; zCap[i]; i++){ |
|
692
|
char c = zCap[i]; |
|
693
|
if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){ |
|
694
|
oa[c&0x7f] = " checked=\"checked\""; |
|
695
|
} |
|
696
|
} |
|
697
|
} |
|
698
|
|
|
699
|
/* figure out inherited permissions */ |
|
700
|
memset((char *)inherit, 0, sizeof(inherit)); |
|
701
|
if( fossil_strcmp(zLogin, "developer") ){ |
|
702
|
char *z1, *z2; |
|
703
|
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); |
|
704
|
while( z1 && *z1 ){ |
|
705
|
inherit[0x7f & *(z1++)] = |
|
706
|
"<span class=\"ueditInheritDeveloper\"><sub>[D]</sub></span>"; |
|
707
|
} |
|
708
|
free(z2); |
|
709
|
} |
|
710
|
if( fossil_strcmp(zLogin, "reader") ){ |
|
711
|
char *z1, *z2; |
|
712
|
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); |
|
713
|
while( z1 && *z1 ){ |
|
714
|
inherit[0x7f & *(z1++)] = |
|
715
|
"<span class=\"ueditInheritReader\"><sub>[R]</sub></span>"; |
|
716
|
} |
|
717
|
free(z2); |
|
718
|
} |
|
719
|
if( fossil_strcmp(zLogin, "anonymous") ){ |
|
720
|
char *z1, *z2; |
|
721
|
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); |
|
722
|
while( z1 && *z1 ){ |
|
723
|
inherit[0x7f & *(z1++)] = |
|
724
|
"<span class=\"ueditInheritAnonymous\"><sub>[A]</sub></span>"; |
|
725
|
} |
|
726
|
free(z2); |
|
727
|
} |
|
728
|
if( fossil_strcmp(zLogin, "nobody") ){ |
|
729
|
char *z1, *z2; |
|
730
|
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); |
|
731
|
while( z1 && *z1 ){ |
|
732
|
inherit[0x7f & *(z1++)] = |
|
733
|
"<span class=\"ueditInheritNobody\"><sub>[N]</sub></span>"; |
|
734
|
} |
|
735
|
free(z2); |
|
736
|
} |
|
737
|
|
|
738
|
/* Begin generating the page |
|
739
|
*/ |
|
740
|
style_submenu_element("Cancel", "%s", cgi_referer("setup_ulist")); |
|
741
|
if( uid ){ |
|
742
|
style_header("Edit User %h", zLogin); |
|
743
|
if( !login_is_special(zLogin) ){ |
|
744
|
style_submenu_element("Access Log", "%R/access_log?u=%t", zLogin); |
|
745
|
style_submenu_element("Timeline","%R/timeline?u=%t", zLogin); |
|
746
|
} |
|
747
|
}else{ |
|
748
|
style_header("Add A New User"); |
|
749
|
} |
|
750
|
@ <div class="ueditCapBox"> |
|
751
|
@ <form action="%s(g.zPath)" method="post"><div> |
|
752
|
login_insert_csrf_secret(); |
|
753
|
if( login_is_special(zLogin) ){ |
|
754
|
@ <input type="hidden" name="login" value="%s(zLogin)"> |
|
755
|
@ <input type="hidden" name="info" value=""> |
|
756
|
@ <input type="hidden" name="pw" value="*"> |
|
757
|
} |
|
758
|
@ <input type="hidden" name="referer" value="%h(cgi_referer("setup_ulist"))"> |
|
759
|
@ <table width="100%%"> |
|
760
|
@ <tr> |
|
761
|
@ <td class="usetupEditLabel" id="suuid">User ID:</td> |
|
762
|
if( uid ){ |
|
763
|
@ <td>%d(uid) <input aria-labelledby="suuid" type="hidden" \ |
|
764
|
@ name="id" value="%d(uid)"/>\ |
|
765
|
@ </td> |
|
766
|
}else{ |
|
767
|
@ <td>(new user)<input aria-labelledby="suuid" type="hidden" name="id" \ |
|
768
|
@ value="0"></td> |
|
769
|
} |
|
770
|
@ </tr> |
|
771
|
@ <tr> |
|
772
|
@ <td class="usetupEditLabel" id="sulgn">Login:</td> |
|
773
|
if( login_is_special(zLogin) ){ |
|
774
|
@ <td><b>%h(zLogin)</b></td> |
|
775
|
}else{ |
|
776
|
@ <td><input aria-labelledby="sulgn" type="text" name="login" \ |
|
777
|
@ value="%h(zLogin)"> |
|
778
|
if( alert_tables_exist() ){ |
|
779
|
int sid; |
|
780
|
sid = db_int(0, "SELECT subscriberId FROM subscriber" |
|
781
|
" WHERE suname=%Q", zLogin); |
|
782
|
if( sid>0 ){ |
|
783
|
@ <a href="%R/alerts?sid=%d(sid)">\ |
|
784
|
@ (subscription info for %h(zLogin))</a>\ |
|
785
|
} |
|
786
|
} |
|
787
|
@ </td></tr> |
|
788
|
@ <tr> |
|
789
|
@ <td class="usetupEditLabel" id="sucnfo">Contact Info:</td> |
|
790
|
@ <td><textarea aria-labelledby="sucnfo" name="info" cols="40" \ |
|
791
|
@ rows="2">%h(zInfo)</textarea></td> |
|
792
|
} |
|
793
|
@ </tr> |
|
794
|
@ <tr> |
|
795
|
@ <td class="usetupEditLabel">Capabilities:</td> |
|
796
|
@ <td width="100%%"> |
|
797
|
#define B(x) inherit[x] |
|
798
|
@ <div class="columns" style="column-width:13em;"> |
|
799
|
@ <ul style="list-style-type: none;"> |
|
800
|
if( g.perm.Setup ){ |
|
801
|
@ <li><label><input type="checkbox" name="as"%s(oa['s'])> |
|
802
|
@ Setup%s(B('s'))</label> |
|
803
|
} |
|
804
|
@ <li><label><input type="checkbox" name="aa"%s(oa['a'])> |
|
805
|
@ Admin%s(B('a'))</label> |
|
806
|
@ <li><label><input type="checkbox" name="au"%s(oa['u'])> |
|
807
|
@ Reader%s(B('u'))</label> |
|
808
|
@ <li><label><input type="checkbox" name="av"%s(oa['v'])> |
|
809
|
@ Developer%s(B('v'))</label> |
|
810
|
#if 0 /* Not Used */ |
|
811
|
@ <li><label><input type="checkbox" name="ad"%s(oa['d'])> |
|
812
|
@ Delete%s(B('d'))</label> |
|
813
|
#endif |
|
814
|
@ <li><label><input type="checkbox" name="ae"%s(oa['e'])> |
|
815
|
@ View-PII%s(B('e'))</label> |
|
816
|
@ <li><label><input type="checkbox" name="ap"%s(oa['p'])> |
|
817
|
@ Password%s(B('p'))</label> |
|
818
|
@ <li><label><input type="checkbox" name="ai"%s(oa['i'])> |
|
819
|
@ Check-In%s(B('i'))</label> |
|
820
|
@ <li><label><input type="checkbox" name="ao"%s(oa['o'])> |
|
821
|
@ Check-Out%s(B('o'))</label> |
|
822
|
@ <li><label><input type="checkbox" name="ah"%s(oa['h'])> |
|
823
|
@ Hyperlinks%s(B('h'))</label> |
|
824
|
@ <li><label><input type="checkbox" name="ab"%s(oa['b'])> |
|
825
|
@ Attachments%s(B('b'))</label> |
|
826
|
@ <li><label><input type="checkbox" name="ag"%s(oa['g'])> |
|
827
|
@ Clone%s(B('g'))</label> |
|
828
|
@ <li><label><input type="checkbox" name="aj"%s(oa['j'])> |
|
829
|
@ Read Wiki%s(B('j'))</label> |
|
830
|
@ <li><label><input type="checkbox" name="af"%s(oa['f'])> |
|
831
|
@ New Wiki%s(B('f'))</label> |
|
832
|
@ <li><label><input type="checkbox" name="am"%s(oa['m'])> |
|
833
|
@ Append Wiki%s(B('m'))</label> |
|
834
|
@ <li><label><input type="checkbox" name="ak"%s(oa['k'])> |
|
835
|
@ Write Wiki%s(B('k'))</label> |
|
836
|
@ <li><label><input type="checkbox" name="al"%s(oa['l'])> |
|
837
|
@ Moderate Wiki%s(B('l'))</label> |
|
838
|
@ <li><label><input type="checkbox" name="ar"%s(oa['r'])> |
|
839
|
@ Read Ticket%s(B('r'))</label> |
|
840
|
@ <li><label><input type="checkbox" name="an"%s(oa['n'])> |
|
841
|
@ New Tickets%s(B('n'))</label> |
|
842
|
@ <li><label><input type="checkbox" name="ac"%s(oa['c'])> |
|
843
|
@ Append To Ticket%s(B('c'))</label> |
|
844
|
@ <li><label><input type="checkbox" name="aw"%s(oa['w'])> |
|
845
|
@ Write Tickets%s(B('w'))</label> |
|
846
|
@ <li><label><input type="checkbox" name="aq"%s(oa['q'])> |
|
847
|
@ Moderate Tickets%s(B('q'))</label> |
|
848
|
@ <li><label><input type="checkbox" name="at"%s(oa['t'])> |
|
849
|
@ Ticket Report%s(B('t'))</label> |
|
850
|
@ <li><label><input type="checkbox" name="ax"%s(oa['x'])> |
|
851
|
@ Private%s(B('x'))</label> |
|
852
|
@ <li><label><input type="checkbox" name="ay"%s(oa['y'])> |
|
853
|
@ Write Unversioned%s(B('y'))</label> |
|
854
|
@ <li><label><input type="checkbox" name="az"%s(oa['z'])> |
|
855
|
@ Download Zip%s(B('z'))</label> |
|
856
|
@ <li><label><input type="checkbox" name="a2"%s(oa['2'])> |
|
857
|
@ Read Forum%s(B('2'))</label> |
|
858
|
@ <li><label><input type="checkbox" name="a3"%s(oa['3'])> |
|
859
|
@ Write Forum%s(B('3'))</label> |
|
860
|
@ <li><label><input type="checkbox" name="a4"%s(oa['4'])> |
|
861
|
@ WriteTrusted Forum%s(B('4'))</label> |
|
862
|
@ <li><label><input type="checkbox" name="a5"%s(oa['5'])> |
|
863
|
@ Moderate Forum%s(B('5'))</label> |
|
864
|
@ <li><label><input type="checkbox" name="a6"%s(oa['6'])> |
|
865
|
@ Supervise Forum%s(B('6'))</label> |
|
866
|
@ <li><label><input type="checkbox" name="a7"%s(oa['7'])> |
|
867
|
@ Email Alerts%s(B('7'))</label> |
|
868
|
@ <li><label><input type="checkbox" name="aA"%s(oa['A'])> |
|
869
|
@ Send Announcements%s(B('A'))</label> |
|
870
|
@ <li><label><input type="checkbox" name="aC"%s(oa['C'])> |
|
871
|
@ Chatroom%s(B('C'))</label> |
|
872
|
@ <li><label><input type="checkbox" name="aD"%s(oa['D'])> |
|
873
|
@ Enable Debug%s(B('D'))</label> |
|
874
|
@ </ul></div> |
|
875
|
@ </td> |
|
876
|
@ </tr> |
|
877
|
@ <tr> |
|
878
|
@ <td class="usetupEditLabel">Selected Cap:</td> |
|
879
|
@ <td> |
|
880
|
@ <span id="usetupEditCapability">(missing JS?)</span> |
|
881
|
@ <a href="%R/setup_ucap_list">(key)</a> |
|
882
|
@ </td> |
|
883
|
@ </tr> |
|
884
|
if( !login_is_special(zLogin) ){ |
|
885
|
@ <tr> |
|
886
|
@ <td align="right" id="supw">Password:</td> |
|
887
|
if( zPw[0] ){ |
|
888
|
/* Obscure the password for all users */ |
|
889
|
@ <td><input aria-labelledby="supw" type="password" autocomplete="off" \ |
|
890
|
@ name="pw" value="**********"> |
|
891
|
@ (Leave unchanged to retain password)</td> |
|
892
|
}else{ |
|
893
|
/* Show an empty password as an empty input field */ |
|
894
|
char *zRPW = fossil_random_password(12); |
|
895
|
@ <td><input aria-labelledby="supw" type="password" name="pw" \ |
|
896
|
@ autocomplete="off" value=""> Password suggestion: %z(zRPW)</td> |
|
897
|
} |
|
898
|
@ </tr> |
|
899
|
} |
|
900
|
zGroup = login_group_name(); |
|
901
|
if( zGroup ){ |
|
902
|
@ <tr> |
|
903
|
@ <td valign="top" align="right">Scope:</td> |
|
904
|
@ <td valign="top"> |
|
905
|
@ <input type="radio" name="all" checked value="0"> |
|
906
|
@ Apply changes to this repository only.<br> |
|
907
|
@ <input type="radio" name="all" value="1"> |
|
908
|
@ Apply changes to all repositories in the "<b>%h(zGroup)</b>" |
|
909
|
@ login group.</td></tr> |
|
910
|
} |
|
911
|
if( !higherUser ){ |
|
912
|
if( zDeleteVerify ){ |
|
913
|
@ <tr> |
|
914
|
@ <td valign="top" align="right">Verify:</td> |
|
915
|
@ <td><label><input type="checkbox" name="verifydelete">\ |
|
916
|
@ Confirm Delete \ |
|
917
|
@ <span class="loginError">← %h(zDeleteVerify)</span> |
|
918
|
@ </label></td> |
|
919
|
@ <tr> |
|
920
|
} |
|
921
|
@ <tr> |
|
922
|
@ <td> </td> |
|
923
|
@ <td><input type="submit" name="apply" value="Apply Changes"> |
|
924
|
if( !login_is_special(zLogin) ){ |
|
925
|
@ <input type="submit" name="delete" value="Delete User"> |
|
926
|
} |
|
927
|
@ <input type="submit" name="can" value="Cancel"></td> |
|
928
|
@ </tr> |
|
929
|
} |
|
930
|
@ </table> |
|
931
|
@ </div></form> |
|
932
|
@ </div> |
|
933
|
builtin_request_js("useredit.js"); |
|
934
|
@ <hr> |
|
935
|
@ <h1>Notes On Privileges And Capabilities:</h1> |
|
936
|
@ <ul> |
|
937
|
if( higherUser ){ |
|
938
|
@ <li><p class="missingPriv"> |
|
939
|
@ User %h(zLogin) has Setup privileges and you only have Admin privileges |
|
940
|
@ so you are not permitted to make changes to %h(zLogin). |
|
941
|
@ </p></li> |
|
942
|
@ |
|
943
|
} |
|
944
|
@ <li><p> |
|
945
|
@ The <span class="capability">Setup</span> user can make arbitrary |
|
946
|
@ configuration changes. An <span class="usertype">Admin</span> user |
|
947
|
@ can add other users and change user privileges |
|
948
|
@ and reset user passwords. Both automatically get all other privileges |
|
949
|
@ listed below. Use these two settings with discretion. |
|
950
|
@ </p></li> |
|
951
|
@ |
|
952
|
@ <li><p> |
|
953
|
@ The "<span class="ueditInheritNobody"><sub>N</sub></span>" subscript suffix |
|
954
|
@ indicates the privileges of <span class="usertype">nobody</span> that |
|
955
|
@ are available to all users regardless of whether or not they are logged in. |
|
956
|
@ </p></li> |
|
957
|
@ |
|
958
|
@ <li><p> |
|
959
|
@ The "<span class="ueditInheritAnonymous"><sub>A</sub></span>" |
|
960
|
@ subscript suffix |
|
961
|
@ indicates the privileges of <span class="usertype">anonymous</span> that |
|
962
|
@ are inherited by all logged-in users. |
|
963
|
@ </p></li> |
|
964
|
@ |
|
965
|
@ <li><p> |
|
966
|
@ The "<span class="ueditInheritDeveloper"><sub>D</sub></span>" |
|
967
|
@ subscript suffix indicates the privileges of |
|
968
|
@ <span class="usertype">developer</span> that |
|
969
|
@ are inherited by all users with the |
|
970
|
@ <span class="capability">Developer</span> privilege. |
|
971
|
@ </p></li> |
|
972
|
@ |
|
973
|
@ <li><p> |
|
974
|
@ The "<span class="ueditInheritReader"><sub>R</sub></span>" subscript suffix |
|
975
|
@ indicates the privileges of <span class="usertype">reader</span> that |
|
976
|
@ are inherited by all users with the <span class="capability">Reader</span> |
|
977
|
@ privilege. |
|
978
|
@ </p></li> |
|
979
|
@ |
|
980
|
@ <li><p> |
|
981
|
@ The <span class="capability">Delete</span> privilege give the user the |
|
982
|
@ ability to erase wiki, tickets, and attachments that have been added |
|
983
|
@ by anonymous users. This capability is intended for deletion of spam. |
|
984
|
@ The delete capability is only in effect for 24 hours after the item |
|
985
|
@ is first posted. The <span class="usertype">Setup</span> user can |
|
986
|
@ delete anything at any time. |
|
987
|
@ </p></li> |
|
988
|
@ |
|
989
|
@ <li><p> |
|
990
|
@ The <span class="capability">Hyperlinks</span> privilege allows a user |
|
991
|
@ to see most hyperlinks. This is recommended ON for most logged-in users |
|
992
|
@ but OFF for user "nobody" to avoid problems with spiders trying to walk |
|
993
|
@ every diff and annotation of every historical check-in and file. |
|
994
|
@ </p></li> |
|
995
|
@ |
|
996
|
@ <li><p> |
|
997
|
@ The <span class="capability">Zip</span> privilege allows a user to |
|
998
|
@ see the "download as ZIP" |
|
999
|
@ hyperlink and permits access to the <tt>/zip</tt> page. This allows |
|
1000
|
@ users to download ZIP archives without granting other rights like |
|
1001
|
@ <span class="capability">Read</span> or |
|
1002
|
@ <span class="capability">Hyperlink</span>. The "z" privilege is recommended |
|
1003
|
@ for user <span class="usertype">nobody</span> so that automatic package |
|
1004
|
@ downloaders can obtain the sources without going through the login |
|
1005
|
@ procedure. |
|
1006
|
@ </p></li> |
|
1007
|
@ |
|
1008
|
@ <li><p> |
|
1009
|
@ The <span class="capability">Check-in</span> privilege allows remote |
|
1010
|
@ users to "push". The <span class="capability">Check-out</span> privilege |
|
1011
|
@ allows remote users to "pull". The <span class="capability">Clone</span> |
|
1012
|
@ privilege allows remote users to "clone". |
|
1013
|
@ </p></li> |
|
1014
|
@ |
|
1015
|
@ <li><p> |
|
1016
|
@ The <span class="capability">Read Wiki</span>, |
|
1017
|
@ <span class="capability">New Wiki</span>, |
|
1018
|
@ <span class="capability">Append Wiki</span>, and |
|
1019
|
@ <b>Write Wiki</b> privileges control access to wiki pages. The |
|
1020
|
@ <span class="capability">Read Ticket</span>, |
|
1021
|
@ <span class="capability">New Ticket</span>, |
|
1022
|
@ <span class="capability">Append Ticket</span>, and |
|
1023
|
@ <span class="capability">Write Ticket</span> privileges control access |
|
1024
|
@ to trouble tickets. |
|
1025
|
@ The <span class="capability">Ticket Report</span> privilege allows |
|
1026
|
@ the user to create or edit ticket report formats. |
|
1027
|
@ </p></li> |
|
1028
|
@ |
|
1029
|
@ <li><p> |
|
1030
|
@ Users with the <span class="capability">Password</span> privilege |
|
1031
|
@ are allowed to change their own password. Recommended ON for most |
|
1032
|
@ users but OFF for special users <span class="usertype">developer</span>, |
|
1033
|
@ <span class="usertype">anonymous</span>, |
|
1034
|
@ and <span class="usertype">nobody</span>. |
|
1035
|
@ </p></li> |
|
1036
|
@ |
|
1037
|
@ <li><p> |
|
1038
|
@ The <span class="capability">View-PII</span> privilege allows the display |
|
1039
|
@ of personally-identifiable information information such as the |
|
1040
|
@ email address of users and contact |
|
1041
|
@ information on tickets. Recommended OFF for |
|
1042
|
@ <span class="usertype">anonymous</span> and for |
|
1043
|
@ <span class="usertype">nobody</span> but ON for |
|
1044
|
@ <span class="usertype">developer</span>. |
|
1045
|
@ </p></li> |
|
1046
|
@ |
|
1047
|
@ <li><p> |
|
1048
|
@ The <span class="capability">Attachment</span> privilege is needed in |
|
1049
|
@ order to add attachments to tickets or wiki. Write privilege on the |
|
1050
|
@ ticket or wiki is also required. |
|
1051
|
@ </p></li> |
|
1052
|
@ |
|
1053
|
@ <li><p> |
|
1054
|
@ Login is prohibited if the password is an empty string. |
|
1055
|
@ </p></li> |
|
1056
|
@ </ul> |
|
1057
|
@ |
|
1058
|
@ <h2>Special Logins</h2> |
|
1059
|
@ |
|
1060
|
@ <ul> |
|
1061
|
@ <li><p> |
|
1062
|
@ No login is required for user <span class="usertype">nobody</span>. The |
|
1063
|
@ capabilities of the <span class="usertype">nobody</span> user are |
|
1064
|
@ inherited by all users, regardless of whether or not they are logged in. |
|
1065
|
@ To disable universal access to the repository, make sure that the |
|
1066
|
@ <span class="usertype">nobody</span> user has no capabilities |
|
1067
|
@ enabled. The password for <span class="usertype">nobody</span> is ignored. |
|
1068
|
@ </p></li> |
|
1069
|
@ |
|
1070
|
@ <li><p> |
|
1071
|
@ Login is required for user <span class="usertype">anonymous</span> but the |
|
1072
|
@ password is displayed on the login screen beside the password entry box |
|
1073
|
@ so anybody who can read should be able to login as anonymous. |
|
1074
|
@ On the other hand, spiders and web-crawlers will typically not |
|
1075
|
@ be able to login. Set the capabilities of the |
|
1076
|
@ <span class="usertype">anonymous</span> |
|
1077
|
@ user to things that you want any human to be able to do, but not any |
|
1078
|
@ spider. Every other logged-in user inherits the privileges of |
|
1079
|
@ <span class="usertype">anonymous</span>. |
|
1080
|
@ </p></li> |
|
1081
|
@ |
|
1082
|
@ <li><p> |
|
1083
|
@ The <span class="usertype">developer</span> user is intended as a template |
|
1084
|
@ for trusted users with check-in privileges. When adding new trusted users, |
|
1085
|
@ simply select the <span class="capability">developer</span> privilege to |
|
1086
|
@ cause the new user to inherit all privileges of the |
|
1087
|
@ <span class="usertype">developer</span> |
|
1088
|
@ user. Similarly, the <span class="usertype">reader</span> user is a |
|
1089
|
@ template for users who are allowed more access than |
|
1090
|
@ <span class="usertype">anonymous</span>, |
|
1091
|
@ but less than a <span class="usertype">developer</span>. |
|
1092
|
@ </p></li> |
|
1093
|
@ </ul> |
|
1094
|
style_finish_page(); |
|
1095
|
} |
|
1096
|
|
|
1097
|
/* |
|
1098
|
** WEBPAGE: setup_uinfo |
|
1099
|
** |
|
1100
|
** Detailed information about a user account, available to administrators |
|
1101
|
** only. |
|
1102
|
** |
|
1103
|
** u=UID |
|
1104
|
** l=LOGIN |
|
1105
|
*/ |
|
1106
|
void setup_uinfo_page(void){ |
|
1107
|
Stmt q; |
|
1108
|
Blob sql; |
|
1109
|
const char *zLogin; |
|
1110
|
int uid; |
|
1111
|
|
|
1112
|
/* Must have ADMIN privileges to access this page |
|
1113
|
*/ |
|
1114
|
login_check_credentials(); |
|
1115
|
if( !g.perm.Admin ){ login_needed(0); return; } |
|
1116
|
style_set_current_feature("setup"); |
|
1117
|
zLogin = P("l"); |
|
1118
|
uid = atoi(PD("u","0")); |
|
1119
|
if( zLogin==0 && uid==0 ){ |
|
1120
|
uid = db_int(1,"SELECT uid FROM user"); |
|
1121
|
} |
|
1122
|
blob_init(&sql, 0, 0); |
|
1123
|
blob_append_sql(&sql, |
|
1124
|
"SELECT " |
|
1125
|
/* 0 */ "uid," |
|
1126
|
/* 1 */ "login," |
|
1127
|
/* 2 */ "cap," |
|
1128
|
/* 3 */ "cookie," |
|
1129
|
/* 4 */ "datetime(cexpire)," |
|
1130
|
/* 5 */ "info," |
|
1131
|
/* 6 */ "datetime(user.mtime,'unixepoch')," |
|
1132
|
); |
|
1133
|
if( db_table_exists("repository","subscriber") ){ |
|
1134
|
blob_append_sql(&sql, |
|
1135
|
/* 7 */ "subscriberId," |
|
1136
|
/* 8 */ "semail," |
|
1137
|
/* 9 */ "sverified," |
|
1138
|
/* 10 */ "date(lastContact+2440587.5)" |
|
1139
|
" FROM user LEFT JOIN subscriber ON suname=login" |
|
1140
|
); |
|
1141
|
}else{ |
|
1142
|
blob_append_sql(&sql, |
|
1143
|
/* 7 */ "NULL," |
|
1144
|
/* 8 */ "NULL," |
|
1145
|
/* 9 */ "NULL," |
|
1146
|
/* 10 */ "NULL" |
|
1147
|
" FROM user" |
|
1148
|
); |
|
1149
|
} |
|
1150
|
if( zLogin!=0 ){ |
|
1151
|
blob_append_sql(&sql, " WHERE login=%Q", zLogin); |
|
1152
|
}else{ |
|
1153
|
blob_append_sql(&sql, " WHERE uid=%d", uid); |
|
1154
|
} |
|
1155
|
db_prepare(&q, "%s", blob_sql_text(&sql)); |
|
1156
|
blob_zero(&sql); |
|
1157
|
if( db_step(&q)!=SQLITE_ROW ){ |
|
1158
|
style_header("No Such User"); |
|
1159
|
if( zLogin ){ |
|
1160
|
@ <p>Cannot find any information on user %h(zLogin). |
|
1161
|
}else{ |
|
1162
|
@ <p>Cannot find any information on userid %d(uid). |
|
1163
|
} |
|
1164
|
style_finish_page(); |
|
1165
|
db_finalize(&q); |
|
1166
|
return; |
|
1167
|
} |
|
1168
|
style_header("User %h", db_column_text(&q,1)); |
|
1169
|
@ <table class="label-value"> |
|
1170
|
@ <tr><th>uid:</th><td>%d(db_column_int(&q,0)) |
|
1171
|
@ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr> |
|
1172
|
@ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr> |
|
1173
|
@ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr> |
|
1174
|
@ <tr><th valign="top">info:</th> |
|
1175
|
@ <td valign="top"><span style='white-space:pre-line;'>\ |
|
1176
|
@ %h(db_column_text(&q,5))</span></td></tr> |
|
1177
|
@ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr> |
|
1178
|
if( db_column_type(&q,7)!=SQLITE_NULL ){ |
|
1179
|
@ <tr><th>subscriberId:</th><td>%d(db_column_int(&q,7)) |
|
1180
|
@ (<a href="%R/alerts?sid=%d(db_column_int(&q,7))">edit</a>)</td></tr> |
|
1181
|
@ <tr><th>semail:</th><td>%h(db_column_text(&q,8))</td></tr> |
|
1182
|
@ <tr><th>verified:</th><td>%s(db_column_int(&q,9)?"yes":"no")</td></th> |
|
1183
|
@ <tr><th>lastContact:</th><td>%h(db_column_text(&q,10))</td></tr> |
|
1184
|
} |
|
1185
|
@ </table> |
|
1186
|
db_finalize(&q); |
|
1187
|
style_finish_page(); |
|
1188
|
} |
|
1189
|
|