Commit 5e25aa17 authored by Leigh Stoller's avatar Leigh Stoller

Move the core approval code from the web interface to the backend so

that we can run project approval from the command line. Part of the
ongoing push to get stuff out of php and into the backend ...

The command line is now:

	mkproj [-s] [-h leader_uid] [-m <message> | -f <file>] <pid>
	switches and arguments:
	  -s         - silent; do not send approval email to leader
  	  -h <uid>   - switch project leader to specified uid
	  -m <text>  - Include text in approval email message
	  -f <file>  - Include text from file in approval email message
	  <pid>      - project to approve.

Notes:

* The leader can be switched to a new user only at initial project creation.
  Once a project is actually approved (created), its too late. We need
  more stuff in place to change the leader after that, and that code
  is not written yet.

* Email is now sent from the backend script, so easier to recover from
  problems. When invoked from the web interface, the message text will
  be appended to the tberror email if the backend fails for some
  reason.  This should avoid the problem of that text getting lost and
  not being able to recover it.

* The web interface still handles part of project denial internally.
  Move that later.
parent 43eac695
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
#
# Make a project directory hierarchy. Must be called as tbroot.
# Creates a directory rooted /proj/pid. The directory is setuid
# to the project leader, and setgid to the project gid. We get
# this info from the database.
#
# usage: mkproj <pid>
#
use Getopt::Std;
#
# Perform project approval. Does a lot of stuff, see below!
#
sub usage()
{
print(STDERR
"Usage: mkproj [-s] [-h leader_uid] [-m <message> | -f <file>] ".
"<pid>\n".
"switches and arguments:\n".
"-s - silent; do not send approval email to leader\n".
"-h <uid> - switch project leader to specified uid\n".
"-m <text> - Include text in approval email message\n".
"-f <file> - Include text from file in approval email message\n".
"<pid> - project to approve.\n");
exit(-1);
}
my $optlist = "qsh:m:f:";
my $quiet = 0;
my $silent = 0;
my $leader_uid;
my $message;
my $mfilename;
my $pid;
# Protos
sub fatal($);
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $MKGROUP = "$TB/sbin/mkgroup";
my $MODGROUPS= "$TB/sbin/modgroups";
my $MKACCT = "$TB/sbin/tbacct add";
my $CVSBIN = "/usr/bin/cvs";
my $CHOWN = "/usr/sbin/chown";
my $GRANTTYPE= "$TB/sbin/grantnodetype -d";
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAPPROVAL = "@TBAPPROVALEMAIL@";
my $TBBASE = "@TBBASE@";
my $MKGROUP = "$TB/sbin/mkgroup";
my $MODGROUPS = "$TB/sbin/modgroups";
my $MKACCT = "$TB/sbin/tbacct add";
my $CVSBIN = "/usr/bin/cvs";
my $CHOWN = "/usr/sbin/chown";
my $GRANTTYPE = "$TB/sbin/grantnodetype -d";
my $WIKISUPPORT = @WIKISUPPORT@;
my $BUGDBSUPPORT = @BUGDBSUPPORT@;
my $OPSDBSUPPORT = @OPSDBSUPPORT@;
my $CVSSUPPORT = @CVSSUPPORT@;
my $MAILMANSUPPORT= @MAILMANSUPPORT@;
my $ADDWIKIPROJ = "$TB/sbin/addwikiproj";
my $ADDBUGDBPROJ= "$TB/sbin/addbugdbproj";
my $ADDMMLIST = "$TB/sbin/addmmlist";
my $OPSDBCONTROL= "$TB/sbin/opsdb_control";
my $ADDWIKIPROJ = "$TB/sbin/addwikiproj";
my $ADDBUGDBPROJ = "$TB/sbin/addbugdbproj";
my $ADDMMLIST = "$TB/sbin/addmmlist";
my $OPSDBCONTROL = "$TB/sbin/opsdb_control";
my @DIRLIST = ("exp", "images", "logs", "deltas", "tarfiles", "rpms",
"groups", "tiplogs", "images/sigs", "templates");
my $projhead;
#
# Untaint the path
......@@ -62,6 +78,8 @@ use lib "@prefix@/lib";
use libaudit;
use libdb;
use libtestbed;
use User;
use Project;
my $PROJROOT = PROJROOT();
my $GRPROOT = GROUPROOT();
......@@ -73,8 +91,12 @@ my $SCRATCHROOT = SCRATCHROOT();
my $TFTPDIR = "/tftpboot/$PROJROOT";
my $CVSREPOS = "$PROJROOT/cvsrepos";
# Locals
my $leader;
my $projhead;
#
# We don't want to run this script unless its the real version.
# We do not want to run this script unless its the real version.
#
if ($EUID != 0) {
die("*** $0:\n".
......@@ -93,36 +115,53 @@ if ($UID == 0) {
#
# Check args.
#
if ($#ARGV < 0) {
die("Usage: mkprojdir <pid>\n");
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"q"})) {
$quiet = 1;
}
my $pid = $ARGV[0];
if (defined($options{"s"})) {
$silent = 1;
}
if (defined($options{"m"})) {
$message = $options{"m"};
}
if (defined($options{"f"})) {
$mfilename = $options{"f"};
fatal("$mfilename does not exist!")
if (! -e $mfilename);
}
if (defined($options{"h"})) {
$leader_uid = $options{"h"};
}
usage()
if (! @ARGV);
$pid = $ARGV[0];
#
# Untaint the argument.
#
if ($pid =~ /^([-\@\w.]+)$/) {
if ($pid =~ /^([-\w]+)$/) {
$pid = $1;
}
else {
die("Invalid pid '$pid' contains illegal characters.\n");
}
#
# Figure out who called us. Only with admin status in the DB can run
# this script.
#
if (!TBAdmin($UID)) {
die("*** $0:\n".
" You must be a TB administrator to run this script!\n");
# Map invoking user to object.
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
#
# We need the project leader name.
# Figure out who called us. Must have admin status to do this.
#
if (! ($projhead = ProjLeader($pid))) {
die("*** $0:\n".
" Could not get project leader for project $pid!\n");
if (!TBAdmin()) {
fatal("You must be a TB administrator to run this script!");
}
#
......@@ -135,6 +174,74 @@ if (AuditStart(0)) {
exit(0);
}
#
# Map project name to object.
#
my $target_project = Project->Lookup($pid);
if (! defined($target_project)) {
fatal("Could not map project $pid to its object!");
}
#
# The welcome message ...
#
if (defined($mfilename)) {
open(MFILE, $mfilename) or
fatal("Could not open $mfilename");
$message = "";
while (<MFILE>) {
$message .= $_;
}
close(MFILE);
}
#
# If a leader uid was provided on the command line, we are changing the
# leader. Note that this is allowed *only* for projects that have not
# been approved yet.
#
if (defined($leader_uid)) {
$leader = User->Lookup($leader_uid);
if (!defined($leader)) {
fatal("Could not map user $leader_uid to its object!");
}
# See if already did this; is so skip the following checks.
my $curleader = $target_project->GetLeader();
if (!defined($curleader)) {
fatal("Could not map current leader of project $pid to its object!");
}
if (! $curleader->SameUser($leader)) {
fatal("Not allowed to change the leader of an approved project!")
if ($target_project->approved());
# Update the project structure with the new leader. We are going
# to set the approved bit below, so this is the last chance to do
# this until we have code in place to change it later.
$target_project->ChangeLeader($leader) == 0 or
fatal("Could not change project leader for $pid to $leader_uid!");
}
}
else {
$leader = $target_project->GetLeader();
if (!defined($leader)) {
fatal("Could not map current leader of project $pid to its object!");
}
}
# Avoid taint check problem.
$leader_uid = $leader->uid();
# Approve the project; we are committed to the leader.
$target_project->SetApproved(1) == 0 or
fatal("Could not set the approval bit on project $target_project!");
#
# Leader needs to have his approved bit set. Eventually this should be done
# in mkaccount when that code moves from the web interface.
#
$leader->SetStatus(USERSTATUS_ACTIVE()) == 0 or
fatal("Could not change $leader to active!");
#
# Before we can proceed, we need to create the project (unix) group
# and then create an account for the project leader. We pass this off
......@@ -163,19 +270,19 @@ if ($MAILMANSUPPORT) {
fatal("$ADDMMLIST -a ${pid}-users failed!");
}
system("$MKACCT $projhead") == 0 or
fatal("$MKACCT $projhead failed!");
system("$MKACCT $leader_uid") == 0 or
fatal("$MKACCT $leader_uid failed!");
system("$MODGROUPS -a $pid:$pid:project_root $projhead") == 0 or
fatal("$MODGROUPS -a $pid:$pid:project_root $projhead failed!");
system("$MODGROUPS -a $pid:$pid:project_root $leader_uid") == 0 or
fatal("$MODGROUPS -a $pid:$pid:project_root $leader_uid failed!");
$EUID = 0;
#
# This acts as check (and we need the numeric uid) in case mkacct failed!
#
my (undef,undef,$uid) = getpwnam($projhead)
or fatal("$projhead not in passwd file");
my (undef,undef,$uid) = getpwnam($leader_uid)
or fatal("$leader_uid not in passwd file");
my (undef,undef,$gid) = getgrnam($pid)
or fatal("$pid not in group file");
......@@ -354,6 +461,28 @@ if ($query_result->num_rows) {
}
}
# Send email, unless silent option given.
if (!$silent) {
my $leader_name = $leader->name();
my $leader_email = $leader->email();
SENDMAIL("$leader_name <$leader_email>",
"Project '$pid' Approval",
"\n".
"This message is to notify you that your project '$pid'\n".
"has been approved. We recommend that you save this link so that\n".
"you can send it to people you wish to have join your project.\n".
"Otherwise, tell them to go to ${TBBASE} and join it.\n".
"\n".
" ${TBBASE}/joinproject.php3?target_pid=$pid\n".
(defined($message) ? "\n${message}\n" : "") .
"\n".
"Thanks,\n".
"Testbed Operations\n",
"$TBAPPROVAL",
"Bcc: $TBAPPROVAL");
}
print "Project Creation Completed!\n";
exit(0);
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2003, 2005, 2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
......@@ -49,7 +49,7 @@ $headuid = $this_project->head_uid();
# If the user wanted to change the head uid, do that now (we change both
# the head_uid and the leader of the default project)
#
if (isset($head_uid) && $head_uid != "") {
if ($approval == "approve" && isset($head_uid) && $head_uid != "") {
if (! ($newleader = User::Lookup($head_uid))) {
TBERROR("Unknown user $head_uid", 1);
}
......@@ -78,6 +78,7 @@ if (!isset($user_interface) ||
$curstatus = $leader->status();
$headuid_email = $leader->email();
$headname = $leader->name();
#$headidx = $leader->uid_idx();
#echo "Status = $curstatus, Email = $headuid_email<br>\n";
#
......@@ -175,28 +176,15 @@ elseif ((strcmp($approval, "deny") == 0) ||
</h3>\n";
}
elseif (strcmp($approval, "approve") == 0) {
#
# Change the status if necessary. This only happens for new users
# being approved in their first project. After this, the status is
# going to be "active", and we just leave it that way.
#
if (strcmp($curstatus, "active")) {
if (strcmp($curstatus, "unapproved") == 0) {
$newstatus = "active";
}
else {
TBERROR("Invalid $headuid status $curstatus in ".
"approveproject.php3", 1);
}
$leader->SetUserInterface($user_interface);
$leader->SetStatus($newstatus);
$optargs = "";
# Sanity check the leader status.
if ($curstatus != TBDB_USERSTATUS_ACTIVE &&
$curstatus != TBDB_USERSTATUS_UNAPPROVED) {
TBERROR("Invalid $headuid status $curstatus", 1);
}
#
# Set the project "approved" field to true.
#
$this_project->SetApproved(1);
# Why is this here?
$leader->SetUserInterface($user_interface);
#
# XXX
......@@ -218,41 +206,43 @@ elseif (strcmp($approval, "approve") == 0) {
$this_project->SetRemoteOK($foo);
}
unset($tmpfname);
if (isset($message)) {
$tmpfname = tempnam("/tmp", "approveproj");
$fp = fopen($tmpfname, "w");
fwrite($fp, $message);
fclose($fp);
$optargs = " -f " . escapeshellarg($tmpfname);
}
#
# Invoke the script. This does it all. If it fails, we will find out
# about it.
#
echo "<br>
Project '$pid' is being created!<br><br>
This will take a minute or two. <b>Please</b> do not click the Stop
button during this time. If you do not receive notification within
a reasonable amount of time, please contact $TBMAILADDR.\n";
flush();
STARTBUSY("Project '$pid' is being created");
$retval = SUEXEC($uid, $TBADMINGROUP, "webmkproj $optargs $pid",
SUEXEC_ACTION_IGNORE);
SUEXEC($uid, $TBADMINGROUP, "webmkproj $pid", SUEXEC_ACTION_DIE);
CLEARBUSY();
TBMAIL("$headname '$headuid' <$headuid_email>",
"Project '$pid' Approval",
"\n".
"This message is to notify you that your project '$pid'\n".
"has been approved. We recommend that you save this link so that\n".
"you can send it to people you wish to have join your project.\n".
"Otherwise, tell them to go to ${TBBASE} and join it.\n".
"\n".
" ${TBBASE}/joinproject.php3?target_pid=$pid\n".
"\n".
"$message\n".
"\n".
"Thanks,\n".
"Testbed Operations\n",
"From: $TBMAIL_APPROVAL\n".
"Bcc: $TBMAIL_APPROVAL\n".
"Errors-To: $TBMAIL_WWW");
if (isset($tmpfname)) {
unlink($tmpfname);
}
if ($retval) {
# Lets tack the message onto the output so we have a record.
if (isset($message)) {
$suexec_output .= "\n\n*** Saved approval message text:\n\n";
$suexec_output .= $message;
}
SUEXECERROR(SUEXEC_ACTION_DIE);
return;
}
if (!$FirstInitState) {
echo "<p><b>
Project $pid (User: $headuid) has been approved.
</b>\n";
sleep(1);
PAGEREPLACE(CreateURL("showproject", $this_project));
}
else {
echo "<br><br><font size=+1>\n";
......
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