Commit 13c85de6 authored by Leigh B Stoller's avatar Leigh B Stoller

Powder license support. Just project level, easy to add user level

licensing later.
parent 33b207d3
......@@ -44,6 +44,7 @@ my $impotent= 0;
my $silent = 0;
my $portal;
my $resend;
my %licenses = ();
#
# Configure variables
......@@ -53,6 +54,7 @@ my $TBOPS = "@TBOPSEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $TBBASE = "@TBBASE@";
my $TBWWW = "@TBWWW@";
my $LICENSES = "$TB/sbin/manage_licenses";
#
# This script is setuid, so please do not run it as root. Hard to track
......@@ -233,12 +235,28 @@ if (exists($xmlparse->{'attribute'}->{"portal"})) {
fatal("Bad portal: $portal");
}
}
# Licenses. Save for later, but need to delete.
foreach my $key (keys(%{ $xmlparse->{'attribute'} })) {
if ($key =~ /^license_([-\w]+)$/) {
my $value = $xmlparse->{'attribute'}->{"$key"}->{'value'};
my $name = $1;
if (lc($value) eq "yes") {
system("$LICENSES show $name");
if ($?) {
fatal("Invalid license name: $name");
}
$licenses{$name} = $name;
print "requested license $name\n";
}
delete($xmlparse->{'attribute'}->{"$key"});
}
}
#
# Make sure all the required arguments were provided.
#
foreach my $key (keys(%required)) {
fatal("Missing required attribute '$key'")
if (! exists($xmlparse->{'attribute'}->{"$key"}));
}
......@@ -386,6 +404,18 @@ if (!defined($newproj)) {
}
my $new_idx = $newproj->pid_idx();
#
# Add any licenses.
#
if (keys(%licenses)) {
foreach my $name (keys(%licenses)) {
system("$LICENSES require $name $new_pid");
if ($?) {
fatal("Invalid license name: $name");
}
}
}
#
# See if we are in an initial Emulab setup. If so, no email sent.
#
......
......@@ -36,7 +36,8 @@ BIN_SCRIPTS = manage_profile manage_instance manage_dataset \
manage_images rtecheck checkprofile manage_extensions \
create_slivers searchip
SBIN_SCRIPTS = apt_daemon aptevent_daemon portal_xmlrpc apt_checkup \
portal_monitor apt_scheduler portal_resources
portal_monitor apt_scheduler portal_resources \
manage_licenses
LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm APT_Dataset.pm APT_Geni.pm \
APT_Aggregate.pm APT_Utility.pm APT_Rspec.pm
WEB_BIN_SCRIPTS = webmanage_profile webmanage_instance webmanage_dataset \
......@@ -44,7 +45,7 @@ WEB_BIN_SCRIPTS = webmanage_profile webmanage_instance webmanage_dataset \
webrspec2genilib webmanage_reservations webmanage_gitrepo \
webmanage_images webrtecheck websearchip
APACHEHOOKS = apt_gitrepo.hook
WEB_SBIN_SCRIPTS= webportal_xmlrpc
WEB_SBIN_SCRIPTS= webportal_xmlrpc webmanage_licenses
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
USERLIBEXEC = rungenilib.proxy genilib-jail genilib-iocage gitrepo.proxy
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file 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. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
use English;
use strict;
use Getopt::Std;
use Data::Dumper;
use Date::Parse;
#
# Manage licenses ...
#
sub usage()
{
print STDERR "Usage: manage_licenses add [-h] ...\n";
print STDERR "Usage: manage_licenses modify [-h] ...\n";
print STDERR " manage_licenses delete ...\n";
print STDERR " manage_licenses require ...\n";
print STDERR " manage_licenses norequire ...\n";
print STDERR " manage_licenses outstanding ...\n";
print STDERR " manage_licenses accept ...\n";
print STDERR " manage_licenses accepted ...\n";
print STDERR " manage_licenses list ...\n";
print STDERR " manage_licenses show ...\n";
print STDERR "Use -h on any command to get more help\n";
exit(-1);
}
my $optlist = "dt:";
my $debug = 0;
my $webtask_id;
my $webtask;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $OURDOMAIN = "@OURDOMAIN@";
#
# 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 emdb;
use emutil;
use Brand;
use User;
use Project;
use Group;
use libtestbed;
use WebTask;
# Protos
sub AddLicense();
sub DeleteLicense();
sub RequireLicense($);
sub ListLicenses();
sub ShowLicense();
sub OutstandingLicenses($);
sub LookupLicense($);
sub fatal($);
#
# 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{"t"})) {
$webtask_id = $options{"t"};
$webtask = WebTask->Lookup($webtask_id);
if (!defined($webtask)) {
fatal("Could not lookup webtask $webtask_id");
}
# Convenient.
$webtask->AutoStore(1);
}
if (defined($options{"d"})) {
$debug++;
}
if (@ARGV < 1) {
usage();
}
my $action = shift(@ARGV);
if ($action eq "add") {
AddLicense();
}
elsif ($action eq "list") {
ListLicenses();
}
elsif ($action eq "show") {
ShowLicense();
}
elsif ($action eq "delete") {
DeleteLicense();
}
elsif ($action eq "require") {
RequireLicense("require");
}
elsif ($action eq "norequire") {
RequireLicense("norequire");
}
elsif ($action eq "accept") {
RequireLicense("accept");
}
elsif ($action eq "outstanding") {
OutstandingLicenses("outstanding");
}
elsif ($action eq "accepted") {
OutstandingLicenses("accepted");
}
else {
usage();
}
exit(0);
#
# Add a license to the list of licenses.
#
sub AddLicense()
{
my $optlist = "hu";
my ($license_type, $description_type);
my $usage = sub {
print STDERR "Usage: add <name> [-u] <form prompt> <license.[md,txt,html]> ";
print STDERR " [description.[md,txt,html]]\n";
print STDERR " -u - Users must agree; default ".
"is project leader only\n";
print STDERR " form prompt - Query for the form ".
"('Do you need MathLab')\n";
print STDERR " name - A descriptive token\n";
print STDERR " license - License as markdown,text or html\n";
print STDERR " description - Optional description to display above\n";
print STDERR " license in the web UI\n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
if (defined($options{"u"})) {
fatal("The -u option is not implemented yet");
}
&$usage()
if (@ARGV < 3);
my $token = shift(@ARGV);
my $prompt = shift(@ARGV);
my $lfile = shift(@ARGV);
my $dfile = shift(@ARGV) if (@ARGV);
if ($token !~ /^\w+$/) {
fatal("Invalid characters in name, alphanumeric only please");
}
if (! TBcheck_dbslot($prompt, "default",
"tinytext", TBDB_CHECKDBSLOT_ERROR)) {
fatal("Invalid prompt: " . TBFieldErrorString());
}
if ($lfile =~ /\.((md|txt|html))$/) {
$license_type = $1;
}
else {
fatal("License file extension must be one of .md, .txt, or .html");
}
if (! -e $lfile) {
fatal("License file does not exist or cannot be read");
}
if (defined($dfile)) {
if ($dfile =~ /\.((md|txt|html))$/) {
$description_type = $1;
}
else {
fatal("Description file extension must be one of ".
".md, .txt, or .html");
}
if (! -e $dfile) {
fatal("Description file does not exist or cannot be read");
}
}
my ($license, $description, $safe_license, $safe_description, $safe_prompt);
$license = emutil::ReadFile($lfile)
or fatal("Could not open $lfile: $!");
$safe_license = DBQuoteSpecial($license);
if (defined($dfile)) {
$description = emutil::ReadFile($dfile)
or fatal("Could not open $dfile: $!");
$safe_description= DBQuoteSpecial($description);
}
$safe_prompt = DBQuoteSpecial($prompt);
my $query_result =
DBQueryFatal("select license_idx from licenses ".
"where license_name='$token'");
if ($query_result->numrows) {
my ($idx) = $query_result->fetchrow_array();
fatal("Already have a license with that name: $idx");
}
$query_result =
DBQueryFatal("insert into licenses set created=now(), ".
" license_name='$token', license_text=$safe_license, ".
" license_type='$license_type',form_text=$safe_prompt " .
(defined($dfile) ?
", description_text=$safe_description, ".
" description_type='$description_type'" : ""));
my ($idx) = $query_result->insertid();
print "License created with index $idx\n";
exit(0);
}
#
# List licenses
#
sub ListLicenses()
{
my $optlist = "h";
my $usage = sub {
print STDERR "Usage: list \n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
&$usage()
if (@ARGV);
my $query_result =
DBQueryFatal("select * from licenses order by license_idx");
if ($query_result->numrows) {
printf("%-5s %-12s %s\n", "Index", "Name", "Level");
printf("------------------------\n");
while (my $row = $query_result->fetchrow_hashref()) {
my $license_idx = $row->{'license_idx'};
my $license_name = $row->{'license_name'};
my $license_level = $row->{'license_level'};
my $license_text = $row->{'license_text'};
printf("%-5d %-12s %-10s %s\n", $license_idx, $license_name,
$license_level, substr($license_text, 0, 50));
}
}
exit(0);
}
#
# Show a license
#
sub ShowLicense()
{
my $optlist = "hv";
my $verbose = 0;
my $usage = sub {
print STDERR "Usage: show [-v] license_name \n";
print STDERR " -v - Show full license text instead of first line\n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
if (defined($options{"v"})) {
$verbose = 1;
}
&$usage()
if (@ARGV != 1);
my $name = shift(@ARGV);
my $row = LookupLicense($name);
if (!defined($row)) {
exit(-1);
}
print "IDX: " . $row->{'license_idx'} . "\n";
print "Name: " . $row->{'license_name'} . "\n";
print "Level: " . $row->{'license_level'} . "\n";
print "Created: " . $row->{'created'} . "\n";
print "Prompt: " . $row->{'form_text'} . "\n";
if ($row->{'description_text'}) {
print "Description:\n";
print " Text Type: " . $row->{'description_type'} . "\n";
if ($verbose) {
print " Text: " . $row->{'description_text'} . "\n";
}
else {
print " Text: " . substr($row->{'description_text'},0,60)."\n";
}
}
print "License:\n";
print " Text Type: " . $row->{'license_type'} . "\n";
if ($verbose) {
print " Text: " . $row->{'license_text'} . "\n";
}
else {
print " Text: " . substr($row->{'license_text'}, 0, 60) . "\n";
}
exit(0);
}
#
# Delete a license
#
sub DeleteLicense()
{
my $optlist = "hf";
my $force = 0;
my $usage = sub {
print STDERR "Usage: delete [-f] license_name\n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
if (defined($options{"f"})) {
$force = 1;
}
&$usage()
if (@ARGV != 1);
my $name = shift(@ARGV);
my $row = LookupLicense($name);
if (!defined($row)) {
exit(-1);
}
if (!$force) {
fatal("Are you sure? Use -f to force deletion");
}
my $idx = $row->{'license_idx'};
DBQueryFatal("delete from project_licenses where license_idx='$idx'");
DBQueryFatal("delete from user_licenses where license_idx='$idx'");
DBQueryFatal("delete from licenses where license_idx='$idx'");
exit(0);
}
#
# Require a license to be accepted
#
sub RequireLicense($)
{
my ($op) = @_;
my $optlist = "h";
my $usage = sub {
print STDERR "Usage: $op license_name pid|uid\n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
&$usage()
if (@ARGV != 2);
my $name = shift(@ARGV);
my $row = LookupLicense($name);
if (!defined($row)) {
exit(-1);
}
my $license_idx = $row->{'license_idx'};
my $token = shift(@ARGV);
if ($row->{'license_level'} eq "project") {
my $project = Project->Lookup($token);
if (!defined($project)) {
fatal("No such project");
}
my $pid = $project->pid();
my $pid_idx = $project->pid_idx();
if ($op eq "norequire") {
DBQueryFatal("delete from project_licenses ".
"where license_idx='$license_idx' and ".
" pid_idx='$pid_idx'");
}
elsif ($op eq "accept") {
DBQueryFatal("update project_licenses set accepted=now() ".
"where license_idx='$license_idx' and ".
" pid_idx='$pid_idx'");
}
else {
DBQueryFatal("insert into project_licenses set ".
" license_idx='$license_idx', pid='$pid', ".
" pid_idx='$pid_idx'");
}
}
else {
fatal("User level licenses are not supported yet")
}
if (defined($webtask)) {
$webtask->Exited(0);
}
exit(0);
}
#
# Outstanding licenses for a user or project.
#
sub OutstandingLicenses($)
{
my ($op) = @_;
my $optlist = "hpul";
my $trust = $Group::MemberShip::TRUSTSTRING_PROJROOT;
my $usage = sub {
print STDERR "Usage: $op -l <license_name>\n";
print STDERR " $op -p <pid>\n";
print STDERR " $op -u <uid>\n";
exit(-1);
};
my %options = ();
if (! getopts($optlist, \%options)) {
&$usage();
}
if (defined($options{"h"})) {
&$usage();
}
&$usage()
if (@ARGV != 1);
my $token = shift(@ARGV);
if (defined($options{"l"})) {
my $row = LookupLicense($token);
if (!defined($row)) {
exit(-1);
}
my $license_idx = $row->{'license_idx'};
my $query_result =
DBQueryFatal("select pl.* from project_licenses as pl ".
"where license_idx='$license_idx' and ".
($op eq "accepted" ?
" pl.accepted is not null" : " pl.accepted is null"));
if ($query_result->numrows) {
printf("%-16s %s\n", "Pid", "Accepted");
printf("-------------------------------\n");
while (my $row = $query_result->fetchrow_hashref()) {
my $pid = $row->{'pid'};
my $accepted = $row->{'accepted'};
printf("%-16s %s\n", $pid,
($op eq "accepted" ? $accepted : "--"));
}
}
}
elsif (defined($options{"p"})) {
my $project = Project->Lookup($token);
if (!defined($project)) {
fatal("No such project");
}
my $pid = $project->pid();
my $pid_idx = $project->pid_idx();
my $query_result =
DBQueryFatal("select pl.* from project_licenses as pl ".
"where pl.pid_idx='$pid_idx' and ".
($op eq "accepted" ?
" pl.accepted is not null" : " pl.accepted is null"));
if ($query_result->numrows) {
printf("%-5s %s\n", "Index", "Name");
printf("------------------------\n");
while (my $row = $query_result->fetchrow_hashref()) {
my $license_idx = $row->{'license_idx'};
my $lrow = LookupLicense($license_idx);
my $license_name = $lrow->{'license_name'};
my $license_text = $lrow->{'license_text'};
printf("%-5d %-12s %s\n", $license_idx, $license_name,
substr($license_text, 0, 60));
}
}
}
elsif (defined($options{"u"})) {
fatal("The -u option is not implemented yet");
}
exit(0);
}
sub LookupLicense($)
{
my ($token) = @_;
my $query_result;
if ($token =~ /^\d+$/) {
$query_result =
DBQueryFatal("select * from licenses where license_idx='$token'");
}
elsif ($token =~ /^\w+$/) {
$query_result =
DBQueryFatal("select * from licenses where license_name='$token'");
}
else {
print STDERR "Invalid characters in name, alphanumeric only please\n";
return undef;
}