/* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** Setup pages associated with user management. The code in this ** file was formerly part of the "setup.c" module, but has been broken ** out into its own module to improve maintainability. */ #include "config.h" #include #include "setupuser.h" /* ** WEBPAGE: setup_ulist ** ** Show a list of users. Clicking on any user jumps to the edit ** screen for that user. Requires Admin privileges. ** ** Query parameters: ** ** with=CAP Only show users that have one or more capabilities in CAP. */ void setup_ulist(void){ Stmt s; double rNow; const char *zWith = P("with"); login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); return; } style_submenu_element("Add", "setup_uedit"); style_submenu_element("Log", "access_log"); style_submenu_element("Help", "setup_ulist_notes"); style_header("User List"); if( zWith==0 || zWith[0]==0 ){ @ @ @ @ db_prepare(&s, "SELECT uid, login, cap, date(mtime,'unixepoch')" " FROM user" " WHERE login IN ('anonymous','nobody','developer','reader')" " ORDER BY login" ); while( db_step(&s)==SQLITE_ROW ){ int uid = db_column_int(&s, 0); const char *zLogin = db_column_text(&s, 1); const char *zCap = db_column_text(&s, 2); const char *zDate = db_column_text(&s, 4); @ @ } db_finalize(&s); @
Category @ Capabilities (key) @ Info Last Change
%h(zLogin) @ %h(zCap) if( fossil_strcmp(zLogin,"anonymous")==0 ){ @ All logged-in users }else if( fossil_strcmp(zLogin,"developer")==0 ){ @ Users with 'v' capability }else if( fossil_strcmp(zLogin,"nobody")==0 ){ @ All users without login }else if( fossil_strcmp(zLogin,"reader")==0 ){ @ Users with 'u' capability }else{ @ } if( zDate && zDate[0] ){ @ %h(zDate) }else{ @ } @
@
Users
}else{ style_submenu_element("All Users", "setup_ulist"); if( zWith[1]==0 ){ @
Users with capability "%h(zWith)"
}else{ @
Users with any capability in "%h(zWith)"
} } @ @ @ @ db_multi_exec( "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)" "WITHOUT ROWID;" ); if( db_table_exists("repository","accesslog") ){ db_multi_exec( "INSERT INTO lastAccess(uname, atime)" " SELECT uname, max(mtime) FROM (" " SELECT uname, mtime FROM accesslog WHERE success" " UNION ALL" " SELECT login AS uname, rcvfrom.mtime AS mtime" " FROM rcvfrom JOIN user USING(uid))" " GROUP BY 1;" ); } if( zWith && zWith[0] ){ zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); }else{ zWith = ""; } db_prepare(&s, "SELECT uid, login, cap, info, date(mtime,'unixepoch')," " lower(login) AS sortkey, " " CASE WHEN info LIKE '%%expires 20%%'" " THEN substr(info,instr(lower(info),'expires')+8,10)" " END AS exp," "atime" " FROM user LEFT JOIN lastAccess ON login=uname" " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" " ORDER BY sortkey", zWith/*safe-for-%s*/ ); rNow = db_double(0.0, "SELECT julianday('now');"); while( db_step(&s)==SQLITE_ROW ){ int uid = db_column_int(&s, 0); const char *zLogin = db_column_text(&s, 1); const char *zCap = db_column_text(&s, 2); const char *zInfo = db_column_text(&s, 3); const char *zDate = db_column_text(&s, 4); const char *zSortKey = db_column_text(&s,5); const char *zExp = db_column_text(&s,6); double rATime = db_column_double(&s,7); char *zAge = 0; if( rATime>0.0 ){ zAge = human_readable_age(rNow - rATime); } @ @ fossil_free(zAge); } @
Login NameCapsInfoDateExpireLast Login
\ @ %h(zLogin) @ %h(zCap) @ %h(zInfo) @ %h(zDate?zDate:"") @ %h(zExp?zExp:"") @ %s(zAge?zAge:"") @
db_finalize(&s); style_table_sorter(); style_footer(); } /* ** WEBPAGE: setup_ulist_notes ** ** A documentation page showing notes about user configuration. This ** information used to be a side-bar on the user list page, but has been ** factored out for improved presentation. */ void setup_ulist_notes(void){ style_header("User Configuration Notes"); @

User Configuration Notes:

@
    @
  1. @ Every user, logged in or not, inherits the privileges of @ nobody. @

  2. @ @
  3. @ Any human can login as anonymous since the @ password is clearly displayed on the login page for them to type. The @ purpose of requiring anonymous to log in is to prevent access by spiders. @ Every logged-in user inherits the combined privileges of @ anonymous and @ nobody. @

  4. @ @
  5. @ Users with privilege u inherit the combined @ privileges of reader, @ anonymous, and @ nobody. @

  6. @ @
  7. @ Users with privilege v inherit the combined @ privileges of developer, @ anonymous, and @ nobody. @

  8. @ @
  9. The permission flags are as follows:

    capabilities_table(CAPCLASS_ALL); @
  10. @
style_footer(); } /* ** WEBPAGE: setup_ucap_list ** ** A documentation page showing the meaning of the various user capabilities ** code letters. */ void setup_ucap_list(void){ style_header("User Capability Codes"); @

All capabilities

capabilities_table(CAPCLASS_ALL); @

Capabilities associated with checked-in content

capabilities_table(CAPCLASS_CODE); @

Capabilities associated with data transfer and sync

capabilities_table(CAPCLASS_DATA); @

Capabilities associated with the forum

capabilities_table(CAPCLASS_FORUM); @

Capabilities associated with tickets

capabilities_table(CAPCLASS_TKT); @

Capabilities associated with wiki

capabilities_table(CAPCLASS_WIKI); @

Administrative capabilities

capabilities_table(CAPCLASS_SUPER); @

Miscellaneous capabilities

capabilities_table(CAPCLASS_OTHER); style_footer(); } /* ** Return true if zPw is a valid password string. A valid ** password string is: ** ** (1) A zero-length string, or ** (2) a string that contains a character other than '*'. */ static int isValidPwString(const char *zPw){ if( zPw==0 ) return 0; if( zPw[0]==0 ) return 1; while( zPw[0]=='*' ){ zPw++; } return zPw[0]!=0; } /* ** WEBPAGE: setup_uedit ** ** Edit information about a user or create a new user. ** Requires Admin privileges. */ void user_edit(void){ const char *zId, *zLogin, *zInfo, *zCap, *zPw; const char *zGroup; const char *zOldLogin; int uid, i; char *zDeleteVerify = 0; /* Delete user verification text */ int higherUser = 0; /* True if user being edited is SETUP and the */ /* user doing the editing is ADMIN. Disallow editing */ const char *inherit[128]; int a[128]; const char *oa[128]; /* Must have ADMIN privileges to access this page */ login_check_credentials(); if( !g.perm.Admin ){ login_needed(0); 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.perm.Setup && uid>0 ){ char *zOldCaps; zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); higherUser = zOldCaps && strchr(zOldCaps,'s'); } if( P("can") ){ /* User pressed the cancel button */ cgi_redirect(cgi_referer("setup_ulist")); return; } /* Check for requests to delete the user */ if( P("delete") && cgi_csrf_safe(1) ){ int n; if( P("verifydelete") ){ /* Verified delete user request */ db_multi_exec("DELETE FROM user WHERE uid=%d", uid); cgi_redirect(cgi_referer("setup_ulist")); return; } n = db_int(0, "SELECT count(*) FROM event" " WHERE user=%Q AND objid NOT IN private", P("login")); if( n==0 ){ zDeleteVerify = mprintf("Check this box and press \"Delete User\" again"); }else{ zDeleteVerify = mprintf( "User \"%s\" has %d or more artifacts in the block-chain. " "Delete anyhow?", P("login")/*safe-for-%s*/, n); } } /* 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. */ if( !cgi_all("login","info","pw","apply") ){ /* need all of the above properties to make a change. Since one or ** more are missing, no-op */ }else if( higherUser ){ /* An Admin (a) user cannot edit a Superuser (s) */ }else if( zDeleteVerify!=0 ){ /* Need to verify a delete request */ }else if( !cgi_csrf_safe(1) ){ /* This might be a cross-site request forgery, so ignore it */ }else{ /* We have all the information we need to make the change to the user */ char c; char zCap[70], zNm[4]; zNm[0] = 'a'; zNm[2] = 0; for(i=0, c='a'; c<='z'; c++){ zNm[1] = c; a[c&0x7f] = (c!='s' || g.perm.Setup) && P(zNm)!=0; if( a[c&0x7f] ) zCap[i++] = c; } for(c='0'; c<='9'; c++){ zNm[1] = c; a[c&0x7f] = P(zNm)!=0; if( a[c&0x7f] ) zCap[i++] = c; } for(c='A'; c<='Z'; c++){ zNm[1] = c; a[c&0x7f] = P(zNm)!=0; if( a[c&0x7f] ) zCap[i++] = c; } zCap[i] = 0; zPw = P("pw"); zLogin = P("login"); if( strlen(zLogin)==0 ){ const char *zRef = cgi_referer("setup_ulist"); style_header("User Creation Error"); @ Empty login not allowed. @ @

@ [Bummer]

style_footer(); return; } if( isValidPwString(zPw) ){ zPw = sha1_shared_secret(zPw, zLogin, 0); }else{ zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); } zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d",zLogin,uid) ){ const char *zRef = cgi_referer("setup_ulist"); style_header("User Creation Error"); @ Login "%h(zLogin)" is already used by @ a different user. @ @

@ [Bummer]

style_footer(); return; } login_verify_csrf_secret(); db_multi_exec( "REPLACE INTO user(uid,login,info,pw,cap,mtime) " "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", uid, zLogin, P("info"), zPw, zCap ); setup_incr_cfgcnt(); admin_log( "Updated user [%q] with capabilities [%q].", zLogin, zCap ); if( atoi(PD("all","0"))>0 ){ Blob sql; char *zErr = 0; blob_zero(&sql); if( zOldLogin==0 ){ blob_appendf(&sql, "INSERT INTO user(login)" " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", zLogin, zLogin ); zOldLogin = zLogin; } blob_appendf(&sql, "UPDATE user SET login=%Q," " pw=coalesce(shared_secret(%Q,%Q," "(SELECT value FROM config WHERE name='project-code')),pw)," " info=%Q," " cap=%Q," " mtime=now()" " WHERE login=%Q;", zLogin, P("pw"), zLogin, P("info"), zCap, zOldLogin ); login_group_sql(blob_str(&sql), "
  • ", "
  • \n", &zErr); blob_reset(&sql); admin_log( "Updated user [%q] in all login groups " "with capabilities [%q].", zLogin, zCap ); if( zErr ){ const char *zRef = cgi_referer("setup_ulist"); style_header("User Change Error"); admin_log( "Error updating user '%q': %s'.", zLogin, zErr ); @ %h(zErr) @ @

    @ [Bummer]

    style_footer(); return; } } cgi_redirect(cgi_referer("setup_ulist")); return; } /* Load the existing information about the user, if any */ zLogin = ""; zInfo = ""; zCap = ""; zPw = ""; for(i='a'; i<='z'; i++) oa[i] = ""; for(i='0'; i<='9'; i++) oa[i] = ""; for(i='A'; i<='Z'; i++) oa[i] = ""; 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); zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); for(i=0; zCap[i]; i++){ char c = zCap[i]; if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){ oa[c&0x7f] = " checked=\"checked\""; } } } /* figure out inherited permissions */ memset((char *)inherit, 0, sizeof(inherit)); if( fossil_strcmp(zLogin, "developer") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = "[D]"; } free(z2); } if( fossil_strcmp(zLogin, "reader") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = "[R]"; } free(z2); } if( fossil_strcmp(zLogin, "anonymous") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = "[A]"; } free(z2); } if( fossil_strcmp(zLogin, "nobody") ){ char *z1, *z2; z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); while( z1 && *z1 ){ inherit[0x7f & *(z1++)] = "[N]"; } free(z2); } /* Begin generating the page */ style_submenu_element("Cancel", "%s", cgi_referer("setup_ulist")); if( uid ){ style_header("Edit User %h", zLogin); style_submenu_element("Access Log", "%R/access_log?u=%t", zLogin); }else{ style_header("Add A New User"); } @
    @
    login_insert_csrf_secret(); if( login_is_special(zLogin) ){ @ @ @ } @ @ @ @ if( uid ){ @ }else{ @ } @ @ @ if( login_is_special(zLogin) ){ @ }else{ @ @ @ @ @ } @ @ @ @ @ @ @ @ @ if( !login_is_special(zLogin) ){ @ @ if( zPw[0] ){ /* Obscure the password for all users */ @ }else{ /* Show an empty password as an empty input field */ @ } @ } zGroup = login_group_name(); if( zGroup ){ @ @ @ } if( !higherUser ){ if( zDeleteVerify ){ @ @ @ @ } @ @ @ @ } @
    User ID:%d(uid) (new user)
    Login:%h(zLogin)
    Contact Info:
    Capabilities: #define B(x) inherit[x] @
    @
      if( g.perm.Setup ){ @
    • } @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    • @
    @
    Selected Cap: @ (missing JS?) @ (key) @
    Password:
    Scope: @ @ Apply changes to this repository only.
    @ @ Apply changes to all repositories in the "%h(zGroup)" @ login group.
    Verify:
      if( !login_is_special(zLogin) ){ @ } @
    @
    @
    style_load_one_js_file("useredit.js"); @
    @

    Notes On Privileges And Capabilities:

    @ @ @

    Special Logins

    @ @ style_footer(); }