instantiate.php 32.4 KB
Newer Older
Leigh B Stoller's avatar
Leigh B Stoller committed
1 2
<?php
#
Leigh B Stoller's avatar
Leigh B Stoller committed
3
# Copyright (c) 2000-2015 University of Utah and the Flux Group.
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B Stoller committed
28
include_once("webtask.php");
29
chdir("apt");
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B Stoller committed
34
$dblink = GetDBLink("sa");
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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
	#
Leigh B Stoller's avatar
Leigh B Stoller committed
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 B Stoller's avatar
Leigh B Stoller committed
212
}
Leigh B Stoller's avatar
Leigh B Stoller committed
213

214
function SPITFORM($formfields, $newuser, $errors)
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B Stoller committed
235

236 237 238
    # XSS prevention.
    while (list ($key, $val) = each ($formfields)) {
	$formfields[$key] = CleanString($val);
Leigh B Stoller's avatar
Leigh B Stoller committed
239
    }
240
    # XSS prevention.
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B Stoller committed
266
    SPITHEADER(1);
Leigh B Stoller's avatar
Leigh B Stoller committed
267

268
    echo "<div id='ppviewmodal_div'></div>\n";
269
    echo "<div class='row'>
Leigh B Stoller's avatar
Leigh B Stoller committed
270
          <div class='col-lg-6  col-lg-offset-3
271
                      col-md-6  col-md-offset-3
Leigh B Stoller's avatar
Leigh B Stoller committed
272 273
                      col-sm-8  col-sm-offset-2
                      col-xs-12 col-xs-offset-0'>\n";
Jonathon Duerig's avatar
Jonathon Duerig committed
274

275 276
    # Placeholder for the "about" panel, which is now a template file.
    echo "<div id='about_div'></div>\n";
Jonathon Duerig's avatar
Jonathon Duerig committed
277

278
    echo "<form id='quickvm_form' role='form'
279
            enctype='multipart/form-data'
280
            method='post' action='instantiate.php'>\n";
281 282 283 284 285 286 287 288
    if (!$this_user) {
	echo "<div class='panel panel-default'>
                <div class='panel-heading'>
                   <h3 class='panel-title'>\n";
    }
    else {
	echo "<h3 style='margin-top: 0px;'>";
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
289
    echo "<center>Start Experiment";
290 291 292
    if (isset($profilename)) {
        echo " using profile &quot;$profilename&quot";
    }
293 294 295 296 297
    echo "</center></h3>\n";
    if (!$this_user) {
	echo "   </div>
	      <div class='panel-body'>\n";
    }
298 299 300 301 302
    
    #
    # If linked to a specific profile, description goes here
    #
    if ($profile) {
303 304
	$cluster = ($ISCLOUD ? "Cloudlab" : "APT");
	
305 306 307 308
	if (!$this_user) {
	    echo "  <p>Fill out the form below to run an experiment ".
		"using this profile:</p>\n";
	}
309
        # Note: Following line is also duplicated below
310 311 312
        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
313
                   on ${cluster}'s hardware<p>\n";
314 315
    }

316 317 318 319 320 321
    echo "   <fieldset>\n";

    #
    # Look for non-specific error.
    #
    if ($errors && array_key_exists("error", $errors)) {
322 323
	echo "<font color=red><center>" . $errors["error"] .
	    "</center></font><br>";
324
    }
325 326 327 328

    #
    # Ask for user information
    #
329 330 331 332 333 334 335 336 337 338 339 340
    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"] . "'
341
                          class='form-control'
342
                          placeholder='Your email address' type='text'>");
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
    }
    # 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'>";
	
367 368
	$formatter("keyfile",
		   "<span class='help-block'>
369 370 371
                     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 ".
372 373 374
		   "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? ".
375
		   "See <a href='https://help.github.com/articles/generating-ssh-keys'>this tutorial.</a></span>".
376 377
		   "<input type=file name='keyfile'>");

378 379
	$formatter("sshkey", 
		  "<textarea name=\"formfields[sshkey]\" 
380
                             placeholder='Paste in your ssh public key.'
381 382 383
                             class='form-control'
                             rows=4 cols=45>" . $formfields["sshkey"] .
                  "</textarea>");
384 385 386 387 388 389 390 391 392

        echo "       </div>";
        echo "       <div class='clearfix'></div>";
        echo "      </div>";
        echo "    </div>";
        echo "</div></div>"; # End of panel/row.
    };
    if (!isset($this_user)) {
	$spitsshkeystuff();
393
    }
394 395 396 397 398 399

    #
    # Only print profile selection box if we weren't linked to a specific
    # profile
    #
    if (!isset($profile)) {
400
        echo "<div class='form-group row' style='margin-bottom: 0px;'>";
401 402
        echo "<input id='selected_profile' type='hidden' 
                       name='formfields[profile]'/>";
403
        echo "<div class='col-md-12'><div class='panel panel-default'>\n";
404
        echo "<div class='panel-heading'>
Robert Ricci's avatar
Robert Ricci committed
405
                  <span class='panel-title'><strong>Selected Profile:</strong> 
406
                  <span id='selected_profile_text'>
Robert Ricci's avatar
Robert Ricci committed
407
                  </span></span>\n";
408 409 410 411 412 413
        if ($errors && array_key_exists("profile", $errors)) {
            echo "<label class='control-label' for='inputError'>" .
                $errors["profile"] .
                " </label>\n";
        }
        echo " </div>\n";
414
        # Note: Following line is also duplicated above
415 416 417 418
        echo "<div class='panel-body'>";
        echo "  <div id='selected_profile_description'></div>\n";
        echo "</div>";
        echo "<div class='panel-footer'>";
419
        if (isset($this_user) && !$this_user->webonly()) {
420
            echo "<button class='btn btn-default btn-sm pull-left' 
421
                         type='button' id='profile_copy_button'
Leigh B Stoller's avatar
Leigh B Stoller committed
422 423 424 425 426 427 428 429 430 431
                         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
432
                  </button>";
433 434 435 436
            echo "<button class='btn btn-default btn-sm pull-left'
                          type='button' id='profile_show_button'>
                    Show Profile
                  </button>";
437 438
        }
        echo "<button id='profile' class='btn btn-primary btn-sm pull-right' 
439
                         type='button' name='profile_button'>
440 441 442 443
                    Change Profile
                  </button>";
        echo "<div class='clearfix'></div>";
        echo "</div>";
444
        echo "</div></div></div>"; # End of panel/row.
445
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
446
    else {
447 448 449 450 451 452
	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.
453 454
        $thisuuid = $profile->uuid();
	echo "<input type='hidden' name='profile' value='$thisuuid'>\n";
Leigh B Stoller's avatar
Leigh B Stoller committed
455
    }
456
    if (isset($this_user) && !$this_user->webonly()) {
457 458
        echo "<div class='panel panel-info'>\n";
        echo "  <div class='panel-body bg-info' style='padding: 5px;'>\n";
459 460 461 462 463 464
        #
        # Local users, show a link to the ssh keys page.
        # Nonlocal users, remind them ssh keys go into their portal.
        #
        if ($this_user->IsNonLocal()) {
            echo "<div>";
465
            echo "  <div>
466 467 468 469 470 471 472 473
                    GENI Users; be sure to add ssh keys at <b>your</b> portal if
                    you want to log in from your desktop, else you
	            will be limited to using a shell window in your browser. 
                    </div>
                  </div>\n";
        }
        else {
            echo "<div>";
474
            echo "  <div>
475 476 477 478 479 480
                     <a href='ssh-keys.php'>Manage your SSH keys</a> if
                     you want to log in from your desktop, else you
		     will be limited to using a shell window in your browser. 
                    </div>
                  </div>\n";
        }
481 482
        echo "  </div>\n";
        echo "</div>\n";
483
    }
484

485 486 487 488
    #
    # Spit out an experient name box, which is optional and prefilled
    # with a default.
    #
489
    if ($this_user && ISADMIN()) {
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
        $thisclass = "form-group";
        if ($errors && array_key_exists("name", $errors)) {
            $thisclass .= " has-error";
        }
        echo "<div class='form-horizontal'>
                <div class='$thisclass'>
                  <label class='col-sm-4 control-label'
                         style='text-align: right;'>Name:</label>
                  <div class='col-sm-6'
                       data-toggle='popover'
		       data-delay='{hide:1500, show:500}'
		       data-html='true'
		       data-content='Provide a unique name to identity your
                           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";
    }
     
521 522 523
    #
    # Spit out a project selection list if more then one project membership
    #
524
    if ($this_user && !$this_user->webonly()) {
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
        if (count($projlist) == 1) {
            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 =
                "<div class='form-horizontal'><div class='form-group'>
                   <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;
        }
    }

552 553
    if (isset($this_user) && !$this_user->webonly() && 
        ($ISCLOUD || ISADMIN() || STUDLY())) {
554 555
	$am_options = "";
	while (list($am, $urn) = each($am_array)) {
556
	    $amlist[] = $am;
557 558 559 560 561 562 563
	    $selected = "";
	    if ($formfields["where"] == $am) {
		$selected = "selected";
	    }
	    $am_options .= 
		"<option $selected value='$am'>$am</option>\n";
	}
564 565
        $html = "<div id='aggregate_selector'>
                  <div class='form-horizontal' id='nosite_selector'>
Leigh B Stoller's avatar
Leigh B Stoller committed
566
                   <div class='form-group'>
567
                   <label class='col-sm-4 control-label'
568 569
                      style='text-align: right;'>
       <a href=cluster-status.php target=_blank>Cluster:</a></label>
570 571 572
                   <div class='col-sm-6'>
                       <select name=\"formfields[where]\"
		              id='profile_where' class='form-control'>
573 574
                       $am_options</select><br>
		       <div class='alert alert-warning' id='where-warning' style='display: none'>This profile only works on some clusters. Incompatible clusters are unselectable.</div>
575 576 577
</div></div></div>";
        $html = $html . "<div id='site_selector' class='hidden'></div></div>";
        echo $html;
578
    }
579
    echo "</fieldset>
580
           <div class='form-group row'>
581 582 583 584 585
           <div class='col-sm-6 col-sm-offset-3'>
           <button class='btn btn-primary btn-block hidden'
              id='configurator_button'
              type='button'>Configure
           </button>
586
           <button class='btn btn-success btn-block' id='instantiate_submit'
Keith Downie's avatar
Keith Downie committed
587
              type='submit' name='create'>Create!
588
           </button>
589
           </div>
590
           </div>
Leigh B Stoller's avatar
Leigh B Stoller committed
591
        </div>\n";
592
    if (!isset($this_user)) {
593 594
        echo "</div>
              </div>\n";
595
	SpitVerifyModal("verify_modal", "Create");
596
    
597 598 599 600 601 602 603 604
	if ($newuser) {
	    if (is_string($newuser)) {
		$stuffing = $newuser;
	    }
	    else {
		$stuffing = substr(GENHASH(), 0, 16);
	    }
	    mail($formfields["email"],
605
		 "aptlab.net: Verification code for creating your experiment",
606 607 608
		 "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 B Stoller's avatar
Leigh B Stoller committed
609
		 "From: $APTMAIL");
610
	    echo "<input type='hidden' name='stuffing' value='$stuffing' />";
Leigh B Stoller's avatar
Leigh B Stoller committed
611 612
	}
    }
613
    echo "</div>\n";
614 615 616 617 618
    # This is for a PP rspec.
    echo "<textarea name='formfields[pp_rspec]'
		    id='pp_rspec_textarea'
                    class='form-control hidden'
                    type='textarea'></textarea>\n";
Leigh B Stoller's avatar
Leigh B Stoller committed
619 620
    echo "</form>\n";

621
    SpitTopologyViewModal("quickvm_topomodal", $profile_array);
622 623 624 625 626
    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");
Keith Downie's avatar
Keith Downie committed
627

628 629
    if (isset($this_user) && !$this_user->webonly() &&
        ($ISCLOUD || ISADMIN() || STUDLY())) {
630 631 632 633
	echo "<script type='text/plain' id='amlist-json'>\n";
	echo htmlentities(json_encode($amlist));
	echo "</script>\n";
    }
634
    echo "<script type='text/javascript'>\n";
635 636 637
    echo "    window.PROFILE    = '" . $formfields["profile"] . "';\n";
    echo "    window.AJAXURL    = 'server-ajax.php';\n";
    echo "    window.SHOWABOUT  = $showabout;\n";
638
    echo "    window.NOPPRSPEC  = $nopprspec;\n";
639
    echo "    window.REGISTERED = $registered;\n";
640 641
    echo "    window.WEBONLY    = $webonly;\n";
    echo "    window.PORTAL     = '$portal';\n";
642 643 644 645
    if ($newuser) {
	echo "window.APT_OPTIONS.isNewUser = true;\n";
    }
    echo "</script>\n";
646
    echo "<script src='js/lib/jquery-2.0.3.min.js'></script>\n";
647
    echo "<script src='js/lib/bootstrap.js'></script>\n";
648
    echo "<script src='js/lib/require.js' data-main='js/instantiate'></script>";
Leigh B Stoller's avatar
Leigh B Stoller committed
649 650 651
}

if (!isset($create)) {
652 653 654 655
    $defaults = array();
    $defaults["username"] = "";
    $defaults["email"]    = "";
    $defaults["sshkey"]   = "";
656 657
    $defaults["profile"]  = (isset($profile) ?
                             $profile->uuid() : $profile_default);
658
    $defaults["where"]    = $DEFAULT_AGGREGATE;
659 660 661 662 663 664 665 666
    if ($this_user && count($projlist)) {
	list($project, $grouplist) = each($projlist);
        $defaults["pid"] = $project;
        reset($projlist);
    }
    else {
        $defaults["pid"] = "";
    }
667

668
    # 
669
    # Look for current user or cookie that tells us who the user is. 
670
    #
671
    if ($this_user) {
672 673
	$defaults["username"] = $this_user->uid();
	$defaults["email"]    = $this_user->email();
674 675 676 677 678 679 680
	#
	# 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) {
681 682
	    $sshkeys = $this_user->GetSSHKeys();
	    if (count($sshkeys)) {
683
		$sshkey = $sshkeys[0];
684 685
	    }
	}
686 687 688
	if ($sshkey) {
	    $defaults["sshkey"] = $sshkey;
	}
689 690
    }
    elseif (isset($_COOKIE['quickvm_user'])) {
Leigh B Stoller's avatar
Leigh B Stoller committed
691 692 693 694 695 696
	$geniuser = GeniUser::Lookup("sa", $_COOKIE['quickvm_user']);
	if ($geniuser) {
	    #
	    # Look for existing quickvm. User not allowed to create
	    # another one.
	    #
Leigh B Stoller's avatar
Leigh B Stoller committed
697 698
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
699 700
		header("Location: status.php?oneonly=1&uuid=" .
		       $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
701 702
		return;
	    }
703 704 705 706 707 708 709 710
            #
            # 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;
            }
711 712 713
	    $defaults["username"] = $geniuser->name();
	    $defaults["email"]    = $geniuser->email();
	    $defaults["sshkey"]   = $geniuser->SSHKey();
Leigh B Stoller's avatar
Leigh B Stoller committed
714 715
	}
    }
716
    SPITFORM($defaults, false, array());
717
    echo "<div style='display: none'><div id='jacks-dummy'></div></div>\n";
Leigh B Stoller's avatar
Leigh B Stoller committed
718 719 720 721 722 723 724 725 726
    SPITFOOTER();
    return;
}
#
# Otherwise, must validate and redisplay if errors
#
$errors = array();
$args   = array();

727 728 729 730
if (!$this_user) {
    #
    # These check do not matter for a logged in user; we ignore the values.
    #
731
    if (!isset($formfields["email"]) || $formfields["email"] == "") {
732 733
	$errors["email"] = "Missing Field";
    }
734
    elseif (! TBvalid_email($formfields["email"])) {
735 736
	$errors["email"] = TBFieldErrorString();
    }
737
    if (!isset($formfields["username"]) || $formfields["username"] == "") {
738 739
	$errors["username"] = "Missing Field";
    }
740
    elseif (! TBvalid_uid($formfields["username"])) {
741 742
	$errors["username"] = TBFieldErrorString();
    }
743
    elseif (User::LookupByUid($formfields["username"])) {
744
        # Do not allow uid overlap with real users.
745
	$errors["username"] = "Already in use - if you have an Emulab account, log in first";
746
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
747
}
748
if (!isset($formfields["profile"]) || $formfields["profile"] == "") {
Leigh B Stoller's avatar
Leigh B Stoller committed
749
    $errors["profile"] = "No selection made";
Leigh B Stoller's avatar
Leigh B Stoller committed
750
}
751 752
elseif (! array_key_exists($formfields["profile"], $profile_array)) {
    $errors["profile"] = "Invalid Profile: " . $formfields["profile"];
Leigh B Stoller's avatar
Leigh B Stoller committed
753
}
754 755 756 757 758 759
else {
    $profile = Profile::Lookup($formfields["profile"]);
    if (!$profile) {
	$errors["profile"] = "No such profile in the database";
    }
}
Leigh B Stoller's avatar
Leigh B Stoller committed
760

761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
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();
    }
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793

    # 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"];
        }
    }
794 795
}

Leigh B Stoller's avatar
Leigh B Stoller committed
796 797 798
#
# More sanity checks. 
#
799
if (!$this_user) {
800
    $geniuser = GeniUser::LookupByEmail("sa", $formfields["email"]);
801
    if ($geniuser) {
802
	if ($geniuser->name() != $formfields["username"]) {    
Leigh B Stoller's avatar
Leigh B Stoller committed
803
            $errors["email"] = "Already in use by another guest user";
804 805
	    unset($geniuser);
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
806 807
    }
}
808

809 810 811 812 813 814 815 816 817 818
#
# 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"];
}

819 820 821 822
#
# Allow admin users to select the Aggregate. Experimental.
#
$aggregate_urn = "";
823
$sitemap = array();
824

825
if ($this_user && ($ISCLOUD || ISADMIN() || STUDLY())) {
826 827 828 829 830 831 832 833 834 835 836 837 838 839
    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)) {
840
	    $aggregate_urn = $am_array[$formfields["where"]];
841 842 843
    }
    else {
        $errors["where"] = "Invalid Aggregate";
844 845
    }
}
846 847 848 849 850 851
if (count($errors)) {
    SPITFORM($formfields, false, $errors);
    SPITFOOTER();
    return;
}

852 853
#
# SSH keys are now optional for guest users; they just have to
854 855 856 857
# 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.
858
#
859 860 861 862 863 864
if (isset($_FILES['keyfile']) &&
    $_FILES['keyfile']['name'] != "" &&
    $_FILES['keyfile']['name'] != "none") {

    $localfile = $_FILES['keyfile']['tmp_name'];
    $args["sshkey"] = file_get_contents($localfile);
Leigh B Stoller's avatar
Leigh B Stoller committed
865 866 867 868 869
    #
    # The filename will be lost on another trip through the browser.
    # So stick the key into the box.
    #
    $formfields["sshkey"] = $args["sshkey"];
870 871
}
elseif (isset($formfields["sshkey"]) && $formfields["sshkey"] != "") {
872
    $args["sshkey"] = $formfields["sshkey"];
Leigh B Stoller's avatar
Leigh B Stoller committed
873 874 875
}

if (count($errors)) {
876
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
877 878 879
    SPITFOOTER();
    return;
}
880
# Silently ignore the form for a logged in user. 
881 882 883
$args["username"] = ($this_user ? $this_user->uid() : $formfields["username"]);
$args["email"]    = ($this_user ? $this_user->email() : $formfields["email"]);
$args["profile"]  = $formfields["profile"];
Leigh B Stoller's avatar
Leigh B Stoller committed
884 885 886 887 888

#
# 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 B Stoller's avatar
Leigh B Stoller committed
889 890 891 892 893
# 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.
#
894 895 896
if (!$this_user &&
    (!$geniuser || !isset($_COOKIE['quickvm_authkey']) ||
     $_COOKIE['quickvm_authkey'] != $geniuser->auth_token())) {
Leigh B Stoller's avatar
Leigh B Stoller committed
897 898
    if (isset($stuffing) && $stuffing != "") {
	if (! (isset($verify) && $verify == $stuffing)) {
899
	    SPITFORM($formfields, $stuffing, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
900 901 902
	    SPITFOOTER();
	    return;
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
903 904 905 906 907
	#
	# 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.
	#
908
	if ($geniuser) {
Leigh B Stoller's avatar
Leigh B Stoller committed
909 910
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
911 912 913 914 915
		# 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);
916 917
		header("Location: status.php?oneonly=1&uuid=" .
		       $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
918 919
		return;
	    }
920 921 922 923 924 925 926 927
            #
            # 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 B Stoller's avatar
Leigh B Stoller committed
928
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
929 930 931 932
	# Pass to backend to save in user object.
	$args["auth_token"] = $stuffing;
    }
    else {
Leigh B Stoller's avatar
Leigh B Stoller committed
933 934
	# Existing user, use existing auth token.
	# New user, we create a new one.
935
	$token = ($geniuser ? $geniuser->auth_token() : true);
Leigh B Stoller's avatar
Leigh B Stoller committed
936

937
	SPITFORM($formfields, $token, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
938 939 940 941 942
	SPITFOOTER();
	return;
    }
}

943
# Admins can change aggregate.
944 945 946 947 948 949 950 951 952 953
$options = "";

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

#
956
# Invoke the backend.
Leigh B Stoller's avatar
Leigh B Stoller committed
957
#
958 959
list ($instance, $creator) =
    Instance::Instantiate($this_user, $options, $args, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
960

Leigh B Stoller's avatar
Leigh B Stoller committed
961
if (!$instance) {
962
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
963 964 965
    SPITFOOTER();
    return;
}
966
    
967
#
Leigh B Stoller's avatar
Leigh B Stoller committed
968
# Remember the user and auth key so that we can verify.
969 970 971 972 973 974
#
# 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. 
#
975
if (!$this_user) {
Leigh B Stoller's avatar
Leigh B Stoller committed
976 977
    $cookiedomain = $TBAUTHDOMAIN;

978 979 980 981 982 983
    setcookie("quickvm_user",
	      $creator->uuid(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
    setcookie("quickvm_authkey",
	      $creator->auth_token(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
984
}
Leigh B Stoller's avatar
Leigh B Stoller committed
985
header("Location: status.php?uuid=" . $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
986
?>