instantiate.php 14.5 KB
Newer Older
Leigh B Stoller's avatar
Leigh B Stoller committed
1 2
<?php
#
3
# Copyright (c) 2000-2014 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");
28
chdir("apt");
Leigh B Stoller's avatar
Leigh B Stoller committed
29
include("quickvm_sup.php");
Leigh B Stoller's avatar
Leigh B Stoller committed
30
include("instance_defs.php");
31
$page_title = "QuickVM Create";
Leigh B Stoller's avatar
Leigh B Stoller committed
32
$dblink = GetDBLink("sa");
Leigh B Stoller's avatar
Leigh B Stoller committed
33

34 35 36 37 38 39
#
# Get current user but make sure coming in on SSL.
#
RedirectSecure();
$this_user = CheckLogin($check_status);

Leigh B Stoller's avatar
Leigh B Stoller committed
40 41 42
#
# Verify page arguments.
#
43 44 45 46 47 48
$optargs = OptionalPageArguments("create",        PAGEARG_STRING,
				 "profile",       PAGEARG_STRING,
				 "stuffing",      PAGEARG_STRING,
				 "verify",        PAGEARG_STRING,
				 "project",       PAGEARG_PROJECT,
				 "formfields",    PAGEARG_ARRAY,
Leigh B Stoller's avatar
Leigh B Stoller committed
49 50 51 52 53 54 55
				 "ajax_request",  PAGEARG_BOOLEAN,
				 "ajax_method",   PAGEARG_STRING,
				 "ajax_argument", PAGEARG_STRING);

#
# Deal with ajax requests.
#
56 57
# XXX Need permission checks here. 
#
Leigh B Stoller's avatar
Leigh B Stoller committed
58 59
if (isset($ajax_request)) {
    if ($ajax_method == "getprofile") {
60
	$profile_idx = addslashes($ajax_argument);
Leigh B Stoller's avatar
Leigh B Stoller committed
61
	$query_result =
62 63
	    DBQueryWarn("select * from apt_profiles ".
			"where idx='$profile_idx'");
Leigh B Stoller's avatar
Leigh B Stoller committed
64 65

	if (!$query_result || !mysql_num_rows($query_result)) {
66
	    SPITAJAX_ERROR(1, "No such profile $profile_idx!");
Leigh B Stoller's avatar
Leigh B Stoller committed
67 68 69 70 71 72
	    exit();
	}
	$row = mysql_fetch_array($query_result);
	
	SPITAJAX_RESPONSE(array('rspec' => $row['rspec'],
				'name'  => $row['name'],
73
				'idx'   => $row['idx'],
Leigh B Stoller's avatar
Leigh B Stoller committed
74 75 76 77
				'description' => $row['description']));
    }
    exit();
}
Leigh B Stoller's avatar
Leigh B Stoller committed
78

79
$profile_default  = "ThreeVMs";
Leigh B Stoller's avatar
Leigh B Stoller committed
80
$profile_array    = array();
Leigh B Stoller's avatar
Leigh B Stoller committed
81

Leigh B Stoller's avatar
Leigh B Stoller committed
82
$query_result =
83 84 85
    DBQueryFatal("select * from apt_profiles ".
		 "where public=1 " .
		 ($this_user ? "or creator_idx=" . $this_user->uid_idx() : ""));
Leigh B Stoller's avatar
Leigh B Stoller committed
86
while ($row = mysql_fetch_array($query_result)) {
87 88 89 90
    $profile_array[$row["idx"]] = $row["name"];
    if ($row["pid"] == $TBOPSPID && $row["name"] == $profile_default) {
	$profile_default = $row["idx"];
    }
91 92 93 94 95 96 97 98 99 100 101
    if (isset($profile)) {
        # Look for the profile by project/name and switch to index.
	if (isset($project) &&
	    $row["pid"] == $project->pid() &&
	    $row["name"] == $profile) {
	    $profile = $row["idx"];
	}
        # Look for the profile by uuid and switch to index.
	elseif ($profile == $row["uuid"]) {
	    $profile = $row["idx"];
	}
102
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
103
}
Leigh B Stoller's avatar
Leigh B Stoller committed
104

105
function SPITFORM($formfields, $newuser, $errors)
Leigh B Stoller's avatar
Leigh B Stoller committed
106 107
{
    global $TBBASE, $TBMAIL_OPS;
108
    global $profile_array, $this_user;
Leigh B Stoller's avatar
Leigh B Stoller committed
109

110 111 112
    # XSS prevention.
    while (list ($key, $val) = each ($formfields)) {
	$formfields[$key] = CleanString($val);
Leigh B Stoller's avatar
Leigh B Stoller committed
113
    }
114
    # XSS prevention.
Leigh B Stoller's avatar
Leigh B Stoller committed
115
    if ($errors) {
116 117
	while (list ($key, $val) = each ($errors)) {
	    $errors[$key] = CleanString($val);
Leigh B Stoller's avatar
Leigh B Stoller committed
118 119
	}
    }
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134

    $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)) {
	    echo "<label class='control-label' for='inputError'>" .
		$errors[$field] . "</label>\n";
	}
	echo "</div>\n";
    };

Leigh B Stoller's avatar
Leigh B Stoller committed
135
    SPITHEADER(1);
Leigh B Stoller's avatar
Leigh B Stoller committed
136

137
    echo "<div class='row'>
Leigh B Stoller's avatar
Leigh B Stoller committed
138
          <div class='col-lg-6  col-lg-offset-3
139
                      col-md-6  col-md-offset-3
Leigh B Stoller's avatar
Leigh B Stoller committed
140 141
                      col-sm-8  col-sm-offset-2
                      col-xs-12 col-xs-offset-0'>\n";
142
    echo "<form id='quickvm_form' role='form'
143
            method='post' action='instantiate.php'>\n";
144 145 146 147
    echo "<div class='panel panel-default'>
           <div class='panel-heading'>
              <h3 class='panel-title'>
                 Create an Experiment</h3></div>
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
           <div class='panel-body'>\n";
    echo "   <fieldset>\n";

    #
    # Look for non-specific error.
    #
    if ($errors && array_key_exists("error", $errors)) {
	echo "<font color=red><center>" . $errors["error"] . "</center></font>";
    }
    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"] . "'
169
                          class='form-control'
170
                          placeholder='Your email address' type='text'>");
Keith Downie's avatar
Keith Downie committed
171

172 173
	$formatter("sshkey", 
		  "<textarea name=\"formfields[sshkey]\" 
174
                             placeholder='Optional: your ssh public key.'
175 176 177 178 179
                             class='form-control'
                             rows=4 cols=45>" . $formfields["sshkey"] .
                  "</textarea>");
    }
    echo "<div id='profile_well' class='form-group well well-md'>
Keith Downie's avatar
Keith Downie committed
180 181
            <span id='selected_profile_text' class='pull-left'>
            </span>
182 183
            <input id='selected_profile' type='hidden' 
                   name='formfields[profile]'/>
Keith Downie's avatar
Keith Downie committed
184
              <button id='profile' class='btn btn-primary btn-xs pull-right' 
185 186
                     type='button' name='profile_button'>
                Select a Profile
Keith Downie's avatar
Keith Downie committed
187
              </button>\n";
188 189 190 191 192 193
    if ($errors && array_key_exists("profile", $errors)) {
	echo "<label class='control-label' for='inputError'>" .
	    $errors["profile"] .
	    " </label>\n";
    }
    echo " </div>\n";
194 195
    echo "  <span class=''
                  id='selected_profile_description'></span>\n";
196 197
    echo "</fieldset>
           <button class='btn btn-success pull-right'
Keith Downie's avatar
Keith Downie committed
198
              type='submit' name='create'>Create!
199 200
           </button>
           <br> 
201 202 203
        </div>
        </div>
        </div>
Leigh B Stoller's avatar
Leigh B Stoller committed
204
        </div>\n";
205 206
    if (!isset($this_user)) {
	SpitVerifyModal("verify_modal", "Create");
207
    
208 209 210 211 212 213 214 215 216 217 218 219 220 221
	if ($newuser) {
	    if (is_string($newuser)) {
		$stuffing = $newuser;
	    }
	    else {
		$stuffing = substr(GENHASH(), 0, 16);
	    }
	    mail($formfields["email"],
		 "Confirm your email to create your Experiment",
		 "Here is your user verification code. Please copy and\n".
		 "paste this code into the box on the experiment page.\n\n".
		 "      $stuffing\n",
		 "From: $TBMAIL_OPS");
	    echo "<input type='hidden' name='stuffing' value='$stuffing' />";
Leigh B Stoller's avatar
Leigh B Stoller committed
222 223 224 225
	}
    }
    echo "</form>\n";

226
    SpitTopologyViewModal("quickvm_topomodal", $profile_array);
Keith Downie's avatar
Keith Downie committed
227

228
    echo "<script type='text/javascript'>\n";
229
    echo "    window.PROFILE = '" . $formfields["profile"] . "';\n";
230 231 232 233
    if ($newuser) {
	echo "window.APT_OPTIONS.isNewUser = true;\n";
    }
    echo "</script>\n";
234
    echo "<script src='js/lib/require.js' data-main='js/instantiate'></script>";
Leigh B Stoller's avatar
Leigh B Stoller committed
235 236 237
}

if (!isset($create)) {
238 239 240 241 242 243
    $defaults = array();
    $defaults["username"] = "";
    $defaults["email"]    = "";
    $defaults["sshkey"]   = "";
    $defaults["profile"]  = (isset($profile) ? $profile : $profile_default);
	
244
    # 
245
    # Look for current user or cookie that tells us who the user is. 
246
    #
247
    if ($this_user) {
248 249
	$defaults["username"] = $this_user->uid();
	$defaults["email"]    = $this_user->email();
250 251
    }
    elseif (isset($_COOKIE['quickvm_user'])) {
Leigh B Stoller's avatar
Leigh B Stoller committed
252 253 254 255 256 257
	$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
258 259 260
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
		header("Location: status.php?uuid=" . $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
261 262
		return;
	    }
263 264 265
	    $defaults["username"] = $geniuser->name();
	    $defaults["email"]    = $geniuser->email();
	    $defaults["sshkey"]   = $geniuser->SSHKey();
Leigh B Stoller's avatar
Leigh B Stoller committed
266 267
	}
    }
268
    SPITFORM($defaults, false, array());
Leigh B Stoller's avatar
Leigh B Stoller committed
269 270 271 272 273 274 275 276 277
    SPITFOOTER();
    return;
}
#
# Otherwise, must validate and redisplay if errors
#
$errors = array();
$args   = array();

278 279 280 281
if (!$this_user) {
    #
    # These check do not matter for a logged in user; we ignore the values.
    #
282
    if (!isset($formfields["email"]) || $formfields["email"] == "") {
283 284
	$errors["email"] = "Missing Field";
    }
285
    elseif (! TBvalid_email($formfields["email"])) {
286 287
	$errors["email"] = TBFieldErrorString();
    }
288
    if (!isset($formfields["username"]) || $formfields["username"] == "") {
289 290
	$errors["username"] = "Missing Field";
    }
291
    elseif (! TBvalid_uid($formfields["username"])) {
292 293
	$errors["username"] = TBFieldErrorString();
    }
294
    elseif (User::LookupByUid($formfields["username"])) {
295 296 297
        # Do not allow uid overlap with real users.
	$errors["username"] = "Already in use";
    }
Leigh B Stoller's avatar
Leigh B Stoller committed
298
}
299
if (!isset($formfields["profile"]) || $formfields["profile"] == "") {
Leigh B Stoller's avatar
Leigh B Stoller committed
300
    $errors["profile"] = "No selection made";
Leigh B Stoller's avatar
Leigh B Stoller committed
301
}
302 303
elseif (! array_key_exists($formfields["profile"], $profile_array)) {
    $errors["profile"] = "Invalid Profile: " . $formfields["profile"];
Leigh B Stoller's avatar
Leigh B Stoller committed
304 305 306
}

if (count($errors)) {
307
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
308 309 310 311 312 313 314
    SPITFOOTER();
    return;
}

#
# More sanity checks. 
#
315
if (!$this_user) {
316
    $geniuser = GeniUser::LookupByEmail("sa", $formfields["email"]);
317
    if ($geniuser) {
318
	if ($geniuser->name() != $formfields["username"]) {    
319 320 321
	    $errors["email"] = "Already in use by another user";
	    unset($geniuser);
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
322 323
    }
}
324 325 326 327 328
#
# SSH keys are now optional for guest users; they just have to
# use the web based ssh windo.
#
if (isset($formfields["sshkey"]) && $formfields["sshkey"] != "") {
329
    $args["sshkey"] = $formfields["sshkey"];
Leigh B Stoller's avatar
Leigh B Stoller committed
330 331 332
}

if (count($errors)) {
333
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
334 335 336
    SPITFOOTER();
    return;
}
337
# Silently ignore the form for a logged in user. 
338 339 340
$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
341 342 343 344 345

#
# 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
346 347 348 349 350
# 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.
#
351 352 353
if (!$this_user &&
    (!$geniuser || !isset($_COOKIE['quickvm_authkey']) ||
     $_COOKIE['quickvm_authkey'] != $geniuser->auth_token())) {
Leigh B Stoller's avatar
Leigh B Stoller committed
354 355
    if (isset($stuffing) && $stuffing != "") {
	if (! (isset($verify) && $verify == $stuffing)) {
356
	    SPITFORM($formfields, $stuffing, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
357 358 359
	    SPITFOOTER();
	    return;
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
360 361 362 363 364
	#
	# 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.
	#
365
	if ($geniuser) {
Leigh B Stoller's avatar
Leigh B Stoller committed
366 367 368
	    $instance = Instance::LookupByCreator($geniuser->uuid());
	    if ($instance && $instance->status() != "terminating") {
		header("Location: status.php?uuid=" . $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
369 370 371
		return;
	    }
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
372 373 374 375
	# Pass to backend to save in user object.
	$args["auth_token"] = $stuffing;
    }
    else {
Leigh B Stoller's avatar
Leigh B Stoller committed
376 377
	# Existing user, use existing auth token.
	# New user, we create a new one.
378
	$token = ($geniuser ? $geniuser->auth_token() : true);
Leigh B Stoller's avatar
Leigh B Stoller committed
379

380
	SPITFORM($formfields, $token, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
381 382 383 384 385
	SPITFOOTER();
	return;
    }
}

Leigh B Stoller's avatar
Leigh B Stoller committed
386
#
Leigh B Stoller's avatar
Leigh B Stoller committed
387
# This is so we can look up the slice after the backend creates it.
Leigh B Stoller's avatar
Leigh B Stoller committed
388 389 390
# We tell the backend what uuid to use.
#
$quickvm_uuid = NewUUID();
Leigh B Stoller's avatar
Leigh B Stoller committed
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

#
# Generate a temporary file and write in the XML goo. 
#
$xmlname = tempnam("/tmp", "quickvm");
if (! $xmlname) {
    TBERROR("Could not create temporary filename", 0);
    $errors["internal"] = "Transient error(1); please try again later.";
}
elseif (! ($fp = fopen($xmlname, "w"))) {
    TBERROR("Could not open temp file $xmlname", 0);
    $errors["internal"] = "Transient error(2); please try again later.";
}
else {
    fwrite($fp, "<quickvm>\n");
    foreach ($args as $name => $value) {
	fwrite($fp, "<attribute name=\"$name\">");
	fwrite($fp, "  <value>" . htmlspecialchars($value) . "</value>");
	fwrite($fp, "</attribute>\n");
    }
    fwrite($fp, "</quickvm>\n");
    fclose($fp);
    chmod($xmlname, 0666);
}
if (count($errors)) {
416
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
417 418 419 420 421 422 423 424 425 426 427
    SPITFOOTER();
    return;
}

#
# Invoke the backend. This will create the user and the slice record
# in the SA database, and then fork off in the background. If the
# first part works, we can return to the user and use some nifty ajax
# and javascript to watch for progress. We use a cookie that holds
# the slice uuid so that the JS code can ask about it.
#
428 429
# This option is used to tell the backend that it is okay to look
# in the emulab users table.
Leigh B Stoller's avatar
Leigh B Stoller committed
430
#
431 432
$opt = ($this_user ? "-l" : "");

Leigh B Stoller's avatar
Leigh B Stoller committed
433 434
$retval = SUEXEC("nobody", "nobody",
		 "webquickvm $opt -u $quickvm_uuid $xmlname",
Leigh B Stoller's avatar
Leigh B Stoller committed
435
		 SUEXEC_ACTION_CONTINUE);
Leigh B Stoller's avatar
Leigh B Stoller committed
436 437 438

if ($retval != 0) {
    if ($retval < 0) {
439
	$errors["error"] = "Transient error(3); please try again later.";
Leigh B Stoller's avatar
Leigh B Stoller committed
440 441 442 443
    }
    else {
	if (count($suexec_output_array)) {
	    $line = $suexec_output_array[$i];
444
	    $errors["error"] = $line;
Leigh B Stoller's avatar
Leigh B Stoller committed
445 446
	}
	else {
447
	    $errors["error"] = "Transient error(4); please try again later.";
Leigh B Stoller's avatar
Leigh B Stoller committed
448 449
	}
    }
450
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
451 452 453 454 455
    SPITFOOTER();
    return;
}
unlink($xmlname);

Leigh B Stoller's avatar
Leigh B Stoller committed
456 457
$instance = Instance::Lookup($quickvm_uuid);
if (!$instance) {
458 459
    $errors["error"] = "Transient error(5); please try again later.";
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
460 461 462
    SPITFOOTER();
    return;
}
463 464 465 466
if ($this_user) {
    $creator = $this_user;
}
else {
Leigh B Stoller's avatar
Leigh B Stoller committed
467
    $creator = GeniUser::Lookup("sa", $instance->creator_uuid());
468
}
Leigh B Stoller's avatar
Leigh B Stoller committed
469
if (! $creator) {
470 471
    $errors["error"] = "Transient error(6); please try again later.";
    SPITFORM($formfields, false, $errors);
Leigh B Stoller's avatar
Leigh B Stoller committed
472 473 474
    SPITFOOTER();
    return;
}
475
#
Leigh B Stoller's avatar
Leigh B Stoller committed
476
# Remember the user and auth key so that we can verify.
477 478 479 480 481 482
#
# 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. 
#
483
if (!$this_user) {
Leigh B Stoller's avatar
Leigh B Stoller committed
484 485
    $cookiedomain = $TBAUTHDOMAIN;

486 487 488 489 490 491
    setcookie("quickvm_user",
	      $creator->uuid(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
    setcookie("quickvm_authkey",
	      $creator->auth_token(), time() + (24 * 3600 * 30),
	      "/", $cookiedomain, 0);
492
}
Leigh B Stoller's avatar
Leigh B Stoller committed
493
header("Location: status.php?uuid=" . $instance->uuid());
Leigh B Stoller's avatar
Leigh B Stoller committed
494
?>