Commit 2f373d5b authored by Russ Fish's avatar Russ Fish

Avoid a problem in newproject.php3. When the DB is locked for daily backup,

NewNewUser()/newuser would block and then unblock and get done; meanwhile the PHP
thread went away so we never returned to call NewNewProject/mkproj.  Move the call
on the newuser script from PHP into the back-end Perl newproj script for atomicity.

    www/newproject.php3 - When the project leader is a new user, pass two xml
        files to the newproj backend script, one describing the project and the
        second one (an optional) file describing the newuser.

    www/user_defs.php - Factor the xml-making part of NewNewUser into NewNewUserXML.

    www/project_defs.php - Remove the required $leader arg of NewNewProject.
        newproj may call newuser, which may generate the leader uid.

    backend/newproj.in - Call newuser with an optional 'newuser_xml' XML file.

    sql/database-fill.sql - Add 'projects','newuser_xml'.
parent 62eb03b0
......@@ -152,7 +152,6 @@ if (! defined($this_user)) {
# These are the fields that we allow to come in from the XMLfile.
#
my %required = ("name" => "pid",
"leader" => "head_uid",
"short description" => "name",
"URL" => "URL",
"funders" => "funders",
......@@ -161,7 +160,9 @@ my %required = ("name" => "pid",
"num_pcs" => "num_pcs",
"linkedtous" => "linked_to_us");
my %optional = ("members" => "num_members",
my %optional = ("newuser_xml" => "newuser_xml",
"leader" => "head_uid", # May be chosen by newuser.
"members" => "num_members",
"ron" => "num_ron",
"plab" => "num_pcplab",
"whynotpublic" => "public_whynot",
......@@ -194,7 +195,7 @@ foreach my $key (keys(%required)) {
}
#
# We build up an array of arguments to pass to User->Create() as we check
# We build up an array of arguments to pass to Project->Create() as we check
# the attributes.
#
my %newproj_args = ();
......@@ -242,6 +243,57 @@ foreach my $key (keys(%{ $xmlparse->{'attribute'} })) {
$newproj_args{$dbslot} = $value;
}
#
# Create the user if an XML file for newuser was supplied.
#
if (exists($newproj_args{'newuser_xml'})) {
my $newuser_xml = $newproj_args{'newuser_xml'};
delete($newproj_args{'newuser_xml'});
#
# Check the filename when invoked from the web interface; must be a
# file in /tmp.
#
if (! defined($this_user)) {
if ($newuser_xml =~ /^([-\w\.\/]+)$/) {
$newuser_xml = $1;
}
else {
fatal("Bad data in pathname: $newuser_xml");
}
# Use realpath to resolve any symlinks.
my $translated = `realpath $newuser_xml`;
if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) {
$newuser_xml = $1;
}
else {
fatal("Bad data in translated pathname: $newuser_xml");
}
}
#
# We can't pass the user type to the newuser script in xml, because it
# determines which args are required or optional in the xml data! The
# user type is always "project leader" for new users under newproj.
#
my $cmd = "newuser -t leader $newuser_xml";
print $cmd . "\n"
if ($debug);
my $cmd_out = `$cmd`;
UserError("Transient Error: (3, $?, $cmd) $cmd_out")
if ($?);
#
# Parse the last line of output. Ick.
#
my @out_lines = split(/^/, $cmd_out);
if (!( $out_lines[@out_lines-1] =~ /^User\s+(\w+)\/(\d+)\s+/)) {
UserError("Transient error: (4, $cmd) $cmd_out");
}
$newproj_args{'head_uid'} = $2;
}
#
# Now do special checks.
#
......@@ -278,6 +330,11 @@ TBGetSiteVar("general/firstinit/state", \$firstinitstate);
#
$newproj->SendNewProjectEmail($firstinitstate eq "createproject");
# Unlink this here, so that the newuser file is left behind in case of error.
# We can then create the user by hand from the xmlfile, if desired.
unlink($newuser_xml)
if (defined($newuser_xml));
# The web interface requires this line to be printed!
print "Project $new_pid/$new_idx has been created\n";
exit(0);
......
......@@ -576,6 +576,8 @@ REPLACE INTO table_regex VALUES ('nseconfigs','pid','text','redirect','projects:
REPLACE INTO table_regex VALUES ('nseconfigs','eid','text','redirect','experiments:eid',0,0,NULL);
REPLACE INTO table_regex VALUES ('nseconfigs','vname','text','redirect','virt_nodes:vname',0,0,NULL);
REPLACE INTO table_regex VALUES ('nseconfigs','nseconfig','text','regex','^[\\040-\\176\\012\\011\\015]*$',0,16777215,NULL);
REPLACE INTO table_regex VALUES ('projects','newuser_xml','text','regex','^[-_\\w\\.\\/:+]*$',1,256,NULL);
REPLACE INTO table_regex VALUES ('projects','newpid','text','regex','^[a-zA-Z][-a-zA-Z0-9]+$',2,12,NULL);
REPLACE INTO table_regex VALUES ('projects','head_uid','text','redirect','users:uid',0,0,NULL);
REPLACE INTO table_regex VALUES ('projects','name','text','redirect','default:tinytext',0,256,NULL);
......
......@@ -887,6 +887,7 @@ if (count($errors)) {
}
#
# Create the User first, then the Project/Group.
# Certain of these values must be escaped or otherwise sanitized.
#
if (!$returning) {
......@@ -923,27 +924,36 @@ if (!$returning) {
$args["pubkey"] = file_get_contents($localfile);
}
if (! ($leader = User::NewNewUser(TBDB_NEWACCOUNT_PROJLEADER,
$args,
$error)) != 0) {
$errors["Error Creating User"] = $error;
# Just collect the user XML args here and pass the file to NewNewProject.
# Underneath, newproj calls newuser with the XML file.
#
# Calling newuser down in Perl land makes creation of the leader account
# and the project "atomic" from the user's point of view. This avoids a
# problem when the DB is locked for daily backup: in newproject, the call
# on NewNewUser would block and then unblock and get done; meanwhile the
# PHP thread went away so we never returned here to call NewNewProject.
#
if (! ($newuser_xml = User::NewNewUserXML($args, $errors)) != 0) {
$errors["Error Creating User XML"] = $error;
TBERROR("B\n${error}\n\n" . print_r($args, TRUE), 0);
SPITFORM($formfields, $returning, $errors);
PAGEFOOTER();
return;
}
# If null; used below
$proj_head_uid = $leader->uid();
}
else {
$leader = $this_user;
}
#
# Now for the new Project
#
$args = array();
$args["name"] = $formfields["pid"];
if (isset($newuser_xml)) {
$args["newuser_xml"] = $newuser_xml;
}
if ($returning) {
# An existing, logged-in user is starting the project.
$args["leader"] = $this_user->uid();
}
$args["name"] = $formfields["pid"];
$args["short description"] = $formfields["proj_name"];
$args["URL"] = $formfields["proj_URL"];
$args["members"] = $formfields["proj_members"];
......@@ -975,7 +985,7 @@ if (isset($formfields["proj_ronpcs"]) &&
$args["ron"] = 1;
}
if (! ($project = Project::NewNewProject($leader, $args, $error))) {
if (! ($project = Project::NewNewProject($args, $error))) {
$errors["Error Creating Project"] = $error;
TBERROR("C\n${error}\n\n" . print_r($args, TRUE), 0);
SPITFORM($formfields, $returning, $errors);
......@@ -987,6 +997,8 @@ if (! ($project = Project::NewNewProject($leader, $args, $error))) {
# Need to do some extra work for the first project; eventually move to backend
#
if ($FirstInitState) {
$leader = $project->GetLeader();
$proj_head_uid = $leader->uid();
# Set up the management group (emulab-ops).
Group::Initialize($proj_head_uid);
......
......@@ -235,7 +235,7 @@ class Project
return $newproject;
}
function NewNewProject($leader, $args, &$error) {
function NewNewProject($args, &$error) {
global $suexec_output, $suexec_output_array;
#
......@@ -253,9 +253,6 @@ class Project
return null;
}
# Need to say who is going to be leading this project.
$args["leader"] = $leader->uid();
fwrite($fp, "<project>\n");
foreach ($args as $name => $value) {
fwrite($fp, "<attribute name=\"$name\">");
......@@ -303,7 +300,7 @@ class Project
# We can then create the project by hand from the xmlfile, if desired.
unlink($xmlname);
return $newproj;
}
}
#
# Class function to return a list of pending (unapproved) projects.
......
......@@ -500,17 +500,7 @@ class User
return $newuser;
}
function NewNewUser($flags, $args, &$error) {
global $suexec_output, $suexec_output_array;
$typearg = "";
if ($flags & TBDB_NEWACCOUNT_PROJLEADER)
$typearg = "-t leader";
elseif ($flags & TBDB_NEWACCOUNT_WIKIONLY)
$typearg = "-t wikionly";
elseif ($flags & TBDB_NEWACCOUNT_WEBONLY)
$typearg = "-t webonly";
function NewNewUserXML($args, &$error) {
#
# Generate a temporary file and write in the XML goo.
#
......@@ -536,6 +526,24 @@ class User
fclose($fp);
chmod($xmlname, 0666);
return $xmlname;
}
function NewNewUser($flags, $args, &$error) {
global $suexec_output, $suexec_output_array;
$typearg = "";
if ($flags & TBDB_NEWACCOUNT_PROJLEADER)
$typearg = "-t leader";
elseif ($flags & TBDB_NEWACCOUNT_WIKIONLY)
$typearg = "-t wikionly";
elseif ($flags & TBDB_NEWACCOUNT_WEBONLY)
$typearg = "-t webonly";
if (! ($xmlname = User::NewNewUserXML($args, $error))) {
return null;
}
$retval = SUEXEC("nobody", "nobody", "webnewuser $typearg $xmlname",
SUEXEC_ACTION_IGNORE);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment