/*
** 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/
**
*******************************************************************************
**
** Implementation of the Setup page
*/
#include The permission flags are as follows:
@ Every user, logged in or not, inherits the privileges of
@ nobody.
@
@ 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.
@
@ Users with privilege v inherit the combined
@ privileges of developer,
@ anonymous, and
@ nobody.
@
@ User %h(zLogin) has Setup privileges and you only have Admin privileges
@ so you are not permitted to make changes to %h(zLogin).
@
@ The Setup user can make arbitrary
@ configuration changes. An Admin user
@ can add other users and change user privileges
@ and reset user passwords. Both automatically get all other privileges
@ listed below. Use these two settings with discretion.
@
@ The "•" mark
@ indicates the privileges of nobody that
@ are available to all users regardless of whether or not they are logged in.
@
@ The "•" mark
@ indicates the privileges of anonymous that
@ are inherited by all logged-in users.
@
@ The "•" mark
@ indicates the privileges of developer that
@ are inherited by all users with the
@ Developer privilege.
@
@ The "•" mark
@ indicates the privileges of reader that
@ are inherited by all users with the Reader
@ privilege.
@
@ The Delete privilege give the user the
@ ability to erase wiki, tickets, and attachments that have been added
@ by anonymous users. This capability is intended for deletion of spam.
@ The delete capability is only in effect for 24 hours after the item
@ is first posted. The Setup user can
@ delete anything at any time.
@
@ The History privilege allows a user
@ to see most hyperlinks. This is recommended ON for most logged-in users
@ but OFF for user "nobody" to avoid problems with spiders trying to walk
@ every historical version of every baseline and file.
@
@ The Zip privilege allows a user to
@ see the "download as ZIP"
@ hyperlink and permits access to the /zip page. This allows
@ users to download ZIP archives without granting other rights like
@ Read or
@ History. This privilege is recommended for
@ user nobody so that automatic package
@ downloaders can obtain the sources without going through the login
@ procedure.
@
@ The Check-in privilege allows remote
@ users to "push". The Check-out privilege
@ allows remote users to "pull". The Clone
@ privilege allows remote users to "clone".
@
@ The Read Wiki,
@ New Wiki,
@ Append Wiki, and
@ Write Wiki privileges control access to wiki pages. The
@ Read Ticket,
@ New Ticket,
@ Append Ticket, and
@ Write Ticket privileges control access
@ to trouble tickets.
@ The Ticket Report privilege allows
@ the user to create or edit ticket report formats.
@
@ Users with the Password privilege
@ are allowed to change their own password. Recommended ON for most
@ users but OFF for special users developer,
@ anonymous,
@ and nobody.
@
@ The EMail privilege allows the display of
@ sensitive information such as the email address of users and contact
@ information on tickets. Recommended OFF for
@ anonymousy and for
@ nobody but ON for
@ developer.
@
@ The Attachment privilege is needed in
@ order to add attachments to tickets or wiki. Write privilege on the
@ ticket or wiki is also required.
@
@ Login is prohibited if the password is an empty string.
@
@ No login is required for user nobody. The
@ capabilities of the nobody user are
@ inherited by all users, regardless of whether or not they are logged in.
@ To disable universal access to the repository, make sure no user named
@ nobody exists or that the
@ nobody user has no capabilities
@ enabled. The password for nobody is ignore.
@ To avoid problems with spiders overloading the server, it is recommended
@ that the h (History) capability be turned
@ off for the nobody user.
@
@ Login is required for user anonymous but the
@ password is displayed on the login screen beside the password entry box
@ so anybody who can read should be able to login as anonymous.
@ On the other hand, spiders and web-crawlers will typically not
@ be able to login. Set the capabilities of the
@ anonymous
@ user to things that you want any human to be able to do, but not any
@ spider. Every other logged-in user inherits the privileges of
@ anonymous.
@
@ The developer user is intended as a template
@ for trusted users with check-in privileges. When adding new trusted users,
@ simply select the developer privilege to
@ cause the new user to inherit all privileges of the
@ developer
@ user. Similarly, the reader user is a
@ template for users who are allowed more access than
@ anonymous,
@ but less than a developer.
@ This page provides a simple interface to the "fossil setting" command.
@ See the "fossil help setting" output below for further information on
@ the meaning of each setting.
@ These settings work in the same way, as the set commandline:
}
/*
** WEBPAGE: /setup
*/
void setup_page(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Server Administration");
@
if( zLink && zLink[0] ){
@ %h(zTitle)
}else{
@ %h(zTitle)
}
@ %h(zDesc)
setup_menu_entry("Users", "setup_ulist",
"Grant privileges to individual users.");
setup_menu_entry("Access", "setup_access",
"Control access settings.");
setup_menu_entry("Configuration", "setup_config",
"Configure the WWW components of the repository");
setup_menu_entry("Settings", "setup_settings",
"Web interface to the \"fossil settings\" command");
setup_menu_entry("Timeline", "setup_timeline",
"Timeline display preferences");
setup_menu_entry("Tickets", "tktsetup",
"Configure the trouble-ticketing system for this repository");
setup_menu_entry("Skins", "setup_skin",
"Select from a menu of prepackaged \"skins\" for the web interface");
setup_menu_entry("CSS", "setup_editcss",
"Edit the Cascading Style Sheet used by all pages of this repository");
setup_menu_entry("Header", "setup_header",
"Edit HTML text inserted at the top of every page");
setup_menu_entry("Footer", "setup_footer",
"Edit HTML text inserted at the bottom of every page");
setup_menu_entry("Logo", "setup_logo",
"Change the logo image for the server");
setup_menu_entry("Shunned", "shun",
"Show artifacts that are shunned by this repository");
setup_menu_entry("Log", "rcvfromlist",
"A record of received artifacts and their sources");
setup_menu_entry("Stats", "stat",
"Display repository statistics");
@
style_footer();
}
/*
** WEBPAGE: setup_ulist
**
** Show a list of users. Clicking on any user jumps to the edit
** screen for that user.
*/
void setup_ulist(void){
Stmt s;
login_check_credentials();
if( !g.okAdmin ){
login_needed();
return;
}
style_submenu_element("Add", "Add User", "setup_uedit");
style_header("User List");
@
@
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
*/
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap, *zPw;
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
char *oat, *oau, *oav, *oab, *oaz;
const char *inherit[128];
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.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 cap 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 ){
char zCap[50];
int i = 0;
int aa = P("aa")!=0;
int ab = P("ab")!=0;
int ad = P("ad")!=0;
int ae = P("ae")!=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 ar = P("ar")!=0;
int as = g.okSetup && P("as")!=0;
int aw = P("aw")!=0;
int ac = P("ac")!=0;
int af = P("af")!=0;
int am = P("am")!=0;
int ah = P("ah")!=0;
int ag = P("ag")!=0;
int at = P("at")!=0;
int au = P("au")!=0;
int av = P("av")!=0;
int az = P("az")!=0;
if( aa ){ zCap[i++] = 'a'; }
if( ab ){ zCap[i++] = 'b'; }
if( ac ){ zCap[i++] = 'c'; }
if( ad ){ zCap[i++] = 'd'; }
if( ae ){ zCap[i++] = 'e'; }
if( af ){ zCap[i++] = 'f'; }
if( ah ){ zCap[i++] = 'h'; }
if( ag ){ zCap[i++] = 'g'; }
if( ai ){ zCap[i++] = 'i'; }
if( aj ){ zCap[i++] = 'j'; }
if( ak ){ zCap[i++] = 'k'; }
if( am ){ zCap[i++] = 'm'; }
if( an ){ zCap[i++] = 'n'; }
if( ao ){ zCap[i++] = 'o'; }
if( ap ){ zCap[i++] = 'p'; }
if( ar ){ zCap[i++] = 'r'; }
if( as ){ zCap[i++] = 's'; }
if( at ){ zCap[i++] = 't'; }
if( au ){ zCap[i++] = 'u'; }
if( av ){ zCap[i++] = 'v'; }
if( aw ){ zCap[i++] = 'w'; }
if( az ){ zCap[i++] = 'z'; }
zCap[i] = 0;
zPw = P("pw");
zLogin = P("login");
if( isValidPwString(zPw) ){
zPw = sha1_shared_secret(zPw, zLogin);
}else{
zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
}
if( uid>0 &&
db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
){
style_header("User Creation Error");
@ Login "%h(zLogin)" is already used by
@ a different user.
@
@
style_footer();
return;
}
login_verify_csrf_secret();
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");
return;
}
/* Load the existing information about the user, if any
*/
zLogin = "";
zInfo = "";
zCap = "";
zPw = "";
oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
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);
if( strchr(zCap, 'a') ) oaa = " checked=\"checked\"";
if( strchr(zCap, 'b') ) oab = " checked=\"checked\"";
if( strchr(zCap, 'c') ) oac = " checked=\"checked\"";
if( strchr(zCap, 'd') ) oad = " checked=\"checked\"";
if( strchr(zCap, 'e') ) oae = " checked=\"checked\"";
if( strchr(zCap, 'f') ) oaf = " checked=\"checked\"";
if( strchr(zCap, 'g') ) oag = " checked=\"checked\"";
if( strchr(zCap, 'h') ) oah = " checked=\"checked\"";
if( strchr(zCap, 'i') ) oai = " checked=\"checked\"";
if( strchr(zCap, 'j') ) oaj = " checked=\"checked\"";
if( strchr(zCap, 'k') ) oak = " checked=\"checked\"";
if( strchr(zCap, 'm') ) oam = " checked=\"checked\"";
if( strchr(zCap, 'n') ) oan = " checked=\"checked\"";
if( strchr(zCap, 'o') ) oao = " checked=\"checked\"";
if( strchr(zCap, 'p') ) oap = " checked=\"checked\"";
if( strchr(zCap, 'r') ) oar = " checked=\"checked\"";
if( strchr(zCap, 's') ) oas = " checked=\"checked\"";
if( strchr(zCap, 't') ) oat = " checked=\"checked\"";
if( strchr(zCap, 'u') ) oau = " checked=\"checked\"";
if( strchr(zCap, 'v') ) oav = " checked=\"checked\"";
if( strchr(zCap, 'w') ) oaw = " checked=\"checked\"";
if( strchr(zCap, 'z') ) oaz = " checked=\"checked\"";
}
/* figure out inherited permissions */
memset(inherit, 0, sizeof(inherit));
if( strcmp(zLogin, "developer") ){
char *z1, *z2;
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'");
while( z1 && *z1 ){
inherit[0x7f & *(z1++)] =
"•";
}
free(z2);
}
if( strcmp(zLogin, "reader") ){
char *z1, *z2;
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'");
while( z1 && *z1 ){
inherit[0x7f & *(z1++)] =
"•";
}
free(z2);
}
if( strcmp(zLogin, "anonymous") ){
char *z1, *z2;
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'");
while( z1 && *z1 ){
inherit[0x7f & *(z1++)] =
"•";
}
free(z2);
}
if( strcmp(zLogin, "nobody") ){
char *z1, *z2;
z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'");
while( z1 && *z1 ){
inherit[0x7f & *(z1++)] =
"•";
}
free(z2);
}
/* Begin generating the page
*/
style_submenu_element("Cancel", "Cancel", "setup_ulist");
if( uid ){
style_header(mprintf("Edit User %h", zLogin));
}else{
style_header("Add A New User");
}
@
@ Users:
@
@
@
@
db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login");
while( db_step(&s)==SQLITE_ROW ){
const char *zCap = db_column_text(&s, 2);
if( strstr(zCap, "s") ) zCap = "s";
@ User ID
@ Capabilities
@ Contact Info
@
@
}
@
if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){
@
}
@ %h(db_column_text(&s,1))
if( g.okAdmin ){
@
}
@
@ %s(zCap)
@ %s(db_column_text(&s,3))
@
@ Notes:
@
@
@
@
@
@ a
@ Admin: Create and delete users
@ b
@ Attach: Add attachments to wiki or tickets
@ c
@ Append-Tkt: Append to tickets
@ d
@ Delete: Delete wiki and tickets
@ e
@ Email: View sensitive data such as EMail addresses
@ f
@ New-Wiki: Create new wiki pages
@ g
@ Clone: Clone the repository
@ h
@ Hyperlinks: Show hyperlinks to detailed
@ repository history
@ i
@ Check-In: Commit new versions in the repository
@ j
@ Read-Wiki: View wiki pages
@ k
@ Write-Wiki: Edit wiki pages
@ m
@ Append-Wiki: Append to wiki pages
@ n
@ New-Tkt: Create new tickets
@ o
@ Check-Out: Check out versions
@ p
@ Password: Change your own password
@ r
@ Read-Tkt: View tickets
@ s
@ Setup/Super-user: Setup and configure this website
@ t
@ Tkt-Report: Create new bug summary reports
@ u
@ Reader: Inherit privileges of
@ user reader
@ v
@ Developer: Inherit privileges of
@ user developer
@ w
@ Write-Tkt: Edit tickets
@ z
@ Zip download: Download a baseline via the
@ /zip URL even without
@ checkout
@ and history permissions Privileges And Capabilities:
@
if( higherUser ){
@
@
@ Special Logins
@
@
@
style_footer();
}
/*
** Generate a checkbox for an attribute.
*/
static void onoff_attribute(
const char *zLabel, /* The text label on the checkbox */
const char *zVar, /* The corresponding row in the VAR table */
const char *zQParm, /* The query parameter */
int dfltVal /* Default value if VAR table entry does not exist */
){
const char *zQ = P(zQParm);
int iVal = db_get_boolean(zVar, dfltVal);
if( zQ==0 && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
db_set(zVar, iQ ? "1" : "0", 0);
iVal = iQ;
}
}
if( iVal ){
@
@ %s(zLabel)
}else{
@ %s(zLabel)
}
}
/*
** Generate an entry box for an attribute.
*/
void entry_attribute(
const char *zLabel, /* The text label on the entry box */
int width, /* Width of the entry box */
const char *zVar, /* The corresponding row in the VAR table */
const char *zQParm, /* The query parameter */
char *zDflt /* Default value if VAR table entry does not exist */
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && strcmp(zQ,zVal)!=0 ){
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
zVal = zQ;
}
@
@ %s(zLabel)
}
/*
** Generate a text box for an attribute.
*/
static void textarea_attribute(
const char *zLabel, /* The text label on the textarea */
int rows, /* Rows in the textarea */
int cols, /* Columns in the textarea */
const char *zVar, /* The corresponding row in the VAR table */
const char *zQP, /* The query parameter */
const char *zDflt /* Default value if VAR table entry does not exist */
){
const char *z = db_get(zVar, (char*)zDflt);
const char *zQ = P(zQP);
if( zQ && strcmp(zQ,z)!=0 ){
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
z = zQ;
}
if( rows>0 && cols>0 ){
@
if (zLabel && *zLabel)
@ %s(zLabel)
}
}
/*
** WEBPAGE: setup_access
*/
void setup_access(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Access Control Settings");
db_begin_transaction();
@
db_end_transaction(0);
style_footer();
}
/*
** WEBPAGE: setup_timeline
*/
void setup_timeline(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Timeline Display Preferences");
db_begin_transaction();
@
db_end_transaction(0);
style_footer();
}
/*
** WEBPAGE: setup_settings
*/
void setup_settings(void){
struct stControlSettings const *pSet;
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Settings");
db_begin_transaction();
@
@
@
@ %s(zHelp_setting_cmd)
Note: Press your browser Reload button after @ modifying the CSS in order to pull in the modified CSS file.
@style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_header */ void setup_header(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='header'"); cgi_replace_parameter("header", zDefaultHeader); }else{ textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader); } style_header("Edit Page Header"); @ @cgi_append_default_css(); @
style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_footer */ void setup_footer(void){ login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("clear")!=0 ){ db_multi_exec("DELETE FROM config WHERE name='footer'"); cgi_replace_parameter("footer", zDefaultFooter); }else{ textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter); } style_header("Edit Page Footer"); @ @@ %h(zDefaultHeader) @
style_footer(); db_end_transaction(0); } /* ** WEBPAGE: setup_logo */ void setup_logo(void){ const char *zMime = "image/gif"; const char *aImg = P("im"); int szImg = atoi(PD("im:bytes","0")); if( szImg>0 ){ zMime = PD("im:mimetype","image/gif"); } login_check_credentials(); if( !g.okSetup ){ login_needed(); } db_begin_transaction(); if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){ Blob img; Stmt ins; blob_init(&img, aImg, szImg); db_prepare(&ins, "REPLACE INTO config(name, value)" " VALUES('logo-image',:bytes)" ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( "REPLACE INTO config(name, value) VALUES('logo-mimetype',%Q)", zMime ); db_end_transaction(0); cgi_redirect("setup_logo"); }else if( P("clr")!=0 ){ db_multi_exec( "DELETE FROM config WHERE name GLOB 'logo-*'" ); db_end_transaction(0); cgi_redirect("setup_logo"); } style_header("Edit Project Logo"); @@ %h(zDefaultFooter) @
The current project logo has a MIME-Type of %h(zMime) and looks @ like this:
@@ @
The logo is accessible to all users at this URL: @ %s(g.zBaseURL)/logo. @ The logo may or may not appear on each @ page depending on the CSS and @ header setup.
@ @ @ @Note: Your browser has probably cached the @ logo image, so you will probably need to press the Reload button on your @ browser after changing the logo to provoke your browser to reload the new @ logo image.
style_footer(); db_end_transaction(0); }