Check-in [916b6e4b3b]
Not logged in
Overview

SHA1 Hash:916b6e4b3b701915f2670ce40e776abbd05f71d0
Date: 2007-07-21 19:32:06
User: drh
Comment:Improvements to web-based user management.
Timelines: ancestors | descendants | both | trunk
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/db.c from [2bb8e10f4d] to [e56e457646].

@@ -572,23 +572,34 @@
 ** fall back to the -R or --repository option.
 **
 ** Error out if the repository cannot be opened.
 */
 void db_find_and_open_repository(void){
-  db_open_repository(find_option("repository", "R", 1));
-  if( g.repositoryOpen==0 ){
-    fossil_fatal("use --repository or -R to specific the repository database");
+  char *zRep = find_option("repository", "R", 1);
+  if( zRep==0 ){
+    if( db_open_local()==0 ){
+      goto rep_not_found;
+    }
+    zRep = db_lget("repository", 0);
+    if( zRep==0 ){
+      goto rep_not_found;
+    }
+  }
+  db_open_repository(zRep);
+  if( g.repositoryOpen ){
+    return;
   }
+rep_not_found:
+  fossil_fatal("use --repository or -R to specific the repository database");
 }
 
 /*
 ** Open the local database.  If unable, exit with an error.
 */
 void db_must_be_within_tree(void){
   if( db_open_local()==0 ){
-    fprintf(stderr,"%s: not within an open checkout\n", g.argv[0]);
-    exit(1);
+    fossil_fatal("not within an open checkout");
   }
   db_open_repository(0);
 }
 
 /*

Modified src/login.c from [ed7e76839e] to [eed2c5dedd].

@@ -81,11 +81,11 @@
     }
   }
   if( zUsername!=0 && zPasswd!=0 && strcmp(zUsername,"anonymous")!=0 ){
     int uid = db_int(0,
         "SELECT uid FROM user"
-        " WHERE login=%Q AND pw=%B", zUsername, zPasswd);
+        " WHERE login=%Q AND pw=%Q", zUsername, zPasswd);
     if( uid<=0 ){
       sleep(1);
       zErrMsg =
          @ <p><font color="red">
          @ You entered an unknown user or an incorrect password.
@@ -92,11 +92,11 @@
          @ </font></p>
       ;
     }else{
       char *zCookie;
       const char *zCookieName = login_cookie_name();
-      const char *zIpAddr = PD("REMOTE_ADDR","x");
+      const char *zIpAddr = PD("REMOTE_ADDR","nil");
       const char *zExpire = db_get("cookie-expire","8766");
       int expires;
 
       zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid);
       expires = atoi(zExpire)*3600;
@@ -194,14 +194,15 @@
   ** user credentials.
   */
   zRemoteAddr = PD("REMOTE_ADDR","nil");
   if( strcmp(zRemoteAddr, "127.0.0.1")==0
         && db_get_int("authenticate-localhost",1)==0 ){
-    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%s%'");
+    uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
     g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
     zCap = "s";
     g.noPswd = 1;
+    g.isAnon = 0;
   }
 
   /* Check the login cookie to see if it matches a known valid user.
   */
   if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){
@@ -214,18 +215,20 @@
             atoi(zCookie), zCookie, zRemoteAddr
          );
   }
 
   if( uid==0 ){
+    g.isAnon = 1;
     g.zLogin = "";
     zCap = db_get("nologin-cap","onrj");
   }else if( zCap==0 ){
     Stmt s;
     db_prepare(&s, "SELECT login, cap FROM user WHERE uid=%d", uid);
     db_step(&s);
     g.zLogin = db_column_malloc(&s, 0);
     zCap = db_column_malloc(&s, 1);
+    g.isAnon = 0;
     db_finalize(&s);
   }
   g.userUid = uid;
 
   login_set_capabilities(zCap);

Modified src/setup.c from [cb7dcac5a1] to [570fc89ab7].

@@ -77,50 +77,312 @@
 ** Show a list of users.  Clicking on any user jumps to the edit
 ** screen for that user.
 */
 void setup_ulist(void){
   Stmt s;
+
+  style_footer();
   login_check_credentials();
-  if( !g.okSetup ){
+  if( !g.okWrite || g.isAnon ){
     login_needed();
-  }
-
+    return;
+  }
+
+  style_submenu_element("Add", "Add User", "setup_uedit");
   style_header();
-
-  @ <table border="0" cellpadding="0" cellspacing="0">
-  db_prepare(&s, "SELECT uid, login, cap FROM repuser ORDER BY login");
+  @ <h2>List Of Users</h2>
+  @ <table cellspacing=0 cellpadding=0 border=0>
+  @ <tr>
+  @   <th align="right"><nobr>User ID</nobr></th>
+  @   <th>&nbsp;&nbsp;&nbsp;Capabilities&nbsp;&nbsp;&nbsp;</th>
+  @   <th><nobr>Contact Info</nobr></th>
+  @ </tr>
+  db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
   while( db_step(&s)==SQLITE_ROW ){
-    @ <tr><td><a href="%s(g.zBaseURL)/setup_uedit?uid=%d(db_column_int(&s,0))">
-    @ %h(db_column_text(&s,1))</a></td><td width="10"></td>
-    @ <td>%h(db_column_text(&s,2))</td></tr>
-  }
-  db_finalize(&s);
+    @ <tr>
+    @ <td align="right">
+    if( g.okAdmin ){
+      @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
+    }
+    @ <nobr>%h(db_column_text(&s,1))</nobr>
+    if( g.okAdmin ){
+      @ </a>
+    }
+    @ </td>
+    @ <td align="center">%s(db_column_text(&s,2))</td>
+    @ <td align="center">%s(db_column_text(&s,3))</td>
+    @ </tr>
+  }
+  @ </table>
+  @ <p><hr>
+  @ <b>Notes:</b>
+  @ <ol>
+  @ <li><p>The permission flags are as follows:</p>
+  @ <table>
+  @ <tr><td>a</td><td width="10"></td>
+  @     <td>Admin: Create or delete users and ticket report formats</td></tr>
+  @ <tr><td>d</td><td></td>
+  @     <td>Delete: Erase anonymous wiki, tickets, and attachments</td></tr>
+  @ <tr><td>i</td><td></td>
+  @     <td>Check-in: Add new code to the repository</td></tr>
+  @ <tr><td>j</td><td></td><td>Read-Wiki: View wiki pages</td></tr>
+  @ <tr><td>k</td><td></td><td>Wiki: Create or modify wiki pages</td></tr>
+  @ <tr><td>n</td><td></td><td>New: Create new tickets</td></tr>
+  @ <tr><td>o</td><td></td>
+  @     <td>Check-out: Read code out of the repository</td></tr>
+  @ <tr><td>p</td><td></td><td>Password: Change password</td></tr>
+  @ <tr><td>q</td><td></td><td>Query: Create or edit report formats</td></tr>
+  @ <tr><td>r</td><td></td><td>Read: View tickets and change histories</td></tr>
+  @ <tr><td>s</td><td></td><td>Setup: Change CVSTrac options</td></tr>
+  @ <tr><td>w</td><td></td><td>Write: Edit tickets</td></tr>
   @ </table>
-
+  @ </p></li>
+  @
+  @ <li><p>
+  @ If a user named "<b>anonymous</b>" exists, then anyone can access
+  @ the server without having to log in.  The permissions on the
+  @ anonymous user determine the access rights for anyone who is not
+  @ logged in.
+  @ </p></li>
+  @
+  @ </ol>
   style_footer();
 }
 
 /*
-** WEBPAGE: setup_uedit
-**
-** Edit the user with REPUSER.UID equal to the "u" query parameter.
+** WEBPAGE: /setup_uedit
 */
-void setup_uedit(void){
+void user_edit(void){
+  const char *zId, *zLogin, *zInfo, *zCap;
+  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap ;
+  char *oak, *oad, *oaq;
+  int doWrite;
   int uid;
-
+  int higherUser = 0;  /* True if user being edited is SETUP and the */
+                       /* user doing the editing is ADMIN.  Disallow editing */
+
+  /* Must have ADMIN privleges to access this page
+  */
   login_check_credentials();
-  if( !g.okSetup ){
-    login_needed();
-  }
-  uid = atoi(PD("u","0"));
-  if( uid<=0 ){
+  if( !g.okAdmin ){ login_needed(); return; }
+
+  /* Check to see if an ADMIN user is trying to edit a SETUP account.
+  ** Don't allow that.
+  */
+  zId = PD("id", "0");
+  uid = atoi(zId);
+  if( zId && !g.okSetup && uid>0 ){
+    char *zOldCaps;
+    zOldCaps = db_text(0, "SELECT caps FROM user WHERE uid=%d",uid);
+    higherUser = zOldCaps && strchr(zOldCaps,'s');
+  }
+
+  if( P("can") ){
+    cgi_redirect("setup_ulist");
+    return;
+  }
+
+  /* If we have all the necessary information, write the new or
+  ** modified user record.  After writing the user record, redirect
+  ** to the page that displays a list of users.
+  */
+  doWrite = cgi_all("login","info","pw") && !higherUser;
+  if( doWrite ){
+    const char *zPw;
+    const char *zLogin;
+    char zCap[20];
+    int i = 0;
+    int aa = P("aa")!=0;
+    int ad = P("ad")!=0;
+    int ai = P("ai")!=0;
+    int aj = P("aj")!=0;
+    int ak = P("ak")!=0;
+    int an = P("an")!=0;
+    int ao = P("ao")!=0;
+    int ap = P("ap")!=0;
+    int aq = P("aq")!=0;
+    int ar = P("ar")!=0;
+    int as = g.okSetup && P("as")!=0;
+    int aw = P("aw")!=0;
+    if( as ) aa = 1;
+    if( aa ) ai = aw = ap = 1;
+    if( aw ) an = ar = 1;
+    if( ai ) ao = 1;
+    if( ak ) aj = 1;
+    if( aa ){ zCap[i++] = 'a'; }
+    if( ad ){ zCap[i++] = 'd'; }
+    if( ai ){ zCap[i++] = 'i'; }
+    if( aj ){ zCap[i++] = 'j'; }
+    if( ak ){ zCap[i++] = 'k'; }
+    if( an ){ zCap[i++] = 'n'; }
+    if( ao ){ zCap[i++] = 'o'; }
+    if( ap ){ zCap[i++] = 'p'; }
+    if( aq ){ zCap[i++] = 'q'; }
+    if( ar ){ zCap[i++] = 'r'; }
+    if( as ){ zCap[i++] = 's'; }
+    if( aw ){ zCap[i++] = 'w'; }
+
+    zCap[i] = 0;
+    zPw = P("pw");
+    if( zPw==0 || zPw[0]==0 ){
+      zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
+    }
+    zLogin = P("login");
+    if( uid>0 &&
+        db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
+    ){
+      style_header();
+      @ <font color="red">Login "%h(zLogin)" is already used by a different
+      @ user.</font>
+      @
+      @ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p>
+      style_footer();
+      return;
+    }
+    db_multi_exec(
+       "REPLACE INTO user(uid,login,info,pw,cap) "
+       "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')",
+      uid, P("login"), P("info"), zPw, zCap
+    );
     cgi_redirect("setup_ulist");
-    assert(0);
-  }
+    return;
+  }
+
+  /* Load the existing information about the user, if any
+  */
+  zLogin = "";
+  zInfo = "";
+  zCap = "";
+  oaa = oad = oai = oaj = oak = oan = oao = oap = oaq = oar = oas = oaw = "";
+  if( uid ){
+    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
+    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
+    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
+    if( strchr(zCap, 'a') ) oaa = " checked";
+    if( strchr(zCap, 'd') ) oad = " checked";
+    if( strchr(zCap, 'i') ) oai = " checked";
+    if( strchr(zCap, 'j') ) oaj = " checked";
+    if( strchr(zCap, 'k') ) oak = " checked";
+    if( strchr(zCap, 'n') ) oan = " checked";
+    if( strchr(zCap, 'o') ) oao = " checked";
+    if( strchr(zCap, 'p') ) oap = " checked";
+    if( strchr(zCap, 'q') ) oaq = " checked";
+    if( strchr(zCap, 'r') ) oar = " checked";
+    if( strchr(zCap, 's') ) oas = " checked";
+    if( strchr(zCap, 'w') ) oaw = " checked";
+  }
+
+  /* Begin generating the page
+  */
+  style_submenu_element("Cancel", "Cancel", "setup_ulist");
   style_header();
+  if( uid ){
+    @ <h2>Edit User %h(zLogin)</h2>
+  }else{
+    @ <h2>Add A New User</h2>
+  }
+  @ <table align="left" hspace="20" vspace="10"><tr><td>
+  @ <form action="%s(g.zPath)" method="POST">
+  @ <table>
+  @ <tr>
+  @   <td align="right"><nobr>User ID:</nobr></td>
+  if( uid ){
+    @   <td>%d(uid) <input type="hidden" name="id" value="%d(uid)"></td>
+  }else{
+    @   <td>(new user)<input type="hidden" name="id" value=0></td>
+  }
+  @ </tr>
+  @ <tr>
+  @   <td align="right"><nobr>Login:</nobr></td>
+  @   <td><input type="text" name="login" value="%h(zLogin)"></td>
+  @ </tr>
+  @ <tr>
+  @   <td align="right"><nobr>Contact&nbsp;Info:</nobr></td>
+  @   <td><input type="text" name="info" size=40 value="%h(zInfo)"></td>
+  @ </tr>
+  @ <tr>
+  @   <td align="right" valign="top">Capabilities:</td>
+  @   <td>
+  @     <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
+  @     <input type="checkbox" name="ad"%s(oad)>Delete</input><br>
+  @     <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
+  @     <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
+  @     <input type="checkbox" name="ak"%s(oak)>Write Wiki</input><br>
+  @     <input type="checkbox" name="an"%s(oan)>New Tkt</input><br>
+  @     <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
+  @     <input type="checkbox" name="ap"%s(oap)>Password</input><br>
+  @     <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
+  @     <input type="checkbox" name="ar"%s(oar)>Read</input><br>
+  if( g.okSetup ){
+    @     <input type="checkbox" name="as"%s(oas)>Setup</input><br>
+  }
+  @     <input type="checkbox" name="aw"%s(oaw)>Write</input>
+  @   </td>
+  @ </tr>
+  @ <tr>
+  @   <td align="right">Password:</td>
+  @   <td><input type="password" name="pw" value=""></td>
+  @ </tr>
+  if( !higherUser ){
+    @ <tr>
+    @   <td>&nbsp</td>
+    @   <td><input type="submit" name="submit" value="Apply Changes">
+    @ </tr>
+  }
+  @ </table></td></tr></table>
+  @ <p><b>Notes:</b></p>
+  @ <ol>
+  if( higherUser ){
+    @ <li><p>
+    @ User %h(zId) has Setup privileges and you only have Admin privileges
+    @ so you are not permitted to make changes to %h(zId).
+    @ </p></li>
+    @
+  }
+  @ <li><p>
+  @ The <b>Read</b> and <b>Write</b> privileges give the user the ability
+  @ to read and write tickets.  The <b>New Tkt</b> capability means that
+  @ the user is able to create new tickets.
+  @ </p></li>
+  @
+  @ <li><p>
+  @ The <b>Delete</b> privilege give the user the ability to erase
+  @ wiki, tickets, and atttachments that have been added by anonymous
+  @ users.  This capability is intended for deletion of spam.
+  @ </p></li>
+  @
+  @ <li><p>
+  @ The <b>Query</b> privilege allows the user to create or edit
+  @ report formats by specifying appropriate SQL.  Users can run
+  @ existing reports without the Query privilege.
+  @ </p></li>
+  @
+  @ <li><p>
+  @ An <b>Admin</b> user can add other users, create new ticket report
+  @ formats, and change system defaults.  But only the <b>Setup</b> user
+  @ is able to change the repository to
+  @ which this program is linked.
+  @ </p></li>
+  @
+  if( zId==0 || strcmp(zId,"anonymous")==0 ){
+    @ <li><p>
+    @ No login is required for user "<b>anonymous</b>".  The capabilities
+    @ of this user are available to anyone without supplying a username or
+    @ password.  To disable anonymous access, make sure there is no user
+    @ with an ID of <b>anonymous</b>.
+    @ </p></li>
+    @
+    @ <li><p>
+    @ The password for the "<b>anonymous</b>" user is used for anonymous
+    @ access.  The recommended value for the anonymous password
+    @ is "anonymous".
+    @ </p></li>
+  }
+  @ </form>
   style_footer();
 }
+
 
 /*
 ** Generate a checkbox for an attribute.
 */
 static void onoff_attribute(

Modified src/style.c from [73ef49d142] to [b4958eee41].

@@ -69,11 +69,11 @@
 /*
 ** Draw the header.
 */
 void style_header(void){
   const char *zLogInOut = "Logout";
-  /* login_check_credentials(); */
+  login_check_credentials();
   @ <html>
   @ <body bgcolor="white">
   @ <hr size="1">
   @ <table border="0" cellpadding="0" cellspacing="0" width="100%%">
   @ <tr><td valign="top" align="left">
@@ -104,11 +104,11 @@
       struct Submenu *p = &aSubmenu[i];
       char *zTail = i<nSubmenu-1 ? " | " : "";
       if( p->zLink==0 ){
         @ <font color="#888888">%h(p->zLabel)</font> %s(zTail)
       }else{
-        @ <a href="p->zLink">%h(p->zLabel)</a> %s(zTail)
+        @ <a href="%T(p->zLink)">%h(p->zLabel)</a> %s(zTail)
       }
     }
   }
   @ </td></tr></table>
   @ <hr size="1">

Modified src/user.c from [0a8a17727c] to [f41abe0610].

@@ -106,15 +106,17 @@
 ** COMMAND:  user
 **
 ** Dispatcher for various user subcommands.
 */
 void user_cmd(void){
+  int n;
   db_find_and_open_repository();
   if( g.argc<3 ){
-    usage("create|default|list|password ...");
+    usage("capabilities|default|list|new|password ...");
   }
-  if( strcmp(g.argv[2],"create")==0 ){
+  n = strlen(g.argv[2]);
+  if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
     Blob passwd, login, contact;
 
     prompt_user("login: ", &login);
     prompt_user("contact-info: ", &contact);
     get_passphrase("password: ", &passwd, 1);
@@ -121,27 +123,27 @@
     db_multi_exec(
       "INSERT INTO user(login,pw,cap,info)"
       "VALUES(%B,%B,'jnor',%B)",
       &login, &passwd, &contact
     );
-  }else if( strcmp(g.argv[2],"default")==0 ){
+  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
     user_select();
     if( g.argc==3 ){
       printf("%s\n", g.zLogin);
     }else if( g.localOpen ){
       db_lset("default-user", g.zLogin);
     }else{
       db_set("default-user", g.zLogin);
     }
-  }else if( strcmp(g.argv[2],"list")==0 ){
+  }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
     Stmt q;
     db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
     while( db_step(&q)==SQLITE_ROW ){
       printf("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1));
     }
     db_finalize(&q);
-  }else if( strcmp(g.argv[2],"password")==0 ){
+  }else if( n>=2 && strncmp(g.argv[2],"password",2)==0 ){
     char *zPrompt;
     int uid;
     Blob pw;
     if( g.argc!=4 ) usage("user password USERNAME");
     uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
@@ -153,13 +155,29 @@
     if( blob_size(&pw)==0 ){
       printf("password unchanged\n");
     }else{
       db_multi_exec("UPDATE user SET pw=%B WHERE uid=%d", &pw, uid);
     }
+  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
+    int uid;
+    if( g.argc!=4 && g.argc!=5 ){
+      usage("user capabilities USERNAME ?PERMISSIONS?");
+    }
+    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
+    if( uid==0 ){
+      fossil_fatal("no such user: %s", g.argv[3]);
+    }
+    if( g.argc==5 ){
+      db_multi_exec(
+        "UPDATE user SET cap=%Q WHERE uid=%d", g.argv[4],
+        uid
+      );
+    }
+    printf("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
   }else{
     fossil_panic("user subcommand should be one of: "
-                 "create default list password");
+                 "capabilities default list new password");
   }
 }
 
 /*
 ** Attempt to set the user to zLogin

Modified src/xfer.c from [03c0de5a4e] to [00d4a363f4].

@@ -222,11 +222,11 @@
     blob_append(&combined, blob_buffer(&pw), blob_size(&pw));
     sha1sum_blob(&combined, &hash);
     rc = blob_compare(&hash, pSig);
     blob_reset(&hash);
     blob_reset(&combined);
-    if( rc ){
+    if( rc==0 ){
       const char *zCap;
       zCap = db_column_text(&q, 1);
       login_set_capabilities(zCap);
       g.userUid = db_column_int(&q, 2);
       g.zLogin = mprintf("%b", pLogin);
@@ -394,11 +394,11 @@
       @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
       db_multi_exec(
         "INSERT OR IGNORE INTO pending(rid) "
         "SELECT rid FROM blob WHERE size>=0"
       );
-    }
+    }else
 
     /*    login  USER  NONCE  SIGNATURE
     **
     ** Check for a valid login.  This has to happen before anything else.
     */