1 /*
2 SPDX-FileCopyrightText: 2005 Sean Harmer <sh@rama.homelinux.org>
3 SPDX-FileCopyrightText: 2005-2007 Till Adam <adam@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "kacleditwidget.h"
9 #include "kacleditwidget_p.h"
10 #include "kio_widgets_debug.h"
11
12 #if HAVE_POSIX_ACL
13
14 #include <QButtonGroup>
15 #include <QCheckBox>
16 #include <QComboBox>
17 #include <QDialog>
18 #include <QDialogButtonBox>
19 #include <QGroupBox>
20 #include <QHeaderView>
21 #include <QLabel>
22 #include <QLayout>
23 #include <QMouseEvent>
24 #include <QPainter>
25 #include <QPushButton>
26 #include <QRadioButton>
27 #include <QScrollBar>
28 #include <QStackedWidget>
29
30 #include <KLocalizedString>
31 #include <kfileitem.h>
32
33 #if HAVE_ACL_LIBACL_H
34 #include <acl/libacl.h>
35 #endif
36 extern "C" {
37 #include <grp.h>
38 #include <pwd.h>
39 }
40 #include <assert.h>
41
42 class KACLEditWidget::KACLEditWidgetPrivate
43 {
44 public:
KACLEditWidgetPrivate()45 KACLEditWidgetPrivate()
46 {
47 }
48
49 // slots
50 void _k_slotUpdateButtons();
51
52 KACLListView *m_listView;
53 QPushButton *m_AddBtn;
54 QPushButton *m_EditBtn;
55 QPushButton *m_DelBtn;
56 };
57
KACLEditWidget(QWidget * parent)58 KACLEditWidget::KACLEditWidget(QWidget *parent)
59 : QWidget(parent)
60 , d(new KACLEditWidgetPrivate)
61 {
62 QHBoxLayout *hbox = new QHBoxLayout(this);
63 hbox->setContentsMargins(0, 0, 0, 0);
64 d->m_listView = new KACLListView(this);
65 hbox->addWidget(d->m_listView);
66 connect(d->m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this]() {
67 d->_k_slotUpdateButtons();
68 });
69 QVBoxLayout *vbox = new QVBoxLayout();
70 hbox->addLayout(vbox);
71 d->m_AddBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@action:button", "Add..."), this);
72 vbox->addWidget(d->m_AddBtn);
73 d->m_AddBtn->setObjectName(QStringLiteral("add_entry_button"));
74 connect(d->m_AddBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotAddEntry);
75 d->m_EditBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit")), i18nc("@action:button", "Edit..."), this);
76 vbox->addWidget(d->m_EditBtn);
77 d->m_EditBtn->setObjectName(QStringLiteral("edit_entry_button"));
78 connect(d->m_EditBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotEditEntry);
79 d->m_DelBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18nc("@action:button", "Delete"), this);
80 vbox->addWidget(d->m_DelBtn);
81 d->m_DelBtn->setObjectName(QStringLiteral("delete_entry_button"));
82 connect(d->m_DelBtn, &QAbstractButton::clicked, d->m_listView, &KACLListView::slotRemoveEntry);
83 vbox->addItem(new QSpacerItem(10, 10, QSizePolicy::Fixed, QSizePolicy::Expanding));
84 d->_k_slotUpdateButtons();
85 }
86
~KACLEditWidget()87 KACLEditWidget::~KACLEditWidget()
88 {
89 delete d;
90 }
91
_k_slotUpdateButtons()92 void KACLEditWidget::KACLEditWidgetPrivate::_k_slotUpdateButtons()
93 {
94 bool atLeastOneIsNotDeletable = false;
95 bool atLeastOneIsNotAllowedToChangeType = false;
96 int selectedCount = 0;
97 QList<QTreeWidgetItem *> selected = m_listView->selectedItems();
98 QListIterator<QTreeWidgetItem *> it(selected);
99 while (it.hasNext()) {
100 KACLListViewItem *item = static_cast<KACLListViewItem *>(it.next());
101 ++selectedCount;
102 if (!item->isDeletable()) {
103 atLeastOneIsNotDeletable = true;
104 }
105 if (!item->isAllowedToChangeType()) {
106 atLeastOneIsNotAllowedToChangeType = true;
107 }
108 }
109 m_EditBtn->setEnabled(selectedCount && !atLeastOneIsNotAllowedToChangeType);
110 m_DelBtn->setEnabled(selectedCount && !atLeastOneIsNotDeletable);
111 }
112
getACL() const113 KACL KACLEditWidget::getACL() const
114 {
115 return d->m_listView->getACL();
116 }
117
getDefaultACL() const118 KACL KACLEditWidget::getDefaultACL() const
119 {
120 return d->m_listView->getDefaultACL();
121 }
122
setACL(const KACL & acl)123 void KACLEditWidget::setACL(const KACL &acl)
124 {
125 d->m_listView->setACL(acl);
126 }
127
setDefaultACL(const KACL & acl)128 void KACLEditWidget::setDefaultACL(const KACL &acl)
129 {
130 d->m_listView->setDefaultACL(acl);
131 }
132
setAllowDefaults(bool value)133 void KACLEditWidget::setAllowDefaults(bool value)
134 {
135 d->m_listView->setAllowDefaults(value);
136 }
137
KACLListViewItem(QTreeWidget * parent,KACLListView::EntryType _type,unsigned short _value,bool defaults,const QString & _qualifier)138 KACLListViewItem::KACLListViewItem(QTreeWidget *parent, KACLListView::EntryType _type, unsigned short _value, bool defaults, const QString &_qualifier)
139 : QTreeWidgetItem(parent)
140 , type(_type)
141 , value(_value)
142 , isDefault(defaults)
143 , qualifier(_qualifier)
144 , isPartial(false)
145 {
146 m_pACLListView = qobject_cast<KACLListView *>(parent);
147 repaint();
148 }
149
~KACLListViewItem()150 KACLListViewItem::~KACLListViewItem()
151 {
152 }
153
key() const154 QString KACLListViewItem::key() const
155 {
156 QString key;
157 if (!isDefault) {
158 key = QLatin1Char('A');
159 } else {
160 key = QLatin1Char('B');
161 }
162 switch (type) {
163 case KACLListView::User:
164 key += QLatin1Char('A');
165 break;
166 case KACLListView::Group:
167 key += QLatin1Char('B');
168 break;
169 case KACLListView::Others:
170 key += QLatin1Char('C');
171 break;
172 case KACLListView::Mask:
173 key += QLatin1Char('D');
174 break;
175 case KACLListView::NamedUser:
176 key += QLatin1Char('E') + text(1);
177 break;
178 case KACLListView::NamedGroup:
179 key += QLatin1Char('F') + text(1);
180 break;
181 default:
182 key += text(0);
183 break;
184 }
185 return key;
186 }
187
operator <(const QTreeWidgetItem & other) const188 bool KACLListViewItem::operator<(const QTreeWidgetItem &other) const
189 {
190 return key() < static_cast<const KACLListViewItem &>(other).key();
191 }
192
updatePermissionIcons()193 void KACLListViewItem::updatePermissionIcons()
194 {
195 unsigned int partialPerms = value;
196
197 if (value & ACL_READ) {
198 setIcon(2, QIcon::fromTheme(QStringLiteral("checkmark")));
199 } else if (partialPerms & ACL_READ) {
200 setIcon(2, QIcon::fromTheme(QStringLiteral("checkmark-partial")));
201 } else {
202 setIcon(2, QIcon());
203 }
204
205 if (value & ACL_WRITE) {
206 setIcon(3, QIcon::fromTheme(QStringLiteral("checkmark")));
207 } else if (partialPerms & ACL_WRITE) {
208 setIcon(3, QIcon::fromTheme(QStringLiteral("checkmark-partial")));
209 } else {
210 setIcon(3, QIcon());
211 }
212
213 if (value & ACL_EXECUTE) {
214 setIcon(4, QIcon::fromTheme(QStringLiteral("checkmark")));
215 } else if (partialPerms & ACL_EXECUTE) {
216 setIcon(4, QIcon::fromTheme(QStringLiteral("checkmark-partial")));
217 } else {
218 setIcon(4, QIcon());
219 }
220 }
221
repaint()222 void KACLListViewItem::repaint()
223 {
224 QString text;
225 QString icon;
226
227 switch (type) {
228 case KACLListView::User:
229 default:
230 text = i18nc("Unix permissions", "Owner");
231 icon = QStringLiteral("user-gray");
232 break;
233 case KACLListView::Group:
234 text = i18nc("UNIX permissions", "Owning Group");
235 icon = QStringLiteral("group-gray");
236 break;
237 case KACLListView::Others:
238 text = i18nc("UNIX permissions", "Others");
239 icon = QStringLiteral("user-others-gray");
240 break;
241 case KACLListView::Mask:
242 text = i18nc("UNIX permissions", "Mask");
243 icon = QStringLiteral("view-filter");
244 break;
245 case KACLListView::NamedUser:
246 text = i18nc("UNIX permissions", "Named User");
247 icon = QStringLiteral("user");
248 break;
249 case KACLListView::NamedGroup:
250 text = i18nc("UNIX permissions", "Others");
251 icon = QStringLiteral("user-others");
252 break;
253 }
254 setText(0, text);
255 setIcon(0, QIcon::fromTheme(icon));
256 if (isDefault) {
257 setText(0, i18n("Owner (Default)"));
258 }
259 setText(1, qualifier);
260 // Set the icons for which of the perms are set
261 updatePermissionIcons();
262 }
263
calcEffectiveRights()264 void KACLListViewItem::calcEffectiveRights()
265 {
266 QString strEffective = QStringLiteral("---");
267
268 // Do we need to worry about the mask entry? It applies to named users,
269 // owning group, and named groups
270 if (m_pACLListView->hasMaskEntry() // clang-format off
271 && (type == KACLListView::NamedUser || type == KACLListView::Group || type == KACLListView::NamedGroup)
272 && !isDefault) { // clang-format on
273 strEffective[0] = QLatin1Char((m_pACLListView->maskPermissions() & value & ACL_READ) ? 'r' : '-');
274 strEffective[1] = QLatin1Char((m_pACLListView->maskPermissions() & value & ACL_WRITE) ? 'w' : '-');
275 strEffective[2] = QLatin1Char((m_pACLListView->maskPermissions() & value & ACL_EXECUTE) ? 'x' : '-');
276 /*
277 // What about any partial perms?
278 if ( maskPerms & partialPerms & ACL_READ || // Partial perms on entry
279 maskPartialPerms & perms & ACL_READ || // Partial perms on mask
280 maskPartialPerms & partialPerms & ACL_READ ) // Partial perms on mask and entry
281 strEffective[0] = 'R';
282 if ( maskPerms & partialPerms & ACL_WRITE || // Partial perms on entry
283 maskPartialPerms & perms & ACL_WRITE || // Partial perms on mask
284 maskPartialPerms & partialPerms & ACL_WRITE ) // Partial perms on mask and entry
285 strEffective[1] = 'W';
286 if ( maskPerms & partialPerms & ACL_EXECUTE || // Partial perms on entry
287 maskPartialPerms & perms & ACL_EXECUTE || // Partial perms on mask
288 maskPartialPerms & partialPerms & ACL_EXECUTE ) // Partial perms on mask and entry
289 strEffective[2] = 'X';
290 */
291 } else {
292 // No, the effective value are just the value in this entry
293 strEffective[0] = QLatin1Char((value & ACL_READ) ? 'r' : '-');
294 strEffective[1] = QLatin1Char((value & ACL_WRITE) ? 'w' : '-');
295 strEffective[2] = QLatin1Char((value & ACL_EXECUTE) ? 'x' : '-');
296
297 /*
298 // What about any partial perms?
299 if ( partialPerms & ACL_READ )
300 strEffective[0] = 'R';
301 if ( partialPerms & ACL_WRITE )
302 strEffective[1] = 'W';
303 if ( partialPerms & ACL_EXECUTE )
304 strEffective[2] = 'X';
305 */
306 }
307 setText(5, strEffective);
308 }
309
isDeletable() const310 bool KACLListViewItem::isDeletable() const
311 {
312 bool isMaskAndDeletable = false;
313 if (type == KACLListView::Mask) {
314 if (!isDefault && m_pACLListView->maskCanBeDeleted()) {
315 isMaskAndDeletable = true;
316 } else if (isDefault && m_pACLListView->defaultMaskCanBeDeleted()) {
317 isMaskAndDeletable = true;
318 }
319 }
320
321 /* clang-format off */
322 return type != KACLListView::User
323 && type != KACLListView::Group
324 && type != KACLListView::Others
325 && (type != KACLListView::Mask || isMaskAndDeletable);
326 /* clang-format on */
327 }
328
isAllowedToChangeType() const329 bool KACLListViewItem::isAllowedToChangeType() const
330 {
331 /* clang-format off */
332 return type != KACLListView::User
333 && type != KACLListView::Group
334 && type != KACLListView::Others
335 && type != KACLListView::Mask;
336 /* clang-format on */
337 }
338
togglePerm(acl_perm_t perm)339 void KACLListViewItem::togglePerm(acl_perm_t perm)
340 {
341 value ^= perm; // Toggle the perm
342 if (type == KACLListView::Mask && !isDefault) {
343 m_pACLListView->setMaskPermissions(value);
344 }
345 calcEffectiveRights();
346 updatePermissionIcons();
347 /*
348 // If the perm is in the partial perms then remove it. i.e. Once
349 // a user changes a partial perm it then applies to all selected files.
350 if ( m_pEntry->m_partialPerms & perm )
351 m_pEntry->m_partialPerms ^= perm;
352
353 m_pEntry->setPartialEntry( false );
354 // Make sure that all entries have their effective rights calculated if
355 // we are changing the ACL_MASK entry.
356 if ( type == Mask )
357 {
358 m_pACLListView->setMaskPartialPermissions( m_pEntry->m_partialPerms );
359 m_pACLListView->setMaskPermissions( value );
360 m_pACLListView->calculateEffectiveRights();
361 }
362 */
363 }
364
EditACLEntryDialog(KACLListView * listView,KACLListViewItem * item,const QStringList & users,const QStringList & groups,const QStringList & defaultUsers,const QStringList & defaultGroups,int allowedTypes,int allowedDefaultTypes,bool allowDefaults)365 EditACLEntryDialog::EditACLEntryDialog(KACLListView *listView,
366 KACLListViewItem *item,
367 const QStringList &users,
368 const QStringList &groups,
369 const QStringList &defaultUsers,
370 const QStringList &defaultGroups,
371 int allowedTypes,
372 int allowedDefaultTypes,
373 bool allowDefaults)
374 : QDialog(listView)
375 , m_listView(listView)
376 , m_item(item)
377 , m_users(users)
378 , m_groups(groups)
379 , m_defaultUsers(defaultUsers)
380 , m_defaultGroups(defaultGroups)
381 , m_allowedTypes(allowedTypes)
382 , m_allowedDefaultTypes(allowedDefaultTypes)
383 , m_defaultCB(nullptr)
384 {
385 setObjectName(QStringLiteral("edit_entry_dialog"));
386 setModal(true);
387 setWindowTitle(i18n("Edit ACL Entry"));
388
389 QVBoxLayout *mainLayout = new QVBoxLayout(this);
390 QGroupBox *gb = new QGroupBox(i18n("Entry Type"), this);
391 QVBoxLayout *gbLayout = new QVBoxLayout(gb);
392
393 m_buttonGroup = new QButtonGroup(this);
394
395 if (allowDefaults) {
396 m_defaultCB = new QCheckBox(i18n("Default for new files in this folder"), this);
397 m_defaultCB->setObjectName(QStringLiteral("defaultCB"));
398 mainLayout->addWidget(m_defaultCB);
399 connect(m_defaultCB, &QAbstractButton::toggled, this, &EditACLEntryDialog::slotUpdateAllowedUsersAndGroups);
400 connect(m_defaultCB, &QAbstractButton::toggled, this, &EditACLEntryDialog::slotUpdateAllowedTypes);
401 }
402
403 QRadioButton *ownerType = new QRadioButton(i18n("Owner"), gb);
404 ownerType->setObjectName(QStringLiteral("ownerType"));
405 gbLayout->addWidget(ownerType);
406 m_buttonGroup->addButton(ownerType);
407 m_buttonIds.insert(ownerType, KACLListView::User);
408 QRadioButton *owningGroupType = new QRadioButton(i18n("Owning Group"), gb);
409 owningGroupType->setObjectName(QStringLiteral("owningGroupType"));
410 gbLayout->addWidget(owningGroupType);
411 m_buttonGroup->addButton(owningGroupType);
412 m_buttonIds.insert(owningGroupType, KACLListView::Group);
413 QRadioButton *othersType = new QRadioButton(i18n("Others"), gb);
414 othersType->setObjectName(QStringLiteral("othersType"));
415 gbLayout->addWidget(othersType);
416 m_buttonGroup->addButton(othersType);
417 m_buttonIds.insert(othersType, KACLListView::Others);
418 QRadioButton *maskType = new QRadioButton(i18n("Mask"), gb);
419 maskType->setObjectName(QStringLiteral("maskType"));
420 gbLayout->addWidget(maskType);
421 m_buttonGroup->addButton(maskType);
422 m_buttonIds.insert(maskType, KACLListView::Mask);
423 QRadioButton *namedUserType = new QRadioButton(i18n("Named user"), gb);
424 namedUserType->setObjectName(QStringLiteral("namesUserType"));
425 gbLayout->addWidget(namedUserType);
426 m_buttonGroup->addButton(namedUserType);
427 m_buttonIds.insert(namedUserType, KACLListView::NamedUser);
428 QRadioButton *namedGroupType = new QRadioButton(i18n("Named group"), gb);
429 namedGroupType->setObjectName(QStringLiteral("namedGroupType"));
430 gbLayout->addWidget(namedGroupType);
431 m_buttonGroup->addButton(namedGroupType);
432 m_buttonIds.insert(namedGroupType, KACLListView::NamedGroup);
433
434 mainLayout->addWidget(gb);
435
436 connect(m_buttonGroup, qOverload<QAbstractButton *>(&QButtonGroup::buttonClicked), this, &EditACLEntryDialog::slotSelectionChanged);
437
438 m_widgetStack = new QStackedWidget(this);
439 mainLayout->addWidget(m_widgetStack);
440
441 // users box
442 QWidget *usersBox = new QWidget(m_widgetStack);
443 QHBoxLayout *usersLayout = new QHBoxLayout(usersBox);
444 m_widgetStack->addWidget(usersBox);
445
446 QLabel *usersLabel = new QLabel(i18n("User: "), usersBox);
447 m_usersCombo = new QComboBox(usersBox);
448 m_usersCombo->setEditable(false);
449 m_usersCombo->setObjectName(QStringLiteral("users"));
450 usersLabel->setBuddy(m_usersCombo);
451
452 usersLayout->addWidget(usersLabel);
453 usersLayout->addWidget(m_usersCombo);
454
455 // groups box
456 QWidget *groupsBox = new QWidget(m_widgetStack);
457 QHBoxLayout *groupsLayout = new QHBoxLayout(usersBox);
458 m_widgetStack->addWidget(groupsBox);
459
460 QLabel *groupsLabel = new QLabel(i18n("Group: "), groupsBox);
461 m_groupsCombo = new QComboBox(groupsBox);
462 m_groupsCombo->setEditable(false);
463 m_groupsCombo->setObjectName(QStringLiteral("groups"));
464 groupsLabel->setBuddy(m_groupsCombo);
465
466 groupsLayout->addWidget(groupsLabel);
467 groupsLayout->addWidget(m_groupsCombo);
468
469 if (m_item) {
470 m_buttonIds.key(m_item->type)->setChecked(true);
471 if (m_defaultCB) {
472 m_defaultCB->setChecked(m_item->isDefault);
473 }
474 slotUpdateAllowedTypes();
475 slotSelectionChanged(m_buttonIds.key(m_item->type));
476 slotUpdateAllowedUsersAndGroups();
477 if (m_item->type == KACLListView::NamedUser) {
478 m_usersCombo->setItemText(m_usersCombo->currentIndex(), m_item->qualifier);
479 } else if (m_item->type == KACLListView::NamedGroup) {
480 m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), m_item->qualifier);
481 }
482 } else {
483 // new entry, preselect "named user", arguably the most common one
484 m_buttonIds.key(KACLListView::NamedUser)->setChecked(true);
485 slotUpdateAllowedTypes();
486 slotSelectionChanged(m_buttonIds.key(KACLListView::NamedUser));
487 slotUpdateAllowedUsersAndGroups();
488 }
489
490 QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
491 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
492 connect(buttonBox, &QDialogButtonBox::accepted, this, &EditACLEntryDialog::slotOk);
493 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
494 mainLayout->addWidget(buttonBox);
495
496 adjustSize();
497 }
498
slotUpdateAllowedTypes()499 void EditACLEntryDialog::slotUpdateAllowedTypes()
500 {
501 int allowedTypes = m_allowedTypes;
502 if (m_defaultCB && m_defaultCB->isChecked()) {
503 allowedTypes = m_allowedDefaultTypes;
504 }
505 for (int i = 1; i < KACLListView::AllTypes; i = i * 2) {
506 if (allowedTypes & i) {
507 m_buttonIds.key(i)->show();
508 } else {
509 m_buttonIds.key(i)->hide();
510 }
511 }
512 }
513
slotUpdateAllowedUsersAndGroups()514 void EditACLEntryDialog::slotUpdateAllowedUsersAndGroups()
515 {
516 const QString oldUser = m_usersCombo->currentText();
517 const QString oldGroup = m_groupsCombo->currentText();
518 m_usersCombo->clear();
519 m_groupsCombo->clear();
520 if (m_defaultCB && m_defaultCB->isChecked()) {
521 m_usersCombo->addItems(m_defaultUsers);
522 if (m_defaultUsers.contains(oldUser)) {
523 m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser);
524 }
525 m_groupsCombo->addItems(m_defaultGroups);
526 if (m_defaultGroups.contains(oldGroup)) {
527 m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup);
528 }
529 } else {
530 m_usersCombo->addItems(m_users);
531 if (m_users.contains(oldUser)) {
532 m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser);
533 }
534 m_groupsCombo->addItems(m_groups);
535 if (m_groups.contains(oldGroup)) {
536 m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup);
537 }
538 }
539 }
slotOk()540 void EditACLEntryDialog::slotOk()
541 {
542 KACLListView::EntryType type = static_cast<KACLListView::EntryType>(m_buttonIds[m_buttonGroup->checkedButton()]);
543
544 qCWarning(KIO_WIDGETS) << "Type 2: " << type;
545
546 QString qualifier;
547 if (type == KACLListView::NamedUser) {
548 qualifier = m_usersCombo->currentText();
549 }
550 if (type == KACLListView::NamedGroup) {
551 qualifier = m_groupsCombo->currentText();
552 }
553
554 if (!m_item) {
555 m_item = new KACLListViewItem(m_listView, type, ACL_READ | ACL_WRITE | ACL_EXECUTE, false, qualifier);
556 } else {
557 m_item->type = type;
558 m_item->qualifier = qualifier;
559 }
560 if (m_defaultCB) {
561 m_item->isDefault = m_defaultCB->isChecked();
562 }
563 m_item->repaint();
564
565 QDialog::accept();
566 }
567
slotSelectionChanged(QAbstractButton * button)568 void EditACLEntryDialog::slotSelectionChanged(QAbstractButton *button)
569 {
570 switch (m_buttonIds[button]) {
571 case KACLListView::User:
572 case KACLListView::Group:
573 case KACLListView::Others:
574 case KACLListView::Mask:
575 m_widgetStack->setEnabled(false);
576 break;
577 case KACLListView::NamedUser:
578 m_widgetStack->setEnabled(true);
579 m_widgetStack->setCurrentIndex(0 /* User */);
580 break;
581 case KACLListView::NamedGroup:
582 m_widgetStack->setEnabled(true);
583 m_widgetStack->setCurrentIndex(1 /* Group */);
584 break;
585 default:
586 break;
587 }
588 }
589
KACLListView(QWidget * parent)590 KACLListView::KACLListView(QWidget *parent)
591 : QTreeWidget(parent)
592 , m_hasMask(false)
593 , m_allowDefaults(false)
594 {
595 // Add the columns
596 setColumnCount(6);
597 const QStringList headers{
598 i18n("Type"),
599 i18n("Name"),
600 i18nc("read permission", "r"),
601 i18nc("write permission", "w"),
602 i18nc("execute permission", "x"),
603 i18n("Effective"),
604 };
605 setHeaderLabels(headers);
606
607 setSortingEnabled(false);
608 setSelectionMode(QAbstractItemView::ExtendedSelection);
609 header()->setSectionResizeMode(QHeaderView::ResizeToContents);
610 setRootIsDecorated(false);
611
612 connect(this, &QTreeWidget::itemClicked, this, &KACLListView::slotItemClicked);
613
614 connect(this, &KACLListView::itemDoubleClicked, this, &KACLListView::slotItemDoubleClicked);
615 }
616
~KACLListView()617 KACLListView::~KACLListView()
618 {
619 }
620
sizeHint() const621 QSize KACLListView::sizeHint() const
622 {
623 const int width = header()->length() + verticalScrollBar()->width();
624 const int height = 7 * rowHeight(model()->index(0, 0, QModelIndex()));
625 return {width, height};
626 }
627
allowedUsers(bool defaults,KACLListViewItem * allowedItem)628 QStringList KACLListView::allowedUsers(bool defaults, KACLListViewItem *allowedItem)
629 {
630 if (m_allUsers.isEmpty()) {
631 struct passwd *user = nullptr;
632 setpwent();
633 while ((user = getpwent()) != nullptr) {
634 m_allUsers << QString::fromLatin1(user->pw_name);
635 }
636 endpwent();
637 m_allUsers.sort();
638 }
639
640 QStringList allowedUsers = m_allUsers;
641 QTreeWidgetItemIterator it(this);
642 while (*it) {
643 const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it);
644 ++it;
645 if (item->type != NamedUser || item->isDefault != defaults) {
646 continue;
647 }
648 if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) {
649 continue;
650 }
651 allowedUsers.removeAll(item->qualifier);
652 }
653 return allowedUsers;
654 }
655
allowedGroups(bool defaults,KACLListViewItem * allowedItem)656 QStringList KACLListView::allowedGroups(bool defaults, KACLListViewItem *allowedItem)
657 {
658 if (m_allGroups.isEmpty()) {
659 struct group *gr = nullptr;
660 setgrent();
661 while ((gr = getgrent()) != nullptr) {
662 m_allGroups << QString::fromLatin1(gr->gr_name);
663 }
664 endgrent();
665 m_allGroups.sort();
666 }
667
668 QStringList allowedGroups = m_allGroups;
669 QTreeWidgetItemIterator it(this);
670 while (*it) {
671 const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it);
672 ++it;
673 if (item->type != NamedGroup || item->isDefault != defaults) {
674 continue;
675 }
676 if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) {
677 continue;
678 }
679 allowedGroups.removeAll(item->qualifier);
680 }
681 return allowedGroups;
682 }
683
fillItemsFromACL(const KACL & pACL,bool defaults)684 void KACLListView::fillItemsFromACL(const KACL &pACL, bool defaults)
685 {
686 // clear out old entries of that ilk
687 QTreeWidgetItemIterator it(this);
688 while (KACLListViewItem *item = static_cast<KACLListViewItem *>(*it)) {
689 ++it;
690 if (item->isDefault == defaults) {
691 delete item;
692 }
693 }
694
695 new KACLListViewItem(this, User, pACL.ownerPermissions(), defaults);
696
697 new KACLListViewItem(this, Group, pACL.owningGroupPermissions(), defaults);
698
699 new KACLListViewItem(this, Others, pACL.othersPermissions(), defaults);
700
701 bool hasMask = false;
702 unsigned short mask = pACL.maskPermissions(hasMask);
703 if (hasMask) {
704 new KACLListViewItem(this, Mask, mask, defaults);
705 }
706
707 // read all named user entries
708 const ACLUserPermissionsList &userList = pACL.allUserPermissions();
709 ACLUserPermissionsConstIterator itu = userList.begin();
710 while (itu != userList.end()) {
711 new KACLListViewItem(this, NamedUser, (*itu).second, defaults, (*itu).first);
712 ++itu;
713 }
714
715 // and now all named groups
716 const ACLUserPermissionsList &groupList = pACL.allGroupPermissions();
717 ACLUserPermissionsConstIterator itg = groupList.begin();
718 while (itg != groupList.end()) {
719 new KACLListViewItem(this, NamedGroup, (*itg).second, defaults, (*itg).first);
720 ++itg;
721 }
722 }
723
setACL(const KACL & acl)724 void KACLListView::setACL(const KACL &acl)
725 {
726 if (!acl.isValid()) {
727 return;
728 }
729 // Remove any entries left over from displaying a previous ACL
730 m_ACL = acl;
731 fillItemsFromACL(m_ACL);
732
733 m_mask = acl.maskPermissions(m_hasMask);
734 calculateEffectiveRights();
735 }
736
setDefaultACL(const KACL & acl)737 void KACLListView::setDefaultACL(const KACL &acl)
738 {
739 if (!acl.isValid()) {
740 return;
741 }
742 m_defaultACL = acl;
743 fillItemsFromACL(m_defaultACL, true);
744 calculateEffectiveRights();
745 }
746
itemsToACL(bool defaults) const747 KACL KACLListView::itemsToACL(bool defaults) const
748 {
749 KACL newACL(0);
750 bool atLeastOneEntry = false;
751 ACLUserPermissionsList users;
752 ACLGroupPermissionsList groups;
753 QTreeWidgetItemIterator it(const_cast<KACLListView *>(this));
754 while (QTreeWidgetItem *qlvi = *it) {
755 ++it;
756 const KACLListViewItem *item = static_cast<KACLListViewItem *>(qlvi);
757 if (item->isDefault != defaults) {
758 continue;
759 }
760 atLeastOneEntry = true;
761 switch (item->type) {
762 case User:
763 newACL.setOwnerPermissions(item->value);
764 break;
765 case Group:
766 newACL.setOwningGroupPermissions(item->value);
767 break;
768 case Others:
769 newACL.setOthersPermissions(item->value);
770 break;
771 case Mask:
772 newACL.setMaskPermissions(item->value);
773 break;
774 case NamedUser:
775 users.append(qMakePair(item->text(1), item->value));
776 break;
777 case NamedGroup:
778 groups.append(qMakePair(item->text(1), item->value));
779 break;
780 default:
781 break;
782 }
783 }
784 if (atLeastOneEntry) {
785 newACL.setAllUserPermissions(users);
786 newACL.setAllGroupPermissions(groups);
787 if (newACL.isValid()) {
788 return newACL;
789 }
790 }
791 return KACL();
792 }
793
getACL()794 KACL KACLListView::getACL()
795 {
796 return itemsToACL(false);
797 }
798
getDefaultACL()799 KACL KACLListView::getDefaultACL()
800 {
801 return itemsToACL(true);
802 }
803
contentsMousePressEvent(QMouseEvent *)804 void KACLListView::contentsMousePressEvent(QMouseEvent * /*e*/)
805 {
806 /*
807 QTreeWidgetItem *clickedItem = itemAt( e->pos() );
808 if ( !clickedItem ) return;
809 // if the click is on an as yet unselected item, select it first
810 if ( !clickedItem->isSelected() )
811 QAbstractItemView::contentsMousePressEvent( e );
812
813 if ( !currentItem() ) return;
814 int column = header()->sectionAt( e->x() );
815 acl_perm_t perm;
816 switch ( column )
817 {
818 case 2:
819 perm = ACL_READ;
820 break;
821 case 3:
822 perm = ACL_WRITE;
823 break;
824 case 4:
825 perm = ACL_EXECUTE;
826 break;
827 default:
828 return QTreeWidget::contentsMousePressEvent( e );
829 }
830 KACLListViewItem* referenceItem = static_cast<KACLListViewItem*>( clickedItem );
831 unsigned short referenceHadItSet = referenceItem->value & perm;
832 QTreeWidgetItemIterator it( this );
833 while ( KACLListViewItem* item = static_cast<KACLListViewItem*>( *it ) ) {
834 ++it;
835 if ( !item->isSelected() ) continue;
836 // toggle those with the same value as the clicked item, leave the others
837 if ( referenceHadItSet == ( item->value & perm ) )
838 item->togglePerm( perm );
839 }
840 */
841 }
842
slotItemClicked(QTreeWidgetItem * pItem,int col)843 void KACLListView::slotItemClicked(QTreeWidgetItem *pItem, int col)
844 {
845 if (!pItem) {
846 return;
847 }
848
849 QTreeWidgetItemIterator it(this);
850 while (KACLListViewItem *item = static_cast<KACLListViewItem *>(*it)) {
851 ++it;
852 if (!item->isSelected()) {
853 continue;
854 }
855 switch (col) {
856 case 2:
857 item->togglePerm(ACL_READ);
858 break;
859 case 3:
860 item->togglePerm(ACL_WRITE);
861 break;
862 case 4:
863 item->togglePerm(ACL_EXECUTE);
864 break;
865
866 default:; // Do nothing
867 }
868 }
869 /*
870 // Has the user changed one of the required entries in a default ACL?
871 if ( m_pACL->aclType() == ACL_TYPE_DEFAULT &&
872 ( col == 2 || col == 3 || col == 4 ) &&
873 ( pACLItem->entryType() == ACL_USER_OBJ ||
874 pACLItem->entryType() == ACL_GROUP_OBJ ||
875 pACLItem->entryType() == ACL_OTHER ) )
876 {
877 // Mark the required entries as no longer being partial entries.
878 // That is, they will get applied to all selected directories.
879 KACLListViewItem* pUserObj = findACLEntryByType( this, ACL_USER_OBJ );
880 pUserObj->entry()->setPartialEntry( false );
881
882 KACLListViewItem* pGroupObj = findACLEntryByType( this, ACL_GROUP_OBJ );
883 pGroupObj->entry()->setPartialEntry( false );
884
885 KACLListViewItem* pOther = findACLEntryByType( this, ACL_OTHER );
886 pOther->entry()->setPartialEntry( false );
887
888 update();
889 }
890 */
891 }
892
slotItemDoubleClicked(QTreeWidgetItem * item,int column)893 void KACLListView::slotItemDoubleClicked(QTreeWidgetItem *item, int column)
894 {
895 if (!item) {
896 return;
897 }
898
899 // avoid conflict with clicking to toggle permission
900 if (column >= 2 && column <= 4) {
901 return;
902 }
903
904 KACLListViewItem *aclListItem = static_cast<KACLListViewItem *>(item);
905 if (!aclListItem->isAllowedToChangeType()) {
906 return;
907 }
908
909 setCurrentItem(item);
910 slotEditEntry();
911 }
912
calculateEffectiveRights()913 void KACLListView::calculateEffectiveRights()
914 {
915 QTreeWidgetItemIterator it(this);
916 KACLListViewItem *pItem;
917 while ((pItem = dynamic_cast<KACLListViewItem *>(*it)) != nullptr) {
918 ++it;
919 pItem->calcEffectiveRights();
920 }
921 }
922
maskPermissions() const923 unsigned short KACLListView::maskPermissions() const
924 {
925 return m_mask;
926 }
927
setMaskPermissions(unsigned short maskPerms)928 void KACLListView::setMaskPermissions(unsigned short maskPerms)
929 {
930 m_mask = maskPerms;
931 calculateEffectiveRights();
932 }
933
maskPartialPermissions() const934 acl_perm_t KACLListView::maskPartialPermissions() const
935 {
936 // return m_pMaskEntry->m_partialPerms;
937 return 0;
938 }
939
setMaskPartialPermissions(acl_perm_t)940 void KACLListView::setMaskPartialPermissions(acl_perm_t /*maskPartialPerms*/)
941 {
942 // m_pMaskEntry->m_partialPerms = maskPartialPerms;
943 calculateEffectiveRights();
944 }
945
hasDefaultEntries() const946 bool KACLListView::hasDefaultEntries() const
947 {
948 QTreeWidgetItemIterator it(const_cast<KACLListView *>(this));
949 while (*it) {
950 const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it);
951 ++it;
952 if (item->isDefault) {
953 return true;
954 }
955 }
956 return false;
957 }
958
findDefaultItemByType(EntryType type) const959 const KACLListViewItem *KACLListView::findDefaultItemByType(EntryType type) const
960 {
961 return findItemByType(type, true);
962 }
963
findItemByType(EntryType type,bool defaults) const964 const KACLListViewItem *KACLListView::findItemByType(EntryType type, bool defaults) const
965 {
966 QTreeWidgetItemIterator it(const_cast<KACLListView *>(this));
967 while (*it) {
968 const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it);
969 ++it;
970 if (item->isDefault == defaults && item->type == type) {
971 return item;
972 }
973 }
974 return nullptr;
975 }
976
calculateMaskValue(bool defaults) const977 unsigned short KACLListView::calculateMaskValue(bool defaults) const
978 {
979 // KACL auto-adds the relevant mask entries, so we can simply query
980 bool dummy;
981 return itemsToACL(defaults).maskPermissions(dummy);
982 }
983
slotAddEntry()984 void KACLListView::slotAddEntry()
985 {
986 int allowedTypes = NamedUser | NamedGroup;
987 if (!m_hasMask) {
988 allowedTypes |= Mask;
989 }
990 int allowedDefaultTypes = NamedUser | NamedGroup;
991 if (!findDefaultItemByType(Mask)) {
992 allowedDefaultTypes |= Mask;
993 }
994 if (!hasDefaultEntries()) {
995 allowedDefaultTypes |= User | Group;
996 }
997 EditACLEntryDialog dlg(this,
998 nullptr,
999 allowedUsers(false),
1000 allowedGroups(false),
1001 allowedUsers(true),
1002 allowedGroups(true),
1003 allowedTypes,
1004 allowedDefaultTypes,
1005 m_allowDefaults);
1006 dlg.exec();
1007 KACLListViewItem *item = dlg.item();
1008 if (!item) {
1009 return; // canceled
1010 }
1011 if (item->type == Mask && !item->isDefault) {
1012 m_hasMask = true;
1013 m_mask = item->value;
1014 }
1015 if (item->isDefault && !hasDefaultEntries()) {
1016 // first default entry, fill in what is needed
1017 if (item->type != User) {
1018 unsigned short v = findDefaultItemByType(User)->value;
1019 new KACLListViewItem(this, User, v, true);
1020 }
1021 if (item->type != Group) {
1022 unsigned short v = findDefaultItemByType(Group)->value;
1023 new KACLListViewItem(this, Group, v, true);
1024 }
1025 if (item->type != Others) {
1026 unsigned short v = findDefaultItemByType(Others)->value;
1027 new KACLListViewItem(this, Others, v, true);
1028 }
1029 }
1030 const KACLListViewItem *defaultMaskItem = findDefaultItemByType(Mask);
1031 if (item->isDefault && !defaultMaskItem) {
1032 unsigned short v = calculateMaskValue(true);
1033 new KACLListViewItem(this, Mask, v, true);
1034 }
1035 if (!item->isDefault && !m_hasMask && (item->type == Group || item->type == NamedUser || item->type == NamedGroup)) {
1036 // auto-add a mask entry
1037 unsigned short v = calculateMaskValue(false);
1038 new KACLListViewItem(this, Mask, v, false);
1039 m_hasMask = true;
1040 m_mask = v;
1041 }
1042 calculateEffectiveRights();
1043 sortItems(sortColumn(), Qt::AscendingOrder);
1044 setCurrentItem(item);
1045 // QTreeWidget doesn't seem to emit, in this case, and we need to update
1046 // the buttons...
1047 if (topLevelItemCount() == 1) {
1048 Q_EMIT currentItemChanged(item, item);
1049 }
1050 }
1051
slotEditEntry()1052 void KACLListView::slotEditEntry()
1053 {
1054 QTreeWidgetItem *current = currentItem();
1055 if (!current) {
1056 return;
1057 }
1058 KACLListViewItem *item = static_cast<KACLListViewItem *>(current);
1059 int allowedTypes = item->type | NamedUser | NamedGroup;
1060 bool itemWasMask = item->type == Mask;
1061 if (!m_hasMask || itemWasMask) {
1062 allowedTypes |= Mask;
1063 }
1064 int allowedDefaultTypes = item->type | NamedUser | NamedGroup;
1065 if (!findDefaultItemByType(Mask)) {
1066 allowedDefaultTypes |= Mask;
1067 }
1068 if (!hasDefaultEntries()) {
1069 allowedDefaultTypes |= User | Group;
1070 }
1071
1072 EditACLEntryDialog dlg(this,
1073 item,
1074 allowedUsers(false, item),
1075 allowedGroups(false, item),
1076 allowedUsers(true, item),
1077 allowedGroups(true, item),
1078 allowedTypes,
1079 allowedDefaultTypes,
1080 m_allowDefaults);
1081 dlg.exec();
1082 if (itemWasMask && item->type != Mask) {
1083 m_hasMask = false;
1084 m_mask = 0;
1085 }
1086 if (!itemWasMask && item->type == Mask) {
1087 m_mask = item->value;
1088 m_hasMask = true;
1089 }
1090 calculateEffectiveRights();
1091 sortItems(sortColumn(), Qt::AscendingOrder);
1092 }
1093
slotRemoveEntry()1094 void KACLListView::slotRemoveEntry()
1095 {
1096 QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected);
1097 while (*it) {
1098 KACLListViewItem *item = static_cast<KACLListViewItem *>(*it);
1099 ++it;
1100 /* First check if it's a mask entry and if so, make sure that there is
1101 * either no name user or group entry, which means the mask can be
1102 * removed, or don't remove it, but reset it. That is allowed. */
1103 if (item->type == Mask) {
1104 bool itemWasDefault = item->isDefault;
1105 if (!itemWasDefault && maskCanBeDeleted()) {
1106 m_hasMask = false;
1107 m_mask = 0;
1108 delete item;
1109 } else if (itemWasDefault && defaultMaskCanBeDeleted()) {
1110 delete item;
1111 } else {
1112 item->value = 0;
1113 item->repaint();
1114 }
1115 if (!itemWasDefault) {
1116 calculateEffectiveRights();
1117 }
1118 } else {
1119 // for the base permissions, disable them, which is what libacl does
1120 if (!item->isDefault && (item->type == User || item->type == Group || item->type == Others)) {
1121 item->value = 0;
1122 item->repaint();
1123 } else {
1124 delete item;
1125 }
1126 }
1127 }
1128 }
1129
maskCanBeDeleted() const1130 bool KACLListView::maskCanBeDeleted() const
1131 {
1132 return !findItemByType(NamedUser) && !findItemByType(NamedGroup);
1133 }
1134
defaultMaskCanBeDeleted() const1135 bool KACLListView::defaultMaskCanBeDeleted() const
1136 {
1137 return !findDefaultItemByType(NamedUser) && !findDefaultItemByType(NamedGroup);
1138 }
1139
1140 #include "moc_kacleditwidget.cpp"
1141 #include "moc_kacleditwidget_p.cpp"
1142 #endif
1143