instantiate.php 34 KB
Newer Older
Leigh Stoller's avatar
Leigh Stoller committed
1 2
<?php
#
Leigh Stoller's avatar
Leigh Stoller committed
3
# Copyright (c) 2000-2015 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");
Robert Ricci's avatar
Robert Ricci committed
33
$page_title = "Instantiate a Profile";
Leigh Stoller's avatar
Leigh Stoller committed
34
$dblink = GetDBLink("sa");
Leigh Stoller's avatar
Leigh Stoller committed
35

36
#
37 38
# Get current user but make sure coming in on SSL. Guest users allowed
# via APT Portal.
39 40 41
#
RedirectSecure();
$this_user = CheckLogin($check_status);
42
if (isset($this_user)) {
43
    CheckLoginOrDie(CHECKLOGIN_NONLOCAL|CHECKLOGIN_WEBONLY);
44 45
}
elseif ($ISCLOUD) {
46
    RedirectLoginPage();
47
}
48

Leigh Stoller's avatar
Leigh Stoller committed
49 50 51
#
# Verify page arguments.
#
52 53
$optargs = OptionalPageArguments("create",        PAGEARG_STRING,
				 "profile",       PAGEARG_STRING,
54
				 "version",       PAGEARG_INTEGER,
55 56 57
				 "stuffing",      PAGEARG_STRING,
				 "verify",        PAGEARG_STRING,
				 "project",       PAGEARG_PROJECT,
58
				 "asguest",       PAGEARG_BOOLEAN,
59
				 "default",       PAGEARG_STRING,
60
				 "formfields",    PAGEARG_ARRAY);
61

62
if ($ISAPT && !$this_user) {
63 64 65 66 67 68 69 70 71 72 73 74 75
    #
    # If user appears to have an account, go to login page.
    # Continue as guest on that page.
    #
    if (REMEMBERED_ID()) {
	if (isset($asguest) && $asguest) {
	    # User clicked on continue as guest. If we do not delete the
	    # cookie, then user will go through the same loop next time
            # they click the Home button, since that points here. So delete
	    # the UID cookie. Not sure I like this.
	    ClearRememberedID();
	}
	else {
Leigh Stoller's avatar
Leigh Stoller committed
76 77
            header("Location: login.php?from=instantiate&referrer=".
                   urlencode($_SERVER['REQUEST_URI']));
78 79 80
	}
    }
}
81 82
if ($this_user) {
    $projlist = $this_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
83 84 85 86 87 88
    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();
    }
89
}
90
if ($ISCLOUD) {
91 92
    $profile_default     = "ARM64OpenStack";
    $profile_default_pid = "emulab-ops";
93 94 95 96 97
}
else {
    $profile_default     = "OneVM";
    $profile_default_pid = $TBOPSPID;
}
98 99
$profile_array  = array();
$am_array       = Instance::DefaultAggregateList();
Leigh Stoller's avatar
Leigh Stoller committed
100

101 102 103 104
#
# 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.
#
105
if (isset($profile)) {
106 107
    #
    # Guest users must use the uuid, but logged in users may use the
108
    # internal index. But, we have to support simple the URL too, which
109
    # is /p/project/profilename, but only for public profiles.
110
    #
111
    if (isset($project) && isset($profile)) {
112
	$obj = Profile::LookupByName($project, $profile, $version);
113 114 115 116 117
    }
    elseif ($this_user || IsValidUUID($profile)) {
	$obj = Profile::Lookup($profile);
    }
    else {
118 119 120 121 122 123 124 125
	SPITUSERERROR("Illegal profile for guest user: $profile");
	exit();
    }
    if (! $obj) {
	SPITUSERERROR("No such profile: $profile");
	exit();
    }
    if (IsValidUUID($profile)) {
126 127 128 129 130 131 132 133 134 135 136 137
	#
	# 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.
	#
	if ($profile == $obj->profile_uuid() && !$obj->published()) {
	    $obj = $obj->LookupMostRecentPublished();
	    if (! $obj) {
		SPITUSERERROR("No published version for profile");
		exit();
	    }
	}
138 139 140
        $profile = $obj;
	$profile_array[$profile->uuid()] = $profile->name();
	$profilename = $profile->name();
141 142
    }
    else {
143 144 145 146 147 148 149 150 151 152 153 154 155
	#
	# 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.
	#
	if (!isset($version) && !$obj->published()) {
	    $obj = $obj->LookupMostRecentPublished();
	    if (! $obj) {
		SPITUSERERROR("No published version for profile");
		exit();
	    }
	}
	 
156
	#
157
	# Must be public or pass the permission test for the user.
158 159
	#
	if (! ($obj->ispublic() ||
160
	       (isset($this_user) && $obj->CanInstantiate($this_user)))) {
161 162 163
	    SPITUSERERROR("No permission to use profile: $profile");
	    exit();
	}
164 165 166
	$profile = $obj;
	$profile_array[$profile->uuid()] = $profile->name();
	$profilename = $profile->name();
167
    }
168
}
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
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 ".
	    "     g.pid_idx=v.pid_idx and g.pid_idx=g.gid_idx";
	$whereclause =
	    "p.public=1 or p.shared=1 or v.creator_idx='$this_idx' or ".
	    "g.uid_idx is not null ";
190
    }
191 192 193 194 195 196 197

    $query_result =
	DBQueryFatal("select p.*,v.* from apt_profiles as p ".
		     "left join apt_profile_versions as v on ".
		     "     v.profileid=p.profileid and ".
		     "     v.version=p.version ".
		     "$joinclause ".
198 199
		     "where locked is null and ($whereclause) ".
		     "order by p.topdog desc");
200 201
    while ($row = mysql_fetch_array($query_result)) {
	$profile_array[$row["uuid"]] = $row["name"];
202 203 204 205 206 207 208
        if (isset($default)) {
            if ($default == $row["uuid"]) {
                $profile_default = $row["uuid"];
            }
        }
        elseif ($row["pid"] == $profile_default_pid &&
                $row["name"] == $profile_default) {
209
	    $profile_default = $row["uuid"];
210
	}
211
    }
Leigh Stoller's avatar
Leigh Stoller committed
212
}
Leigh Stoller's avatar
Leigh Stoller committed
213

214
function SPITFORM($formfields, $newuser, $errors)
Leigh Stoller's avatar
Leigh Stoller committed
215
{
216
    global $TBBASE, $APTMAIL, $ISCLOUD;
217
    global $profile_array, $this_user, $profilename, $profile, $am_array;
218
    global $projlist;
219 220 221
    $amlist     = array();
    $showabout  = ($ISCLOUD || !$this_user ? 1 : 0);
    $registered = (isset($this_user) ? "true" : "false");
Leigh Stoller's avatar
Leigh Stoller committed
222 223
    $webonly    = (isset($this_user) &&
                   $this_user->webonly() ? "true" : "false");
224 225
    $nopprspec  = (!isset($this_user) || $this_user->IsNonLocal() ?
                   "true" : "false");
226 227 228 229 230 231 232 233 234
    $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/";
        }
    }
Leigh Stoller's avatar
Leigh Stoller committed
235

236 237 238
    # XSS prevention.
    while (list ($key, $val) = each ($formfields)) {
	$formfields[$key] = CleanString($val);
Leigh Stoller's avatar
Leigh Stoller committed
239
    }
240
    # XSS prevention.
Leigh Stoller's avatar
Leigh Stoller committed
241
    if ($errors) {
242
	while (list ($key, $val) = each ($errors)) {
243
	    # Skip internal error, we want the html in those errors
244
	    # and we know it is safe.
245 246 247
	    if ($key == "error") {
		continue;
	    }
248
	    $errors[$key] = CleanString($val);
Leigh Stoller's avatar
Leigh Stoller committed
249 250
	}
    }
251 252 253 254 255 256 257 258 259

    $formatter = function($field, $html) use ($errors) {
	$class = "form-group";
	if ($errors && array_key_exists($field, $errors)) {
	    $class .= " has-error";
	}
	echo "<div class='$class'>\n";
	echo "     $html\n";
	if ($errors && array_key_exists($field, $errors)) {
260
	    echo "<label class='control-label' for='$field'>" .
261 262 263 264 265
		$errors[$field] . "</label>\n";
	}
	echo "</div>\n";
    };

Leigh Stoller's avatar
Leigh Stoller committed
266
    SPITHEADER(1);
Leigh Stoller's avatar
Leigh Stoller committed
267

268
    echo "<div id='ppviewmodal_div'></div>\n";
269 270 271 272 273
    # Placeholder for the "about" panel, which is now a template file.
    echo "<div id='about_div' class='col-lg-8  col-lg-offset-2
                                      col-md-8  col-md-offset-2
                                      col-sm-10  col-sm-offset-1
                                      col-xs-12 col-xs-offset-0'></div>\n";
274 275
    echo "<div id='stepsContainer' class='row'>
          <h3>Select a Profile</h3>
276 277 278
          <div class='col-lg-8  col-lg-offset-2
                      col-md-8  col-md-offset-2
                      col-sm-10  col-sm-offset-1
Leigh Stoller's avatar
Leigh Stoller committed
279
                      col-xs-12 col-xs-offset-0'>\n";
Jonathon Duerig's avatar
Jonathon Duerig committed
280 281


282
    echo "<form id='quickvm_form' role='form'
283
            enctype='multipart/form-data'
284
            method='post' action='instantiate.php'>\n";
285 286 287 288
    if (!$this_user) {
	echo "<div class='panel panel-default'>
                <div class='panel-heading'>
                   <h3 class='panel-title'>\n";
289
    echo "<center>Start Experiment";
290 291 292
    if (isset($profilename)) {
        echo " using profile &quot;$profilename&quot";
    }
293
    echo "</center></h3>\n";
294 295 296 297 298 299 300 301 302 303 304
    }
    else {
    # Will only show header when linked to a profile
    if (isset($profilename)) {
        echo "<h3 style='margin: 0px;'>";
        echo "<center>Start Experiment";
        echo " using profile &quot;$profilename&quot";
        echo "</center></h3>\n";
    }
    }

305 306 307 308
    if (!$this_user) {
	echo "   </div>
	      <div class='panel-body'>\n";
    }
309 310 311 312 313
    
    #
    # If linked to a specific profile, description goes here
    #
    if ($profile) {
314 315
	$cluster = ($ISCLOUD ? "Cloudlab" : "APT");
	
316 317 318 319
	if (!$this_user) {
	    echo "  <p>Fill out the form below to run an experiment ".
		"using this profile:</p>\n";
	}
320
        # Note: Following line is also duplicated below
321 322 323
        echo "  <blockquote><p><span id='selected_profile_description'></span></p></blockquote>\n";
        echo "  <p>When you click the &quot;Create&quot; button, the virtual or
                   physical machines described in the profile will be booted
324
                   on ${cluster}'s hardware<p>\n";
325 326
    }

327 328 329 330 331 332
    echo "   <fieldset>\n";

    #
    # Look for non-specific error.
    #
    if ($errors && array_key_exists("error", $errors)) {
333 334
	echo "<font color=red><center>" . $errors["error"] .
	    "</center></font><br>";
335
    }
336 337 338 339

    #
    # Ask for user information
    #
340 341 342 343 344 345 346 347 348 349 350 351
    if (!isset($this_user)) {
	$formatter("username", 
		  "<input name=\"formfields[username]\"
		          value='" . $formfields["username"] . "'
                          class='form-control'
                          placeholder='Pick a user name'
                          autofocus type='text'>");
   
	$formatter("email", 
		  "<input name=\"formfields[email]\"
                          type='text'
                          value='" . $formfields["email"] . "'
352
                          class='form-control'
353
                          placeholder='Your email address' type='text'>");
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
    }
    # We put the ssh stuff in two different places, so make it a function.
    $spitsshkeystuff = function() use ($formfields, $formatter) {
	if ($formfields["sshkey"] == "") {
	    $title_text = "<span class='text-warning'>
                    No SSH key, browser shell only!<span>";
	    $expand_text = "Add Key";
	}
	else {
	    $title_text = "<span class='text-info'>
                    Your SSH key</span>";
	    $expand_text = "Update";
	}
        echo "<div class='form-group row' style='margin-bottom: 0px;'>";
        echo "  <div class='col-md-12'>
                  <div class='panel panel-default'>\n";
        echo "      <div class='panel-heading'>$title_text
                     <a class='pull-right'
                        data-toggle='collapse' href='#mysshkey'>
                      $expand_text</a>\n";
        echo "      </div>\n";
	echo "      <div id='mysshkey' class='panel-collapse collapse'>\n";
        echo "       <div class='panel-body'>";
	
378 379
	$formatter("keyfile",
		   "<span class='help-block'>
380 381 382
                     Upload a file or paste it in the text box. This will ".
		   "allow you to login using your favorite ssh client. Without ".
		   "a SSH key, you will be limited to using a shell window in ".
383 384 385
		   "your browser. If you already see a key here, you can ".
		   "change it and we will remember your new key for next time. ".
                   "Don't know how to generate your SSH key? ".
386
		   "See <a href='https://help.github.com/articles/generating-ssh-keys'>this tutorial.</a></span>".
387 388
		   "<input type=file name='keyfile'>");

389 390
	$formatter("sshkey", 
		  "<textarea name=\"formfields[sshkey]\" 
391
                             placeholder='Paste in your ssh public key.'
392 393 394
                             class='form-control'
                             rows=4 cols=45>" . $formfields["sshkey"] .
                  "</textarea>");
395 396 397 398 399 400 401 402 403

        echo "       </div>";
        echo "       <div class='clearfix'></div>";
        echo "      </div>";
        echo "    </div>";
        echo "</div></div>"; # End of panel/row.
    };
    if (!isset($this_user)) {
	$spitsshkeystuff();
404
    }
405 406 407 408 409 410

    #
    # Only print profile selection box if we weren't linked to a specific
    # profile
    #
    if (!isset($profile)) {
411
        echo "<div class='form-group row' style='margin-bottom: 0px;'>";
412 413
        echo "<input id='selected_profile' type='hidden' 
                       name='formfields[profile]'/>";
414
        echo "<div class='col-md-12'><div class='panel panel-default'>\n";
415
        echo "<div class='panel-heading'>
Robert Ricci's avatar
Robert Ricci committed
416
                  <span class='panel-title'><strong>Selected Profile:</strong> 
417
                  <span id='selected_profile_text'>
Robert Ricci's avatar
Robert Ricci committed
418
                  </span></span>\n";
419 420 421 422 423 424
        if ($errors && array_key_exists("profile", $errors)) {
            echo "<label class='control-label' for='inputError'>" .
                $errors["profile"] .
                " </label>\n";
        }
        echo " </div>\n";
425
        # Note: Following line is also duplicated above
426 427 428 429
        echo "<div class='panel-body'>";
        echo "  <div id='selected_profile_description'></div>\n";
        echo "</div>";
        echo "<div class='panel-footer'>";
430
        if (isset($this_user) && !$this_user->webonly()) {
431
            echo "<button class='btn btn-default btn-sm pull-left' 
432
                         type='button' id='profile_copy_button'
433 434 435 436 437 438 439 440 441 442
                         style='margin-right: 10px;'
		    data-toggle='popover'
		    data-delay='{hide:1500, show:500}'
		    data-html='true'
		    data-content='When you copy a profile
		    you are creating a new profile that
		    uses the same source code and metadata (description,
		    instructions) as the original profile, but without
		    creating a new disk image. Instead, the new profile uses
		    whatever images the original profile uses.'>Copy Profile
443
                  </button>";
444 445 446 447
            echo "<button class='btn btn-default btn-sm pull-left'
                          type='button' id='profile_show_button'>
                    Show Profile
                  </button>";
448 449
        }
        echo "<button id='profile' class='btn btn-primary btn-sm pull-right' 
450
                         type='button' name='profile_button'>
451 452 453 454
                    Change Profile
                  </button>";
        echo "<div class='clearfix'></div>";
        echo "</div>";
455
        echo "</div></div></div>"; # End of panel/row.
456
    }
Leigh Stoller's avatar
Leigh Stoller committed
457
    else {
458 459 460 461 462 463
	echo "<input id='selected_profile' type='hidden'
                     name='formfields[profile]'
                     value='" . $formfields["profile"] . "'>\n";

	# Send the original argument for the initial array stuff above.
        # Needs more work.
464 465
        $thisuuid = $profile->uuid();
	echo "<input type='hidden' name='profile' value='$thisuuid'>\n";
Leigh Stoller's avatar
Leigh Stoller committed
466
    }
467

468 469 470 471
    #
    # Container for the inputs that get copied to the last step of the wizard
    #
    echo "<div id='experiment_options' class='hidden'>\n";
472 473 474 475
    #
    # Spit out an experient name box, which is optional and prefilled
    # with a default.
    #
476
    if ($this_user) {
477 478 479 480
        $thisclass = "form-group";
        if ($errors && array_key_exists("name", $errors)) {
            $thisclass .= " has-error";
        }
481
        echo "<div id='name_selector' class='form-horizontal experiment_option'>
482 483 484 485 486
                <div class='$thisclass'>
                  <label class='col-sm-4 control-label'
                         style='text-align: right;'>Name:</label>
                  <div class='col-sm-6'
                       data-toggle='popover'
487 488 489
               data-delay='{hide:1500, show:500}'
               data-html='true'
               data-content='Provide a unique name to identity your
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
                           new experiment. If you are in
                           the habit of starting more then one experiment at
                           a time this is really handy when trying to tell
                           one experiment from another, or when referring to
                           an experiment when asking for help.'>
                    <input id='experiment_name'
                           placeholder='Optional'
                           class='form-control'
                           name='formfields[name]'
                           value='" . $formfields["name"] . "'>\n";
        if ($errors && array_key_exists("name", $errors)) {
            echo "  <label class='control-label'>". $errors["name"] . "</label>";
        }
        echo "    </div>
                </div>
              </div>\n";
    }
     
508 509 510
    #
    # Spit out a project selection list if more then one project membership
    #
511
    if ($this_user && !$this_user->webonly()) {
512
        if (count($projlist) == 1) {
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
            echo "<input id='profile_pid' type='hidden'
                     name='formfields[pid]'
                     value='" . $formfields["pid"] . "'>\n";
        }
        else {
            $project_options = "";
            while (list($pid) = each($projlist)) {
                $selected = "";
                if ($formfields["pid"] == $pid) {
                    $selected = "selected";
                }
                $project_options .= 
                    "<option $selected value='$pid'>$pid</option>\n";
            }
            $html =
528
                "<div class='form-horizontal experiment_option' id='profile_selector'><div class='form-group'>
529 530 531 532 533 534 535 536 537 538
                   <label class='col-sm-4 control-label'
                      style='text-align: right;'>Project:</label>
                   <div class='col-sm-6'>
                       <select name=\"formfields[pid]\"
		              id='profile_pid' class='form-control'>
                       $project_options</select></div></div></div>\n";
            echo $html;
        }
    }

539 540
    if (isset($this_user) && !$this_user->webonly() && 
        ($ISCLOUD || ISADMIN() || STUDLY())) {
541 542
	$am_options = "";
	while (list($am, $urn) = each($am_array)) {
543
	    $amlist[] = $am;
544 545 546 547 548 549 550
	    $selected = "";
	    if ($formfields["where"] == $am) {
		$selected = "selected";
	    }
	    $am_options .= 
		"<option $selected value='$am'>$am</option>\n";
	}
551

552
        $html = "<div id='aggregate_selector'>
553
                  <div class='form-horizontal experiment_option' id='nosite_selector'>
Leigh Stoller's avatar
Leigh Stoller committed
554
                   <div class='form-group'>
555
                   <label class='col-sm-4 control-label'
556 557
                      style='text-align: right;'>
       <a href=cluster-status.php target=_blank>Cluster:</a></label>
558 559 560
                   <div class='col-sm-6'>
                       <select name=\"formfields[where]\"
		              id='profile_where' class='form-control'>
561
                       $am_options</select><br>
562
                </div><div class='col-sm-10 col-sm-offset-1' style='text-align: center'>
563
		       <div class='alert alert-warning' id='where-warning' style='display: none'>This profile only works on some clusters. Incompatible clusters are unselectable.</div>
564 565 566
</div></div></div>";
        $html = $html . "<div id='site_selector' class='hidden'></div></div>";
        echo $html;
567
    }
568
    echo "</div>\n";
569
    echo "</fieldset>
570
           <div class='form-group row hidden'>
571
           <div class='col-sm-6 col-sm-offset-3'>
572
           
573 574 575 576
           <button class='btn btn-primary btn-block hidden'
              id='configurator_button'
              type='button'>Configure
           </button>
577
           <button class='btn btn-success btn-block hidden' id='instantiate_submit'
578
              type='submit' name='create'>Create!
579
           </button>
580
           </div>
581 582 583 584 585 586 587 588 589
           </div>\n";

    # This is for a PP rspec.
    echo "<textarea name='formfields[pp_rspec]'
            id='pp_rspec_textarea'
                    class='form-control hidden'
                    type='textarea'></textarea>\n";
    echo "</form>\n
          </div>\n";
590
    if (!isset($this_user)) {
591 592
        echo "</div>
              </div>\n";
593
    }
594 595 596
    echo "<h3>Parameterize</h3><div class='col-lg-8  col-lg-offset-2
                                    col-md-8  col-md-offset-2
                                    col-sm-10  col-sm-offset-1
597
                                    col-xs-12 col-xs-offset-0'></div>\n";
598 599 600
    echo "<h3>Finalize</h3><div class='col-lg-8  col-lg-offset-2
                                    col-md-8  col-md-offset-2
                                    col-sm-10  col-sm-offset-1
601 602 603 604 605 606 607 608 609 610
                                    col-xs-12 col-xs-offset-0'>
                            <div id='finalize_container' class='col-lg-8 col-md-8 col-sm-8 col-xs-12'>
                                <div class='panel panel-default'>
                                    <div class='panel-heading'>Please review the selections below and then click Finish.</div>
                                    <div class='panel-body'>
                                        <div id='finalize_options'></div>
                                    </div>
                                </div>
                            </div>
                            <div id='inline_container' class='col-lg-4 col-md-4 col-sm-4 col-xs-12'>
611
                                <a id='inline_overlay' href='#'><span class='glyphicon glyphicon-fullscreen' aria-hidden='true'></span></a> 
612 613 614 615 616 617 618 619 620
                                <div id='inline_jacks'></div>
                            </div>
                                    </div>\n";
    echo "</div>\n";
    echo "<button class='btn btn-primary btn-sm'
        style='display:block;visibility:hidden;'
                id='modal_profile_continue_button'
                type='button' name='create'>Continue</button>\n";
    if (!isset($this_user)) {
621
	SpitVerifyModal("verify_modal", "Create");
622
    
623 624 625 626 627 628 629 630
	if ($newuser) {
	    if (is_string($newuser)) {
		$stuffing = $newuser;
	    }
	    else {
		$stuffing = substr(GENHASH(), 0, 16);
	    }
	    mail($formfields["email"],
631
		 "aptlab.net: Verification code for creating your experiment",
632 633 634
		 "Here is your user verification code. Please copy and\n".
		 "paste this code into the box on the experiment page.\n\n".
		 "      $stuffing\n",
Leigh Stoller's avatar
Leigh Stoller committed
635
		 "From: $APTMAIL");
636
	    echo "<input type='hidden' name='stuffing' value='$stuffing' />";
Leigh Stoller's avatar
Leigh Stoller committed
637 638 639
	}
    }

640
    SpitTopologyViewModal("quickvm_topomodal", $profile_array);
641 642 643 644 645
    echo "<div id='waitwait_div'></div>\n";
    echo "<div id='ppmodal_div'></div>\n";
    echo "<div id='instantiate_div'></div>\n";
    echo "<div id='editmodal_div'></div>\n";
    SpitOopsModal("oops");
646

647 648
    if (isset($this_user) && !$this_user->webonly() &&
        ($ISCLOUD || ISADMIN() || STUDLY())) {
649 650 651 652
	echo "<script type='text/plain' id='amlist-json'>\n";
	echo htmlentities(json_encode($amlist));
	echo "</script>\n";
    }
653
    echo "<script type='text/javascript'>\n";
654 655 656
    echo "    window.PROFILE    = '" . $formfields["profile"] . "';\n";
    echo "    window.AJAXURL    = 'server-ajax.php';\n";
    echo "    window.SHOWABOUT  = $showabout;\n";
657
    echo "    window.NOPPRSPEC  = $nopprspec;\n";
658
    echo "    window.REGISTERED = $registered;\n";
659 660
    echo "    window.WEBONLY    = $webonly;\n";
    echo "    window.PORTAL     = '$portal';\n";
661 662 663
    if ($newuser) {
	echo "window.APT_OPTIONS.isNewUser = true;\n";
    }
664 665
    $isadmin = (isset($this_user) && ISADMIN() ? 1 : 0);
    echo "    window.ISADMIN    = $isadmin;\n";
666
    echo "</script>\n";
667
    echo "<script src='js/lib/jquery-2.0.3.min.js'></script>\n";
668
    echo "<script src='js/lib/bootstrap.js'></script>\n";
669
    echo "<script src='js/lib/require.js' data-main='js/instantiate'></script>";
Leigh Stoller's avatar
Leigh Stoller committed
670 671 672
}

if (!isset($create)) {
673 674 675 676
    $defaults = array();
    $defaults["username"] = "";
    $defaults["email"]    = "";
    $defaults["sshkey"]   = "";
677 678
    $defaults["profile"]  = (isset($profile) ?
                             $profile->uuid() : $profile_default);
679
    $defaults["where"]    = $DEFAULT_AGGREGATE;
680 681 682 683 684 685 686 687
    if ($this_user && count($projlist)) {
	list($project, $grouplist) = each($projlist);
        $defaults["pid"] = $project;
        reset($projlist);
    }
    else {
        $defaults["pid"] = "";
    }
688

689
    # 
690
    # Look for current user or cookie that tells us who the user is. 
691
    #
692
    if ($this_user) {
693 694
	$defaults["username"] = $this_user->uid();
	$defaults["email"]    = $this_user->email();
695 696 697 698 699 700 701
	#
	# 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) {
702 703
	    $sshkeys = $this_user->GetSSHKeys();
	    if (count($sshkeys)) {
704
		$sshkey = $sshkeys[0];
705 706
	    }
	}
707 708 709
	if ($sshkey) {
	    $defaults["sshkey"] = $sshkey;
	}
710 711
    }
    elseif (isset($_COOKIE['quickvm_user'])) {
Leigh Stoller's avatar
Leigh Stoller committed
712 713 714 715 716 717
	$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
718 719
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
720 721
		header("Location: status.php?oneonly=1&uuid=" .
		       $instance->uuid());
Leigh Stoller's avatar
Leigh Stoller committed
722 723
		return;
	    }
724 725 726 727 728 729 730 731
            #
            # 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;
            }
732 733 734
	    $defaults["username"] = $geniuser->name();
	    $defaults["email"]    = $geniuser->email();
	    $defaults["sshkey"]   = $geniuser->SSHKey();
Leigh Stoller's avatar
Leigh Stoller committed
735 736
	}
    }
737
    SPITFORM($defaults, false, array());
738
    echo "<div style='display: none'><div id='jacks-dummy'></div></div>\n";
Leigh Stoller's avatar
Leigh Stoller committed
739 740 741 742 743 744 745 746 747
    SPITFOOTER();
    return;
}
#
# Otherwise, must validate and redisplay if errors
#
$errors = array();
$args   = array();

748 749 750 751
if (!$this_user) {
    #
    # These check do not matter for a logged in user; we ignore the values.
    #
752
    if (!isset($formfields["email"]) || $formfields["email"] == "") {
753 754
	$errors["email"] = "Missing Field";
    }
755
    elseif (! TBvalid_email($formfields["email"])) {
756 757
	$errors["email"] = TBFieldErrorString();
    }
758
    if (!isset($formfields["username"]) || $formfields["username"] == "") {
759 760
	$errors["username"] = "Missing Field";
    }
761
    elseif (! TBvalid_uid($formfields["username"])) {
762 763
	$errors["username"] = TBFieldErrorString();
    }
764
    elseif (User::LookupByUid($formfields["username"])) {
765
        # Do not allow uid overlap with real users.
766
	$errors["username"] = "Already in use - if you have an Emulab account, log in first";
767
    }
Leigh Stoller's avatar
Leigh Stoller committed
768
}
769
if (!isset($formfields["profile"]) || $formfields["profile"] == "") {
Leigh Stoller's avatar
Leigh Stoller committed
770
    $errors["profile"] = "No selection made";
Leigh Stoller's avatar
Leigh Stoller committed
771
}
772 773
elseif (! array_key_exists($formfields["profile"], $profile_array)) {
    $errors["profile"] = "Invalid Profile: " . $formfields["profile"];
Leigh Stoller's avatar
Leigh Stoller committed
774
}
775 776 777 778 779 780
else {
    $profile = Profile::Lookup($formfields["profile"]);
    if (!$profile) {
	$errors["profile"] = "No such profile in the database";
    }
}
Leigh Stoller's avatar
Leigh Stoller committed
781

782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
if ($this_user) {
    #
    # Project has to exist.  
    #
    $project = Project::LookupByPid($formfields["pid"]);
    if (!$project) {
        $errors["pid"] = "No such project";
    }
    # User better be a member.
    elseif (!ISADMIN() &&
        (!$project->IsMember($this_user, $isapproved) || !$isapproved)) {
        $errors["pid"] = "Illegal project";
    }
    else {
        $args["pid"] = $project->pid_idx();
    }
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814

    # Experiment name is optional, we generate one later.
    if (isset($formfields["name"]) && $formfields["name"] != "") {
        if (strlen($formfields["name"]) > 16) {
            $errors["name"] = "Too long; must be <= 16 characters";
        }
        elseif (!TBvalid_eid($formfields["name"])) {
            $errors["name"] = TBFieldErrorString();
        }
        elseif ($project &&
                Instance::LookupByName($project, $formfields["name"])) {
            $errors["name"] = "Already in use by another experiment";
        }
        else {
            $args["instance_name"] = $formfields["name"];
        }
    }
815 816
}

Leigh Stoller's avatar
Leigh Stoller committed
817 818 819
#
# More sanity checks. 
#
820
if (!$this_user) {
821
    $geniuser = GeniUser::LookupByEmail("sa", $formfields["email"]);
822
    if ($geniuser) {
823
	if ($geniuser->name() != $formfields["username"]) {    
824
            $errors["email"] = "Already in use by another guest user";
825 826
	    unset($geniuser);
	}
Leigh Stoller's avatar
Leigh Stoller committed
827 828
    }
}
829

830 831 832 833 834 835 836 837 838 839
#
# Real users are allowed to use Paramterized Profiles, which means
# we could get an rspec.
#
if ($profile && $profile->isParameterized() && 
    $this_user && !$this_user->IsNonLocal() &&
    isset($formfields["pp_rspec"]) && $formfields["pp_rspec"] != "") {
    $args["rspec"] = $formfields["pp_rspec"];
}

840 841 842 843
#
# Allow admin users to select the Aggregate. Experimental.
#
$aggregate_urn = "";
844
$sitemap = array();
845

846
if ($this_user && ($ISCLOUD || ISADMIN() || STUDLY())) {
847 848 849 850 851 852 853 854 855 856 857 858 859 860
    if (isset($formfields["sites"]) && is_array($formfields["sites"])) {
        while (list($siteid, $am) = each($formfields["sites"])) {
            if (array_key_exists($am, $am_array)) {
                $sitemap[$siteid] = $am_array[$am];
            }
            else {
                $errors["sites"] = "Invalid Aggregate";
                break;
            }
        }
    }
    elseif (isset($formfields["where"]) &&
            $formfields["where"] != "" &&
            array_key_exists($formfields["where"], $am_array)) {
861
	    $aggregate_urn = $am_array[$formfields["where"]];
862 863 864
    }
    else {
        $errors["where"] = "Invalid Aggregate";
865 866
    }
}
867 868 869 870 871 872
if (count($errors)) {
    SPITFORM($formfields, false, $errors);
    SPITFOOTER();
    return;
}

873 874
#
# SSH keys are now optional for guest users; they just have to
875 876 877 878
# use the web based ssh window.
#
# Backend verifies pubkey and returns error. We first look for a 
# file and then fall back to an inline field.
879
#
880 881 882 883 884 885
if (isset($_FILES['keyfile']) &&
    $_FILES['keyfile']['name'] != "" &&
    $_FILES['keyfile']['name'] != "none") {

    $localfile = $_FILES['keyfile']['tmp_name'];
    $args["sshkey"] = file_get_contents($localfile);
Leigh Stoller's avatar
Leigh Stoller committed
886 887 888 889 890
    #
    # The filename will be lost on another trip through the browser.
    # So stick the key into the box.
    #
    $formfields["sshkey"] = $args["sshkey"];
891 892
}
elseif (isset($formfields["sshkey"]) && $formfields["sshkey"] != "") {
893
    $args["sshkey"] = $formfields["sshkey"];
Leigh Stoller's avatar
Leigh Stoller committed
894 895 896
}

if (count($errors)) {
897
    SPITFORM($formfields, false, $errors);
Leigh Stoller's avatar
Leigh Stoller committed
898 899 900
    SPITFOOTER();
    return;
}
901
# Silently ignore the form for a logged in user. 
902 903 904
$args["username"] = ($this_user ? $this_user->uid() : $formfields["username"]);
$args["email"]    = ($this_user ? $this_user->email() : $formfields["email"]);
$args["profile"]  = $formfields["profile"];
Leigh Stoller's avatar
Leigh Stoller committed
905 906 907 908 909

#
# See if user exists and is verified. We send email with a code, which
# they have to paste back into a box we add to the form. See above.
#
Leigh Stoller's avatar
Leigh Stoller committed
910 911 912 913 914
# We also get here if the user exists, but the browser did not have
# the tokens, as will happen if switching to another browser. We
# force the user to repeat the verification with the same code we
# have stored in the DB.
#
915 916 917
if (!$this_user &&
    (!$geniuser || !isset($_COOKIE['quickvm_authkey']) ||
     $_COOKIE['quickvm_authkey'] != $geniuser->auth_token())) {
Leigh Stoller's avatar
Leigh Stoller committed
918 919
    if (isset($stuffing) && $stuffing != "") {
	if (! (isset($verify) && $verify == $stuffing)) {
920
	    SPITFORM($formfields, $stuffing, $errors);
Leigh Stoller's avatar
Leigh Stoller committed
921 922 923
	    SPITFOOTER();
	    return;
	}
Leigh Stoller's avatar
Leigh Stoller committed
924 925 926 927 928
	#
	# If this is an existing user and they give us the right code,
	# we can check again for an existing VM and redirect to the
	# status page, like we do above.
	#
929
	if ($geniuser) {
Leigh Stoller's avatar
Leigh Stoller committed
930 931
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
932 933 934 935 936
		# Reset this cookie so status page is happy and so we
                # will stop asking.
		setcookie("quickvm_user",
			  $geniuser->uuid(), time() + (24 * 3600 * 30),
			  "/", $TBAUTHDOMAIN, 0);
937 938
		header("Location: status.php?oneonly=1&uuid=" .
		       $instance->uuid());
Leigh Stoller's avatar
Leigh Stoller committed
939 940
		return;
	    }
941 942 943 944 945 946 947 948
            #
            # 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;
            }
Leigh Stoller's avatar
Leigh Stoller committed
949
	}
Leigh Stoller's avatar
Leigh Stoller committed
950 951 952 953
	# Pass to backend to save in user object.
	$args["auth_token"] = $stuffing;
    }
    else {
Leigh Stoller's avatar
Leigh Stoller committed
954 955
	# Existing user, use existing auth token.
	# New user, we create a new one.
956
	$token = ($geniuser ? $geniuser->auth_token() : true);
Leigh Stoller's avatar
Leigh Stoller committed
957

958
	SPITFORM($formfields, $token, $errors);
Leigh Stoller's avatar
Leigh Stoller committed
959 960 961 962 963
	SPITFOOTER();
	return;
    }
}

964
# Admins can change aggregate.
965 966 967 968 969 970 971 972 973 974
$options = "";

if ($aggregate_urn != "") {
    $options = " -a '$aggregate_urn'";
}
elseif (count($sitemap)) {
    while (list($siteid, $urn) = each($sitemap)) {
        $options .= "--site 'site:${siteid}=${urn}' ";
    }
}
Leigh Stoller's avatar
Leigh Stoller committed
975 976

#
977
# Invoke the backend.
Leigh Stoller's avatar
Leigh Stoller committed
978
#
979 980
list ($instance, $creator) =
    Instance::Instantiate($this_user, $options, $args, $errors);
Leigh Stoller's avatar
Leigh Stoller committed
981

Leigh Stoller's avatar
Leigh Stoller committed
982
if (!$instance) {
983
    SPITFORM($formfields, false, $errors);
Leigh Stoller's avatar
Leigh Stoller committed
984 985 986
    SPITFOOTER();
    return;
}
987
    
988
#
Leigh Stoller's avatar
Leigh Stoller committed
989
# Remember the user and auth key so that we can verify.
990 991 992 993 994 995
#
# The cookie handling is a pain since we run this under the aptlab
# virtual host, but the config uses a different domain, and so the
# cookies do not work. So, we have to look at our SERVER_NAME and
# set the cookie appropriately. 
#
996
if (!$this_user) {
997 998
    $cookiedomain = $TBAUTHDOMAIN;

999 1000 1001 1002 1003 1004
    setcookie("quickvm_user",
	      $creator->uuid(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
    setcookie("quickvm_authkey",
	      $creator->auth_token(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
1005
}
Leigh Stoller's avatar
Leigh Stoller committed
1006
header("Location: status.php?uuid=" . $instance->uuid());
Leigh Stoller's avatar
Leigh Stoller committed
1007
?>