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: |
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 | |
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->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 | |
87 | KACLEditWidget::~KACLEditWidget() = default; |
88 | |
89 | void 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 | |
110 | KACL KACLEditWidget::getACL() const |
111 | { |
112 | return d->m_listView->getACL(); |
113 | } |
114 | |
115 | KACL KACLEditWidget::getDefaultACL() const |
116 | { |
117 | return d->m_listView->getDefaultACL(); |
118 | } |
119 | |
120 | void KACLEditWidget::setACL(const KACL &acl) |
121 | { |
122 | d->m_listView->setACL(acl); |
123 | } |
124 | |
125 | void KACLEditWidget::setDefaultACL(const KACL &acl) |
126 | { |
127 | d->m_listView->setDefaultACL(acl); |
128 | } |
129 | |
130 | void KACLEditWidget::setAllowDefaults(bool value) |
131 | { |
132 | d->m_listView->setAllowDefaults(value); |
133 | } |
134 | |
135 | KACLListViewItem::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 | |
147 | KACLListViewItem::~KACLListViewItem() |
148 | { |
149 | } |
150 | |
151 | QString 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 | |
185 | bool KACLListViewItem::operator<(const QTreeWidgetItem &other) const |
186 | { |
187 | return key() < static_cast<const KACLListViewItem &>(other).key(); |
188 | } |
189 | |
190 | void 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 | |
219 | void 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 | |
261 | void 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 | |
307 | bool 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 | |
326 | bool 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 | |
336 | void 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 | |
362 | EditACLEntryDialog::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 | gb->setFlat(true); |
389 | QVBoxLayout *gbLayout = new QVBoxLayout(gb); |
390 | |
391 | m_buttonGroup = new QButtonGroup(this); |
392 | |
393 | if (allowDefaults) { |
394 | m_defaultCB = new QCheckBox(i18n("Default for new files in this folder" ), this); |
395 | m_defaultCB->setObjectName(QStringLiteral("defaultCB" )); |
396 | mainLayout->addWidget(m_defaultCB); |
397 | connect(m_defaultCB, &QAbstractButton::toggled, this, &EditACLEntryDialog::slotUpdateAllowedUsersAndGroups); |
398 | connect(m_defaultCB, &QAbstractButton::toggled, this, &EditACLEntryDialog::slotUpdateAllowedTypes); |
399 | } |
400 | |
401 | QRadioButton *ownerType = new QRadioButton(i18n("Owner" ), gb); |
402 | ownerType->setObjectName(QStringLiteral("ownerType" )); |
403 | gbLayout->addWidget(ownerType); |
404 | m_buttonGroup->addButton(ownerType); |
405 | m_buttonIds.insert(ownerType, KACLListView::User); |
406 | QRadioButton *owningGroupType = new QRadioButton(i18n("Owning Group" ), gb); |
407 | owningGroupType->setObjectName(QStringLiteral("owningGroupType" )); |
408 | gbLayout->addWidget(owningGroupType); |
409 | m_buttonGroup->addButton(owningGroupType); |
410 | m_buttonIds.insert(owningGroupType, KACLListView::Group); |
411 | QRadioButton *othersType = new QRadioButton(i18n("Others" ), gb); |
412 | othersType->setObjectName(QStringLiteral("othersType" )); |
413 | gbLayout->addWidget(othersType); |
414 | m_buttonGroup->addButton(othersType); |
415 | m_buttonIds.insert(othersType, KACLListView::Others); |
416 | QRadioButton *maskType = new QRadioButton(i18n("Mask" ), gb); |
417 | maskType->setObjectName(QStringLiteral("maskType" )); |
418 | gbLayout->addWidget(maskType); |
419 | m_buttonGroup->addButton(maskType); |
420 | m_buttonIds.insert(maskType, KACLListView::Mask); |
421 | QRadioButton *namedUserType = new QRadioButton(i18n("Named user" ), gb); |
422 | namedUserType->setObjectName(QStringLiteral("namesUserType" )); |
423 | gbLayout->addWidget(namedUserType); |
424 | m_buttonGroup->addButton(namedUserType); |
425 | m_buttonIds.insert(namedUserType, KACLListView::NamedUser); |
426 | QRadioButton *namedGroupType = new QRadioButton(i18n("Named group" ), gb); |
427 | namedGroupType->setObjectName(QStringLiteral("namedGroupType" )); |
428 | gbLayout->addWidget(namedGroupType); |
429 | m_buttonGroup->addButton(namedGroupType); |
430 | m_buttonIds.insert(namedGroupType, KACLListView::NamedGroup); |
431 | |
432 | mainLayout->addWidget(gb); |
433 | |
434 | connect(m_buttonGroup, &QButtonGroup::buttonClicked, this, &EditACLEntryDialog::slotSelectionChanged); |
435 | |
436 | m_widgetStack = new QStackedWidget(this); |
437 | mainLayout->addWidget(m_widgetStack); |
438 | |
439 | // users box |
440 | QWidget *usersBox = new QWidget(m_widgetStack); |
441 | QHBoxLayout *usersLayout = new QHBoxLayout(usersBox); |
442 | m_widgetStack->addWidget(usersBox); |
443 | |
444 | QLabel *usersLabel = new QLabel(i18n("User: " ), usersBox); |
445 | m_usersCombo = new QComboBox(usersBox); |
446 | m_usersCombo->setEditable(false); |
447 | m_usersCombo->setObjectName(QStringLiteral("users" )); |
448 | usersLabel->setBuddy(m_usersCombo); |
449 | |
450 | usersLayout->addWidget(usersLabel); |
451 | usersLayout->addWidget(m_usersCombo); |
452 | |
453 | // groups box |
454 | QWidget *groupsBox = new QWidget(m_widgetStack); |
455 | QHBoxLayout *groupsLayout = new QHBoxLayout(usersBox); |
456 | m_widgetStack->addWidget(groupsBox); |
457 | |
458 | QLabel *groupsLabel = new QLabel(i18n("Group: " ), groupsBox); |
459 | m_groupsCombo = new QComboBox(groupsBox); |
460 | m_groupsCombo->setEditable(false); |
461 | m_groupsCombo->setObjectName(QStringLiteral("groups" )); |
462 | groupsLabel->setBuddy(m_groupsCombo); |
463 | |
464 | groupsLayout->addWidget(groupsLabel); |
465 | groupsLayout->addWidget(m_groupsCombo); |
466 | |
467 | if (m_item) { |
468 | m_buttonIds.key(m_item->type)->setChecked(true); |
469 | if (m_defaultCB) { |
470 | m_defaultCB->setChecked(m_item->isDefault); |
471 | } |
472 | slotUpdateAllowedTypes(); |
473 | slotSelectionChanged(m_buttonIds.key(m_item->type)); |
474 | slotUpdateAllowedUsersAndGroups(); |
475 | if (m_item->type == KACLListView::NamedUser) { |
476 | m_usersCombo->setItemText(m_usersCombo->currentIndex(), m_item->qualifier); |
477 | } else if (m_item->type == KACLListView::NamedGroup) { |
478 | m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), m_item->qualifier); |
479 | } |
480 | } else { |
481 | // new entry, preselect "named user", arguably the most common one |
482 | m_buttonIds.key(KACLListView::NamedUser)->setChecked(true); |
483 | slotUpdateAllowedTypes(); |
484 | slotSelectionChanged(m_buttonIds.key(KACLListView::NamedUser)); |
485 | slotUpdateAllowedUsersAndGroups(); |
486 | } |
487 | |
488 | QDialogButtonBox *buttonBox = new QDialogButtonBox(this); |
489 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); |
490 | connect(buttonBox, &QDialogButtonBox::accepted, this, &EditACLEntryDialog::slotOk); |
491 | connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); |
492 | mainLayout->addWidget(buttonBox); |
493 | |
494 | adjustSize(); |
495 | } |
496 | |
497 | void EditACLEntryDialog::slotUpdateAllowedTypes() |
498 | { |
499 | int allowedTypes = m_allowedTypes; |
500 | if (m_defaultCB && m_defaultCB->isChecked()) { |
501 | allowedTypes = m_allowedDefaultTypes; |
502 | } |
503 | for (int i = 1; i < KACLListView::AllTypes; i = i * 2) { |
504 | if (allowedTypes & i) { |
505 | m_buttonIds.key(i)->show(); |
506 | } else { |
507 | m_buttonIds.key(i)->hide(); |
508 | } |
509 | } |
510 | } |
511 | |
512 | void EditACLEntryDialog::slotUpdateAllowedUsersAndGroups() |
513 | { |
514 | const QString oldUser = m_usersCombo->currentText(); |
515 | const QString oldGroup = m_groupsCombo->currentText(); |
516 | m_usersCombo->clear(); |
517 | m_groupsCombo->clear(); |
518 | if (m_defaultCB && m_defaultCB->isChecked()) { |
519 | m_usersCombo->addItems(m_defaultUsers); |
520 | if (m_defaultUsers.contains(oldUser)) { |
521 | m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); |
522 | } |
523 | m_groupsCombo->addItems(m_defaultGroups); |
524 | if (m_defaultGroups.contains(oldGroup)) { |
525 | m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); |
526 | } |
527 | } else { |
528 | m_usersCombo->addItems(m_users); |
529 | if (m_users.contains(oldUser)) { |
530 | m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); |
531 | } |
532 | m_groupsCombo->addItems(m_groups); |
533 | if (m_groups.contains(oldGroup)) { |
534 | m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); |
535 | } |
536 | } |
537 | } |
538 | void EditACLEntryDialog::slotOk() |
539 | { |
540 | KACLListView::EntryType type = static_cast<KACLListView::EntryType>(m_buttonIds[m_buttonGroup->checkedButton()]); |
541 | |
542 | qCWarning(KIO_WIDGETS) << "Type 2: " << type; |
543 | |
544 | QString qualifier; |
545 | if (type == KACLListView::NamedUser) { |
546 | qualifier = m_usersCombo->currentText(); |
547 | } |
548 | if (type == KACLListView::NamedGroup) { |
549 | qualifier = m_groupsCombo->currentText(); |
550 | } |
551 | |
552 | if (!m_item) { |
553 | m_item = new KACLListViewItem(m_listView, type, ACL_READ | ACL_WRITE | ACL_EXECUTE, false, qualifier); |
554 | } else { |
555 | m_item->type = type; |
556 | m_item->qualifier = qualifier; |
557 | } |
558 | if (m_defaultCB) { |
559 | m_item->isDefault = m_defaultCB->isChecked(); |
560 | } |
561 | m_item->repaint(); |
562 | |
563 | QDialog::accept(); |
564 | } |
565 | |
566 | void EditACLEntryDialog::slotSelectionChanged(QAbstractButton *button) |
567 | { |
568 | switch (m_buttonIds[button]) { |
569 | case KACLListView::User: |
570 | case KACLListView::Group: |
571 | case KACLListView::Others: |
572 | case KACLListView::Mask: |
573 | m_widgetStack->setEnabled(false); |
574 | break; |
575 | case KACLListView::NamedUser: |
576 | m_widgetStack->setEnabled(true); |
577 | m_widgetStack->setCurrentIndex(0 /* User */); |
578 | break; |
579 | case KACLListView::NamedGroup: |
580 | m_widgetStack->setEnabled(true); |
581 | m_widgetStack->setCurrentIndex(1 /* Group */); |
582 | break; |
583 | default: |
584 | break; |
585 | } |
586 | } |
587 | |
588 | KACLListView::KACLListView(QWidget *parent) |
589 | : QTreeWidget(parent) |
590 | , m_hasMask(false) |
591 | , m_allowDefaults(false) |
592 | { |
593 | // Add the columns |
594 | setColumnCount(6); |
595 | const QStringList headers{ |
596 | i18n("Type" ), |
597 | i18n("Name" ), |
598 | i18nc("read permission" , "r" ), |
599 | i18nc("write permission" , "w" ), |
600 | i18nc("execute permission" , "x" ), |
601 | i18n("Effective" ), |
602 | }; |
603 | setHeaderLabels(headers); |
604 | |
605 | setSortingEnabled(false); |
606 | setSelectionMode(QAbstractItemView::ExtendedSelection); |
607 | header()->setSectionResizeMode(QHeaderView::ResizeToContents); |
608 | setRootIsDecorated(false); |
609 | |
610 | connect(this, &QTreeWidget::itemClicked, this, &KACLListView::slotItemClicked); |
611 | |
612 | connect(this, &KACLListView::itemDoubleClicked, this, &KACLListView::slotItemDoubleClicked); |
613 | } |
614 | |
615 | KACLListView::~KACLListView() |
616 | { |
617 | } |
618 | |
619 | QSize KACLListView::sizeHint() const |
620 | { |
621 | const int width = header()->length() + verticalScrollBar()->width(); |
622 | const int height = 7 * rowHeight(model()->index(0, 0, QModelIndex())); |
623 | return {width, height}; |
624 | } |
625 | |
626 | QStringList KACLListView::allowedUsers(bool defaults, KACLListViewItem *allowedItem) |
627 | { |
628 | if (m_allUsers.isEmpty()) { |
629 | struct passwd *user = nullptr; |
630 | setpwent(); |
631 | while ((user = getpwent()) != nullptr) { |
632 | m_allUsers << QString::fromLatin1(user->pw_name); |
633 | } |
634 | endpwent(); |
635 | m_allUsers.sort(); |
636 | } |
637 | |
638 | QStringList allowedUsers = m_allUsers; |
639 | QTreeWidgetItemIterator it(this); |
640 | while (*it) { |
641 | const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it); |
642 | ++it; |
643 | if (item->type != NamedUser || item->isDefault != defaults) { |
644 | continue; |
645 | } |
646 | if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { |
647 | continue; |
648 | } |
649 | allowedUsers.removeAll(item->qualifier); |
650 | } |
651 | return allowedUsers; |
652 | } |
653 | |
654 | QStringList KACLListView::allowedGroups(bool defaults, KACLListViewItem *allowedItem) |
655 | { |
656 | if (m_allGroups.isEmpty()) { |
657 | struct group *gr = nullptr; |
658 | setgrent(); |
659 | while ((gr = getgrent()) != nullptr) { |
660 | m_allGroups << QString::fromLatin1(gr->gr_name); |
661 | } |
662 | endgrent(); |
663 | m_allGroups.sort(); |
664 | } |
665 | |
666 | QStringList allowedGroups = m_allGroups; |
667 | QTreeWidgetItemIterator it(this); |
668 | while (*it) { |
669 | const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it); |
670 | ++it; |
671 | if (item->type != NamedGroup || item->isDefault != defaults) { |
672 | continue; |
673 | } |
674 | if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { |
675 | continue; |
676 | } |
677 | allowedGroups.removeAll(item->qualifier); |
678 | } |
679 | return allowedGroups; |
680 | } |
681 | |
682 | void KACLListView::fillItemsFromACL(const KACL &pACL, bool defaults) |
683 | { |
684 | // clear out old entries of that ilk |
685 | QTreeWidgetItemIterator it(this); |
686 | while (KACLListViewItem *item = static_cast<KACLListViewItem *>(*it)) { |
687 | ++it; |
688 | if (item->isDefault == defaults) { |
689 | delete item; |
690 | } |
691 | } |
692 | |
693 | new KACLListViewItem(this, User, pACL.ownerPermissions(), defaults); |
694 | |
695 | new KACLListViewItem(this, Group, pACL.owningGroupPermissions(), defaults); |
696 | |
697 | new KACLListViewItem(this, Others, pACL.othersPermissions(), defaults); |
698 | |
699 | bool hasMask = false; |
700 | unsigned short mask = pACL.maskPermissions(hasMask); |
701 | if (hasMask) { |
702 | new KACLListViewItem(this, Mask, mask, defaults); |
703 | } |
704 | |
705 | // read all named user entries |
706 | const ACLUserPermissionsList &userList = pACL.allUserPermissions(); |
707 | ACLUserPermissionsConstIterator itu = userList.begin(); |
708 | while (itu != userList.end()) { |
709 | new KACLListViewItem(this, NamedUser, (*itu).second, defaults, (*itu).first); |
710 | ++itu; |
711 | } |
712 | |
713 | // and now all named groups |
714 | const ACLUserPermissionsList &groupList = pACL.allGroupPermissions(); |
715 | ACLUserPermissionsConstIterator itg = groupList.begin(); |
716 | while (itg != groupList.end()) { |
717 | new KACLListViewItem(this, NamedGroup, (*itg).second, defaults, (*itg).first); |
718 | ++itg; |
719 | } |
720 | } |
721 | |
722 | void KACLListView::setACL(const KACL &acl) |
723 | { |
724 | if (!acl.isValid()) { |
725 | return; |
726 | } |
727 | // Remove any entries left over from displaying a previous ACL |
728 | m_ACL = acl; |
729 | fillItemsFromACL(m_ACL); |
730 | |
731 | m_mask = acl.maskPermissions(m_hasMask); |
732 | calculateEffectiveRights(); |
733 | } |
734 | |
735 | void KACLListView::setDefaultACL(const KACL &acl) |
736 | { |
737 | if (!acl.isValid()) { |
738 | return; |
739 | } |
740 | m_defaultACL = acl; |
741 | fillItemsFromACL(m_defaultACL, true); |
742 | calculateEffectiveRights(); |
743 | } |
744 | |
745 | KACL KACLListView::itemsToACL(bool defaults) const |
746 | { |
747 | KACL newACL(0); |
748 | bool atLeastOneEntry = false; |
749 | ACLUserPermissionsList users; |
750 | ACLGroupPermissionsList groups; |
751 | QTreeWidgetItemIterator it(const_cast<KACLListView *>(this)); |
752 | while (QTreeWidgetItem *qlvi = *it) { |
753 | ++it; |
754 | const KACLListViewItem *item = static_cast<KACLListViewItem *>(qlvi); |
755 | if (item->isDefault != defaults) { |
756 | continue; |
757 | } |
758 | atLeastOneEntry = true; |
759 | switch (item->type) { |
760 | case User: |
761 | newACL.setOwnerPermissions(item->value); |
762 | break; |
763 | case Group: |
764 | newACL.setOwningGroupPermissions(item->value); |
765 | break; |
766 | case Others: |
767 | newACL.setOthersPermissions(item->value); |
768 | break; |
769 | case Mask: |
770 | newACL.setMaskPermissions(item->value); |
771 | break; |
772 | case NamedUser: |
773 | users.append(qMakePair(item->text(1), item->value)); |
774 | break; |
775 | case NamedGroup: |
776 | groups.append(qMakePair(item->text(1), item->value)); |
777 | break; |
778 | default: |
779 | break; |
780 | } |
781 | } |
782 | if (atLeastOneEntry) { |
783 | newACL.setAllUserPermissions(users); |
784 | newACL.setAllGroupPermissions(groups); |
785 | if (newACL.isValid()) { |
786 | return newACL; |
787 | } |
788 | } |
789 | return KACL(); |
790 | } |
791 | |
792 | KACL KACLListView::getACL() |
793 | { |
794 | return itemsToACL(false); |
795 | } |
796 | |
797 | KACL KACLListView::getDefaultACL() |
798 | { |
799 | return itemsToACL(true); |
800 | } |
801 | |
802 | void KACLListView::contentsMousePressEvent(QMouseEvent * /*e*/) |
803 | { |
804 | /* |
805 | QTreeWidgetItem *clickedItem = itemAt( e->pos() ); |
806 | if ( !clickedItem ) return; |
807 | // if the click is on an as yet unselected item, select it first |
808 | if ( !clickedItem->isSelected() ) |
809 | QAbstractItemView::contentsMousePressEvent( e ); |
810 | |
811 | if ( !currentItem() ) return; |
812 | int column = header()->sectionAt( e->x() ); |
813 | acl_perm_t perm; |
814 | switch ( column ) |
815 | { |
816 | case 2: |
817 | perm = ACL_READ; |
818 | break; |
819 | case 3: |
820 | perm = ACL_WRITE; |
821 | break; |
822 | case 4: |
823 | perm = ACL_EXECUTE; |
824 | break; |
825 | default: |
826 | return QTreeWidget::contentsMousePressEvent( e ); |
827 | } |
828 | KACLListViewItem* referenceItem = static_cast<KACLListViewItem*>( clickedItem ); |
829 | unsigned short referenceHadItSet = referenceItem->value & perm; |
830 | QTreeWidgetItemIterator it( this ); |
831 | while ( KACLListViewItem* item = static_cast<KACLListViewItem*>( *it ) ) { |
832 | ++it; |
833 | if ( !item->isSelected() ) continue; |
834 | // toggle those with the same value as the clicked item, leave the others |
835 | if ( referenceHadItSet == ( item->value & perm ) ) |
836 | item->togglePerm( perm ); |
837 | } |
838 | */ |
839 | } |
840 | |
841 | void KACLListView::slotItemClicked(QTreeWidgetItem *pItem, int col) |
842 | { |
843 | if (!pItem) { |
844 | return; |
845 | } |
846 | |
847 | QTreeWidgetItemIterator it(this); |
848 | while (KACLListViewItem *item = static_cast<KACLListViewItem *>(*it)) { |
849 | ++it; |
850 | if (!item->isSelected()) { |
851 | continue; |
852 | } |
853 | switch (col) { |
854 | case 2: |
855 | item->togglePerm(ACL_READ); |
856 | break; |
857 | case 3: |
858 | item->togglePerm(ACL_WRITE); |
859 | break; |
860 | case 4: |
861 | item->togglePerm(ACL_EXECUTE); |
862 | break; |
863 | |
864 | default:; // Do nothing |
865 | } |
866 | } |
867 | /* |
868 | // Has the user changed one of the required entries in a default ACL? |
869 | if ( m_pACL->aclType() == ACL_TYPE_DEFAULT && |
870 | ( col == 2 || col == 3 || col == 4 ) && |
871 | ( pACLItem->entryType() == ACL_USER_OBJ || |
872 | pACLItem->entryType() == ACL_GROUP_OBJ || |
873 | pACLItem->entryType() == ACL_OTHER ) ) |
874 | { |
875 | // Mark the required entries as no longer being partial entries. |
876 | // That is, they will get applied to all selected directories. |
877 | KACLListViewItem* pUserObj = findACLEntryByType( this, ACL_USER_OBJ ); |
878 | pUserObj->entry()->setPartialEntry( false ); |
879 | |
880 | KACLListViewItem* pGroupObj = findACLEntryByType( this, ACL_GROUP_OBJ ); |
881 | pGroupObj->entry()->setPartialEntry( false ); |
882 | |
883 | KACLListViewItem* pOther = findACLEntryByType( this, ACL_OTHER ); |
884 | pOther->entry()->setPartialEntry( false ); |
885 | |
886 | update(); |
887 | } |
888 | */ |
889 | } |
890 | |
891 | void KACLListView::slotItemDoubleClicked(QTreeWidgetItem *item, int column) |
892 | { |
893 | if (!item) { |
894 | return; |
895 | } |
896 | |
897 | // avoid conflict with clicking to toggle permission |
898 | if (column >= 2 && column <= 4) { |
899 | return; |
900 | } |
901 | |
902 | KACLListViewItem *aclListItem = static_cast<KACLListViewItem *>(item); |
903 | if (!aclListItem->isAllowedToChangeType()) { |
904 | return; |
905 | } |
906 | |
907 | setCurrentItem(item); |
908 | slotEditEntry(); |
909 | } |
910 | |
911 | void KACLListView::calculateEffectiveRights() |
912 | { |
913 | QTreeWidgetItemIterator it(this); |
914 | KACLListViewItem *pItem; |
915 | while ((pItem = dynamic_cast<KACLListViewItem *>(*it)) != nullptr) { |
916 | ++it; |
917 | pItem->calcEffectiveRights(); |
918 | } |
919 | } |
920 | |
921 | unsigned short KACLListView::maskPermissions() const |
922 | { |
923 | return m_mask; |
924 | } |
925 | |
926 | void KACLListView::setMaskPermissions(unsigned short maskPerms) |
927 | { |
928 | m_mask = maskPerms; |
929 | calculateEffectiveRights(); |
930 | } |
931 | |
932 | acl_perm_t KACLListView::maskPartialPermissions() const |
933 | { |
934 | // return m_pMaskEntry->m_partialPerms; |
935 | return 0; |
936 | } |
937 | |
938 | void KACLListView::setMaskPartialPermissions(acl_perm_t /*maskPartialPerms*/) |
939 | { |
940 | // m_pMaskEntry->m_partialPerms = maskPartialPerms; |
941 | calculateEffectiveRights(); |
942 | } |
943 | |
944 | bool KACLListView::hasDefaultEntries() const |
945 | { |
946 | QTreeWidgetItemIterator it(const_cast<KACLListView *>(this)); |
947 | while (*it) { |
948 | const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it); |
949 | ++it; |
950 | if (item->isDefault) { |
951 | return true; |
952 | } |
953 | } |
954 | return false; |
955 | } |
956 | |
957 | const KACLListViewItem *KACLListView::findDefaultItemByType(EntryType type) const |
958 | { |
959 | return findItemByType(type, true); |
960 | } |
961 | |
962 | const KACLListViewItem *KACLListView::findItemByType(EntryType type, bool defaults) const |
963 | { |
964 | QTreeWidgetItemIterator it(const_cast<KACLListView *>(this)); |
965 | while (*it) { |
966 | const KACLListViewItem *item = static_cast<const KACLListViewItem *>(*it); |
967 | ++it; |
968 | if (item->isDefault == defaults && item->type == type) { |
969 | return item; |
970 | } |
971 | } |
972 | return nullptr; |
973 | } |
974 | |
975 | unsigned short KACLListView::calculateMaskValue(bool defaults) const |
976 | { |
977 | // KACL auto-adds the relevant mask entries, so we can simply query |
978 | bool dummy; |
979 | return itemsToACL(defaults).maskPermissions(dummy); |
980 | } |
981 | |
982 | void KACLListView::slotAddEntry() |
983 | { |
984 | int allowedTypes = NamedUser | NamedGroup; |
985 | if (!m_hasMask) { |
986 | allowedTypes |= Mask; |
987 | } |
988 | int allowedDefaultTypes = NamedUser | NamedGroup; |
989 | if (!findDefaultItemByType(Mask)) { |
990 | allowedDefaultTypes |= Mask; |
991 | } |
992 | if (!hasDefaultEntries()) { |
993 | allowedDefaultTypes |= User | Group; |
994 | } |
995 | EditACLEntryDialog dlg(this, |
996 | nullptr, |
997 | allowedUsers(false), |
998 | allowedGroups(false), |
999 | allowedUsers(true), |
1000 | allowedGroups(true), |
1001 | allowedTypes, |
1002 | allowedDefaultTypes, |
1003 | m_allowDefaults); |
1004 | dlg.exec(); |
1005 | KACLListViewItem *item = dlg.item(); |
1006 | if (!item) { |
1007 | return; // canceled |
1008 | } |
1009 | if (item->type == Mask && !item->isDefault) { |
1010 | m_hasMask = true; |
1011 | m_mask = item->value; |
1012 | } |
1013 | if (item->isDefault && !hasDefaultEntries()) { |
1014 | // first default entry, fill in what is needed |
1015 | if (item->type != User) { |
1016 | unsigned short v = findDefaultItemByType(User)->value; |
1017 | new KACLListViewItem(this, User, v, true); |
1018 | } |
1019 | if (item->type != Group) { |
1020 | unsigned short v = findDefaultItemByType(Group)->value; |
1021 | new KACLListViewItem(this, Group, v, true); |
1022 | } |
1023 | if (item->type != Others) { |
1024 | unsigned short v = findDefaultItemByType(Others)->value; |
1025 | new KACLListViewItem(this, Others, v, true); |
1026 | } |
1027 | } |
1028 | const KACLListViewItem *defaultMaskItem = findDefaultItemByType(Mask); |
1029 | if (item->isDefault && !defaultMaskItem) { |
1030 | unsigned short v = calculateMaskValue(true); |
1031 | new KACLListViewItem(this, Mask, v, true); |
1032 | } |
1033 | if (!item->isDefault && !m_hasMask && (item->type == Group || item->type == NamedUser || item->type == NamedGroup)) { |
1034 | // auto-add a mask entry |
1035 | unsigned short v = calculateMaskValue(false); |
1036 | new KACLListViewItem(this, Mask, v, false); |
1037 | m_hasMask = true; |
1038 | m_mask = v; |
1039 | } |
1040 | calculateEffectiveRights(); |
1041 | sortItems(sortColumn(), Qt::AscendingOrder); |
1042 | setCurrentItem(item); |
1043 | // QTreeWidget doesn't seem to emit, in this case, and we need to update |
1044 | // the buttons... |
1045 | if (topLevelItemCount() == 1) { |
1046 | Q_EMIT currentItemChanged(item, item); |
1047 | } |
1048 | } |
1049 | |
1050 | void KACLListView::slotEditEntry() |
1051 | { |
1052 | QTreeWidgetItem *current = currentItem(); |
1053 | if (!current) { |
1054 | return; |
1055 | } |
1056 | KACLListViewItem *item = static_cast<KACLListViewItem *>(current); |
1057 | int allowedTypes = item->type | NamedUser | NamedGroup; |
1058 | bool itemWasMask = item->type == Mask; |
1059 | if (!m_hasMask || itemWasMask) { |
1060 | allowedTypes |= Mask; |
1061 | } |
1062 | int allowedDefaultTypes = item->type | NamedUser | NamedGroup; |
1063 | if (!findDefaultItemByType(Mask)) { |
1064 | allowedDefaultTypes |= Mask; |
1065 | } |
1066 | if (!hasDefaultEntries()) { |
1067 | allowedDefaultTypes |= User | Group; |
1068 | } |
1069 | |
1070 | EditACLEntryDialog dlg(this, |
1071 | item, |
1072 | allowedUsers(false, item), |
1073 | allowedGroups(false, item), |
1074 | allowedUsers(true, item), |
1075 | allowedGroups(true, item), |
1076 | allowedTypes, |
1077 | allowedDefaultTypes, |
1078 | m_allowDefaults); |
1079 | dlg.exec(); |
1080 | if (itemWasMask && item->type != Mask) { |
1081 | m_hasMask = false; |
1082 | m_mask = 0; |
1083 | } |
1084 | if (!itemWasMask && item->type == Mask) { |
1085 | m_mask = item->value; |
1086 | m_hasMask = true; |
1087 | } |
1088 | calculateEffectiveRights(); |
1089 | sortItems(sortColumn(), Qt::AscendingOrder); |
1090 | } |
1091 | |
1092 | void KACLListView::slotRemoveEntry() |
1093 | { |
1094 | QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); |
1095 | while (*it) { |
1096 | KACLListViewItem *item = static_cast<KACLListViewItem *>(*it); |
1097 | ++it; |
1098 | /* First check if it's a mask entry and if so, make sure that there is |
1099 | * either no name user or group entry, which means the mask can be |
1100 | * removed, or don't remove it, but reset it. That is allowed. */ |
1101 | if (item->type == Mask) { |
1102 | bool itemWasDefault = item->isDefault; |
1103 | if (!itemWasDefault && maskCanBeDeleted()) { |
1104 | m_hasMask = false; |
1105 | m_mask = 0; |
1106 | delete item; |
1107 | } else if (itemWasDefault && defaultMaskCanBeDeleted()) { |
1108 | delete item; |
1109 | } else { |
1110 | item->value = 0; |
1111 | item->repaint(); |
1112 | } |
1113 | if (!itemWasDefault) { |
1114 | calculateEffectiveRights(); |
1115 | } |
1116 | } else { |
1117 | // for the base permissions, disable them, which is what libacl does |
1118 | if (!item->isDefault && (item->type == User || item->type == Group || item->type == Others)) { |
1119 | item->value = 0; |
1120 | item->repaint(); |
1121 | } else { |
1122 | delete item; |
1123 | } |
1124 | } |
1125 | } |
1126 | } |
1127 | |
1128 | bool KACLListView::maskCanBeDeleted() const |
1129 | { |
1130 | return !findItemByType(NamedUser) && !findItemByType(NamedGroup); |
1131 | } |
1132 | |
1133 | bool KACLListView::defaultMaskCanBeDeleted() const |
1134 | { |
1135 | return !findDefaultItemByType(NamedUser) && !findDefaultItemByType(NamedGroup); |
1136 | } |
1137 | |
1138 | #include "moc_kacleditwidget.cpp" |
1139 | #include "moc_kacleditwidget_p.cpp" |
1140 | #endif |
1141 | |