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