| | @@ -19,10 +19,15 @@ |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include "email.h" |
| 22 | 22 | #include <assert.h> |
| 23 | 23 | |
| 24 | +/* |
| 25 | +** Maximum size of the subscriberCode blob, in bytes |
| 26 | +*/ |
| 27 | +#define SUBSCRIBER_CODE_SZ 32 |
| 28 | + |
| 24 | 29 | /* |
| 25 | 30 | ** SQL code to implement the tables needed by the email notification |
| 26 | 31 | ** system. |
| 27 | 32 | */ |
| 28 | 33 | static const char zEmailInit[] = |
| | @@ -42,23 +47,23 @@ |
| 42 | 47 | @ -- we might also add a separate table that allows subscribing to email |
| 43 | 48 | @ -- notifications for specific branches or tags or tickets. |
| 44 | 49 | @ -- |
| 45 | 50 | @ CREATE TABLE repository.subscriber( |
| 46 | 51 | @ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use |
| 47 | | -@ subscriberCode TEXT UNIQUE, -- UUID for subscriber. External use |
| 48 | | -@ sname TEXT, -- Human readable name |
| 49 | | -@ suname TEXT, -- Corresponding USER or NULL |
| 52 | +@ subscriberCode BLOB UNIQUE, -- UUID for subscriber. External use |
| 50 | 53 | @ semail TEXT, -- email address |
| 54 | +@ suname TEXT, -- corresponding USER entry |
| 51 | 55 | @ sverify BOOLEAN, -- email address verified |
| 52 | 56 | @ sdonotcall BOOLEAN, -- true for Do Not Call |
| 53 | 57 | @ sdigest BOOLEAN, -- true for daily digests only |
| 54 | 58 | @ ssub TEXT, -- baseline subscriptions |
| 55 | 59 | @ sctime DATE, -- When this entry was created. JulianDay |
| 56 | 60 | @ smtime DATE, -- Last change. JulianDay |
| 57 | | -@ sipaddr TEXT, -- IP address for last change |
| 58 | | -@ spswdHash TEXT -- SHA3 hash of password |
| 61 | +@ smip TEXT -- IP address of last change |
| 59 | 62 | @ ); |
| 63 | +@ CREATE INDEX repository.subscriberUname |
| 64 | +@ ON subscriber(suname) WHERE suname IS NOT NULL; |
| 60 | 65 | @ |
| 61 | 66 | @ -- Email notifications that need to be sent. |
| 62 | 67 | @ -- |
| 63 | 68 | @ -- If the eventid key is an integer, then it corresponds to the |
| 64 | 69 | @ -- EVENT.OBJID table. Other kinds of eventids are reserved for |
| | @@ -454,19 +459,24 @@ |
| 454 | 459 | |
| 455 | 460 | login_check_credentials(); |
| 456 | 461 | if( !g.perm.EmailAlert ){ |
| 457 | 462 | login_needed(g.anon.EmailAlert); |
| 458 | 463 | return; |
| 464 | + } |
| 465 | + if( login_is_individual() |
| 466 | + && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin) |
| 467 | + ){ |
| 468 | + cgi_redirect("%R/alerts"); |
| 469 | + return; |
| 459 | 470 | } |
| 460 | 471 | style_header("Email Subscription"); |
| 461 | | - needCaptcha = P("usecaptcha")!=0 || login_is_nobody() |
| 462 | | - || login_is_special(g.zLogin); |
| 472 | + needCaptcha = P("usecaptcha")!=0 || !login_is_individual(); |
| 463 | 473 | form_begin(0, "%R/subscribe"); |
| 464 | 474 | @ <table class="subscribe"> |
| 465 | 475 | @ <tr> |
| 466 | 476 | @ <td class="form_label">Email Address:</td> |
| 467 | | - @ <td><input type="text" name="e" value="" size="30"></td> |
| 477 | + @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td> |
| 468 | 478 | @ <td></td> |
| 469 | 479 | @ </tr> |
| 470 | 480 | if( needCaptcha ){ |
| 471 | 481 | uSeed = captcha_seed(); |
| 472 | 482 | zDecoded = captcha_decode(uSeed); |
| | @@ -474,34 +484,31 @@ |
| 474 | 484 | @ <tr> |
| 475 | 485 | @ <td class="form_label">Security Code:</td> |
| 476 | 486 | @ <td><input type="text" name="captcha" value="" size="30"> |
| 477 | 487 | @ <input type="hidden" name="usecaptcha" value="1"></td> |
| 478 | 488 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
| 479 | | - @ <td><span class="optionalTag">(copy from below)</span></td> |
| 480 | | - @ </tr> |
| 481 | | - } |
| 482 | | - @ <tr> |
| 483 | | - @ <td class="form_label">Nickname:</td> |
| 484 | | - @ <td><input type="text" name="nn" value="" size="30"></td> |
| 485 | | - @ <td><span class="optionalTag">(optional)</span></td> |
| 486 | | - @ </tr> |
| 487 | | - @ <tr> |
| 488 | | - @ <td class="form_label">Password:</td> |
| 489 | | - @ <td><input type="password" name="pw" value="" size="30"></td> |
| 490 | | - @ <td><span class="optionalTag">(optional)</span></td> |
| 491 | | - @ </tr> |
| 492 | | - @ <tr> |
| 493 | | - @ <td class="form_label">Options:</td> |
| 494 | | - @ <td><label><input type="checkbox" name="sa" value="0">\ |
| 495 | | - @ Announcements</label><br> |
| 496 | | - @ <label><input type="checkbox" name="sc" value="0">\ |
| 497 | | - @ Check-ins</label><br> |
| 498 | | - @ <label><input type="checkbox" name="st" value="0">\ |
| 499 | | - @ Ticket changes</label><br> |
| 500 | | - @ <label><input type="checkbox" name="sw" value="0">\ |
| 501 | | - @ Wiki</label><br> |
| 502 | | - @ <label><input type="checkbox" name="di" value="0">\ |
| 489 | + @ </tr> |
| 490 | + } |
| 491 | + if( g.perm.Admin ){ |
| 492 | + @ <tr> |
| 493 | + @ <td class="form_label">User:</td> |
| 494 | + @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \ |
| 495 | + @ size="30"></td> |
| 496 | + @ <td></td> |
| 497 | + @ </tr> |
| 498 | + } |
| 499 | + @ <tr> |
| 500 | + @ <td class="form_label">Options:</td> |
| 501 | + @ <td><label><input type="checkbox" name="sa" value="%d(P10("sa"))">\ |
| 502 | + @ Announcements</label><br> |
| 503 | + @ <label><input type="checkbox" name="sc" value="%d(P01("sc"))">\ |
| 504 | + @ Check-ins</label><br> |
| 505 | + @ <label><input type="checkbox" name="st" value="%d(P01("st"))">\ |
| 506 | + @ Ticket changes</label><br> |
| 507 | + @ <label><input type="checkbox" name="sw" value="%d(P01("sw"))">\ |
| 508 | + @ Wiki</label><br> |
| 509 | + @ <label><input type="checkbox" name="di" value="%d(P01("di"))">\ |
| 503 | 510 | @ Daily digest only</label><br></td> |
| 504 | 511 | @ </tr> |
| 505 | 512 | @ <tr> |
| 506 | 513 | @ <td></td> |
| 507 | 514 | @ <td><input type="submit" value="Submit"></td> |
| | @@ -515,5 +522,152 @@ |
| 515 | 522 | @ </td></tr></table></div> |
| 516 | 523 | } |
| 517 | 524 | @ </form> |
| 518 | 525 | style_footer(); |
| 519 | 526 | } |
| 527 | + |
| 528 | +/* |
| 529 | +** WEBPAGE: alerts |
| 530 | +** |
| 531 | +** Edit email alert and notification settings. |
| 532 | +** |
| 533 | +** The subscriber entry is identified in either of two ways: |
| 534 | +** |
| 535 | +** (1) The name= query parameter contains the subscriberCode. |
| 536 | +** |
| 537 | +** (2) The user is logged into an account other than "nobody" or |
| 538 | +** "anonymous". In that case the notification settings |
| 539 | +** associated with that account can be edited without needing |
| 540 | +** to know the subscriber code. |
| 541 | +*/ |
| 542 | +void alerts_page(void){ |
| 543 | + const char *zName = P("name"); |
| 544 | + Stmt q; |
| 545 | + int sa, sc, st, sw; |
| 546 | + int sdigest, sdonotcall, sverify; |
| 547 | + const char *ssub; |
| 548 | + const char *semail; |
| 549 | + const char *sctime; |
| 550 | + const char *smtime; |
| 551 | + const char *smip; |
| 552 | + int i; |
| 553 | + |
| 554 | + |
| 555 | + login_check_credentials(); |
| 556 | + if( !g.perm.EmailAlert ){ |
| 557 | + cgi_redirect("subscribe"); |
| 558 | + return; |
| 559 | + } |
| 560 | + if( zName==0 && login_is_individual() ){ |
| 561 | + zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber" |
| 562 | + " WHERE suname=%Q", g.zLogin); |
| 563 | + } |
| 564 | + if( zName==0 || !validate16(zName, -1) ){ |
| 565 | + cgi_redirect("subscribe"); |
| 566 | + return; |
| 567 | + } |
| 568 | + if( P("submit")!=0 && cgi_csrf_safe(1) ){ |
| 569 | + int sdonotcall = PB("sdonotcall"); |
| 570 | + int sdigest = PB("sdigest"); |
| 571 | + char ssub[10]; |
| 572 | + int nsub = 0; |
| 573 | + if( PB("sa") ) ssub[nsub++] = 'a'; |
| 574 | + if( PB("sc") ) ssub[nsub++] = 'c'; |
| 575 | + if( PB("st") ) ssub[nsub++] = 't'; |
| 576 | + if( PB("sw") ) ssub[nsub++] = 'w'; |
| 577 | + ssub[nsub] = 0; |
| 578 | + db_multi_exec( |
| 579 | + "UPDATE subscriber SET" |
| 580 | + " sdonotcall=%d," |
| 581 | + " sdigest=%d," |
| 582 | + " ssub=%Q," |
| 583 | + " smtime=julianday('now')," |
| 584 | + " smip=%Q" |
| 585 | + " WHERE subscriberCode=hextoblob(%Q)", |
| 586 | + sdonotcall, |
| 587 | + sdigest, |
| 588 | + ssub, |
| 589 | + g.zIpAddr, |
| 590 | + zName |
| 591 | + ); |
| 592 | + } |
| 593 | + if( PB("dodelete") && P("delete")!=0 && cgi_csrf_safe(1) ){ |
| 594 | + db_multi_exec( |
| 595 | + "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)", |
| 596 | + zName |
| 597 | + ); |
| 598 | + } |
| 599 | + db_prepare(&q, |
| 600 | + "SELECT" |
| 601 | + " semail," |
| 602 | + " sverify," |
| 603 | + " sdonotcall," |
| 604 | + " sdigest," |
| 605 | + " ssub," |
| 606 | + " datetime(sctime)," |
| 607 | + " datetime(smtime)," |
| 608 | + " smip" |
| 609 | + " FROM subscriber WHERE subscriberCode=hextoblob(%Q)", zName); |
| 610 | + if( db_step(&q)!=SQLITE_ROW ){ |
| 611 | + db_finalize(&q); |
| 612 | + cgi_redirect("subscribe"); |
| 613 | + return; |
| 614 | + } |
| 615 | + style_header("Update Subscription"); |
| 616 | + semail = db_column_text(&q, 0); |
| 617 | + sverify = db_column_int(&q, 1); |
| 618 | + sdonotcall = db_column_int(&q, 2); |
| 619 | + sdigest = db_column_int(&q, 3); |
| 620 | + ssub = db_column_text(&q, 4); |
| 621 | + sa = strchr(ssub,'a')!=0; |
| 622 | + sc = strchr(ssub,'c')!=0; |
| 623 | + st = strchr(ssub,'t')!=0; |
| 624 | + sw = strchr(ssub,'w')!=0; |
| 625 | + sctime = db_column_text(&q, 5); |
| 626 | + smtime = db_column_text(&q, 6); |
| 627 | + smip = db_column_text(&q, 7); |
| 628 | + form_begin(0, "%R/alerts"); |
| 629 | + @ <table class="subscribe"> |
| 630 | + @ <tr> |
| 631 | + @ <td class="form_label">Email Address:</td> |
| 632 | + @ <td>%h(semail)</td> |
| 633 | + @ </tr> |
| 634 | + if( g.perm.Admin ){ |
| 635 | + @ <tr> |
| 636 | + @ <td class='form_label'>IP Address:</td> |
| 637 | + @ <td>%h(smip)</td> |
| 638 | + @ </tr> |
| 639 | + } |
| 640 | + @ <tr> |
| 641 | + @ <td class="form_label">Options:</td> |
| 642 | + @ <td><label><input type="checkbox" name="sa" value="%d(sa)">\ |
| 643 | + @ Announcements</label><br> |
| 644 | + @ <label><input type="checkbox" name="sc" value="%d(sc)">\ |
| 645 | + @ Check-ins</label><br> |
| 646 | + @ <label><input type="checkbox" name="st" value="%d(st)">\ |
| 647 | + @ Ticket changes</label><br> |
| 648 | + @ <label><input type="checkbox" name="sw" value="%d(sw)">\ |
| 649 | + @ Wiki</label><br> |
| 650 | + @ <label><input type="checkbox" name="sdigest" value="%d(sdigest)">\ |
| 651 | + @ Daily digest only</label><br> |
| 652 | + if( g.perm.Admin ){ |
| 653 | + @ <label><input type="checkbox" name="sdonotcall" value="%d(sdonotcall)">\ |
| 654 | + @ Do not call</label><br> |
| 655 | + @ <label><input type="checkbox" name="sverify" value="%d(sverify)">\ |
| 656 | + @ Verified</label><br> |
| 657 | + } |
| 658 | + @ </td></tr> |
| 659 | + @ <tr> |
| 660 | + @ <td></td> |
| 661 | + @ <td><input type="submit" value="Submit"></td> |
| 662 | + @ </tr> |
| 663 | + @ <tr> |
| 664 | + @ <td></td> |
| 665 | + @ <td><label><input type="checkbox" name="dodelete" value="0"> |
| 666 | + @ Delete this subscription</label> |
| 667 | + @ <input type="submit" name="delete" value="Delete"></td> |
| 668 | + @ </tr> |
| 669 | + @ </table> |
| 670 | + @ </form> |
| 671 | + db_finalize(&q); |
| 672 | + style_footer(); |
| 673 | +} |
| 520 | 674 | |