All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

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;
}