Commit 13c85de6 authored by Leigh Stoller's avatar Leigh 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
......
This diff is collapsed.
......@@ -1071,5 +1071,20 @@ sub ValidUUID($)
return 0;
}
sub ReadFile($)
{
my ($filename) = @_;
my $contents = "";
open(L, $filename)
or return undef;
while (<L>) {
$contents .= $_;
}
close(L);
return $contents;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -2989,6 +2989,26 @@ CREATE TABLE `lease_permissions` (
PRIMARY KEY (`lease_idx`,`permission_type`,`permission_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `licenses`
--
DROP TABLE IF EXISTS `licenses`;
CREATE TABLE `licenses` (
`license_idx` int(11) NOT NULL auto_increment,
`license_name` varchar(48) NOT NULL default '',
`license_level` enum('project','user') NOT NULL default 'project',
`created` datetime default NULL,
`validfor` int(11) NOT NULL default '0',
`form_text` tinytext,
`license_text` text,
`license_type` enum('md','text','html') NOT NULL default 'md',
`description_text` text,
`description_type` enum('md','text','html') NOT NULL default 'md',
PRIMARY KEY (`license_idx`),
UNIQUE KEY `license_name` (`license_name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `linkdelays`
--
......@@ -4479,6 +4499,20 @@ CREATE TABLE `project_leases` (
UNIQUE KEY `uuid` (`uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `project_licenses`
--
DROP TABLE IF EXISTS `project_licenses`;
CREATE TABLE `project_licenses` (
`pid` varchar(48) NOT NULL default '',
`pid_idx` mediumint(8) unsigned NOT NULL default '0',
`license_idx` int(11) NOT NULL default '0',
`accepted` datetime default NULL,
`expiration` datetime default NULL,
PRIMARY KEY (`pid_idx`,`license_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `project_quotas`
--
......@@ -5246,6 +5280,22 @@ CREATE TABLE `user_features` (
PRIMARY KEY (`feature`,`uid_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `user_licenses`
--
DROP TABLE IF EXISTS `user_licenses`;
CREATE TABLE `user_licenses` (
`uid` varchar(48) NOT NULL default '',
`uid_idx` mediumint(8) unsigned NOT NULL default '0',
`license_idx` int(11) NOT NULL default '0',
`accepted` datetime default NULL,
`expiration` datetime default NULL,
PRIMARY KEY (`uid_idx`,`license_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `user_policies`
--
......
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBTableExists("licenses")) {
DBQueryFatal("CREATE TABLE `licenses` ( ".
" `license_idx` int(11) NOT NULL auto_increment, ".
" `license_name` varchar(48) NOT NULL default '', ".
" `license_level` enum('project','user') ".
" NOT NULL default 'project', ".
" `created` datetime default NULL, ".
" `validfor` int(11) NOT NULL default '0', ".
" `form_text` tinytext, ".
" `license_text` text, ".
" `license_type` enum('md','text', 'html') ".
" NOT NULL default 'md', ".
" `description_text` text, ".
" `description_type` enum('md','text', 'html') ".
" NOT NULL default 'md', ".
" PRIMARY KEY (`license_idx`), ".
" UNIQUE KEY `license_name` (`license_name`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
if (!DBTableExists("project_licenses")) {
DBQueryFatal("CREATE TABLE `project_licenses` ( ".
" `pid` varchar(48) NOT NULL default '', ".
" `pid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `license_idx` int(11) NOT NULL default '0', ".
" `accepted` datetime default NULL, ".
" `expiration` datetime default NULL, ".
" PRIMARY KEY (`pid_idx`,`license_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
if (!DBTableExists("user_licenses")) {
DBQueryFatal("CREATE TABLE `user_licenses` ( ".
" `uid` varchar(48) NOT NULL default '', ".
" `uid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `license_idx` int(11) NOT NULL default '0', ".
" `accepted` datetime default NULL, ".
" `expiration` datetime default NULL, ".
" PRIMARY KEY (`uid_idx`,`license_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
return 0;
}
1;
# Local Variables:
# mode:perl
# End:
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -236,6 +236,7 @@ $EUID = 0;
#
DBQueryFatal("delete FROM last_reservation where pid_idx='$pid_idx'");
DBQueryFatal("delete FROM project_reservations where pid_idx='$pid_idx'");
DBQueryFatal("delete FROM project_licenses where pid_idx='$pid_idx'");
DBQueryFatal("delete FROM nodetypeXpid_permissions where pid_idx='$pid_idx'");
DBQueryFatal("delete FROM project_stats where pid_idx='$pid_idx'");
DBQueryFatal("delete FROM group_stats where pid_idx='$pid_idx'");
......
......@@ -20,7 +20,8 @@ $(function () {
* name to the wrapper so we can find it later to
* add the error stuff.
*/
var wrapper = $("<div id='form-wrapper-' + key></div>");
var wrapper = $("<div id='form-wrapper-" + key + "'>" +
"</div>");
// How do I just move the item into the wrapper?
wrapper.append($(item).clone());
......
$(function ()
{
'use strict';
var template_list = ["licenses", "oops-modal", "waitwait-modal"];
var templates = APT_OPTIONS.fetchTemplateList(template_list);
var licenses = [];
function initialize()
{
window.APT_OPTIONS.initialize(sup);
$('#main-body').html(templates["licenses"]);
$('#oops-div').html(templates["oops-modal"]);
$('#waitwait-div').html(templates["waitwait-modal"]);
GetLicenses();
}
function GetLicenses()
{
var callback = function(json) {
console.log("GetLicenses", json);
if (json.code) {
console.info("Could not list license: " + json.value);
return;
}
licenses = json.value;
if (! licenses.length) {
window.location.replace("landing.php");
return;
}
HandleLicense(licenses.shift());
};
var xmlthing = sup.CallServerMethod(null, "licenses", "List");
xmlthing.done(callback);
}
function HandleLicense(license)
{
console.info("HandleLicense", license);
// Clear for next license.
$('#description-panel .license').html("").addClass("hidden");
$('#license-panel .panel-body .license-text').html("");
// Reset handler for accept button.
$('#accept-license')
.off("click")
.click(function (event) {
event.preventDefault();
Accept(license);
});
$('#reject-license')
.off("click")
.click(function (event) {
event.preventDefault();
Reject(license);
});
if (license.description_text && license.description_text != "") {
var html;
if (license.description_type == "md") {
html = marked(license.description_text);
}
else if (license.description_type == "html") {
html = license.description_text;
}
else if (license.description_type == "text") {
html = "<pre>" + license.description_text + "</pre>";
}
$('#description-panel .license')
.html(html)
.removeClass("hidden");
}
var html;
if (license.license_type == "md") {
html = marked(license.license_text);
}
else if (license.license_type == "html") {
html = license.license_text;
}
else if (license.license_type == "text") {
html = "<pre>" + license.license_text + "</pre>";
}
$('#license-panel .panel-body .license-text').html(html);
}
function Accept(license)
{
console.info("Accept", license);
var callback = function(json) {
console.log(json);
if (json.code) {
console.info("Could not accept license: " + json.value);
return;
}
if (licenses.length) {
sup.ShowWaitWait("One moment please while we check to see if " +
"there are any more licenses to accept ...")
setTimeout(function () {
sup.HideWaitWait();
HandleLicense(licenses.shift());
}, 2000);
return;
}
window.location.replace("landing.php");
};
var xmlthing = sup.CallServerMethod(null,
"licenses", "Accept",
{"idx" : license.idx});
xmlthing.done(callback);
}
function Reject(license)
{
console.info("Reject", license);
var callback = function(json) {
console.log(json);
if (json.code) {
console.info("Could not reject license: " + json.value);
return;
}
if (licenses.length) {
sup.ShowWaitWait("One moment please while we check to see if " +
"there are any more licenses to accept ...")
setTimeout(function () {
sup.HideWaitWait();
HandleLicense(licenses.shift());
}, 2000);
return;
}
window.location.replace("landing.php");
};
var xmlthing = sup.CallServerMethod(null,
"licenses", "Reject",
{"idx" : license.idx});
xmlthing.done(callback);
}
$(document).ready(initialize);
});
......@@ -504,19 +504,29 @@ $(function ()
}
var template = _.template(detailsString);
$('#admin_content')
.html(template({"fields" : json.value}));
$('#project_content')
.html(template({"fields" : json.value,
"isleader" : window.ISLEADER,
"isadmin" : window.ISADMIN}));
// Format dates with moment before display.
$('#admin_table .format-date').each(function() {
$('#project_table .format-date').each(function() {
var date = $.trim($(this).html());
if (date != "") {
$(this).html(moment($(this).html()).format("ll"));
}
});
$('#admin_content .toggle').click(function() {
$('#project_table [data-toggle="popover"]').popover({
trigger: 'hover',
placement: 'auto',
});
$('#project_content .toggle').click(function() {
Toggle(this);
});
$('#project_content .request-license').click(function(event) {
event.preventDefault();
RequestLicense(this);
});
}
var xmlthing = sup.CallServerMethod(null,
"show-project", "ProjectProfile",
......@@ -618,6 +628,31 @@ $(function ()
callback);
}
/*
* Request a license.
*/
function RequestLicense(target) {
var license_idx = $(target).data("license_idx");
var callback = function(json) {
if (json.code) {
sup.SpitOops("oops", json.value);
return;
}
// If this is the leader of the project, zap them to
// the license page. If an admin doing this, stay here.
if (window.ISLEADER) {
window.location.replace("licenses.php");
return;
}
$(target).closest('td').html("Acceptance pending");
};
sup.CallServerMethod(null, "licenses", "Request",
{"pid" : window.TARGET_PROJECT,
"idx" : license_idx},
callback);
}
$(document).ready(initialize);
});
......
......@@ -16,11 +16,11 @@ $(function ()
var fields = JSON.parse(_.unescape($('#form-json')[0].textContent));
var errors = JSON.parse(_.unescape($('#error-json')[0].textContent));
var licenses = JSON.parse(_.unescape($('#licenses-json')[0].textContent));
console.info(fields);
console.info(errors);
renderForm(fields, errors,
renderForm(fields, errors, licenses,
window.APT_OPTIONS.joinproject,
window.APT_OPTIONS.ShowVerifyModal,
window.APT_OPTIONS.this_user,
......@@ -48,7 +48,7 @@ $(function ()
}
}
function renderForm(formfields, errors, joinproject, showVerify,
function renderForm(formfields, errors, licenses, joinproject, showVerify,
thisUser, promoting)
{
var buttonLabel = "Submit Request";
......@@ -68,7 +68,8 @@ $(function ()
});
var project_html = projectTemplate({
joinproject: joinproject,
formfields: formfields
formfields: formfields,
licenses: licenses,
});
var signup = signupTemplate({
button_label: buttonLabel,
......@@ -106,6 +107,11 @@ $(function ()
aptforms.DisableUnsavedWarning('#quickvm_signup_form');
});
}
// This activates the popover subsystem.
$('[data-toggle="popover"]').popover({
trigger: 'hover',
placement: 'auto',
});
}
$(document).ready(initialize);
......
<?php
#
# 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/>.
#
# }}}
#
chdir("..");
include_once("webtask.php");
chdir("apt");
function Do_List()
{
global $this_user;
global $ajax_args;
$result = array();
$licenses = $this_user->ProjectLicenses();
foreach ($licenses as $license) {
$blob = array("idx" => $license["license_idx"],
"name" => $license["license_name"],
"description_type" => $license["description_type"],
"description_text" => $license["description_text"],
"license_type" => $license["license_type"],
"license_text" => $license["license_text"]);
$result[] = $blob;
}
SPITAJAX_RESPONSE($result);
}
function Do_Accept()
{
global $this_user;
global $ajax_args;
$licenses = $this_user->ProjectLicenses();
$license = null;
if (!isset($ajax_args["idx"]) || $ajax_args["idx"] == "") {
SPITAJAX_ERROR(1, "Missing license idx");
return 1;
}
$idx = $ajax_args["idx"];
if (!is_numeric($idx)) {
SPITAJAX_ERROR(1, "License idx is not an integer");
return 1;
}
# Find the License so we know what kind it is.
foreach ($licenses as $tmp) {
if ($tmp["license_idx"] == $idx) {
$license = $tmp;
break;
}
}
if (!$license) {
SPITAJAX_ERROR(1, "Not a license that needs to be accepted.");
return 1;
}
$target = $license["pid"];
$project = Project::Lookup($target);
if (!$project) {
SPITAJAX_ERROR(1, "No such project");
return 1;
}
if (!$project->IsLeader($this_user)) {
SPITAJAX_ERROR(1, "Not allowed to accept/reject this license");
return 1;
}
$this_uid = $this_user->uid();
$webtask = WebTask::CreateAnonymous();
$retval = SUEXEC($this_uid, "nobody",
"webmanage_licenses -t " . $webtask->task_id() . " -- ".
" accept $idx $target ",
SUEXEC_ACTION_IGNORE);
$webtask->Refresh();
if ($retval != 0) {
if (!$webtask->exited() || $retval < 0) {
SUEXECERROR(SUEXEC_ACTION_CONTINUE);
SPITAJAX_ERROR(-1, "Internal error");
}
else {
# Need to pass exitcode through on this one.
SPITAJAX_ERROR($webtask->exitcode(), $webtask->output());
}
$webtask->Delete();
return;
}
$webtask->Delete();
SPITAJAX_RESPONSE(1);
}
function Do_Reject()
{
global $this_user;
global $ajax_args;
$licenses = $this_user->ProjectLicenses();
$license = null;
if (!isset($ajax_args["idx"]) || $ajax_args["idx"] == "") {
SPITAJAX_ERROR(1, "Missing license idx");
return 1;
}
$idx = $ajax_args["idx"];
if (!is_numeric($idx)) {
SPITAJAX_ERROR(1, "License idx is not an integer");
return 1;
}
# Find the License so we know what kind it is.
foreach ($licenses as $tmp) {
if ($tmp["license_idx"] == $idx) {
$license = $tmp;
break;
}
}
if (!$license) {
SPITAJAX_ERROR(1, "Not a license that needs to be rejected");
return 1;
}
$target = $license["pid"];
$project = Project::Lookup($target);
if (!$project) {
SPITAJAX_ERROR(1, "No such project");
return 1;
}
if (!$project->IsLeader($this_user)) {
SPITAJAX_ERROR(1, "Not allowed to accept/reject this license");
return 1;
}
$this_uid = $this_user->uid();
$webtask = WebTask::CreateAnonymous();
$retval = SUEXEC($this_uid, "nobody",
"webmanage_licenses -t " . $webtask->task_id() . " -- ".
" norequire $idx $target ",
SUEXEC_ACTION_IGNORE);
$webtask->Refresh();
if ($retval != 0) {
if (!$webtask->exited() || $retval < 0) {
SUEXECERROR(SUEXEC_ACTION_CONTINUE);
SPITAJAX_ERROR(-1, "Internal error");
}
else {
# Need to pass exitcode through on this one.
SPITAJAX_ERROR($webtask->exitcode(), $webtask->output());
}
$webtask->Delete();
return;
}
$webtask->Delete();
SPITAJAX_RESPONSE(1);
}
function Do_Request()
{
global $this_user;
global $ajax_args;
if (!isset($ajax_args["idx"]) || $ajax_args["idx"] == "") {
SPITAJAX_ERROR(1, "Missing license idx");
return 1;
}
$idx = $ajax_args["idx"];
if (!is_numeric($idx)) {
SPITAJAX_ERROR(1, "License idx is not an integer");
return 1;
}
if (!isset($ajax_args["pid"]) || $ajax_args["pid"] == "") {
SPITAJAX_ERROR(1, "Missing project");
return 1;
}
$project = Project::Lookup($ajax_args["pid"]);
if (!$project) {
SPITAJAX_ERROR(1, "No such project");
return 1;
}
if (!(ISADMIN() || $project->IsLeader($this_user))) {
SPITAJAX_ERROR(1, "Not enough permission to request this license");
return 1;
}
$pid = $project->pid();
$this_uid = $this_user->uid();
$webtask = WebTask::CreateAnonymous();
$retval = SUEXEC($this_uid, "nobody",
"webmanage_licenses -t " . $webtask->task_id() . " -- ".
" require $idx $pid ",
SUEXEC_ACTION_IGNORE);
$webtask->Refresh();
if ($retval != 0) {
if (!$webtask->exited() || $retval < 0) {
SUEXECERROR(SUEXEC_ACTION_CONTINUE);
SPITAJAX_ERROR(-1, "Internal error");
}
else {
# Need to pass exitcode through on this one.
SPITAJAX_ERROR($webtask->exitcode(), $webtask->output());
}
$webtask->Delete();
return;