instantiate.php 18.4 KB
Newer Older
Leigh Stoller's avatar
Leigh Stoller committed
1 2
<?php
#
3
# Copyright (c) 2000-2019 University of Utah and the Flux Group.
Leigh Stoller's avatar
Leigh Stoller committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# 
# {{{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("defs.php3");
include_once("osinfo_defs.php");
include_once("geni_defs.php");
Leigh Stoller's avatar
Leigh Stoller committed
28
include_once("webtask.php");
29
chdir("apt");
Leigh Stoller's avatar
Leigh Stoller committed
30
include("quickvm_sup.php");
31 32
include_once("instance_defs.php");
include_once("profile_defs.php");
33 34
# Must be after quickvm_sup.php since it changes the auth domain.
include_once("../session.php");
Robert Ricci's avatar
Robert Ricci committed
35
$page_title = "Instantiate a Profile";
Leigh Stoller's avatar
Leigh Stoller committed
36
$dblink = GetDBLink("sa");
Leigh Stoller's avatar
Leigh Stoller committed
37

38
#
39 40
# Get current user but make sure coming in on SSL. Guest users allowed
# via APT Portal.
41 42 43
#
RedirectSecure();
$this_user = CheckLogin($check_status);
44
if (isset($this_user)) {
45
    CheckLoginOrDie(CHECKLOGIN_NONLOCAL|CHECKLOGIN_WEBONLY);
46 47 48
    if (NOPROJECTMEMBERSHIP()) {
        return NoProjectMembershipError($this_user);
    }
49
}
50
else {
51
    RedirectLoginPage();
52
}
53

Leigh Stoller's avatar
Leigh Stoller committed
54 55 56
#
# Verify page arguments.
#
57 58
$optargs = OptionalPageArguments("create",        PAGEARG_STRING,
				 "profile",       PAGEARG_STRING,
59
				 "version",       PAGEARG_INTEGER,
60
				 "project",       PAGEARG_PROJECT,
61
				 "default",       PAGEARG_STRING,
62
				 "from",          PAGEARG_STRING,
63
				 "refspec",       PAGEARG_STRING,
64
				 "formfields",    PAGEARG_ARRAY);
65

66 67 68
# Need to make non-hardcoded
$maxduration = 16;

69 70 71 72 73
$skipfirststep = 0;
if (isset($from) && ($from == "manage-profile" || $from == "show-profile")) {
    $skipfirststep = 1;
}

74 75
if ($this_user) {
    $projlist = $this_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
76 77 78 79 80 81
    #
    # Cull out the nonlocal projects, we do not want to show those
    # since they are just the holding projects.
    #
    $tmp = array();
    while (list($pid) = each($projlist)) {
82 83 84
        # Watch out for killing page variable called "project"
        $proj = Project::Lookup($pid);
        if ($proj && !$proj->IsNonLocal()) {
85 86 87 88 89
            $tmp[$pid] = $projlist[$pid];
        }
    }
    $projlist = $tmp;
    
90 91 92 93 94 95
    if (count($projlist) == 0) {
	SPITUSERERROR("You do not belong to any projects with permission to ".
                      "create new experiments. Please contact your project ".
                      "leader to grant you the neccessary privilege.");
	exit();
    }
96
}
97
if ($ISCLOUD) {
98 99 100
    $portal_default_profile = TBGetSiteVar("cloudlab/default_profile");
    list ($profile_default_pid,
          $profile_default) = explode(',', $portal_default_profile);
101
}
102
elseif ($ISPNET) {
103 104 105
    $portal_default_profile = TBGetSiteVar("phantomnet/default_profile");
    list ($profile_default_pid,
          $profile_default) = explode(',', $portal_default_profile);
106
}
107
elseif ($ISPOWDER) {
108
    $portal_default_profile = "PhantomNet,OAI-Real-Hardware";
109 110 111
    list ($profile_default_pid,
          $profile_default) = explode(',', $portal_default_profile);
}
112
else {
113 114 115
    $portal_default_profile = TBGetSiteVar("portal/default_profile");
    list ($profile_default_pid,
          $profile_default) = explode(',', $portal_default_profile);
116
}
117
$profile_array  = array();
118
$usageinfo      = UserUsageInfo($this_user);
Leigh Stoller's avatar
Leigh Stoller committed
119

120 121 122 123
#
# if using the super secret URL, make sure the profile exists, and
# add to the array now since it might not be public or belong to the user.
#
124
if (isset($profile)) {
125 126
    #
    # Guest users must use the uuid, but logged in users may use the
127
    # internal index. But, we have to support simple the URL too, which
128
    # is /p/project/profilename, but only for public profiles.
129
    #
130
    if (isset($project) && isset($profile)) {
131
	$obj = Profile::LookupByName($project, $profile, $version);
132 133 134 135 136
    }
    elseif ($this_user || IsValidUUID($profile)) {
	$obj = Profile::Lookup($profile);
    }
    else {
137 138 139
	SPITUSERERROR("Illegal profile for guest user: $profile");
	exit();
    }
140
    if (! $obj || $obj->deleted()) {
141 142 143 144
	SPITUSERERROR("No such profile: $profile");
	exit();
    }
    if (IsValidUUID($profile)) {
145 146 147 148 149
	#
	# If uuid was to profile, then find the most recently published
	# version and instantiate that, since what we have is the most
	# recent version, but might not be published.
	#
150
	if (0 && $profile == $obj->profile_uuid() && !$obj->published()) {
151 152 153 154 155 156
	    $obj = $obj->LookupMostRecentPublished();
	    if (! $obj) {
		SPITUSERERROR("No published version for profile");
		exit();
	    }
	}
157
        $profile = $obj;
158 159 160 161 162 163 164 165
	$profile_array[$profile->uuid()] =
            array("name"      => $profile->name(),
                  "profileid" => $profile->profileid(),
                  "project"   => $profile->pid(),
                  "pid"       => $profile->pid(), # JS messes with project.
                  "creator"   => $profile->creator(),
                  "usecount"  => $profile->usecount(),
                  "favorite"  => $profile->isFavorite($this_user));
166
	$profilename = $profile->name();
167 168
    }
    else {
169 170 171 172 173
	#
	# If no version provided, then find the most recently published
	# version and instantiate that, since what we have is the most
	# recent version, but might not be published.
	#
174
	if (0 && !isset($version) && !$obj->published()) {
175 176 177 178 179 180 181
	    $obj = $obj->LookupMostRecentPublished();
	    if (! $obj) {
		SPITUSERERROR("No published version for profile");
		exit();
	    }
	}
	 
182
	#
183
	# Must be public or pass the permission test for the user.
184 185
	#
	if (! ($obj->ispublic() ||
186
	       (isset($this_user) && $obj->CanInstantiate($this_user)))) {
187 188 189
	    SPITUSERERROR("No permission to use profile: $profile");
	    exit();
	}
190
	$profile = $obj;
191 192 193 194 195 196 197 198
	$profile_array[$profile->uuid()] = 
            array("name"      => $profile->name(),
                  "profileid" => $profile->profileid(),
                  "project"   => $profile->pid(),
                  "pid"       => $profile->pid(), # JS messes with project.
                  "creator"   => $profile->creator(),
                  "usecount"  => $profile->usecount(),
                  "favorite"  => $profile->isFavorite($this_user));
199
	$profilename = $profile->name();
200
    }
201 202 203 204
    if ($profile->isDisabled()) {
        SPITUSERERROR("This profile is disabled!");
        exit();
    }
205
}
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
else {
    #
    # Find all the public and user profiles. We use the UUID instead of
    # indicies cause we do not want to leak internal DB state to guest
    # users. Need to decide on what clause to use, depending on whether
    # a guest user or not.
    #
    $joinclause   = "";
    $whereclause  = "";
    if (!isset($this_user)) {
	$whereclause = "p.public=1";
    }
    else {
	$this_idx = $this_user->uid_idx();
	$joinclause =
	    "left join group_membership as g on ".
	    "     g.uid_idx='$this_idx' and ".
223 224 225 226
	    "     g.pid_idx=v.pid_idx and g.pid_idx=g.gid_idx ".
            "left join apt_profile_favorites as f on ".
            "     f.profileid=p.profileid and f.uid_idx='$this_idx'";
                    
227 228 229
	$whereclause =
	    "p.public=1 or p.shared=1 or v.creator_idx='$this_idx' or ".
	    "g.uid_idx is not null ";
230
    }
231 232

    $query_result =
233 234 235
	DBQueryFatal("select p.uuid,p.name,p.pid,v.creator,p.profileid, ".
                     "     p.usecount,f.marked ".
                     "   from apt_profiles as p ".
236 237 238 239
		     "left join apt_profile_versions as v on ".
		     "     v.profileid=p.profileid and ".
		     "     v.version=p.version ".
		     "$joinclause ".
240
		     "where locked is null and p.disabled=0 and ".
241
                     "      v.disabled=0 and ($whereclause) ");
242
    while ($row = mysql_fetch_array($query_result)) {
243 244 245 246 247 248 249 250
	$profile_array[$row["uuid"]] =
            array("name"      => $row["name"],
                  "profileid" => $row["profileid"],
                  "project"   => $row["pid"],
                  "pid"       => $row["pid"],
                  "creator"   => $row["creator"],
                  "usecount"  => $row["usecount"],
                  "favorite"  => $row["marked"] ? 1 : 0);
251 252
        if ($row["pid"] == $profile_default_pid &&
            $row["name"] == $profile_default) {
253
	    $profile_default = $row["uuid"];
254
	}
255
    }
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    #
    # A specific profile, but we still want to give the user the selection
    # list above, but the profile might not be in the list if it is not
    # the highest numbered version.
    #
    if (isset($default)) {
        if (IsValidUUID($default)) {
            $obj = Profile::Lookup($default);
            if (!$obj) {
                SPITUSERERROR("Unknown default profile: $default");
                exit();
            }
            if (! ($obj->ispublic() ||
                   (isset($this_user) && $obj->CanInstantiate($this_user)))) {
                SPITUSERERROR("No permission to use profile: $default");
                exit();
            }
273 274 275 276
            if ($obj->isDisabled()) {
                SPITUSERERROR("This profile is disabled!");
                exit();
            }
277 278 279 280 281 282 283 284
            #
            # See if we have the version or profile uuid in the list
            # already, do not add twice since we do not show versions
            # in the picker list.
            #
            if (array_key_exists($obj->profile_uuid(), $profile_array)) {
                unset($profile_array[$obj->profile_uuid()]);
            }
285
            $profile_array[$obj->uuid()] = $obj->name();
286 287 288 289 290 291 292
                    array("name"      => $obj->name(),
                          "profileid" => $obj->profileid(),
                          "project"   => $obj->pid(),
                          "pid"       => $obj->pid(),
                          "creator"   => $obj->creator(),
                          "usecount"  => $obj->usecount(),
                          "favorite"  => $obj->isFavorite($this_user));
293 294 295 296 297 298 299
            $profile_default = $obj->uuid();
        }
        else {
            SPITUSERERROR("Illegal default profile: $default");
            exit();
        }
    }
Leigh Stoller's avatar
Leigh Stoller committed
300
}
Leigh Stoller's avatar
Leigh Stoller committed
301

302
#
303
# Update the array with extra info for the profile picker.
304
#
305 306 307 308 309 310 311 312 313 314
foreach ($profile_array as $uuid => &$details) {
    $profileid = $details["profileid"];
    $usecount  = $details["usecount"];
    $lastused  = 0;
    
    # If profile never used, no need to check if user has used it.
    if ($usecount) {
        if (array_key_exists($profileid, $usageinfo)) {
            $usecount = $usageinfo[$profileid]["count"];
            $lastused = $usageinfo[$profileid]["lastused"];
315
        }
316
    }
317 318
    $details["usecount"] = $usecount;
    $details["lastused"] = $lastused;
319
}
320
reset($profile_array);
321

322
function SPITFORM($formfields, $newuser, $errors)
Leigh Stoller's avatar
Leigh Stoller committed
323
{
324
    global $TBBASE, $APTMAIL, $ISAPT, $ISCLOUD, $ISPNET, $PORTAL_NAME;
325
    global $profile_array, $this_user, $profilename, $profile;
326
    global $projlist, $skipfirststep, $maxduration, $TBMAINSITE;
327
    global $refspec, $ISPOWDER;
328
    
329
    $showabout  = ($ISAPT && !$this_user ? 1 : 0);
330
    $registered = (isset($this_user) ? "true" : "false");
331 332
    # We use webonly to mark users that have no project membership
    # at the Geni portal.
Leigh Stoller's avatar
Leigh Stoller committed
333 334
    $webonly    = (isset($this_user) &&
                   $this_user->webonly() ? "true" : "false");
335
    $cancopy    = (isset($this_user) && !$this_user->webonly() ? 1 : 0);
336
    $nopprspec  = (!isset($this_user) ? "true" : "false");
337
    $portal     = "";
338 339 340
    $showpicker = (isset($profile) ? 0 : 1);
    if (isset($profilename)) {
        $profilename = "'$profilename'";
341
        $profilevers = $profile->version();
342 343 344
    }
    else {
        $profilename = "null";
345
        $profilevers = "null";
346
    }
347
    SPITHEADER(1);
348

349
    echo "<link rel='stylesheet' href='css/jquery-ui.min.css'>\n";
350
    echo "<link rel='stylesheet' href='css/picker.css'>\n";
351
    echo "<link rel='stylesheet' href='css/nv.d3.css'>\n";
352

353 354 355 356 357 358 359
    # I think this will take care of XSS prevention?
    echo "<script type='text/plain' id='form-json'>\n";
    echo htmlentities(json_encode($formfields)) . "\n";
    echo "</script>\n";
    echo "<script type='text/plain' id='error-json'>\n";
    echo htmlentities(json_encode($errors));
    echo "</script>\n";
360 361 362
    echo "<script type='text/plain' id='profiles-json'>\n";
    echo htmlentities(json_encode($profile_array));
    echo "</script>\n";
363
    
364 365 366 367 368 369 370 371
    # 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/";
        }
    }
Leigh Stoller's avatar
Leigh Stoller committed
372

373 374
    # Place to hang the toplevel template.
    echo "<div id='main-body'></div>\n";
375

376
    #
377
    # Spit out a project selection list if a real user.
378
    #
379 380
    if ($this_user && !$this_user->webonly()) {
        echo "<script type='text/plain' id='projects-json'>\n";
381
        echo htmlentities(json_encode($projlist));
382
        echo "</script>\n";
383
    }
384 385
    SpitAggregateStatus(true);

386
    SpitOopsModal("oops");
387
    echo "<script type='text/javascript'>\n";
388
    echo "    window.PROFILE    = '" . $formfields["profile"] . "';\n";
389
    echo "    window.PROFILENAME= $profilename;\n";
390
    echo "    window.PROFILEVERS= $profilevers;\n";
391 392
    echo "    window.AJAXURL    = 'server-ajax.php';\n";
    echo "    window.SHOWABOUT  = $showabout;\n";
393
    echo "    window.NOPPRSPEC  = $nopprspec;\n";
394
    echo "    window.REGISTERED = $registered;\n";
395 396
    echo "    window.WEBONLY    = $webonly;\n";
    echo "    window.PORTAL     = '$portal';\n";
397
    echo "    window.SHOWPICKER = $showpicker;\n";
398
    echo "    window.MAXDURATION = $maxduration;\n";
399
    echo "    window.CANCOPY = $cancopy;\n";
400 401
    $isadmin = (isset($this_user) && ISADMIN() ? 1 : 0);
    echo "    window.ISADMIN    = $isadmin;\n";
402 403
    $isstud = (isset($this_user) && STUDLY() ? 1 : 0);
    echo "    window.ISSTUD    = $isstud;\n";
404
    $multisite = (isset($this_user) && $ISCLOUD ? 1 : 0);
405
    echo "    window.MULTISITE  = $multisite;\n";
406 407
    $doconstraints = $TBMAINSITE;
    echo "    window.DOCONSTRAINTS = $doconstraints;\n";
408
    echo "    window.SKIPFIRSTSTEP = " . ($skipfirststep ? "true" : "false") . ";\n";
409
    echo "    window.PORTAL_NAME = '$PORTAL_NAME';\n";
410
    echo "    window.USERNAME = '" . $formfields["username"] . "';\n";
411 412 413 414 415 416 417 418 419
    if (isset($profile) && $profile->repourl()) {
        echo "    window.FROMREPO = true;\n";
        if (isset($refspec)) {
            echo "    window.REFSPEC = '$refspec';\n";
        }
    }
    else {
        echo "    window.FROMREPO = false;\n";
    }
420 421 422 423 424 425 426 427
    # Do we show an aggregate selector?
    if (isset($this_user) && !$this_user->webonly()
        && !$ISAPT && !$ISPNET && !$ISPOWDER) {
        echo "    window.CLUSTERSELECT = true;\n";
    }
    else {
        echo "    window.CLUSTERSELECT = false;\n";
    }
428
    echo "</script>\n";
429 430
    echo "<script src='js/lib/d3.v3.js'></script>\n";
    echo "<script src='js/lib/nv.d3.js'></script>\n";
431
    echo "<script src='js/lib/jquery-2.0.3.min.js'></script>\n";
432 433
    echo "<script src='js/lib/jquery-ui.js'></script>\n";
   
434 435 436 437 438
    REQUIRE_UNDERSCORE();
    REQUIRE_SUP();
    REQUIRE_PPWIZARDSTART();
    REQUIRE_JACKS_EDITOR();
    REQUIRE_WIZARD_TEMPLATE();
439
    REQUIRE_PICKER();
440 441 442
    REQUIRE_FORMHELPERS();
    REQUIRE_FILESTYLE();
    REQUIRE_MARKED();
443
    REQUIRE_MOMENT();
444
    REQUIRE_JACKSMOD();
445 446
    REQUIRE_JACKS();
    REQUIRE_JQUERY_STEPS();
447
    AddLibrary("js/resgraphs.js");
448
    AddLibrary("js/gitrepo.js");
449
    SPITREQUIRE("js/instantiate-new.js");
Leigh Stoller's avatar
Leigh Stoller committed
450 451 452
}

if (!isset($create)) {
453 454 455 456
    $defaults = array();
    $defaults["username"] = "";
    $defaults["email"]    = "";
    $defaults["sshkey"]   = "";
457 458
    $defaults["profile"]  = (isset($profile) ?
                             $profile->uuid() : $profile_default);
459
    $defaults["where"]    = $DEFAULT_AGGREGATE;
460 461 462 463 464
    #
    # If the user is in the same project as the profile, default to that
    # project, else use the first in the list (which is ordered by last
    # time the user instantiated in it).
    #
465
    if ($this_user && count($projlist)) {
466 467
        if (isset($profile) &&
            array_key_exists($profile->pid(), $projlist)) {
468 469 470 471 472 473
            $project = $profile->pid();
        }
        else {
            list($project, $grouplist) = each($projlist);
            reset($projlist);
        }
474
        $defaults["pid"] = $project;
475
        $defaults["gid"] = $project;
476 477 478
    }
    else {
        $defaults["pid"] = "";
479
        $defaults["gid"] = "";
480
    }
481

482
    # 
483
    # Look for current user or cookie that tells us who the user is. 
484
    #
485
    if ($this_user) {
486 487
	$defaults["username"] = $this_user->uid();
	$defaults["email"]    = $this_user->email();
488 489 490 491 492 493 494
	#
	# Look for an key marked as an APT uploaded key and use that.
	# If no APT key, use any uploaded key; if the user leaves this
	# key in the form, it will become the official APT key.
	#
	$sshkey = $this_user->GetAPTSSHKey();
	if (!$sshkey) {
495 496
	    $sshkeys = $this_user->GetSSHKeys();
	    if (count($sshkeys)) {
497
		$sshkey = $sshkeys[0];
498 499
	    }
	}
500 501 502
	if ($sshkey) {
	    $defaults["sshkey"] = $sshkey;
	}
503 504
    }
    elseif (isset($_COOKIE['quickvm_user'])) {
Leigh Stoller's avatar
Leigh Stoller committed
505 506 507 508 509 510
	$geniuser = GeniUser::Lookup("sa", $_COOKIE['quickvm_user']);
	if ($geniuser) {
	    #
	    # Look for existing quickvm. User not allowed to create
	    # another one.
	    #
Leigh Stoller's avatar
Leigh Stoller committed
511 512
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
513 514
		header("Location: status.php?oneonly=1&uuid=" .
		       $instance->uuid());
Leigh Stoller's avatar
Leigh Stoller committed
515 516
		return;
	    }
517 518 519 520 521 522 523 524
            #
            # Watch for too many instances by guest user and redirect
            # to the signup page.
            #
            if (Instance::GuestInstanceCount($geniuser) > $MAXGUESTINSTANCES) {
		header("Location: signup.php?toomany=1");
		return;
            }
525 526 527
	    $defaults["username"] = $geniuser->name();
	    $defaults["email"]    = $geniuser->email();
	    $defaults["sshkey"]   = $geniuser->SSHKey();
Leigh Stoller's avatar
Leigh Stoller committed
528 529
	}
    }
530 531 532 533
    # We use a session, in case we need to do verification or other things.
    session_start();
    session_unset();

534
    SPITFORM($defaults, false, array());
535
    echo "<div style='display: none'><div id='jacks-dummy'></div></div>\n";
536

537 538 539 540
    AddTemplateList(array("instantiate-new",
                          "aboutapt", "aboutcloudlab", "aboutpnet",
                          "waitwait-modal", "rspectextview-modal",
                          "picker-template","reservation-graph"));
Leigh Stoller's avatar
Leigh Stoller committed
541 542 543 544
    SPITFOOTER();
    return;
}
?>