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

source code of kio/src/widgets/kacleditwidget.cpp