Commit 8fffc2f3 authored by Russ Fish's avatar Russ Fish

Move editgroup page form logic to a backend Perl script.

     www/editgroup.php3 - The reworked PHP page.
     www/editgroup_form.php3 - Removed, form merged into editgroup.php3 .
     www/showgroup.php3 - Link to editgroup.php3, rather than editgroup_form.php3 .
     www/group_defs.php - Add an Image::EditGroup class method
                            bridging to the script via XML.
     backend/{editgroup,GNUmakefile}.in configure configure.in - New backend script.
     db/Group.pm.in - Add an EditGroup worker class method for script arg checking.
                      Also the missing NonMemberList and CheckTrustConsistency methods,
                      and a GETTRUST flag to MemberList, as in the PHP version.
     db/User.pm.in - Add the missing but tiny {Set,Get}TempData methods.
     sql/database-fill.sql - Add gid_idx to the table_regex 'groups' checking patterns.
parent fba8af4a
...@@ -13,9 +13,9 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@ ...@@ -13,9 +13,9 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@
include $(OBJDIR)/Makeconf include $(OBJDIR)/Makeconf
BIN_SCRIPTS = moduserinfo newgroup newmmlist editexp editimageid \ BIN_SCRIPTS = moduserinfo newgroup newmmlist editexp editimageid \
editnodetype editsitevars newimageid editnodetype editsitevars newimageid editgroup
WEB_BIN_SCRIPTS = webmoduserinfo webnewgroup webnewmmlist webeditimageid \ WEB_BIN_SCRIPTS = webmoduserinfo webnewgroup webnewmmlist webeditimageid \
webeditnodetype webeditsitevars webnewimageid webeditnodetype webeditsitevars webnewimageid webeditgroup
WEB_SBIN_SCRIPTS= WEB_SBIN_SCRIPTS=
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use strict;
use Getopt::Std;
use XML::Simple;
use Data::Dumper;
#
# Back-end script to edit group membership.
#
sub usage()
{
print("Usage: editgroup [-v] <xmlfile>\n");
exit(-1);
}
my $optlist = "dv";
my $debug = 0;
my $verify = 0; # Check data and return status only.
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use User;
use Group;
# Protos
sub fatal($);
sub UserError(;$);
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"v"})) {
$verify = 1;
}
if (@ARGV != 1) {
usage();
}
my $xmlfile = shift(@ARGV);
#
# Map invoking user to object.
# If invoked as "nobody" we are coming from the web interface and the
# current user context is "implied" (see tbauth.php3).
#
my $this_user;
if (getpwuid($UID) ne "nobody") {
$this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
# You don't need admin privileges to edit group membership.
}
else {
#
# Check the filename when invoked from the web interface; must be a
# file in /tmp.
#
if ($xmlfile =~ /^([-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in pathname: $xmlfile");
}
# Use realpath to resolve any symlinks.
my $translated = `realpath $xmlfile`;
if ($translated =~ /^(\/tmp\/[-\w\.\/]+)$/) {
$xmlfile = $1;
}
else {
fatal("Bad data in translated pathname: $xmlfile");
}
# The web interface (and in the future the xmlrpc interface) sets this.
$this_user = User->ImpliedUser();
if (! defined($this_user)) {
fatal("Cannot determine implied user!");
}
}
#
# These are the fields that we allow to come in from the XMLfile.
#
my $SLOT_OPTIONAL = 0x1; # The field is not required.
my $SLOT_REQUIRED = 0x2; # The field is required and must be non-null.
my $SLOT_ADMINONLY = 0x4; # Only admins can set this field.
#
# XXX We should encode all of this in the DB so that we can generate the
# forms on the fly, as well as this checking code.
#
my %xmlfields =
# XML Field Name DB slot name Flags Default
("group" => ["gid_idx", $SLOT_REQUIRED]);
# And also trust args, see below.
#
# Must wrap the parser in eval since it exits on error.
#
my $xmlparse = eval { XMLin($xmlfile,
VarAttr => 'name',
ContentKey => '-content',
SuppressEmpty => undef); };
fatal($@)
if ($@);
#
# Process and dump the errors (formatted for the web interface).
# We should probably XML format the errors instead but not sure I want
# to go there yet.
#
my %errors = ();
#
# Make sure all the required arguments were provided.
#
my $key;
foreach $key (keys(%xmlfields)) {
my (undef, $required, undef) = @{$xmlfields{$key}};
$errors{$key} = "Required value not provided"
if ($required & $SLOT_REQUIRED &&
! exists($xmlparse->{'attribute'}->{"$key"}));
}
UserError()
if (keys(%errors));
#
# We build up an array of arguments to pass to Group->EditGroup() as we check
# the attributes.
#
my %editgroup_args = ();
foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
if ($debug) {
print STDERR "User attribute: '$key' -> '$value'\n";
}
# XXX Special for trust args. Either:
# (change|add)_xxx=permit or
# Uxxx$$trust=(user|(local|group)_root)
# where xxx is the uid_idx of a user.
if (($key =~ /^(change|add)_[0-9]+$/ &&
$value eq "permit") ||
($key =~ /^U[0-9]+\$\$trust$/ &&
$value =~ /^(user|(local|group)_root)$/)) {
if ($debug) {
print STDERR "Trust: '$key' -> '$value'\n";
}
$editgroup_args{$key} = $value;
next;
}
$errors{$key} = "Unknown attribute"
if (!exists($xmlfields{$key}));
my ($dbslot, $required, $default) = @{$xmlfields{$key}};
if ($required & $SLOT_REQUIRED) {
# A slot that must be provided, so do not allow a null value.
if (!defined($value)) {
$errors{$key} = "Must provide a non-null value";
next;
}
}
if ($required & $SLOT_OPTIONAL) {
# Optional slot. If value is null skip it. Might not be the correct
# thing to do all the time?
if (!defined($value)) {
next
if (!defined($default));
$value = $default;
}
}
if ($required & $SLOT_ADMINONLY) {
# Admin implies optional, but thats probably not correct approach.
$errors{$key} = "Administrators only"
if (! $this_user->IsAdmin());
}
# Now check that the value is legal.
if (! TBcheck_dbslot($value, "groups", $dbslot, TBDB_CHECKDBSLOT_ERROR)) {
$errors{$key} = TBFieldErrorString();
next;
}
$editgroup_args{$dbslot} = $value;
}
UserError()
if (keys(%errors));
#
# Now do special checks.
#
my $group = Group->Lookup($editgroup_args{"gid_idx"});
if (!defined($group)) {
UserError("Group: No such group");
}
if (!$group->AccessCheck($this_user, TB_PROJECT_EDITGROUP())) {
UserError("Group: Not enough permission");
}
# More checking is done in the first pass of Group->EditGroup().
exit(0)
if ($verify);
#
# Now safe to edit group membership.
#
# We pass the group along as an argument to EditGroup(), so remove it from
# the argument array.
#
delete($editgroup_args{"gid_idx"});
my $usrerr;
my $editgroup_val = Group->EditGroup($group, $this_user,
\%editgroup_args, \$usrerr);
UserError($usrerr)
if (defined($usrerr));
fatal("Could not create new Group!")
if (!defined($editgroup_val));
exit(0);
sub fatal($)
{
my ($mesg) = @_;
print STDERR "*** $0:\n".
" $mesg\n";
# Exit with negative status so web interface treats it as system error.
exit(-1);
}
sub UserError(;$)
{
my ($mesg) = @_;
if (keys(%errors)) {
foreach my $key (keys(%errors)) {
my $val = $errors{$key};
print "${key}: $val\n";
}
}
print "$mesg\n"
if (defined($mesg));
# Exit with positive status so web interface treats it as user error.
exit(1);
}
...@@ -2429,7 +2429,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ ...@@ -2429,7 +2429,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
account/quotamail account/mkusercert account/newproj account/newuser \ account/quotamail account/mkusercert account/newproj account/newuser \
backend/GNUmakefile backend/moduserinfo backend/newgroup \ backend/GNUmakefile backend/moduserinfo backend/newgroup \
backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \
backend/editsitevars backend/newimageid \ backend/editsitevars backend/newimageid backend/editgroup \
tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \
tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \
tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \
......
...@@ -809,7 +809,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ ...@@ -809,7 +809,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
account/quotamail account/mkusercert account/newproj account/newuser \ account/quotamail account/mkusercert account/newproj account/newuser \
backend/GNUmakefile backend/moduserinfo backend/newgroup \ backend/GNUmakefile backend/moduserinfo backend/newgroup \
backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \ backend/newmmlist backend/editexp backend/editimageid backend/editnodetype \
backend/editsitevars backend/newimageid \ backend/editsitevars backend/newimageid backend/editgroup \
tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \ tbsetup/GNUmakefile tbsetup/console_setup tbsetup/spewlogfile \
tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \ tbsetup/spewrpmtar tbsetup/gentopofile tbsetup/power_sgmote.pm \
tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \
......
...@@ -22,7 +22,8 @@ use English; ...@@ -22,7 +22,8 @@ use English;
use Data::Dumper; use Data::Dumper;
use File::Basename; use File::Basename;
use overload ('""' => 'Stringify'); use overload ('""' => 'Stringify');
use vars qw($MEMBERLIST_FLAGS_UIDSONLY $MEMBERLIST_FLAGS_ALLUSERS); use vars qw($MEMBERLIST_FLAGS_UIDSONLY $MEMBERLIST_FLAGS_ALLUSERS
$MEMBERLIST_FLAGS_GETTRUST);
# Configure variables # Configure variables
my $TB = "@prefix@"; my $TB = "@prefix@";
...@@ -42,6 +43,7 @@ my $debug = 0; ...@@ -42,6 +43,7 @@ my $debug = 0;
# MemberList flags. # MemberList flags.
$MEMBERLIST_FLAGS_UIDSONLY = 0x01; $MEMBERLIST_FLAGS_UIDSONLY = 0x01;
$MEMBERLIST_FLAGS_ALLUSERS = 0x02; $MEMBERLIST_FLAGS_ALLUSERS = 0x02;
$MEMBERLIST_FLAGS_GETTRUST = 0x04;
# Little helper and debug function. # Little helper and debug function.
sub mysystem($) sub mysystem($)
...@@ -342,6 +344,262 @@ sub Delete($) ...@@ -342,6 +344,262 @@ sub Delete($)
return 0; return 0;
} }
#
# Worker class method to edit group membership.
# Makes two passes, first checking consistency, then updating the DB.
#
sub EditGroup($$$$)
{
my ($class, $group, $this_user, $argref, $usrerr_ref) = @_;
my %mods;
my $noreport;
#
# The default group membership cannot be changed, but the trust levels can.
#
my $defaultgroup = $group->IsProjectGroup();
#
# See if user is allowed to add non-members to group.
#
my $grabusers = 0;
if ($group->AccessCheck($this_user, TB_PROJECT_GROUPGRABUSERS())) {
$grabusers = 1;
}
#
# See if user is allowed to bestow group_root upon members of group.
#
my $bestowgrouproot = 0;
if ($group->AccessCheck($this_user, TB_PROJECT_BESTOWGROUPROOT())) {
$bestowgrouproot = 1;
}
#
# Grab the user list for the group. Provide a button selection of people
# that can be removed. The group leader cannot be removed!
# Do not include members that have not been approved
# to main group either! This will force them to go through the approval
# page first.
#
my @curmembers;
if ($group->MemberList(\@curmembers, $MEMBERLIST_FLAGS_GETTRUST)) {
$$usrerr_ref = "Error: Could not get member list for $group";
return undef;
}
#
# Grab the user list from the project. These are the people who can be
# added. Do not include people in the above list, obviously! Do not
# include members that have not been approved to main group either! This
# will force them to go through the approval page first.
#
my @nonmembers;
if ($group->NonMemberList(\@nonmembers)) {
$$usrerr_ref = "Error: Could not get nonmember list for $group";
return undef;
}
#
# First pass does checks. Second pass does the real thing.
#
my $g_pid = $group->pid();
my $g_gid = $group->gid();
my $target_user;
my $target_idx;
my $target_uid;
my $oldtrust;
my $newtrust;
my $foo;
my $bar;
my $cmd;
my $cmd_out;
#
# 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. Otherwise, look to see if the trust level
# has been changed.
#
if ($#curmembers>=0) {
foreach $target_user (@curmembers) {
$target_uid = $target_user->uid();
$target_idx = $target_user->uid_idx();
$oldtrust = $target_user->GetTempData();
$foo = "change_$target_idx";
#
# Is member to be deleted?
#
if (!$defaultgroup && !exists($argref->{$foo})) {
# Yes.
next;
}
#
# There should be a corresponding trust variable in the POST vars.
# Note that we construct the variable name and indirect to it.
#
$foo = "U${target_idx}\$\$trust";
if (!exists($argref->{$foo}) || $argref->{$foo} eq "") {
$$usrerr_ref = "Error: finding trust(1) for $target_uid";
return undef;
}
$newtrust = $argref->{$foo};
if ($newtrust ne $Group::MemberShip::TRUSTSTRING_USER &&
$newtrust ne $Group::MemberShip::TRUSTSTRING_LOCALROOT &&
$newtrust ne $Group::MemberShip::TRUSTSTRING_GROUPROOT) {
$$usrerr_ref = "Error: Invalid trust $newtrust for $target_uid";
return undef;
}
#
# If the user is attempting to bestow group_root on a user who
# did not previously have group_root, check to see if the operation is
# permitted.
#
if ($newtrust ne $oldtrust &&
$newtrust eq $Group::MemberShip::TRUSTSTRING_GROUPROOT &&
!$bestowgrouproot) {
$$usrerr_ref = "Group: You do not have permission to bestow".
" group root trust to users in $g_pid/$g_gid!";
}
$group->Group::MemberShip::CheckTrustConsistency($target_user,
$newtrust, 1);
}
}
#
# Go through the list of non members. For each one, check to see if
# the checkbox for that person was checked. If so, add the person
# to the group membership, with the trust level specified.
# Only do this if user has permission to grab users.
#
if ($grabusers && !$defaultgroup && $#nonmembers>=0) {
foreach $target_user (@nonmembers) {
$target_uid = $target_user->uid();
$target_idx = $target_user->uid_idx();
$foo = "add_$target_idx";
if (exists($argref->{$foo}) && $argref->{$foo} eq "permit"){
#
# There should be a corresponding trust variable in the POST vars.
# Note that we construct the variable name and indirect to it.
#
$bar = "U${target_idx}\$\$trust";
if (!exists($argref->{$bar}) || $argref->{$bar} eq "") {
$$usrerr_ref = "Error: finding trust(2) for $target_uid";
return undef;
}
$newtrust = $argref->{$bar};
if ($newtrust ne $Group::MemberShip::TRUSTSTRING_USER &&
$newtrust ne $Group::MemberShip::TRUSTSTRING_LOCALROOT &&
$newtrust ne $Group::MemberShip::TRUSTSTRING_GROUPROOT) {
$$usrerr_ref = "Error: " .
"Invalid trust $newtrust for $target_uid";
return undef;
}
if ($newtrust eq $Group::MemberShip::TRUSTSTRING_GROUPROOT
&& !$bestowgrouproot) {
$$usrerr_ref = "Error: You do not have permission to".
" bestow group root trust to users in $g_pid/$g_gid!";
return undef;
}
$group->Group::MemberShip::CheckTrustConsistency($target_user,
$newtrust, 1);
}
}
}
#
# Now do the second pass, which makes the changes.
#
### STARTBUSY("Applying group membership changes");
#
# 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. Otherwise, look to see if the trust level
# has been changed.
#
if ($#curmembers>=0) {
foreach $target_user (@curmembers) {
$target_uid = $target_user->uid();
$target_idx = $target_user->uid_idx();
$oldtrust = $target_user->GetTempData();
$foo = "change_$target_idx";
if (!$defaultgroup && !exists($argref->{$foo})) {
$cmd = "modgroups -r $g_pid:$g_gid $target_uid";
##print $cmd . "\n";
$cmd_out = `$cmd`;
if ($?) {
$$usrerr_ref = "Error: " . $cmd_out;
return undef;
}
}
#
# There should be a corresponding trust variable in the POST vars.
# Note that we construct the variable name and indirect to it.
#
$foo = "U${target_idx}\$\$trust";
$newtrust = $argref->{$foo};
if ($oldtrust ne $newtrust) {
$cmd = "modgroups -m $g_pid:$g_gid:$newtrust $target_uid";
##print $cmd . "\n";
$cmd_out = `$cmd`;
if ($?) {
$$usrerr_ref = "Error: " . $cmd_out;
return undef;
}
}
}
}
#
# Go through the list of non members. For each one, check to see if
# the checkbox for that person was checked. If so, add the person
# to the group membership, with the trust level specified.
#
if ($grabusers && !$defaultgroup && $#nonmembers>=0) {
foreach $target_user (@nonmembers) {
$target_uid = $target_user->uid();
$target_idx = $target_user->uid_idx();
$foo = "add_$target_idx";
if (exists($argref->{$foo}) && $argref->{$foo} eq "permit"){
#
# There should be a corresponding trust variable in the POST vars.
# Note that we construct the variable name and indirect to it.
#
$bar = "U${target_idx}\$\$trust";
$newtrust = $argref->{$bar};
$cmd = "modgroups -a $g_pid:$g_gid:$newtrust $target_uid";
##print $cmd . "\n";
$cmd_out = `$cmd`;
if ($?) {
$$usrerr_ref = "Error: " . $cmd_out;
return undef;
}
}
}
}
return 1;
}
# #
# Generic function to look up some table values given a set of desired # Generic function to look up some table values given a set of desired
# fields and some conditions. Pretty simple, not widely useful, but it # fields and some conditions. Pretty simple, not widely useful, but it
...@@ -493,7 +751,7 @@ sub AccessCheck($$$) ...@@ -493,7 +751,7 @@ sub AccessCheck($$$)
return 1; return 1;
} }
if ($gid == $pid) { if ($gid eq $pid) {
# #
# Only project_root can bestow group_root in default group, # Only project_root can bestow group_root in default group,
# and we already established that they are not project_root, # and we already established that they are not project_root,
...@@ -858,6 +1116,9 @@ sub LeaderMailList($) ...@@ -858,6 +1116,9 @@ sub LeaderMailList($)
sub MemberList($$;$$) sub MemberList($$;$$)
{ {
my ($self, $prval, $flags, $desired_trust) = @_; my ($self, $prval, $flags, $desired_trust) = @_;
my $exclude_leader = 1; # Could be controlled by a flag.
my $leader = $self->GetLeader();
my $leader_idx = $leader->uid_idx();