| | @@ -302,10 +302,101 @@ |
| 302 | 302 | if( zPw==0 ) return 0; |
| 303 | 303 | if( zPw[0]==0 ) return 1; |
| 304 | 304 | while( zPw[0]=='*' ){ zPw++; } |
| 305 | 305 | return zPw[0]!=0; |
| 306 | 306 | } |
| 307 | + |
| 308 | +/* |
| 309 | +** Return true if user capability string zNew contains any capability |
| 310 | +** letter which is not in user capability string zOrig, else 0. This |
| 311 | +** does not take inherited permissions into account. Either argument |
| 312 | +** may be NULL. |
| 313 | +*/ |
| 314 | +static int userHasNewCaps(const char *zOrig, const char *zNew){ |
| 315 | + for( ; zNew && *zNew; ++zNew ){ |
| 316 | + if( !zOrig || strchr(zOrig,*zNew)==0 ){ |
| 317 | + return *zNew; |
| 318 | + } |
| 319 | + } |
| 320 | + return 0; |
| 321 | +} |
| 322 | + |
| 323 | +/* |
| 324 | +** Sends notification of user permission elevation changes to all |
| 325 | +** subscribers with a "u" subscription. This is a no-op if alerts are |
| 326 | +** not enabled. |
| 327 | +** |
| 328 | +** These subscriptions differ from most, in that: |
| 329 | +** |
| 330 | +** - They currently lack an "unsubscribe" link. |
| 331 | +** |
| 332 | +** - Only an admin can assign this subscription, but if a non-admin |
| 333 | +** edits their subscriptions after an admin assigns them this one, |
| 334 | +** this particular one will be lost. "Feature or bug?" is unclear, |
| 335 | +** but it would be odd for a non-admin to be assigned this |
| 336 | +** capability. |
| 337 | +*/ |
| 338 | +static void alert_user_elevation(const char *zLogin, /*Affected user*/ |
| 339 | + int uid, /*[user].uid*/ |
| 340 | + int bIsNew, /*true if new user*/ |
| 341 | + const char *zOrigCaps,/*Old caps*/ |
| 342 | + const char *zNewCaps /*New caps*/){ |
| 343 | + Blob hdr, body; |
| 344 | + Stmt q; |
| 345 | + int nBody; |
| 346 | + AlertSender *pSender; |
| 347 | + char *zSubname; |
| 348 | + char *zURL; |
| 349 | + char * zSubject; |
| 350 | + |
| 351 | + if( !alert_enabled() ) return; |
| 352 | + zSubject = bIsNew |
| 353 | + ? mprintf("New user created: [%q]", zLogin) |
| 354 | + : mprintf("User [%q] permissions elevated", zLogin); |
| 355 | + zURL = db_get("email-url",0); |
| 356 | + zSubname = db_get("email-subname", "[Fossil Repo]"); |
| 357 | + blob_init(&body, 0, 0); |
| 358 | + blob_init(&hdr, 0, 0); |
| 359 | + if( bIsNew ){ |
| 360 | + blob_appendf(&body, "User [%q] was created by with " |
| 361 | + "permissions [%q] by user [%q].\n", |
| 362 | + zLogin, zNewCaps, g.zLogin); |
| 363 | + } else { |
| 364 | + blob_appendf(&body, "Permissions for user [%q] where elevated " |
| 365 | + "from [%q] to [%q] by user [%q].\n", |
| 366 | + zLogin, zOrigCaps, zNewCaps, g.zLogin); |
| 367 | + } |
| 368 | + if( zURL ){ |
| 369 | + blob_appendf(&body, "\nUser editor: %s/setup_uedit?uid=%d\n", zURL, uid); |
| 370 | + } |
| 371 | + nBody = blob_size(&body); |
| 372 | + pSender = alert_sender_new(0, 0); |
| 373 | + db_prepare(&q, |
| 374 | + "SELECT semail, hex(subscriberCode)" |
| 375 | + " FROM subscriber, user " |
| 376 | + " WHERE sverified AND NOT sdonotcall" |
| 377 | + " AND suname=login" |
| 378 | + " AND ssub GLOB '*u*'"); |
| 379 | + while( !pSender->zErr && db_step(&q)==SQLITE_ROW ){ |
| 380 | + const char *zTo = db_column_text(&q, 0); |
| 381 | + blob_truncate(&hdr, 0); |
| 382 | + blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", |
| 383 | + zTo, zSubname, zSubject); |
| 384 | + if( zURL ){ |
| 385 | + const char *zCode = db_column_text(&q, 1); |
| 386 | + blob_truncate(&body, nBody); |
| 387 | + blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", |
| 388 | + zURL, zCode); |
| 389 | + } |
| 390 | + alert_send(pSender, &hdr, &body, 0); |
| 391 | + } |
| 392 | + db_finalize(&q); |
| 393 | + alert_sender_free(pSender); |
| 394 | + fossil_free(zURL); |
| 395 | + fossil_free(zSubname); |
| 396 | + fossil_free(zSubject); |
| 397 | +} |
| 307 | 398 | |
| 308 | 399 | /* |
| 309 | 400 | ** WEBPAGE: setup_uedit |
| 310 | 401 | ** |
| 311 | 402 | ** Edit information about a user or create a new user. |
| | @@ -314,10 +405,11 @@ |
| 314 | 405 | void user_edit(void){ |
| 315 | 406 | const char *zId, *zLogin, *zInfo, *zCap, *zPw; |
| 316 | 407 | const char *zGroup; |
| 317 | 408 | const char *zOldLogin; |
| 318 | 409 | int uid, i; |
| 410 | + char *zOldCaps = 0; /* Capabilities before edit */ |
| 319 | 411 | char *zDeleteVerify = 0; /* Delete user verification text */ |
| 320 | 412 | int higherUser = 0; /* True if user being edited is SETUP and the */ |
| 321 | 413 | /* user doing the editing is ADMIN. Disallow editing */ |
| 322 | 414 | const char *inherit[128]; |
| 323 | 415 | int a[128]; |
| | @@ -331,14 +423,15 @@ |
| 331 | 423 | /* Check to see if an ADMIN user is trying to edit a SETUP account. |
| 332 | 424 | ** Don't allow that. |
| 333 | 425 | */ |
| 334 | 426 | zId = PD("id", "0"); |
| 335 | 427 | uid = atoi(zId); |
| 336 | | - if( zId && !g.perm.Setup && uid>0 ){ |
| 337 | | - char *zOldCaps; |
| 338 | | - zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); |
| 339 | | - higherUser = zOldCaps && strchr(zOldCaps,'s'); |
| 428 | + if( uid>0 ){ |
| 429 | + zOldCaps = db_text("", "SELECT cap FROM user WHERE uid=%d",uid); |
| 430 | + if( zId && !g.perm.Setup ){ |
| 431 | + higherUser = zOldCaps && strchr(zOldCaps,'s'); |
| 432 | + } |
| 340 | 433 | } |
| 341 | 434 | |
| 342 | 435 | if( P("can") ){ |
| 343 | 436 | /* User pressed the cancel button */ |
| 344 | 437 | cgi_redirect(cgi_referer("setup_ulist")); |
| | @@ -393,10 +486,12 @@ |
| 393 | 486 | }else if( !cgi_csrf_safe(2) ){ |
| 394 | 487 | /* This might be a cross-site request forgery, so ignore it */ |
| 395 | 488 | }else{ |
| 396 | 489 | /* We have all the information we need to make the change to the user */ |
| 397 | 490 | char c; |
| 491 | + int bHasNewCaps = 0 /* 1 if user's permissions are increased */; |
| 492 | + const int bIsNew = uid<=0; |
| 398 | 493 | char aCap[70], zNm[4]; |
| 399 | 494 | zNm[0] = 'a'; |
| 400 | 495 | zNm[2] = 0; |
| 401 | 496 | for(i=0, c='a'; c<='z'; c++){ |
| 402 | 497 | zNm[1] = c; |
| | @@ -413,10 +508,11 @@ |
| 413 | 508 | a[c&0x7f] = P(zNm)!=0; |
| 414 | 509 | if( a[c&0x7f] ) aCap[i++] = c; |
| 415 | 510 | } |
| 416 | 511 | |
| 417 | 512 | aCap[i] = 0; |
| 513 | + bHasNewCaps = bIsNew || userHasNewCaps(zOldCaps, &aCap[0]); |
| 418 | 514 | zPw = P("pw"); |
| 419 | 515 | zLogin = P("login"); |
| 420 | 516 | if( strlen(zLogin)==0 ){ |
| 421 | 517 | const char *zRef = cgi_referer("setup_ulist"); |
| 422 | 518 | style_header("User Creation Error"); |
| | @@ -444,15 +540,16 @@ |
| 444 | 540 | style_finish_page(); |
| 445 | 541 | return; |
| 446 | 542 | } |
| 447 | 543 | cgi_csrf_verify(); |
| 448 | 544 | db_unprotect(PROTECT_USER); |
| 449 | | - db_multi_exec( |
| 450 | | - "REPLACE INTO user(uid,login,info,pw,cap,mtime) " |
| 451 | | - "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", |
| 452 | | - uid, zLogin, P("info"), zPw, &aCap[0] |
| 453 | | - ); |
| 545 | + uid = db_int(0, |
| 546 | + "REPLACE INTO user(uid,login,info,pw,cap,mtime) " |
| 547 | + "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now()) " |
| 548 | + "RETURNING uid", |
| 549 | + uid, zLogin, P("info"), zPw, &aCap[0]); |
| 550 | + assert( uid>0 ); |
| 454 | 551 | if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){ |
| 455 | 552 | if( alert_tables_exist() ){ |
| 456 | 553 | /* Rename matching subscriber entry, else the user cannot |
| 457 | 554 | re-subscribe with their same email address. */ |
| 458 | 555 | db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q", |
| | @@ -460,11 +557,12 @@ |
| 460 | 557 | } |
| 461 | 558 | admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin ); |
| 462 | 559 | } |
| 463 | 560 | db_protect_pop(); |
| 464 | 561 | setup_incr_cfgcnt(); |
| 465 | | - admin_log( "Updated user [%q] with capabilities [%q].", |
| 562 | + admin_log( "%s user [%q] with capabilities [%q].", |
| 563 | + bIsNew ? "Added" : "Updated", |
| 466 | 564 | zLogin, &aCap[0] ); |
| 467 | 565 | if( atoi(PD("all","0"))>0 ){ |
| 468 | 566 | Blob sql; |
| 469 | 567 | char *zErr = 0; |
| 470 | 568 | blob_zero(&sql); |
| | @@ -515,30 +613,36 @@ |
| 515 | 613 | @ <span class="loginError">%h(zErr)</span> |
| 516 | 614 | @ |
| 517 | 615 | @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)"> |
| 518 | 616 | @ [Bummer]</a></p> |
| 519 | 617 | style_finish_page(); |
| 618 | + if( bHasNewCaps ){ |
| 619 | + alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]); |
| 620 | + } |
| 520 | 621 | return; |
| 521 | 622 | } |
| 522 | 623 | } |
| 624 | + if( bHasNewCaps ){ |
| 625 | + alert_user_elevation(zLogin, uid, bIsNew, zOldCaps, &aCap[0]); |
| 626 | + } |
| 523 | 627 | cgi_redirect(cgi_referer("setup_ulist")); |
| 524 | 628 | return; |
| 525 | 629 | } |
| 526 | 630 | |
| 527 | 631 | /* Load the existing information about the user, if any |
| 528 | 632 | */ |
| 529 | 633 | zLogin = ""; |
| 530 | 634 | zInfo = ""; |
| 531 | | - zCap = ""; |
| 635 | + zCap = zOldCaps; |
| 532 | 636 | zPw = ""; |
| 533 | 637 | for(i='a'; i<='z'; i++) oa[i] = ""; |
| 534 | 638 | for(i='0'; i<='9'; i++) oa[i] = ""; |
| 535 | 639 | for(i='A'; i<='Z'; i++) oa[i] = ""; |
| 536 | 640 | if( uid ){ |
| 641 | + assert( zCap ); |
| 537 | 642 | zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); |
| 538 | 643 | zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); |
| 539 | | - zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); |
| 540 | 644 | zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); |
| 541 | 645 | for(i=0; zCap[i]; i++){ |
| 542 | 646 | char c = zCap[i]; |
| 543 | 647 | if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){ |
| 544 | 648 | oa[c&0x7f] = " checked=\"checked\""; |
| 545 | 649 | |