manage_profile.php 13.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# 
# {{{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");
26
include_once("webtask.php");
27
28
chdir("apt");
include("quickvm_sup.php");
29
include("profile_defs.php");
30
include("instance_defs.php");
31
32
# Must be after quickvm_sup.php since it changes the auth domain.
include_once("../session.php");
33
$page_title = "Manage Profile";
34
$notifyupdate = 0;
35
$notifysnapshot = 0;
36
37
38
39

#
# Get current user.
#
40
RedirectSecure();
41
42
43
44
45
46
$this_user = CheckLogin($check_status);

#
# Verify page arguments.
#
$optargs = OptionalPageArguments("create",      PAGEARG_STRING,
47
48
				 "action",      PAGEARG_STRING,
				 "idx",         PAGEARG_INTEGER,
49
				 "uuid",        PAGEARG_STRING,
50
				 "snapuuid",    PAGEARG_STRING,
51
				 "finished",    PAGEARG_BOOLEAN,
52
53
				 "formfields",  PAGEARG_ARRAY);

54
55
56
57
58
59
60
61
62
#
# The user must be logged in.
#
if (!$this_user) {
    RedirectLoginPage();
    exit();
}
$this_idx = $this_user->uid_idx();

63
64
65
#
# Spit the form
#
66
function SPITFORM($formfields, $errors)
67
{
68
69
    global $this_user, $projlist, $action;
    global $notifyupdate, $notifysnapshot, $snapuuid;
70
    $editing = 0;
71

72
73
74
    if ($action == "edit") {
	$button_label = "Modify";
	$title        = "Modify Profile";
75
	$editing      = 1;
Leigh B Stoller's avatar
Leigh B Stoller committed
76
	$uuid         = $formfields["profile_uuid"];
77
78
79
80
81
    }
    else  {
	$button_label = "Create";
	$title        = "Create Profile";
    }
82
83
84

    SPITHEADER(1);

85
86
    # Place to hang the toplevel template.
    echo "<div id='manage-body'></div>\n";
87

88
89
90
91
92
93
94
    # I think this will take care of XSS prevention?
    echo "<script type='text/plain' id='form-json'>\n";
    echo htmlentities(json_encode($formfields)) . "\n";
    echo "</script>\n";
    echo "<script type='text/plain' id='error-json'>\n";
    echo htmlentities(json_encode($errors));
    echo "</script>\n";
95

96
97
98
99
    # Pass project list through. Need to convert to list without groups.
    # When editing, pass through a single value. The template treats a
    # a single value as a read-only field.
    $plist = array();
100
    if ($editing) {
101
	$plist[] = $formfields["profile_pid"];
102
103
    }
    else {
104
	while (list($project) = each($projlist)) {
105
	    $plist[] = $project;
106
	}
Leigh B Stoller's avatar
Leigh B Stoller committed
107
    }
108
109
110
    echo "<script type='text/plain' id='projects-json'>\n";
    echo htmlentities(json_encode($plist));
    echo "</script>\n";
111
    
112
113
114
115
116
117
118
    echo "<link rel='stylesheet'
            href='jquery-ui/css/smoothness/jquery-ui-1.10.4.custom.min.css'>\n";
    echo "<link rel='stylesheet'
            href='jquery.appendGrid/css/jquery.appendGrid-1.3.1.min.css'>\n";
    # For progress bubbles in the imaging modal.
    echo "<link rel='stylesheet' href='progress.css'>\n";

119
    echo "<script type='text/javascript'>\n";
120
121
122
123
124
125
126
127
128
129
130
    echo "    window.EDITING  = " . ($editing ? 1 : 0) . ";\n";
    echo "    window.UUID     = " . (isset($uuid) ? "'$uuid'" : "null") . ";\n";
    echo "    window.UPDATED  = $notifyupdate;\n";
    echo "    window.SNAPPING = $notifysnapshot;\n";
    echo "    window.AJAXURL  = 'server-ajax.php';\n";
    echo "    window.ACTION   = '$action';\n";
    echo "    window.TITLE    = '$title';\n";
    echo "    window.BUTTONLABEL = '$button_label';\n";
    if (isset($snapuuid)) {
	echo "    window.SNAPUUID = '$snapuuid';\n";
    }
131
    echo "</script>\n";
132
133
    echo "<script src='js/lib/require.js' data-main='js/manage_profile'>
          </script>";
134
    
135
136
    SPITFOOTER();
}
137
138
139
140

#
# See what projects the user can do this in.
#
141
$projlist = $this_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
142

143
144
145
# We use a session.
session_start();

146
if (! isset($create)) {
147
148
    $errors   = array();
    $defaults = array();
149
150
151
152
153

    # Default action is create.
    if (! isset($action) || $action == "") {
	$action = "create";
    }
154
155
156
157
158
159
    
    if (! (isset($projlist) && count($projlist))) {
	$errors["error"] =
	    "You do not appear to be a member of any projects in which ".
	    "you have permission to create new profiles";
    }
160
    if ($action == "edit" || $action == "delete" || $action == "snapshot") {
161
162
163
164
165
166
167
	if ($action == "snapshot") {
	    if (! (isset($snapuuid) && IsValidUUID($snapuuid))) {
		$errors["error"] = "No experiment specified for snapshot!";
	    }
	    $instance = Instance::Lookup($snapuuid);
	    if (!$instance) {
		SPITUSERERROR("No such instance to snapshot!");
168
	    }
169
	    else if ($this_idx != $instance->creator_idx() && !ISADMIN()) {
170
		SPITUSERERROR("Not enough permission!");
171
	    }
172
173
174
	    $profile = Profile::Lookup($instance->profile_idx());
	    if (!$profile) {
		SPITUSERERROR("Cannot load profile for instance!");
175
	    }
176
177
178
179
180
	    else if ($this_idx != $profile->creator_idx() &&
		     !$profile->ispublic() && !ISADMIN()) {
		SPITUSERERROR("Not enough permission!");
	    }
	    $defaults["profile_rspec"] = $profile->rspec();
181
	    $defaults["profile_who"]   = "shared";
182
183
	}
	else {
184
	    if (! (isset($idx) || isset($uuid))) {
185
		$errors["error"] = "No profile specified for edit/delete!";
Leigh B Stoller's avatar
Leigh B Stoller committed
186
	    }
187
	    else {
188
189
190
191
192
193
194
		# This can also be a uuid.
		if (isset($idx)) {
		    $profile = Profile::Lookup($idx);
		}
		elseif (isset($uuid)) {
		    $profile = Profile::Lookup($uuid);
		}
195
196
197
		if (!$profile) {
		    SPITUSERERROR("No such profile!");
		}
198
199
200
		else if ($profile->locked()) {
		    SPITUSERERROR("Profile is currently locked!");
		}
201
202
203
204
		else if ($this_idx != $profile->creator_idx() && !ISADMIN()) {
		    SPITUSERERROR("Not enough permission!");
		}
		else if ($action == "delete") {
205
206
207
		    $profile->Delete();
		    session_unset();
		    session_destroy();
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
		    header("Location: $APTBASE/myprofiles.php");
		    return;
		}
		else {
		    $defaults["profile_uuid"]        = $profile->uuid();
		    $defaults["profile_pid"]         = $profile->pid();
		    $defaults["profile_description"] = $profile->description();
		    $defaults["profile_name"]        = $profile->name();
		    $defaults["profile_rspec"]       = $profile->rspec();
		    $defaults["profile_created"]     = $profile->created();
		    $defaults["profile_url"]         = $profile->url();
		    $defaults["profile_listed"]      =
			($profile->listed() ? "checked" : "");
		    $defaults["profile_who"] =
			($profile->shared() ? "shared" : 
			 ($profile->ispublic() ? "public" : "private"));

225
226
227
228
229
230
		    # Warm fuzzy message.
		    if (isset($_SESSION["notifyupdate"])) {
			$notifyupdate = 1;
			unset($_SESSION["notifyupdate"]);
		    }

231
		    #
232
233
234
235
		    # See if we have a task running in the background
		    # for this profile. At the moment it can only be a
		    # snapshot task. If there is one, we have to tell
		    # the js code to show the status of the snapshot.
236
		    #
237
238
239
		    $webtask = WebTask::LookupByObject($profile->uuid());
		    if ($webtask && ! $webtask->exited()) {
			$notifysnapshot = 1;
240
241
		    }
		}
242
243
244
	    }
	}
    }
245
246
247
248
249
    else {
	# Default the project if in only one project.
	if (count($projlist) == 1) {
	    $defaults["profile_pid"] = $projlist[0];
	}
250
	$defaults["profile_who"]   = "shared";
251
    }
252
253
254
255
256
257
258
259
260
261
262
263
    SPITFORM($defaults, $errors);
    return;
}

#
# Otherwise, must validate and redisplay if errors
#
$errors = array();

#
# Quick check for required fields.
#
264
$required = array("pid", "name");
265

266
267
268
269
270
271
272
273
274
275
276
277
foreach ($required as $key) {
    if (!isset($formfields["profile_${key}"]) ||
	strcmp($formfields["profile_${key}"], "") == 0) {
	$errors["profile_${key}"] = "Missing Field";
    }
    elseif (! TBcheck_dbslot($formfields["profile_${key}"], "apt_profiles", $key,
			     TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
	$errors["profile_${key}"] = TBFieldErrorString();
    }
}

#
278
# The rspec file has to be treated specially of course.
279
#
280
if (0 && isset($_FILES['rspecfile']) &&
281
282
283
284
285
    $_FILES['rspecfile']['name'] != "" &&
    $_FILES['rspecfile']['name'] != "none") {

    $rspec = file_get_contents($_FILES['rspecfile']['tmp_name']);
    if (!$rspec) {
286
	$errors["profile_rspec"] = "Could not process file";
287
288
    }
    elseif (! TBvalid_html_fulltext($rspec)) {
289
290
291
292
293
294
295
296
297
298
	$errors["profile_rspec"] = TBFieldErrorString();	
    }
}
elseif (isset($formfields["profile_rspec"]) &&
	$formfields["profile_rspec"] != "") {
    if (! TBvalid_html_fulltext($formfields["profile_rspec"])) {
	$errors["profile_rspec"] = TBFieldErrorString();	
    }
    else {
	$rspec = $formfields["profile_rspec"];
299
300
301
302
303
304
    }
}
else {
    $errors["rspecfile"] = "Missing Field";
}

305
306
307
308
309
310
# Present these errors before we call out to do anything else.
if (count($errors)) {
    SPITFORM($formfields, $errors);
    return;
}

311
312
313
314
315
316
317
318
#
# Project has to exist. We need to know it for the SUEXEC call
# below. 
#
$project = Project::LookupByPid($formfields["profile_pid"]);
if (!$project) {
    $errors["profile_pid"] = "No such project";
}
319
320
321
322
# User better be a member.
if (!$project->IsMember($this_user, $isapproved) || !$isapproved) {
    $errors["profile_pid"] = "Illegal project";
}
323

324
325
326
327
328
329
330
331
332
333
334
335
336
#
# Convert profile_who to arguments.
#
if (!isset($formfields["profile_who"]) || $formfields["profile_who"] == "") {
    $errors["profile_who"] = "Missing value";
}
else {
    $who = $formfields["profile_who"];
    if (! ($who == "private" || $who == "shared" || $who == "public")) {
	$errors["profile_who"] = "Illegal value";
    }
}

337
338
339
340
#
# Sanity check the snapuuid argument. 
#
if (isset($action) && $action == "snapshot") {
341
    if (!isset($snapuuid) || $snapuuid == "" || !IsValidUUID($snapuuid)) {
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
	$errors["error"] = "Invalid experiment specified for snapshot!";
    }
    $instance = Instance::Lookup($snapuuid);
    if (!$instance) {
	$errors["error"] = "No such experiment to snapshot!";
    }
    else if ($this_idx != $instance->creator_idx() && !ISADMIN()) {
	$errors["error"] = "Not enough permission!";
    }
    else {
	$profile = Profile::Lookup($instance->profile_idx());
	if (!$profile) {
	    $errors["error"] = "Cannot load profile for instance!";
	}
	else if ($this_idx != $profile->creator_idx() &&
		 !$profile->ispublic() && !ISADMIN()) {
	    $errors["error"] = "Not enough permission!";
	}
    }
}

363
364
365
# Present these errors before we call out to do anything else.
if (count($errors)) {
    SPITFORM($formfields, $errors);
366
367
368
369
    return;
}

#
370
371
372
# Pass to the backend as an XML data file. If this gets too complicated,
# we might eed to do all the checking in the backend and have it pass
# back the error set. 
373
#
374
375
376
377
378
379
# Generate a temporary file and write in the XML goo.
#
$xmlname = tempnam("/tmp", "newprofile");
if (! $xmlname) {
    TBERROR("Could not create temporary filename", 0);
    $errors["error"] = "Internal error; Could not create temp file";
380
381
    SPITFORM($formfields, $errors);
    return;
382
383
384
385
}
elseif (! ($fp = fopen($xmlname, "w"))) {
    TBERROR("Could not open temp file $xmlname", 0);
    $errors["error"] = "Internal error; Could not open temp file";
386
387
388
    SPITFORM($formfields, $errors);
    unlink($xmlname);
    return;
389
390
391
392
393
394
395
396
397
398
399
400
401
}
else {
    fwrite($fp, "<profile>\n");
    fwrite($fp, "<attribute name='profile_pid'>");
    fwrite($fp, "  <value>" . $formfields["profile_pid"] . "</value>");
    fwrite($fp, "</attribute>\n");
    fwrite($fp, "<attribute name='profile_name'>");
    fwrite($fp, "  <value>" .
	   htmlspecialchars($formfields["profile_name"]) . "</value>");
    fwrite($fp, "</attribute>\n");
    fwrite($fp, "<attribute name='rspec'>");
    fwrite($fp, "  <value>" . htmlspecialchars($rspec) . "</value>");
    fwrite($fp, "</attribute>\n");
402
403
404
405
406
407
408
    fwrite($fp, "<attribute name='profile_listed'><value>");
    if (isset($formfields["profile_listed"]) &&
	$formfields["profile_listed"] == "checked") {
	fwrite($fp, "1");
    }
    else {
	fwrite($fp, "0");
409
    }
410
    fwrite($fp, "</value></attribute>\n");
411
412
413
414
    fwrite($fp, "<attribute name='profile_shared'><value>" .
	   ($who == "shared" ? 1 : 0) . "</value></attribute>\n");
    fwrite($fp, "<attribute name='profile_public'><value>" .
	   ($who == "public" ? 1 : 0) . "</value></attribute>\n");
415
416
417
418
    fwrite($fp, "</profile>\n");
    fclose($fp);
    chmod($xmlname, 0666);
}
419

420
421
422
#
# Call out to the backend.
#
423
$optarg = ($action == "edit" ? "-u" : "");
424
425
if (isset($snapuuid)) {
    $optarg .= "-s " . escapeshellarg($snapuuid);
426
427
428
429

    # We want to pass a webtask id along. 
    $webtask_id = md5(uniqid(rand(),1));
    $optarg .= " -t " . $webtask_id;
430
}
431

432
$retval = SUEXEC($this_user->uid(), $project->unix_gid(),
433
		 "webmanage_profile $optarg $xmlname",
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
		 SUEXEC_ACTION_IGNORE);
if ($retval) {
    if ($retval < 0) {
	$errors["error"] = "Internal Error; please try again later.";
	SUEXECERROR(SUEXEC_ACTION_CONTINUE);
    }
    else {
	#
	# Decode simple XML that is returned. 
	#
	$parsed = simplexml_load_string($suexec_output);
	if (!$parsed) {
	    $errors["error"] = "Internal Error; please try again later.";
	    TBERROR("Could not parse XML output:\n$suexec_output\n", 0);
	}
	else {
	    foreach ($parsed->error as $error) {
451
		$errors[(string)$error['name']] = (string)$error;
452
453
454
455
	    }
	}
    }
}
456
unlink($xmlname);
457
458
459
460
if (count($errors)) {
    SPITFORM($formfields, $errors);
    return;
}
461

462
463
464
#
# Need the index to pass back through.
#
465
466
467
$profile = Profile::LookupByName($project, $formfields["profile_name"]);
if ($profile) {
    $uuid = $profile->uuid();
468
469
}
else {
470
471
472
473
    header("Location: $APTBASE/myprofiles.php");
}
if ($action == "edit") {
    $_SESSION["notifyupdate"] = 1;
474
}
475
header("Location: $APTBASE/manage_profile.php?action=edit&uuid=$uuid");
476
477

?>