Project

General

Profile

Download (17.1 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * system_groupmanager.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * Copyright (c) 2005 Paul Taylor <paultaylor@winn-dixie.com>
10
 * Copyright (c) 2008 Shrew Soft Inc
11
 * All rights reserved.
12
 *
13
 * originally based on m0n0wall (http://m0n0.ch/wall)
14
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
15
 * All rights reserved.
16
 *
17
 * Licensed under the Apache License, Version 2.0 (the "License");
18
 * you may not use this file except in compliance with the License.
19
 * You may obtain a copy of the License at
20
 *
21
 * http://www.apache.org/licenses/LICENSE-2.0
22
 *
23
 * Unless required by applicable law or agreed to in writing, software
24
 * distributed under the License is distributed on an "AS IS" BASIS,
25
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
 * See the License for the specific language governing permissions and
27
 * limitations under the License.
28
 */
29

    
30
##|+PRIV
31
##|*IDENT=page-system-groupmanager
32
##|*NAME=System: Group Manager
33
##|*DESCR=Allow access to the 'System: Group Manager' page.
34
##|*WARN=standard-warning-root
35
##|*MATCH=system_groupmanager.php*
36
##|-PRIV
37

    
38
require_once("guiconfig.inc");
39
require_once("pfsense-utils.inc");
40

    
41
$logging_level = LOG_WARNING;
42
$logging_prefix = gettext("Local User Database");
43

    
44
config_init_path('system/group');
45

    
46
$id = is_numericint($_REQUEST['groupid']) ? $_REQUEST['groupid'] : null;
47
$act = (isset($_REQUEST['act']) ? $_REQUEST['act'] : '');
48

    
49
$dup = null;
50

    
51
if ($act == 'dup') {
52
	$dup = $id;
53
	$act = 'edit';
54
}
55

    
56
function cpusercmp($a, $b) {
57
	return strcasecmp($a['name'], $b['name']);
58
}
59

    
60
function admin_groups_sort() {
61
	$group_config = config_get_path('system/group');
62

    
63
	if (!is_array($group_config)) {
64
		return;
65
	}
66

    
67
	usort($group_config, "cpusercmp");
68
	config_set_path("system/group", $group_config);
69
}
70

    
71
/*
72
 * Check user privileges to test if the user is allowed to make changes.
73
 * Otherwise users can end up in an inconsistent state where some changes are
74
 * performed and others denied. See https://redmine.pfsense.org/issues/9259
75
 */
76
phpsession_begin();
77
$guiuser = getUserEntry($_SESSION['Username']);
78
$guiuser = $guiuser['item'];
79
$read_only = (is_array($guiuser) && userHasPrivilege($guiuser, "user-config-readonly"));
80
phpsession_end();
81

    
82
if (!empty($_POST) && $read_only) {
83
	$input_errors = array(gettext("Insufficient privileges to make the requested change (read only)."));
84
}
85

    
86
if (($_POST['act'] == "delgroup") && !$read_only) {
87

    
88
	if (!isset($id) || !isset($_REQUEST['groupname']) ||
89
	    (config_get_path("system/group/{$id}") === null) ||
90
	    ($_REQUEST['groupname'] != config_get_path("system/group/{$id}/name"))) {
91
		pfSenseHeader("system_groupmanager.php");
92
		exit;
93
	}
94

    
95
	local_group_del(config_get_path("system/group/{$id}"));
96
	$groupdeleted = config_get_path("system/group/{$id}/name");
97
	config_del_path("system/group/{$id}");
98
	/*
99
	 * Reindex the array to avoid operating on an incorrect index
100
	 * https://redmine.pfsense.org/issues/7733
101
	 */
102
	config_set_path("system/group", array_values(config_get_path('system/group')));
103

    
104
	$savemsg = sprintf(gettext("Successfully deleted group: %s"),
105
	    $groupdeleted);
106
	write_config($savemsg);
107
	syslog($logging_level, "{$logging_prefix}: {$savemsg}");
108
}
109

    
110
if (($_POST['act'] == "delpriv") && !$read_only && ($dup === null)) {
111

    
112
	if (!isset($id) || (config_get_path("system/group/{$id}") === null)) {
113
		pfSenseHeader("system_groupmanager.php");
114
		exit;
115
	}
116

    
117
	$privdeleted = array_get_path($priv_list, (config_get_path("system/group/{$id}/priv/{$_REQUEST['privid']}") . "/name"));
118
	config_del_path("system/group/{$id}/priv/{$_REQUEST['privid']}");
119

    
120
	foreach (config_get_path("system/group/{$id}/member", []) as $uid) {
121
		$user = getUserEntryByUID($uid);
122
		$user = $user['item'];
123
		if ($user) {
124
			local_user_set($user);
125
		}
126
	}
127

    
128
	$savemsg = sprintf(gettext("Removed Privilege \"%s\" from group %s"),
129
	    $privdeleted, config_get_path("system/group/{$id}/name"));
130
	write_config($savemsg);
131
	syslog($logging_level, "{$logging_prefix}: {$savemsg}");
132

    
133
	$act = "edit";
134
}
135

    
136
if ($act == "edit") {
137
	if (isset($id)) {
138
		$this_group = config_get_path("system/group/{$id}");
139
		if ($dup === null) {
140
			$pconfig['name'] = $this_group['name'];
141
			$pconfig['gid'] = $this_group['gid'];
142
			$pconfig['gtype'] = empty($this_group['scope'])
143
			    ? "local" : $this_group['scope'];
144
		} else {
145
			$pconfig['gtype'] = ($this_group['scope'] == 'system')
146
			    ? "local" : $this_group['scope'];
147
		}
148
		$pconfig['priv'] = $this_group['priv'];
149
		$pconfig['description'] = $this_group['description'];
150
		$pconfig['members'] = $this_group['member'];
151
	}
152
}
153

    
154
if (isset($_POST['dellall_x']) && !$read_only) {
155

    
156
	$del_groups = $_POST['delete_check'];
157
	$deleted_groups = array();
158

    
159
	if (!empty($del_groups)) {
160
		foreach ($del_groups as $groupid) {
161
			$this_group = config_get_path("system/group/{$groupid}");
162
			if (isset($this_group) &&
163
			    $this_group['scope'] != "system") {
164
				$deleted_groups[] = $this_group['name'];
165
				local_group_del($this_group);
166
				config_del_path("system/group/{$groupid}");
167
			}
168
		}
169

    
170
		$savemsg = sprintf(gettext("Successfully deleted %s: %s"),
171
		    (count($deleted_groups) == 1)
172
		    ? gettext("group") : gettext("groups"),
173
		    implode(', ', $deleted_groups));
174
		/*
175
		 * Reindex the array to avoid operating on an incorrect index
176
		 * https://redmine.pfsense.org/issues/7733
177
		 */
178
		config_set_path("system/group", array_values(config_get_path('system/group')));
179
		write_config($savemsg);
180
		syslog($logging_level, "{$logging_prefix}: {$savemsg}");
181
	}
182
}
183

    
184
if (isset($_POST['save']) && !$read_only) {
185
	unset($input_errors);
186
	$pconfig = $_POST;
187

    
188
	/* input validation */
189
	$reqdfields = explode(" ", "groupname");
190
	$reqdfieldsn = array(gettext("Group Name"));
191

    
192
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
193

    
194
	if ($_POST['gtype'] != "remote") {
195
		if (preg_match("/[^a-zA-Z0-9\.\-_]/", $_POST['groupname'])) {
196
			$input_errors[] = sprintf(gettext(
197
			    "The (%s) group name contains invalid characters."),
198
			    $_POST['gtype']);
199
		}
200
		if (strlen($_POST['groupname']) > 16) {
201
			$input_errors[] = gettext(
202
			    "The group name is longer than 16 characters.");
203
		}
204
	} else {
205
		if (preg_match("/[^a-zA-Z0-9\.\- _]/", $_POST['groupname'])) {
206
			$input_errors[] = sprintf(gettext(
207
			    "The (%s) group name contains invalid characters."),
208
			    $_POST['gtype']);
209
		}
210
	}
211

    
212
	/* Check the POSTed members to ensure they are valid and exist */
213
	if (is_array($_POST['members'])) {
214
		foreach ($_POST['members'] as $newmember) {
215
			if (!is_numeric($newmember) ||
216
			    empty(getUserEntryByUID($newmember))) {
217
				$input_errors[] = gettext("One or more " .
218
				    "invalid group members was submitted.");
219
			}
220
		}
221
	}
222

    
223
	if (!$input_errors && !(isset($id) && config_get_path("system/group/{$id}"))) {
224
		/* make sure there are no dupes */
225
		foreach (config_get_path('system/group', []) as $group) {
226
			if ($group['name'] == $_POST['groupname']) {
227
				$input_errors[] = gettext("Another entry " .
228
				    "with the same group name already exists.");
229
				break;
230
			}
231
		}
232
	}
233

    
234
	if (!$input_errors) {
235
		$group = array();
236
		if (isset($id) && config_get_path("system/group/{$id}")) {
237
			$group = config_get_path("system/group/{$id}");
238
		}
239

    
240
		$group['name'] = $_POST['groupname'];
241
		$group['description'] = $_POST['description'];
242
		$group['scope'] = $_POST['gtype'];
243

    
244
		if (empty($_POST['members'])) {
245
			unset($group['member']);
246
		} else if ($group['gid'] != 1998) { // all group
247
			$group['member'] = $_POST['members'];
248
		}
249

    
250
		if (isset($id) && config_get_path("system/group/{$id}")) {
251
			config_set_path("system/group/{$id}", $group);
252
		} else {
253
			$nextgid = config_get_path('system/nextgid');
254
			$group['gid'] = $nextgid++;
255
			config_set_path('system/nextgid', $nextgid);
256
			if ($_POST['dup']) {
257
				$group['priv'] = config_get_path("system/group/{$_POST['dup']}/priv");
258
			}
259
			config_set_path('system/group/', $group);
260
		}
261

    
262
		admin_groups_sort();
263

    
264
		local_group_set($group);
265

    
266
		/*
267
		 * Refresh users in this group since their privileges may have
268
		 * changed.
269
		 */
270
		if (is_array($group['member'])) {
271
			config_init_path('system/user');
272
			foreach (config_get_path('system/user', []) as $idx => $user) {
273
				if (in_array($user['uid'], $group['member'])) {
274
					local_user_set($user);
275
					config_set_path("system/user/{$idx}", $user);
276
				}
277
			}
278
		}
279

    
280
		/* Sort it alphabetically */
281
		$group_config = config_get_path('system/group');
282
		usort($group_config, function($a, $b) {
283
			return strcmp($a['name'], $b['name']);
284
		});
285
		config_set_path('system/group', $group_config);
286

    
287
		$savemsg = sprintf(gettext("Successfully %s group %s"),
288
		    (strlen($id) > 0) ? gettext("edited") : gettext("created"),
289
		    $group['name']);
290
		write_config($savemsg);
291
		syslog($logging_level, "{$logging_prefix}: {$savemsg}");
292

    
293
		header("Location: system_groupmanager.php");
294
		exit;
295
	}
296

    
297
	$pconfig['name'] = $_POST['groupname'];
298
}
299

    
300
function build_priv_table() {
301
	global $id, $read_only, $dup;
302

    
303
	$privhtml = '<div class="table-responsive">';
304
	$privhtml .=	'<table class="table table-striped table-hover table-condensed">';
305
	$privhtml .=		'<thead>';
306
	$privhtml .=			'<tr>';
307
	$privhtml .=				'<th>' . gettext('Name') . '</th>';
308
	$privhtml .=				'<th>' . gettext('Description') . '</th>';
309
	$privhtml .=				'<th>' . gettext('Action') . '</th>';
310
	$privhtml .=			'</tr>';
311
	$privhtml .=		'</thead>';
312
	$privhtml .=		'<tbody>';
313

    
314
	$user_has_root_priv = false;
315

    
316
	if (isset($id)) {
317
		foreach (get_user_privdesc(config_get_path("system/group/{$id}")) as $i => $priv) {
318
			$privhtml .=		'<tr>';
319
			$privhtml .=			'<td>' . htmlspecialchars($priv['name']) . '</td>';
320
			$privhtml .=			'<td>' . htmlspecialchars($priv['descr']);
321
			if (isset($priv['warn']) && ($priv['warn'] == 'standard-warning-root')) {
322
				$privhtml .=			' ' . gettext('(admin privilege)');
323
				$user_has_root_priv = true;
324
			}
325
			$privhtml .=			'</td>';
326
			if (!$read_only && ($dup === null)) {
327
				$privhtml .=			'<td><a class="fa-solid fa-trash-can" title="' . gettext('Delete Privilege') . '"	href="system_groupmanager.php?act=delpriv&amp;groupid=' . $id . '&amp;privid=' . $i . '" usepost></a></td>';
328
			}
329
			$privhtml .=		'</tr>';
330
		}
331
	}
332

    
333
	if ($user_has_root_priv) {
334
		$privhtml .=		'<tr>';
335
		$privhtml .=			'<td colspan="2">';
336
		$privhtml .=				'<b>' . gettext('Security notice: Users in this group effectively have administrator-level access') . '</b>';
337
		$privhtml .=			'</td>';
338
		$privhtml .=			'<td>';
339
		$privhtml .=			'</td>';
340
		$privhtml .=		'</tr>';
341

    
342
	}
343

    
344
	$privhtml .=		'</tbody>';
345
	$privhtml .=	'</table>';
346
	$privhtml .= '</div>';
347

    
348
	$privhtml .= '<nav class="action-buttons">';
349
	if (!$read_only && ($dup === null)) {
350
		$privhtml .=	'<a href="system_groupmanager_addprivs.php?groupid=' . $id . '" class="btn btn-success"><i class="fa-solid fa-plus icon-embed-btn"></i>' . gettext("Add") . '</a>';
351
	}
352
	$privhtml .= '</nav>';
353

    
354
	return($privhtml);
355
}
356

    
357
$pgtitle = array(gettext("System"), gettext("User Manager"), gettext("Groups"));
358
$pglinks = array("", "system_usermanager.php", "system_groupmanager.php");
359

    
360
if ($act == "new" || $act == "edit") {
361
	$pgtitle[] = gettext('Edit');
362
	$pglinks[] = "@self";
363
}
364

    
365
include("head.inc");
366

    
367
if ($input_errors) {
368
	print_input_errors($input_errors);
369
}
370

    
371
if ($savemsg) {
372
	print_info_box($savemsg, 'success');
373
}
374

    
375
$tab_array = array();
376
$tab_array[] = array(gettext("Users"), false, "system_usermanager.php");
377
$tab_array[] = array(gettext("Groups"), true, "system_groupmanager.php");
378
$tab_array[] = array(gettext("Settings"), false, "system_usermanager_settings.php");
379
$tab_array[] = array(gettext("Change Password"), false, "system_usermanager_passwordmg.php");
380
$tab_array[] = array(gettext("Authentication Servers"), false, "system_authservers.php");
381
display_top_tabs($tab_array);
382

    
383
if (!($act == "new" || $act == "edit")) {
384
?>
385
<div class="panel panel-default">
386
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Groups')?></h2></div>
387
	<div class="panel-body">
388
		<div class="table-responsive">
389
			<table class="table table-striped table-hover table-condensed sortable-theme-bootstrap table-rowdblclickedit" data-sortable>
390
				<thead>
391
					<tr>
392
						<th><?=gettext("Group name")?></th>
393
						<th><?=gettext("Description")?></th>
394
						<th><?=gettext("Member Count")?></th>
395
						<th><?=gettext("Actions")?></th>
396
					</tr>
397
				</thead>
398
				<tbody>
399
<?php
400
	foreach (config_get_path('system/group', []) as $i => $group):
401
		if ($group["name"] == "all") {
402
			$groupcount = count(config_get_path('system/user', []));
403
		} elseif (is_array($group['member'])) {
404
			$groupcount = count($group['member']);
405
		} else {
406
			$groupcount = 0;
407
		}
408
?>
409
					<tr>
410
						<td>
411
							<?=htmlspecialchars($group['name'])?>
412
						</td>
413
						<td>
414
							<?=htmlspecialchars($group['description'])?>
415
						</td>
416
						<td>
417
							<?=$groupcount?>
418
						</td>
419
						<td>
420
							<a class="fa-solid fa-pencil" title="<?=gettext("Edit group"); ?>" href="?act=edit&amp;groupid=<?=$i?>"></a>
421
							<a class="fa-regular fa-clone" title="<?=gettext("Copy group"); ?>" href="?act=dup&amp;groupid=<?=$i?>"></a>
422
							<?php if (($group['scope'] != "system") && !$read_only): ?>
423
								<a class="fa-solid fa-trash-can"	title="<?=gettext("Delete group")?>" href="?act=delgroup&amp;groupid=<?=$i?>&amp;groupname=<?=$group['name']?>" usepost></a>
424
							<?php endif;?>
425
						</td>
426
					</tr>
427
<?php
428
	endforeach;
429
?>
430
				</tbody>
431
			</table>
432
		</div>
433
	</div>
434
</div>
435

    
436
<nav class="action-buttons">
437
	<?php if (!$read_only): ?>
438
	<a href="?act=new" class="btn btn-success btn-sm">
439
		<i class="fa-solid fa-plus icon-embed-btn"></i>
440
		<?=gettext("Add")?>
441
	</a>
442
	<?php endif; ?>
443
</nav>
444
<?php
445
	include('foot.inc');
446
	exit;
447
}
448

    
449
$form = new Form;
450
$form->setAction('system_groupmanager.php?act=edit');
451
if ($dup === null) {
452
	$form->addGlobal(new Form_Input(
453
		'groupid',
454
		null,
455
		'hidden',
456
		$id
457
	));
458
} else {
459
	$form->addGlobal(new Form_Input(
460
		'dup',
461
		null,
462
		'hidden',
463
		$dup
464
	));
465
}
466

    
467
if (isset($id) && config_get_path("system/group/{$id}")) {
468
	$form->addGlobal(new Form_Input(
469
		'id',
470
		null,
471
		'hidden',
472
		$id
473
	));
474

    
475
	$form->addGlobal(new Form_Input(
476
		'gid',
477
		null,
478
		'hidden',
479
		$pconfig['gid']
480
	));
481
}
482

    
483
$section = new Form_Section('Group Properties');
484

    
485
$section->addInput($input = new Form_Input(
486
	'groupname',
487
	'*Group name',
488
	'text',
489
	$pconfig['name']
490
));
491

    
492
if ($pconfig['gtype'] == "system") {
493
	$input->setReadonly();
494

    
495
	$section->addInput(new Form_Input(
496
		'gtype',
497
		'*Scope',
498
		'text',
499
		$pconfig['gtype']
500
	))->setReadonly();
501
} else {
502
	$section->addInput(new Form_Select(
503
		'gtype',
504
		'*Scope',
505
		$pconfig['gtype'],
506
		["local" => gettext("Local"), "remote" => gettext("Remote")]
507
	))->setHelp("<span class=\"text-danger\">Warning: Changing this " .
508
	    "setting may affect the local groups file, in which case a " .
509
	    "reboot may be required for the changes to take effect.</span>");
510
}
511

    
512
$section->addInput(new Form_Input(
513
	'description',
514
	'Description',
515
	'text',
516
	$pconfig['description']
517
))->setHelp('Group description, for administrative information only');
518

    
519
$form->add($section);
520

    
521
/* all users group */
522
if ($pconfig['gid'] != 1998) {
523
	/* Group membership */
524
	$group = new Form_Group('Group membership');
525

    
526
	/*
527
	 * Make a list of all the groups configured on the system, and a list of
528
	 * those which this user is a member of
529
	 */
530
	$systemGroups = array();
531
	$usersGroups = array();
532

    
533
	foreach (config_get_path('system/user', []) as $user) {
534
		if (is_array($pconfig['members']) && in_array($user['uid'],
535
		    $pconfig['members'])) {
536
			/* Add it to the user's list */
537
			$usersGroups[ $user['uid'] ] = $user['name'];
538
		} else {
539
			/* Add it to the 'not a member of' list */
540
			$systemGroups[ $user['uid'] ] = $user['name'];
541
		}
542
	}
543

    
544
	$group->add(new Form_Select(
545
		'notmembers',
546
		null,
547
		array_combine((array)$pconfig['groups'],
548
		    (array)$pconfig['groups']),
549
		$systemGroups,
550
		true
551
	))->setHelp('Not members');
552

    
553
	$group->add(new Form_Select(
554
		'members',
555
		null,
556
		array_combine((array)$pconfig['groups'],
557
		    (array)$pconfig['groups']),
558
		$usersGroups,
559
		true
560
	))->setHelp('Members');
561

    
562
	$section->add($group);
563

    
564
	$group = new Form_Group('');
565

    
566
	$group->add(new Form_Button(
567
		'movetoenabled',
568
		'Move to "Members"',
569
		null,
570
		'fa-solid fa-angle-double-right'
571
	))->setAttribute('type','button')->removeClass('btn-primary')->addClass(
572
	    'btn-info btn-sm');
573

    
574
	$group->add(new Form_Button(
575
		'movetodisabled',
576
		'Move to "Not members',
577
		null,
578
		'fa-solid fa-angle-double-left'
579
	))->setAttribute('type','button')->removeClass('btn-primary')->addClass(
580
	    'btn-info btn-sm');
581

    
582
	$group->setHelp(
583
	    'Hold down CTRL (PC)/COMMAND (Mac) key to select multiple items.');
584
	$section->add($group);
585

    
586
}
587

    
588
if (isset($pconfig['gid']) || ($dup !== null)) {
589
	$section = new Form_Section('Assigned Privileges');
590

    
591
	$section->addInput(new Form_StaticText(
592
		null,
593
		build_priv_table()
594
	));
595

    
596

    
597
	$form->add($section);
598
}
599

    
600
print $form;
601
?>
602
<script type="text/javascript">
603
//<![CDATA[
604
events.push(function() {
605

    
606
	// On click . .
607
	$("#movetodisabled").click(function() {
608
		moveOptions($('[name="members[]"] option'),
609
		    $('[name="notmembers[]"]'));
610
	});
611

    
612
	$("#movetoenabled").click(function() {
613
		moveOptions($('[name="notmembers[]"] option'),
614
		    $('[name="members[]"]'));
615
	});
616

    
617
	// On submit mark all the user's groups as "selected"
618
	$('form').submit(function() {
619
		AllServers($('[name="members[]"] option'), true);
620
	});
621
});
622
//]]>
623
</script>
624
<?php
625
include('foot.inc');
(204-204/232)