manage_profile.php 20.2 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
26
27
<?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");
chdir("apt");
include("quickvm_sup.php");
28
include("profile_defs.php");
29
$page_title = "Manage Profile";
30
$notifyupdate = 0;
31
32
33
34
35
36
37
38
39
40

#
# Get current user.
#
$this_user = CheckLogin($check_status);

#
# Verify page arguments.
#
$optargs = OptionalPageArguments("create",      PAGEARG_STRING,
41
42
				 "action",      PAGEARG_STRING,
				 "idx",         PAGEARG_INTEGER,
43
				 "finished",    PAGEARG_BOOLEAN,
44
45
46
47
48
				 "formfields",  PAGEARG_ARRAY);

#
# Spit the form
#
49
function SPITFORM($formfields, $errors)
50
{
51
    global $this_user, $projlist, $action, $idx, $notifyupdate;
52
    $editing = 0;
53

54
55
56
57
58
59
60
61
62
63
    if ($action == "edit") {
	$button_label = "Modify";
	$title        = "Modify Profile";
	$editing = 1;
    }
    else  {
	$button_label = "Create";
	$title        = "Create Profile";
    }
    
64
65
66
67
68
69
70
71
72
73
74
    # XSS prevention.
    while (list ($key, $val) = each ($formfields)) {
	$formfields[$key] = CleanString($val);
    }
    # XSS prevention.
    if ($errors) {
	while (list ($key, $val) = each ($errors)) {
	    $errors[$key] = CleanString($val);
	}
    }

75
    $format_label = function($field, $label, $help = null) {
76
	echo "  <label for='$field' ".
77
	"         class='col-sm-2 control-label'>$label ";
78
	if ($help) {
79
80
	    echo "<a href='#' class='btn btn-xs'
                     data-toggle='popover' data-content='$help'>".
81
82
		    "<span class='glyphicon glyphicon-question-sign'>
                      </span></a>";
83
84
	}
	echo "  </label>\n";
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
    };

    $formatter = function($field, $label, $html, $help = null)
	          use ($errors, $format_label) {
	$class = "form-group";
	if ($errors && array_key_exists($field, $errors)) {
	    $class .= " has-error";
	}
	$size = 12;
	echo "<div class='$class'>\n";
	if ($label) {
	    $format_label($field, $label, $help);
	    $size = 10;
	}
	echo "  <div class='col-sm-${size}'>\n";
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
	echo "     $html\n";
	if ($errors && array_key_exists($field, $errors)) {
	    echo "<label class='control-label' for='inputError'>" .
		$errors[$field] . "</label>\n";
	}
	echo "  </div>\n";
	echo "</div>\n";
    };

    SPITHEADER(1);

    echo "<div class='row'>
           <div class='col-lg-8  col-lg-offset-2
                       col-md-10 col-md-offset-1
                       col-sm-10 col-sm-offset-1
                       col-xs-12'>\n";
    echo "  <div class='panel panel-default'>
             <div class='panel-heading'>
118
              <h3 class='panel-title'>$title</h3>
119
120
             </div>
             <div class='panel-body'>\n";
121

122
123
124
125
126
127
128
    echo "   <form id='quickvm_create_profile_form'
                   class='form-horizontal' role='form'
                   enctype='multipart/form-data'
                   method='post' action='manage_profile.php'>\n";
    echo "    <div class='row'>\n";
    echo "     <div class='col-sm-12'>\n";

129
130
131
132
    #
    # Look for non-specific error.
    #
    if ($errors && array_key_exists("error", $errors)) {
133
134
135
136
137
	echo "<font color=red><center>" . $errors["error"] . "</center></font>";
    }
    # Did we just complete an update.
    if ($notifyupdate) {
	echo "<font color=green><center>Update Successful!</center></font>";
138
    }
139
140
141
142
    # Mark as editing mode on post.
    if ($editing) {     
	echo "<input type='hidden' name='action' value='edit'>\n";
    }
143
144
145
146
147
148
149
150
    echo "      </div></div><fieldset>\n";

    # First row has both name and project, which makes the layout odd.
    echo "<div class='row'>\n";
    $format_label("profile_name", "Name",
		  ($editing ? null :
		   "alphanumeric, dash, underscore, no whitespace"));
    echo "<div class='col-sm-4'>\n";
151

152
153
    # In editing mode, pass through static values.
    if ($editing) {
154
	$formatter("profile_name", null,
155
		   "<p class='form-control-static'>" .
156
		       $formfields["profile_name"] . "</p>");
157
158
159
160
161
		   
	echo "<input type='hidden' name='formfields[profile_name]' ".
		"value='" . $formfields["profile_name"] . "'>\n";
    }
    else {
162
	$formatter("profile_name", null,
163
		   "<input name=\"formfields[profile_name]\"
164
165
166
		       id='profile_name'
		       value='" . $formfields["profile_name"] . "'
                       class='form-control'
167
                       placeholder='' type='text'>");
168
    }
169
170
171
172
173
174
    # End of first half of row
    echo "  </div>\n";
    # Second half of the row.
    $format_label("profile_pid", "Project");
    echo "<div class='col-sm-4'>\n";

175
176
177
178
179
    #
    # If user is a member of only one project, then just pass it
    # through, no need for the user to see it. Otherwise we have
    # to let the user choose.
    #
180
181
182
    if (count($projlist) == 1 || $editing) {
	$pid = ($editing ? $formfields["profile_pid"] : $projlist[0]);
	
183
184
	$formatter("profile_pid", null,
		   "<p class='form-control-static'>$pid</p>");
185
	echo "<input type='hidden' name='formfields[profile_pid]' ".
186
		"value='$pid'>\n";
187
188
    }
    else {
189
	$pid_options = "<option value=''>Please Select</option>\n";
190
191
192
193
194
195
196
197
	while (list($project) = each($projlist)) {
	    $selected = "";
	    if ($formfields["profile_pid"] == $project) {
		$selected = "selected";
	    }
	    $pid_options .= 
		"<option $selected value='$project'>$project</option>\n";
	}
198
	$formatter("profile_pid", null,
199
200
201
202
		   "<select name=\"formfields[profile_pid]\"
		            id='profile_pid' class='form-control'
                            placeholder='Please Select'>$pid_options</select>");
    }
203
204
205
206
    # End of first row.
    echo "  </div>
          </div>\n";
    echo "</fieldset><fieldset>\n";
207
208
209
210
211
212

    #
    # In edit mode, display current rspec in text area inside a modal.
    # See below for the modal. So, we need buttons to display the source
    # modal, the topo modal, in addition to a file chooser for a new rspec.
    #
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    $invisible = ($editing ? "" : "invisible");
    
    $rspec_html =
	"<div class='row'>
           <div class='col-xs-3'>
                <input name='rspecfile' id='rspecfile' type=file
                 class='filestyle'
	         data-classButton='btn btn-primary btn-xs'
                 data-input='false'
                 data-buttonText='Choose " . ($editing ? "new" : "") . " file'>
           </div>
	   <div class='col-xs-2'>
              <button class='btn btn-primary btn-xs $invisible'
                      id='showtopo_modal_button'>
                      Show</button>
           </div>
           <div class='col-xs-2'>
              <button class='btn btn-primary btn-xs $invisible' type='button'
                      id='show_rspec_textarea_button'
                      data-toggle='collapse' data-target='#rspec_textarea'>
                      Edit</button>
           </div>
         </div>
         <div class='collapse' id='rspec_textarea'
                 style='margin-top: 4px;'>
              <div class='row'>
                <div class='col-xs-12'>
 	          <textarea name=\"formfields[profile_rspec]\"
		            id='profile_rspec_textarea'
		            rows=5
                            class='form-control'
                            type='textarea'>" .
245
		     $formfields["profile_rspec"] . "</textarea>
246
247
248
249
250
251
252
253
254
255
256
                </div>
              </div>
              <div class='row' style='margin-top: 4px;'>
                <div class='col-xs-12'>
                  <button class='btn btn-primary btn-xs' type='button'
                          id='expand_rspec_modal_button'>
                        Expand</button>
                </div>
              </div>
          </div>\n";

257
    $formatter("profile_rspec", "Your rspec", $rspec_html);
258

259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
    $formatter("profile_description", "Description",
	       "<textarea name=\"formfields[profile_description]\"
		          id='profile_description'
		          rows=3
                          class='form-control'
                          placeholder=''
                          type='textarea'>" .
	       $formfields["profile_description"] . "</textarea>",
	       "Briefly describe what this profile does");

    $formatter("profile_instructions", "Instructions",
	       "<textarea name=\"formfields[profile_instructions]\"
		          id='profile_instructions'
		          rows=3
                          class='form-control'
                          placeholder=''
                          type='textarea'></textarea>",
276
277
	       "Briefly describe how to use this profile after it starts. ".
	       "Double click to see it rendered.");
278

279
    $formatter("profile_listed", "Listed?",
280
	       "<div class='checkbox'>
281
282
283
                <label><input name=\"formfields[profile_listed]\" ".
	               $formfields["profile_listed"] .
	       "       id='profile_listed' value=checked
284
285
286
                       type=checkbox> ".
	       "List on the public page for anyone to use?</label></div>");

Leigh B Stoller's avatar
Leigh B Stoller committed
287
288
289
290
291
292
293
294
    if ($editing) {
    	$formatter("profile_url", "Public URL",
		   "<input name=\"formfields[profile_url]\"
		       id='profile_url' readonly
		       value='" . $formfields["profile_url"] . "'
                       class='form-control'
                       placeholder='' type='text'>");
    }
295
296
297
298
    echo "      </fieldset>\n";

    echo "<div class='form-group'>
            <div class='col-sm-offset-2 col-sm-10'>
299
               <button class='btn btn-primary btn-sm pull-right'
300
                   id='profile_submit_button'
301
                   style='margin-right: 10px;'
302
                   type='submit' name='create'>$button_label</button>\n";
303
304
    if ($editing) {
	echo " <a class='btn btn-primary btn-sm pull-right'
305
                   style='margin-right: 10px;'
306
                   href='instantiate.php?profile=$idx'
307
                   type='submit' name='create'>Instantiate</a>\n";
308
	echo " <a class='btn btn-danger btn-sm pull-left'
309
310
311
312
                   style='margin-right: 10px;'
                   href='manage_profile.php?action=delete&idx=$idx'
                   type='button' name='delete'>Delete</a>\n";
    }
313
314
    echo "     </div>\n";
    echo "    </div>\n";
315
316
317
318
319
320
321
322
323

    echo "<!-- This is the rspec text view modal -->
          <div id='rspec_modal' class='modal fade'>
          <div class='modal-dialog'>
            <div class='modal-content'>
               <div class='modal-header'>
                <button type='button' class='close' data-dismiss='modal'
                   aria-hidden='true'>
                   &times;</button>
324
325
326
327
                <button type='button' class='btn btn-primary btn-xs pull-right'
                   style='margin-right: 10px;'
                   id='collapse_rspec_modal_button'>
                   Collapse</button>
328
329
330
331
332
                <h3>rspec XML</h3>
               </div>
               <div class='modal-body'>
                 <div class='panel panel-default'>
                    <div class='panel-body'>
333
334
	              <textarea name=\"formfields[profile_rspec_modal]\"
		          id='modal_profile_rspec_textarea'
335
336
		          rows=20
                          class='form-control'
337
                          type='textarea'></textarea>
338
339
340
341
342
343
344
                    </div>
                 </div>
               </div>
            </div>
          </div>
          </div>\n";
    
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
    echo "   </form></div>\n";
    echo "  </div>\n";
    echo " </div>\n";
    echo "</div>\n";

    echo "<!-- This is the topology view modal -->
          <div id='quickvm_topomodal' class='modal fade'>
          <div class='modal-dialog' id='showtopo_dialog'>
            <div class='modal-content'>
               <div class='modal-header'>
                <button type='button' class='close' data-dismiss='modal'
                   aria-hidden='true'>
                   &times;</button>
                <h3>Topology Viewer</h3>
               </div>
               <div class='modal-body'>
                 <!-- This topo diagram goes inside this div -->
                 <div class='panel panel-default'
                            id='showtopo_container'>
                    <div class='panel-body'>
365
                     <div id='showtopo_nopicker'></div>
366
367
368
                    </div>
                 </div>
               </div>
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
            </div>
          </div>
          </div>\n";
    
    echo "<!-- This is the renderer modal -->
          <div id='renderer_modal' class='modal fade'>
          <div class='modal-dialog'>
            <div class='modal-content'>
               <div class='modal-header'>
                <button type='button' class='close' data-dismiss='modal'
                   aria-hidden='true'>
                   &times;</button>
                <h3>Markdown Renderer</h3>
               </div>
               <div class='modal-body'>
                 <!-- This rendering goes inside this div -->
                 <div class='panel panel-default'>
                    <div class='panel-body'>
                     <div id='renderer_modal_div'></div>
                    </div>
                 </div>
               </div>
391
392
393
394
            </div>
          </div>
          </div>\n";
    
395
396
397
    echo "<script type='text/javascript'>\n";
    echo "    window.EDITING = $editing;\n";
    echo "</script>\n";
398
399
    echo "<script src='js/lib/require.js' data-main='js/manage_profile'>
          </script>";
400
401
    SPITFOOTER();
}
402

403
#
404
# The user must be logged in.
405
#
406
if (!$this_user) {
Leigh B Stoller's avatar
Leigh B Stoller committed
407
    RedirectLoginPage();
408
409
    exit();
}
410
$this_idx = $this_user->uid_idx();
411
412
413
414
415

#
# See what projects the user can do this in.
#
$projlist = $this_user->ProjectAccessList($TB_PROJECT_MAKEIMAGEID);
416
417

if (! isset($create)) {
418
419
    $errors   = array();
    $defaults = array();
420
421
422
423
424
425
    
    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";
    }
426
427
428
429
430
    if ($action == "edit" || $action == "delete") {
	if (!isset($idx)) {
	    $errors["error"] = "No profile specified for edit/delete!";
	}
	else {
431
432
	    $profile = Profile::Lookup($idx);
	    
433
434
435
436
437
	    if (!$profile) {
		SPITUSERERROR("No such profile!");
	    }
	    else if ($this_idx != $profile->creator_idx() && !ISADMIN()) {
		SPITUSERERROR("Not enough permission!");
438
439
440
441
442
443
444
	    }
	    else if ($action == "delete") {
		DBQueryFatal("delete from apt_profiles where idx='$idx'");
		header("Location: $APTBASE/myprofiles.php");
		return;
	    }
	    else {
445
446
447
448
		$defaults["profile_pid"]         = $profile->pid();
		$defaults["profile_description"] = $profile->description();
		$defaults["profile_name"]        = $profile->name();
		$defaults["profile_rspec"]       = $profile->rspec();
449
		$defaults["profile_created"]     = $profile->created();
Leigh B Stoller's avatar
Leigh B Stoller committed
450
		$defaults["profile_url"]         = $profile->url();
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
		$defaults["profile_listed"]      =
		    ($profile->listed() ? "checked" : "");

		#
		# If we are displaying after a successful edit, and it
		# just happened (by looking at the modify time), show
		# a message that the update was successful. This is pretty
		# crappy, but I do not want to go for a fancy thing (popover)
		# just yet, maybe later.
		#
		if (isset($finished) && $profile->modified()) {
		    $mod = new DateTime($profile->modified());
		    if ($mod) {
			$now  = new DateTime("now");
			$diff = $now->getTimestamp() - $mod->getTimestamp();
			if ($diff < 2) {
			    $notifyupdate = 1;
			}
		    }
		}
471
472
473
	    }
	}
    }
474
475
476
477
478
479
480
481
482
483
484
485
    SPITFORM($defaults, $errors);
    return;
}

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

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

488
489
490
491
492
493
494
495
496
497
498
499
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();
    }
}

#
500
# The rspec file has to be treated specially of course.
501
#
502
if (0 && isset($_FILES['rspecfile']) &&
503
504
505
506
507
    $_FILES['rspecfile']['name'] != "" &&
    $_FILES['rspecfile']['name'] != "none") {

    $rspec = file_get_contents($_FILES['rspecfile']['tmp_name']);
    if (!$rspec) {
508
	$errors["profile_rspec"] = "Could not process file";
509
510
    }
    elseif (! TBvalid_html_fulltext($rspec)) {
511
512
513
514
515
516
517
518
519
520
	$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"];
521
522
523
524
525
526
    }
}
else {
    $errors["rspecfile"] = "Missing Field";
}

527
528
529
530
531
532
# Present these errors before we call out to do anything else.
if (count($errors)) {
    SPITFORM($formfields, $errors);
    return;
}

533
534
535
536
537
538
539
540
#
# 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";
}
541
542
543
544
# User better be a member.
if (!$project->IsMember($this_user, $isapproved) || !$isapproved) {
    $errors["profile_pid"] = "Illegal project";
}
545

546
547
548
# Present these errors before we call out to do anything else.
if (count($errors)) {
    SPITFORM($formfields, $errors);
549
550
551
552
    return;
}

#
553
554
555
# 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. 
556
#
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# 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";
}
elseif (! ($fp = fopen($xmlname, "w"))) {
    TBERROR("Could not open temp file $xmlname", 0);
    $errors["error"] = "Internal error; Could not open temp file";
}
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");
580
581
582
583
584
585
586
    fwrite($fp, "<attribute name='profile_listed'><value>");
    if (isset($formfields["profile_listed"]) &&
	$formfields["profile_listed"] == "checked") {
	fwrite($fp, "1");
    }
    else {
	fwrite($fp, "0");
587
    }
588
    fwrite($fp, "</value></attribute>\n");
589
590
591
592
593
594
595
596
    fwrite($fp, "</profile>\n");
    fclose($fp);
    chmod($xmlname, 0666);
}
if (count($errors)) {
    unlink($xmlname);
    SPITFORM($formfields, $errors);
    return;
597
598
}

599
600
601
#
# Call out to the backend.
#
602
$optarg = ($action == "edit" ? "-u" : "");
603
$retval = SUEXEC($this_user->uid(), $project->unix_gid(),
604
		 "webmanage_profile $optarg $xmlname",
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
		 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) {
		$errors[(string)$error['name']] = $error;
	    }
	}
    }
}
if (count($errors)) {
    unlink($xmlname);
    SPITFORM($formfields, $errors);
    return;
}
632
633
634
635
636
637
638
639
640
641
642
643
644
645
#
# Need the index to pass back through.
#
$pid  = $formfields["profile_pid"];
$name = $formfields["profile_name"];
$query_result =
    DBQueryFatal("select idx from apt_profiles ".
		 "where pid='$pid' and name='$name'");
if (!$query_result || !mysql_num_rows($query_result)) {
    header("Location: $APTBASE/myprofiles.php");
}
else {
    $row = mysql_fetch_array($query_result);
    $idx = $row["idx"];
646
647
    header("Location: $APTBASE/manage_profile.php?action=edit&idx=$idx".
	   "&finished=1");
648
}
649
650

?>