Commit c13d27c3 authored by Leigh B. Stoller's avatar Leigh B. Stoller
Browse files

And finally, all those groups changes I've been whining and yammering

and complaining about this week.

1. editgroup: You can now edit the trust levels for existing group
   members (default group too), and you can specify trust levels when
   adding users to subgroups.

2. approveusers: When approving users in the approval page, you can
   specify different levels of trust. Before, I invisibly set all the
   trust values the same. I also added some ordering to the DB query
   to group users together.

3. I added a great deal of error checking to the processing pages for
   both forms. I split things up into a pre/post pass. The prepass
   goes through all of the form args and checks them for consistency
   and correctness. Nothing is changed in the DB unless all checks
   pass for all args. Then I do a second pass and make the changes.
   Both scripts set the ignore_user_abort() flag to prevent the user
   from stopping the script and causing a DB inconsistency.

4. Added trust consistency checks as well. Rather than allow the
   project or group leader to set inconsistent trust levels, I look
   for those and just plain disallow them. You may not give different
   trust levels in different subgroups of the *same* project, and you
   may not give a user a higher trust level in the default group than
   in the subgroups. Both edit and approve make these checks, and the
   code is absolutely awful.
parent f1d9357a
......@@ -12,6 +12,10 @@ PAGEHEADER("New Users Approved");
$uid = GETLOGIN();
LOGGEDINORDIE($uid);
$projectchecks = array();
ignore_user_abort(1);
#
# Walk the list of post variables, looking for the special post format.
# See approveuser_form.php3:
......@@ -19,6 +23,11 @@ LOGGEDINORDIE($uid);
# uid menu project/group
# name=stoller$$approval-testbed/testbed value=approved,denied,postpone
# name=stoller$$trust-testbed/testbed value=user,local_root
#
# We make two passes over the post vars. The first does a sanity check so
# that we can bail out without doing anything. This allows the user to
# back up and make changes without worrying about some stuff being done and
# other stuff not.
#
while (list ($header, $value) = each ($HTTP_POST_VARS)) {
#echo "$header: $value<br>\n";
......@@ -66,30 +75,15 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
}
#
# Get the current status for the user, which we might need to change
# anyway, and to verify that the user is a valid user. We also need
# the email address to let user know what happened.
#
# We change the status only if this person is joining his first project.
# In this case, the status will be either "newuser" or "unapproved",
# and we will change it to "unapproved" or "active", respectively.
# If the status is "active", we leave it alone.
# Verify an actual user that is being approved.
#
$query_result =
DBQueryFatal("SELECT status,usr_email,usr_name from users where ".
"uid='$user'");
if (mysql_num_rows($query_result) == 0) {
TBERROR("Unknown user $user", 1);
if (! TBCurrentUser($user)) {
TBERROR("Trying to approve unknown user $user.", 1);
}
$row = mysql_fetch_row($query_result);
$curstatus = $row[0];
$user_email = $row[1];
$user_name = $row[2];
#echo "Status = $curstatus, Email = $user_email<br>\n";
#
# We need to check that the current uid has the necessary trust level
# to add this user to the project/group. Also, only project leaders
# Check that the current uid has the necessary trust level
# to approver users in the project/group. Also, only project leaders
# can add someone as group_root. This should probably be encoded in
# the permission stuff.
#
......@@ -104,40 +98,256 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
"root status!", 1);
}
TBUserInfo($uid, $uid_name, $uid_email);
#
# If already in the group skip.
# Check if already approved in the project/group. If already an
# approved member, something went wrong.
#
TBGroupMember($user, $project, $group, $isapproved);
if ($isapproved) {
USERERROR("$user is already an approved member of ".
"$project/$group!", 1);
}
#
# Verify approval value.
#
if (strcmp($approval, "postpone") &&
strcmp($approval, "deny") &&
strcmp($approval, "nuke") &&
strcmp($approval, "approve")) {
TBERROR("Invalid approval value $approval in approveuser.php3.", 1);
}
#
# If denying project membership, then there must be equivalent denial
# for all subgroups. We can either do it for the user, or require that the
# user understand whats happening. I prefer the latter, so look for this
# and spit back an error. Note that we cannot rely on the post vars for
# this, but must look in the DB for the group set, and then check to make
# sure there are post vars for *all* of them.
#
if (strcmp($project, $group) == 0 &&
(strcmp($approval, "deny") == 0 ||
strcmp($approval, "nuke") == 0)) {
$query_result =
DBQueryFatal("select gid from group_membership ".
"where uid='$user' and pid='$project' and pid!=gid");
while ($row = mysql_fetch_array($query_result)) {
$gid = $row[gid];
#
# Create and indirect through post var for subgroup approval value.
#
$foo = "$user\$\$approval-$project/$gid";
$subgroup_approval = $$foo;
if (!$subgroup_approval ||
(strcmp($subgroup_approval, "deny") &&
strcmp($subgroup_approval, "nuke"))) {
USERERROR("If you wish to deny/nuke user $user in project ".
"$project then you must deny/nuke in all of the ".
"subgroups $user is attempting to join.", 1);
}
}
}
if (strcmp($approval, "approve") == 0)
$projectchecks[$user][] = array($project, $group, $newtrust);
#
# When operating on a user for a subgroup, the user must already be in the
# default group, or there must be an appropriate default group operation
# in the POST vars. In other words, we do not allow users to be
# approved/denied/postponed to a subgroup without a default group
# operation as well. At present, all users must be in the default group
# in addition to subgroups.
#
if (strcmp($project, $group) == 0)
continue;
TBGroupMember($user, $project, $project, $isapproved);
if ($isapproved)
continue;
#
# Create and indirect through post var for project approval value.
#
$foo = "$user\$\$approval-$project/$project";
$default_approval = $$foo;
if (!$default_approval || strcmp($default_approval, "") == 0) {
USERERROR("You must specify an action for $user in the default group ".
"as well as the subgroup!", 1);
}
if (strcmp($approval, "approve") == 0 &&
strcmp($default_approval, "approve")) {
USERERROR("You cannot approve $user in $project/$group without ".
"approval in the default group ($project/$project)!", 1);
}
}
#
# Sanity check. I hate this stuff.
#
while (list ($user, $value) = each ($projectchecks)) {
$projtrust = array();
$grouptrust = array();
$pidlist = array();
while (list ($a, $b) = each ($value)) {
$pid = $b[0];
$gid = $b[1];
$trust = $b[2];
$foo = $projtrust[$pid];
$bar = $grouptrust[$pid];
#echo "$user $pid $gid $trust $foo $bar<br>\n";
#
# This looks for different trust levels in different subgroups
# of the same project. We are only checking the form arguments
# here; we will do a check against the DB below.
#
if (strcmp($pid, $gid)) {
if (isset($grouptrust[$pid]) &&
strcmp($grouptrust[$pid], $trust)) {
USERERROR("User $user may not have different trust levels in ".
"different subgroups of $pid!", 1);
}
$grouptrust[$pid] = $trust;
}
else {
#
# Stash the project default group trust so that we can also
# do a consistency check against it.
#
$projtrust[$pid] = $trust;
}
$pidlist[$pid] = $pid;
}
reset($value);
while (list ($pid, $foo) = each ($pidlist)) {
# Skip if no subgroups were being approved.
if (! isset($grouptrust[$pid]))
continue;
#
# This does a consistency check against subgroups in the DB.
# If we are approving to any subgroups in the form submittal,
# make sure that the user is not in any other subgroups of the
# project with a different trust level.
#
$query_result =
DBQueryFatal("select trust from group_membership ".
"where uid='$user' and pid='$pid' ".
" and pid!=gid and trust!='none' ".
" and trust!='$grouptrust[$pid]'");
if (mysql_num_rows($query_result)) {
USERERROR("User $user may not have different trust levels in ".
"different subgroups of $pid!", 1);
}
#
# This does a level check between the subgroups and the project.
# Do not allow a higher trust level in the default group than in
# the subgroups.
#
if (isset($projtrust[$pid]))
$ptrust = TBTrustConvert($projtrust[$pid]);
else
$ptrust = TBProjTrust($user, $pid);
$bad = 0;
$query_result =
DBQueryFatal("select trust from group_membership ".
"where uid='$user' and trust!='none' ".
" and pid='$pid' and gid!=pid");
while ($row = mysql_fetch_array($query_result)) {
if ($ptrust > TBTrustConvert($row[0])) {
$bad = 1;
break;
}
}
#echo "F $user $bad $ptrust $pid $grouptrust[$pid]<br>\n";
if ($bad ||
$ptrust > TBTrustConvert($grouptrust[$pid])) {
USERERROR("User $user may not have a higher trust level in ".
"the default group of $pid, than in a subgroup!", 1);
}
}
}
reset($HTTP_POST_VARS);
#
# Okay, all sanity tests passed for all post vars. Now do the actual work.
#
while (list ($header, $value) = each ($HTTP_POST_VARS)) {
#echo "$header: $value<br>\n";
$approval_string = strstr($header, "\$\$approval-");
if (! $approval_string) {
continue;
}
$user = substr($header, 0, strpos($header, "\$\$", 0));
$projgrp = substr($approval_string, strlen("\$\$approval-"));
$project = substr($projgrp, 0, strpos($projgrp, "/", 0));
$group = substr($projgrp, strpos($projgrp, "/", 0) + 1);
$approval = $value;
#
# Lets get group leader email, just in case the person doing the approval
# is not the head of the project or group. This is polite to do.
# Corresponding trust value.
#
$foo = "$user\$\$trust-$project/$group";
$newtrust = $$foo;
#
# Get the current status for the user, which we might need to change.
#
# We change the status only if this person is joining his first project.
# In this case, the status will be either "newuser" or "unapproved",
# and we will change it to "unapproved" or "active", respectively.
# If the status is "active", we leave it alone.
#
$query_result =
DBQueryFatal("SELECT usr_email,usr_name from users as u ".
"left join groups as g on g.leader=u.uid ".
"where g.pid='$project' and g.gid='$group'");
DBQueryFatal("SELECT status,usr_email,usr_name from users where ".
"uid='$user'");
if (mysql_num_rows($query_result) == 0) {
TBERROR("Retrieving user info for project $project leader", 1);
TBERROR("Unknown user $user", 1);
}
$row = mysql_fetch_row($query_result);
$phead_email = $row[0];
$phead_name = $row[1];
$curstatus = $row[0];
$user_email = $row[1];
$user_name = $row[2];
#echo "Status = $curstatus, Email = $user_email<br>\n";
#
# Email info for current user.
#
TBUserInfo($uid, $uid_name, $uid_email);
#
# Email info for the group leader too.
#
TBGroupLeader($project, $group, $groupleader);
TBUserInfo($groupleader, $phead_name, $phead_email);
#
# Well, looks like everything is okay. Change the project membership
# value appropriately.
#
if (strcmp($approval, "postpone") == 0) {
echo "<p><h3>
Membership status for user $user was postponed for
later decision.
</h3>\n";
echo "<p>
Membership status for user $user in $project/$group was
<b>postponed</b> for later decision.\n";
continue;
}
if (strcmp($approval, "deny") == 0) {
......@@ -151,10 +361,10 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
" gid='$group'");
mail("$user_name '$user' <$user_email>",
"TESTBED: Project '$project' Membership Denied",
"TESTBED: Membership Denied in '$project/$group'",
"\n".
"This message is to notify you that you have been denied\n".
"membership in project $project\n".
"membership in project/group $project/$group.\n".
"\n\n".
"Thanks,\n".
"Testbed Ops\n".
......@@ -164,10 +374,10 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
"Bcc: $TBMAIL_AUDIT\n".
"Errors-To: $TBMAIL_WWW");
echo "<h3><p>
User $user was denied membership in project $project.
The user will need to reapply again if this was in error.
</h3>\n";
echo "<p>
User $user was <b>denied</b> membership in $project/$group.
<br>
The user will need to reapply again if this was in error.\n";
continue;
}
......@@ -191,11 +401,11 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
# If yes, then we cannot safely delete the user account.
#
if (mysql_num_rows($query_result)) {
echo "<h3><p>
User $user was denied membership in project $project.<br>
echo "<p>
User $user was <b>denied</b> membership in $project/$group.
<br>
Since the user is a member (or requesting membership)
in other projects, the account cannot be safely removed.
</h3>\n";
in other projects, the account cannot be safely removed.\n";
continue;
}
......@@ -208,20 +418,21 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
#
if (strcmp($curstatus, "newuser") &&
strcmp($curstatus, "unapproved")) {
echo "<h3><p>
User $user was denied membership in project $project.<br>
echo "<p>
User $user was <b>denied</b> membership in $project/$group.
<br>
Since the user has been approved by, or was active in other
projects in the past, the account cannot be safely removed.
</h3>\n";
\n";
continue;
}
$query_result = DBQueryFatal("delete FROM users where uid='$user'");
echo "<h3><p>
User $user was denied membership in project $project.<br>
The account has also been terminated with prejudice!
</h3>\n";
echo "<p>
User $user was <b>denied</b> membership in $project/$group.
<br>
The account has also been <b>terminated</b> with prejudice!\n";
continue;
}
......@@ -235,23 +446,6 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
"WHERE uid='$user' and pid='$project' and ".
" gid='$group'");
#
# Messy. If this is a new user joining a subgroup, and that new user
# is not already in the project, we need to add a second record to
# the project membership.
#
if (strcmp($project, $group)) {
TBGroupMember($user, $project, $project, $isapproved);
if (! $isapproved) {
$query_result =
DBQueryFatal("UPDATE group_membership ".
"set trust='$newtrust',date_approved=now() ".
"WHERE uid='$user' and pid='$project' and ".
" gid='$project'");
}
}
#
# Change the status if necessary. This only happens for new
# users being added to their first project. After this, the status is
......@@ -274,10 +468,11 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
}
mail("$user_name '$user' <$user_email>",
"TESTBED: Project '$project' Membership Approval",
"TESTBED: Membership Approved in '$project/$group' ",
"\n".
"This message is to notify you that you have been approved\n".
"as a member of project $project with $newtrust permissions.\n".
"as a member of project/group $project/$group with\n".
"$newtrust permissions.\n".
"\n\n".
"Thanks,\n".
"Testbed Ops\n".
......@@ -287,19 +482,17 @@ while (list ($header, $value) = each ($HTTP_POST_VARS)) {
"Bcc: $TBMAIL_AUDIT\n".
"Errors-To: $TBMAIL_WWW");
echo "<p>
User $user was <b>granted</b> membership in $project/$group
with $newtrust permissions.\n";
#
# Create user account on control node.
#
SUEXEC($uid, "flux", "mkacct-ctrl $user", 0);
echo "<h3><p>
User $user was granted membership in project $project
with $newtrust permissions.
</h3>\n";
continue;
}
TBERROR("Invalid approval value $approval in approveuser.php3.", 1);
}
#
......
......@@ -17,7 +17,7 @@ echo "
Use this page to approve new members of your Project or Group. Once
approved, they will be able to log into machines in your Project's
experiments. Be sure to toggle the menu options appropriately for
each pending user.
each pending user.
<p>
<table cellspacing=2 border=0>
......@@ -83,6 +83,15 @@ echo "
like.</td>
</tr>
</table>
<center>
<b>Important group
<a href='docwrapper.php3?docname=groups.html#SECURITY'>
security issues</a> are discussed in the
<a href='docwrapper.php3?docname=groups.html'>Groups Tutorial</a>
</b>
</center><br>
\n";
#
......@@ -111,7 +120,8 @@ $query_result =
" g.uid!='$auth_usr' and g.trust='none' ".
"WHERE authed.uid='$auth_usr' and ".
" (authed.trust='group_root' or ".
" authed.trust='project_root')");
" authed.trust='project_root') ".
"ORDER BY g.uid,g.pid,g.gid");
if (mysql_num_rows($query_result) == 0) {
USERERROR("You have no new project members who need approval.", 1);
......@@ -204,18 +214,18 @@ while ($usersrow = mysql_fetch_array($query_result)) {
<td rowspan=2>$date_applied</td>
<td rowspan=2>
<select name=\"$newuid\$\$approval-$pid/$gid\">
<option value='postpone'>Postpone</option>
<option value='approve'>Approve</option>
<option value='deny'>Deny</option>
<option value='nuke'>Nuke</option>
<option value='postpone'>Postpone </option>
<option value='approve'>Approve </option>
<option value='deny'>Deny </option>
<option value='nuke'>Nuke </option>
</select>
</td>
<td rowspan=2>
<select name=\"$newuid\$\$trust-$pid/$gid\">
<option value='user'>User</option>
<option value='local_root'>Local Root</option>\n";
<option value='user'>User </option>
<option value='local_root'>Local Root </option>\n";
if ($isleader) {
echo " <option value='group_root'>Group Root</option>\n";
echo " <option value='group_root'>Group Root </option>\n";
}
echo " </select>
</td>\n";
......
......@@ -13,6 +13,8 @@ PAGEHEADER("Edit Group Membership");
$uid = GETLOGIN();
LOGGEDINORDIE($uid);
ignore_user_abort(1);
#
# First off, sanity check page args.
#
......@@ -26,16 +28,17 @@ if (!isset($gid) ||
}
#
# We do not allow the default group to be edited. Never ever!
# The default group membership cannot be changed, but the trust levels can.
#
$defaultgroup = 0;
if (strcmp($gid, $pid) == 0) {
USERERROR("You are not allowed to modify a project's default group!", 1);
$defaultgroup = 1;
}
#
# Verify permission.
#
if (! TBProjAccessCheck($uid, $pid, 0, $TB_PROJECT_EDITGROUP)) {
if (! TBProjAccessCheck($uid, $pid, $gid, $TB_PROJECT_EDITGROUP)) {
USERERROR("You do not have permission to edit group $gid in ".
"project $pid!", 1);
}
......@@ -56,45 +59,196 @@ $curmembers_result =
# added. Do not include people in the above list, obviously!
#
$nonmembers_result =
DBQueryFatal("select m.uid,m.trust from group_membership as m ".
DBQueryFatal("select m.uid from group_membership as m ".
"left join group_membership as a on ".
" a.uid=m.uid and a.pid=m.pid and a.gid='$gid' ".
"where m.pid='$pid' and m.gid=m.pid and a.uid is NULL");
function TBCheckTrustConsistency($user, $pid, $gid, $newtrust)
{
#
# If changing default group trust level, then compare levels.
# A user may not have greater permission in the default group than
# in a subgroup.
#
if (strcmp($pid, $gid)) {
$projtrust = TBProjTrust($user, $pid);
if ($projtrust > TBTrustConvert($newtrust)) {
USERERROR("User $user may not have a higher trust level in ".
"the default group of $pid, than in subgroup $gid!", 1);
}
}
else
$projtrust = TBTrustConvert($newtrust);
#
# Get all the subgroups not equal to the subgroup being changed.
#
$query_result =
DBQueryFatal("select trust,gid from group_membership ".
"where uid='$user' and pid='$pid' and trust!='none' ".
" and gid!=pid and gid!='$gid'");
while ($row = mysql_fetch_array($query_result)) {
$grptrust = $row[0];
$ogid = $row[1];
if ($projtrust > TBTrustConvert($grptrust)) {
USERERROR("User $user may not have a higher trust level in ".
"the default group of $pid, than in subgroup $ogid!", 1);
}
if (strcmp($pid, $gid)) {
#
# Check to make sure new trust is same as all other subgroup trust.
#
if (strcmp($newtrust, $grptrust)) {
USERERROR("User $user may not have different trust levels in ".
"different subgroups of $pid!", 1);
}
}
}
return 1;
}
#
# First pass does checks. Second pass does the real thing.
#
#
# Go through the list of current members. For each one, check to see if
# the checkbox for that person was checked. If not, delete the person
# from the group membership.
# from the group membership. Otherwise, look to see if the trust level
# has been changed.
#
if (mysql_num_rows($curmembers_result)) {
while ($row = mysql_fetch_array($curmembers_result)) {
$deluid = $row[0];
$foo = "delete_$row[0]";
$user = $row[0];
$foo = "change_$user";
#