Commit 105c42e1 authored by Leigh Stoller's avatar Leigh Stoller

Tighten up permissions granted to geni users coming from the GPO Portal.

We now ask the portal for a the user's project membership list, and if the
user is not a member of any (unexpired) projects, we do not allow them to
create experiments (or much of anything else) in the Cloud Portal. I did
this by setting the local holding project trust to "user" and setting the
webonly bit in the users table. The user can use the picker to see public
profiles, but the create button tells them no dice, go join a project at
the GPO portal.

We make the project check each time the user logs in via the trusted
signer.
parent 56a4ee21
......@@ -458,7 +458,15 @@ if ($localuser) {
if (0) {
fatal("Could not update ssh keys for nonlocal user");
}
#
# Check project membership, must be a member of at least one
# valid project at the GPO portal.
#
system("$UPDATEGENIUSER -p " . $emulab_user->uid());
if ($?) {
fatal("Not a member of any projects");
}
# Nonlocal users get the holding project.
$pid = "CloudLab";
}
......
......@@ -179,10 +179,12 @@ fatal("Could not create user!")
if (!defined($user));
#
# Add them to the holding project. This will need more thought.
# Add them to the holding project. This will need more thought. Start
# them out with user permissions, which will prevent them doing much
# of anything. Adjusted later.
#
if (defined($project) &&
$project->AddMemberShip($user, $Group::MemberShip::TRUSTSTRING_LOCALROOT)) {
$project->AddMemberShip($user, $Group::MemberShip::TRUSTSTRING_USER)) {
$user->Delete();
fatal("Could not add new user to project");
}
......
......@@ -37,12 +37,14 @@ use Data::Dumper;
#
sub usage()
{
print STDERR "Usage: $0 [-c <credfile> -e <certfile>] [-s] <user>\n";
print STDERR "Usage: $0 [-c <credfile> -e <certfile>] [-s] [-p] <user>\n";
exit(1);
}
my $optlist = "c:se:n";
my $optlist = "c:se:np";
my $dosshkeys = 0;
my $doprojects= 0;
my $impotent = 0;
my $debug = 0;
my $credfile;
my $certfile;
......@@ -59,6 +61,7 @@ use lib '@prefix@/lib';
use libtestbed;
use emutil;
use User;
use Group;
use GeniCertificate;
use GeniCredential;
use GeniAuthority;
......@@ -69,6 +72,7 @@ use GeniHRN;
# Protos
sub UpdateCredential();
sub UpdateSSHKeys();
sub ProjectMembership();
sub fatal($)
{
......@@ -89,6 +93,9 @@ if (! getopts($optlist, \%options)) {
if (defined($options{"s"})) {
$dosshkeys = 1;
}
if (defined($options{"p"})) {
$doprojects = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
......@@ -127,6 +134,37 @@ if (defined($certfile)) {
if ($dosshkeys) {
UpdateSSHKeys();
}
if ($doprojects) {
my @geniprojects = ProjectMembership();
if (@geniprojects) {
print "Portal membership: @geniprojects\n";
}
else {
print "Not a member of any projects!\n";
}
exit(0)
if ($impotent);
#
# Update user. No projects means no permission to do anything.
#
if (@geniprojects) {
$target_user->Update({"webonly" => 0});
}
else {
$target_user->Update({"webonly" => 1});
}
my @projects;
$target_user->ProjectMembershipList(\@projects);
foreach my $project (@projects) {
my $membership = $project->GetProjectGroup()->LookupUser($target_user);
next
if (!defined($membership));
$membership->ModifyTrust((@geniprojects ?
$Group::MemberShip::TRUSTSTRING_LOCALROOT :
$Group::MemberShip::TRUSTSTRING_USER));
}
}
exit(0);
#
......@@ -322,4 +360,100 @@ sub UpdateSSHKeys()
}
return 0;
}
#
# Request project membership from GPO Portal.
#
sub ProjectMembership()
{
my $isportal = 0;
#
# Load the SA cert to act as caller context.
#
my $sa_certificate = GeniCertificate->LoadFromFile($SACERT);
if (!defined($sa_certificate)) {
fatal("Could not load certificate from $SACERT\n");
}
my $context = Genixmlrpc->Context($sa_certificate);
if (!defined($context)) {
fatal("Could not create context to talk to MA");
}
#
# Need the credential and the certificate. The certificate allows us
# to figure out who to talk to, to get the keys. For protogeni it is
# the URL in the certificate. For the GCF, well just hardwire it to
# the common federation api URL.
#
my ($cred,$cert) = $target_user->GetStoredCredential();
fatal("No stored credential for $target_user")
if (!defined($cred) || !defined($cert));
my $speaksfor = GeniCredential->CreateFromSigned($cred);
if (!defined($speaksfor)) {
fatal("Could not parse credential from string");
}
my $geni_type = ($speaksfor->type() eq "abac") ? "geni_abac" : "geni_sfa";
my $geni_vers = ($speaksfor->type() eq "abac") ? 1 : 3;
my $certificate = GeniCertificate->LoadFromString($cert);
if (!defined($certificate)) {
fatal("Could not parse certificate from string");
}
my $user_urn = $certificate->urn();
#
# We need a URL to make the RPC. IG certs have that url in
# the certificate (clever people that we are), but GPO certs refer
# to a nonexistent SA. So just hardwire it, just like flack
# does.
#
# We are going to use the FED API.
#
my @params = ([{"geni_type" => $geni_type,
"geni_version" => $geni_vers,
"geni_value" => $speaksfor->asString()}
],
# Options array.
{"speaking_for" => $user_urn,
"geni_speaking_for" => $user_urn,
});
my $method;
my $url;
my ($auth,$type,$id) = GeniHRN::Parse($user_urn);
if ($auth =~ /geni\.net/) {
$url = "https://ch.geni.net/SA";
$method = "lookup_projects_for_member";
@params = ($user_urn, @params);
$isportal = 1;
}
else {
$url = $certificate->url();
$url =~ s/sa$/geni-sa/;
$method = "lookup_projects_for_member";
}
my $response =
Genixmlrpc::CallMethod($url, $context, $method, @params);
if (!defined($response)) {
fatal("Internal error getting self credential");
}
if ($response->code() != GENIRESPONSE_SUCCESS) {
fatal("Could not get project membership: " . $response->output());
}
if (! ref($response->value())) {
fatal("No project list returned in response");
}
my @projects = ();
print Dumper($response->value())
if ($debug);
foreach my $ref (@{ $response->value() }) {
push(@projects, $ref->{'PROJECT_URN'})
if (!$ref->{'EXPIRED'});
}
return @projects;
}
exit(0);
......@@ -312,7 +312,9 @@ function Do_VerifySpeaksfor()
session_destroy();
return;
}
if ($matches[1] == $OURDOMAIN) {
$domain = $matches[1];
if ($domain == $OURDOMAIN) {
if (!TBvalid_uid($matches[3])) {
SPITAJAX_ERROR(1, "Illegal characters in urn id");
session_destroy();
......@@ -365,6 +367,8 @@ function Do_VerifySpeaksfor()
session_destroy();
return;
}
$this_user->Refresh();
$blob = array();
$blob["domain"] = $COOKDIEDOMAIN;
$blob["hashname"] = $TBAUTHCOOKIE;
......@@ -374,12 +378,16 @@ function Do_VerifySpeaksfor()
$blob["username"] = $TBNAMECOOKIE;
$blob["user"] = $this_user->uid_idx();
$blob["timeout"] = time() + $TBAUTHTIMEOUT;
$blob["webonly"] = $this_user->webonly();
$blob["portal"] = ($this_user->webonly() && $domain == "ch.geni.net" ?
"https://portal.geni.net/" : "");
if ($embedded) {
$blob["url"] = "showuser.php3";
}
else {
$blob["url"] = (Instance::UserHasInstances($this_user)
? "myexperiments.php" : "instantiate.php");
$blob["url"] = ($this_user->webonly() ||
!Instance::UserHasInstances($this_user)
? "instantiate.php" : "myexperiments.php");
}
session_destroy();
SPITAJAX_RESPONSE($blob);
......@@ -395,7 +403,7 @@ function CreateNonLocalUser($urn, $email)
$safe_email = escapeshellarg($email);
$retval = SUEXEC("elabman", $TBOPSPID,
"webcreategeniuser -p CloudLab $safe_urn $safe_email",
"webcreategeniuser CloudLab $safe_urn $safe_email",
SUEXEC_ACTION_CONTINUE);
if ($retval)
return -1;
......@@ -429,7 +437,7 @@ function UpdateCredentials($user, $cert, $cred)
chmod($credfile, 0666);
$retval = SUEXEC($uid, $pid,
"webupdategeniuser -c $credfile -e $certfile $arg $uid",
"webupdategeniuser -p -c $credfile -e $certfile $arg $uid",
SUEXEC_ACTION_CONTINUE);
if ($retval) {
......
......@@ -40,7 +40,7 @@ $dblink = GetDBLink("sa");
RedirectSecure();
$this_user = CheckLogin($check_status);
if (isset($this_user)) {
CheckLoginOrDie();
CheckLoginOrDie(CHECKLOGIN_NONLOCAL|CHECKLOGIN_WEBONLY);
}
elseif ($ISCLOUD) {
RedirectLoginPage();
......@@ -213,8 +213,18 @@ function SPITFORM($formfields, $newuser, $errors)
$amlist = array();
$showabout = ($ISCLOUD || !$this_user ? 1 : 0);
$registered = (isset($this_user) ? "true" : "false");
$webonly = ($registered && $this_user->webonly() ? "true" : "false");
$nopprspec = (!isset($this_user) || $this_user->IsNonLocal() ?
"true" : "false");
$portal = "";
# Gack.
if (isset($this_user) && $this_user->IsNonLocal()) {
if (preg_match("/^[^+]*\+([^+]+)\+([^+]+)\+(.+)$/",
$this_user->nonlocal_id(), $matches) &&
$matches[1] == "ch.geni.net") {
$portal = "https://portal.geni.net/";
}
}
# XSS prevention.
while (list ($key, $val) = each ($formfields)) {
......@@ -399,7 +409,7 @@ function SPITFORM($formfields, $newuser, $errors)
echo " <div id='selected_profile_description'></div>\n";
echo "</div>";
echo "<div class='panel-footer'>";
if (isset($this_user)) {
if (isset($this_user) && !$this_user->webonly()) {
echo "<button class='btn btn-default btn-sm pull-left'
type='button' id='profile_copy_button'
style='margin-right: 10px;'
......@@ -436,7 +446,7 @@ function SPITFORM($formfields, $newuser, $errors)
$thisuuid = $profile->uuid();
echo "<input type='hidden' name='profile' value='$thisuuid'>\n";
}
if (isset($this_user)) {
if (isset($this_user) && !$this_user->webonly()) {
echo "<div class='panel panel-info'>\n";
echo " <div class='panel-body bg-info' style='padding: 5px;'>\n";
#
......@@ -468,7 +478,7 @@ function SPITFORM($formfields, $newuser, $errors)
#
# Spit out a project selection list if more then one project membership
#
if ($this_user) {
if ($this_user && !$this_user->webonly()) {
if (count($projlist) == 1) {
echo "<input id='profile_pid' type='hidden'
name='formfields[pid]'
......@@ -496,7 +506,8 @@ function SPITFORM($formfields, $newuser, $errors)
}
}
if (isset($this_user) && ($ISCLOUD || ISADMIN() || STUDLY())) {
if (isset($this_user) && !$this_user->webonly() &&
($ISCLOUD || ISADMIN() || STUDLY())) {
$am_options = "";
while (list($am, $urn) = each($am_array)) {
$amlist[] = $am;
......@@ -580,6 +591,8 @@ function SPITFORM($formfields, $newuser, $errors)
echo " window.SHOWABOUT = $showabout;\n";
echo " window.NOPPRSPEC = $nopprspec;\n";
echo " window.REGISTERED = $registered;\n";
echo " window.WEBONLY = $webonly;\n";
echo " window.PORTAL = '$portal';\n";
if ($newuser) {
echo "window.APT_OPTIONS.isNewUser = true;\n";
}
......
......@@ -14,6 +14,8 @@ function (_, Constraints, sup, ppstart, aboutaptString, aboutcloudString, waitwa
var selected_uuid = null;
var selected_rspec = null;
var ispprofile = 0;
var webonly = 0;
var portal = null;
var registered = false;
var jacks = {
instance: null,
......@@ -31,7 +33,10 @@ function (_, Constraints, sup, ppstart, aboutaptString, aboutcloudString, waitwa
window.APT_OPTIONS.initialize(sup);
registered = window.REGISTERED;
ajaxurl = window.AJAXURL;
webonly = window.WEBONLY;
portal = window.PORTAL;
ajaxurl = window.AJAXURL;
if ($('#amlist-json').length) {
amlist = JSON.parse(_.unescape($('#amlist-json')[0].textContent));
}
......@@ -79,6 +84,17 @@ function (_, Constraints, sup, ppstart, aboutaptString, aboutcloudString, waitwa
$('#quickvm_topomodal').modal('hide');
});
$('#instantiate_submit').click(function (event) {
if (webonly) {
event.preventDefault();
sup.SpitOops("oops",
"You do not belong to any projects at your Portal, " +
"so you have have very limited capabilities. Please " +
"join or create a project at your " +
(portal && portal != "" ?
"<a href='" + portal + "'>Portal</a>" : "Portal") +
" to enable more capabilities. Thanks!")
return false;
}
$("#waitwait-modal").modal('show');
return true;
});
......
......@@ -189,6 +189,13 @@ function VerifySpeaksfor(speaksfor, signature)
document.cookie = cookie1;
document.cookie = cookie2;
document.cookie = cookie3;
if (json.value.webonly) {
alert("You do not belong to any projects at your Portal, " +
"so you will have very limited capabilities. Please " +
"join or create a project at your Portal, to enable " +
"more capabilities.");
}
if ($('#login_referrer').length) {
window.location.replace($('#login_referrer').val());
}
......
......@@ -261,7 +261,7 @@ $PAGEHEADER_FUNCTION = function($thinheader = 0, $ignore1 = NULL,
echo " <li class='apt-left'><form><a class='btn btn-quickvm-home navbar-btn'
href='http://docs.cloudlab.us' target='_blank'>Manual</a></form></li>\n";
}
if ($login_user) {
if ($login_user && !($login_status & CHECKLOGIN_WEBONLY)) {
echo " <li id='quickvm_actions_menu' class='dropdown apt-left'> ".
"<a href='#' class='dropdown-toggle' data-toggle='dropdown'>
Actions <b class='caret'></b></a>
......@@ -272,11 +272,13 @@ $PAGEHEADER_FUNCTION = function($thinheader = 0, $ignore1 = NULL,
<li><a href='instantiate.php'>Start Experiment</a></li>
<li class='divider'></li>
<li><a href='ssh-keys.php'>Manage SSH Keys</a></li>
<li><a href='getcreds.php'>Download Credentials</a></li>
<li><a href='signup.php'>Start/Join Project</a></li>
<li><a href='changepswd.php'>Change Password</a></li>
<li><a href='logout.php'>Logout</a></li>";
echo " <li class='divider'></li>
<li><a href='getcreds.php'>Download Credentials</a></li>";
if (!$login_user->IsNonLocal()) {
echo " <li><a href='signup.php'>Start/Join Project</a></li>
<li><a href='changepswd.php'>Change Password</a></li>";
}
echo " <li><a href='logout.php'>Logout</a></li>
<li class='divider'></li>
<li><a href='list-datasets.php?all=1'>List Datasets</a></li>
<li><a href='create-dataset.php'>Create Dataset</a></li>";
if (ISADMIN()) {
......@@ -756,7 +758,8 @@ function RedirectLoginPage()
}
#
# Check the login and redirect to login page.
# Check the login and redirect to login page. We use NONLOCAL modifier
# since the classic emulab interface refuses service to nonlocal users.
#
function CheckLoginOrRedirect($modifier = 0)
{
......@@ -767,7 +770,7 @@ function CheckLoginOrRedirect($modifier = 0)
if (! ($check_status & CHECKLOGIN_LOGGEDIN)) {
RedirectLoginPage();
}
CheckLoginConditions($check_status & ~$modifier);
CheckLoginConditions($check_status & ~($modifier|CHECKLOGIN_NONLOCAL));
return $this_user;
}
......
......@@ -173,6 +173,13 @@ function CheckLoginForAjax($guestokay = false)
SPITAJAX_ERROR(2, "Your account is no longer active");
exit(2);
}
# Kludge, still thinking about it. If a geni user has no project
# permissions at their SA, then we mark the acount as WEBONLY, and
# deny access to anything that is not marked as guest okay.
if ($check_status & CHECKLOGIN_WEBONLY && !$guestokay) {
SPITAJAX_ERROR(2, "Your account is not allowed to do this");
exit(2);
}
return;
}
if (!$guestokay) {
......
......@@ -34,7 +34,7 @@ $page_title = "Show Profile";
# Get current user.
#
RedirectSecure();
$this_user = CheckLoginOrRedirect();
$this_user = CheckLoginOrRedirect(CHECKLOGIN_WEBONLY);
$this_idx = $this_user->uid_idx();
$isadmin = (ISADMIN() ? 1 : 0);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment