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 | 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 | |
496 | void 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 | |
511 | void 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 | } |
537 | void 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 | |
565 | void 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 | |
587 | KACLListView::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 | |
614 | KACLListView::~KACLListView() |
615 | { |
616 | } |
617 | |
618 | QSize 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 | |
625 | QStringList 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 | |
653 | QStringList 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 | |
681 | void 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 | |
721 | void 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 | |
734 | void 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 | |
744 | KACL 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 | |
791 | KACL KACLListView::getACL() |
792 | { |
793 | return itemsToACL(false); |
794 | } |
795 | |
796 | KACL KACLListView::getDefaultACL() |
797 | { |
798 | return itemsToACL(true); |
799 | } |
800 | |
801 | void 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 | |
840 | void 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 | |
890 | void 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 | |
910 | void 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 | |
920 | unsigned short KACLListView::maskPermissions() const |
921 | { |
922 | return m_mask; |
923 | } |
924 | |
925 | void KACLListView::setMaskPermissions(unsigned short maskPerms) |
926 | { |
927 | m_mask = maskPerms; |
928 | calculateEffectiveRights(); |
929 | } |
930 | |
931 | acl_perm_t KACLListView::maskPartialPermissions() const |
932 | { |
933 | // return m_pMaskEntry->m_partialPerms; |
934 | return 0; |
935 | } |
936 | |
937 | void KACLListView::setMaskPartialPermissions(acl_perm_t /*maskPartialPerms*/) |
938 | { |
939 | // m_pMaskEntry->m_partialPerms = maskPartialPerms; |
940 | calculateEffectiveRights(); |
941 | } |
942 | |
943 | bool 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 | |
956 | const KACLListViewItem *KACLListView::findDefaultItemByType(EntryType type) const |
957 | { |
958 | return findItemByType(type, true); |
959 | } |
960 | |
961 | const 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 | |
974 | unsigned 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 | |
981 | void 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 | |
1049 | void 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 | |
1091 | void 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 | |
1127 | bool KACLListView::maskCanBeDeleted() const |
1128 | { |
1129 | return !findItemByType(NamedUser) && !findItemByType(NamedGroup); |
1130 | } |
1131 | |
1132 | bool 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 | |