// -*- mode: C++; c-file-style: "gnu" -*- /** * * Copyright (c) 2004 David Faure * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "collectionaclpage.h" #include #include #include "messageviewer/autoqpointer.h" #include "imapaclattribute.h" #include "util.h" #include "imapsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // The set of standard permission sets static const struct { KIMAP::Acl::Rights permissions; const char* userString; } standardPermissions[] = { { KIMAP::Acl::None, I18N_NOOP2( "Permissions", "None" ) }, { KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen, I18N_NOOP2( "Permissions", "Read" ) }, { KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen | KIMAP::Acl::Insert | KIMAP::Acl::Post, I18N_NOOP2( "Permissions", "Append" ) }, { KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen | KIMAP::Acl::Insert | KIMAP::Acl::Post | KIMAP::Acl::Write | KIMAP::Acl::Create | KIMAP::Acl::Delete, I18N_NOOP2( "Permissions", "Write" ) }, { KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen | KIMAP::Acl::Insert | KIMAP::Acl::Post | KIMAP::Acl::Write | KIMAP::Acl::Create | KIMAP::Acl::Delete | KIMAP::Acl::Admin, I18N_NOOP2( "Permissions", "All" ) } }; ACLEntryDialog::ACLEntryDialog( const QString& caption, QWidget* parent ) : KDialog( parent ) { setCaption( caption ); setButtons( Ok | Cancel ); QWidget *page = new QWidget( this ); setMainWidget(page); QGridLayout *topLayout = new QGridLayout( page ); topLayout->setSpacing( spacingHint() ); topLayout->setMargin( 0 ); QLabel *label = new QLabel( i18n( "&User identifier:" ), page ); topLayout->addWidget( label, 0, 0 ); mUserIdLineEdit = new KLineEdit( page ); topLayout->addWidget( mUserIdLineEdit, 0, 1 ); label->setBuddy( mUserIdLineEdit ); mUserIdLineEdit->setWhatsThis( i18n( "The User Identifier is the login of the user on the IMAP server. This can be a simple user name or the full email address of the user; the login for your own account on the server will tell you which one it is." ) ); QPushButton* kabBtn = new QPushButton( i18n( "Se&lect..." ), page ); topLayout->addWidget( kabBtn, 0, 2 ); QGroupBox* groupBox = new QGroupBox( i18n( "Permissions" ), page ); QVBoxLayout *vbox = new QVBoxLayout( groupBox ); mButtonGroup = new QButtonGroup( groupBox ); for ( unsigned int i = 0; i < sizeof( standardPermissions ) / sizeof( *standardPermissions ); ++i ) { QRadioButton* cb = new QRadioButton( i18nc( "Permissions", standardPermissions[i].userString ), groupBox ); vbox->addWidget( cb ); // We store the permission value (bitfield) as the id of the radiobutton in the group mButtonGroup->addButton( cb, standardPermissions[i].permissions ); } vbox->addStretch( 1 ); topLayout->addWidget( groupBox, 1, 0, 1, 3 ); topLayout->setRowStretch(2, 10); connect( mUserIdLineEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotChanged() ) ); connect( kabBtn, SIGNAL( clicked() ), SLOT( slotSelectAddresses() ) ); connect( mButtonGroup, SIGNAL( buttonClicked( int ) ), SLOT( slotChanged() ) ); enableButtonOk( false ); mUserIdLineEdit->setFocus(); // Ensure the lineedit is rather wide so that email addresses can be read in it incrementInitialSize( QSize( 200, 0 ) ); } void ACLEntryDialog::slotChanged() { enableButtonOk( !mUserIdLineEdit->text().isEmpty() && mButtonGroup->checkedButton() != 0 ); } void ACLEntryDialog::slotSelectAddresses() { MessageViewer::AutoQPointer dlg( new Akonadi::EmailAddressSelectionDialog( this ) ); dlg->view()->view()->setSelectionMode( QAbstractItemView::MultiSelection ); if ( dlg->exec() != QDialog::Accepted || !dlg ) return; QStringList addresses; foreach ( const Akonadi::EmailAddressSelection &selection, dlg->selectedAddresses() ) addresses << selection.quotedEmail(); if ( !mUserIdLineEdit->text().isEmpty() ) addresses.prepend( mUserIdLineEdit->text() ); mUserIdLineEdit->setText( addresses.join( ", " ) ); } void ACLEntryDialog::setValues( const QString& userId, KIMAP::Acl::Rights permissions ) { mUserIdLineEdit->setText( userId ); QAbstractButton* button = mButtonGroup->button( permissions ); if ( button ) button->setChecked( true ); enableButtonOk( !userId.isEmpty() ); } QString ACLEntryDialog::userId() const { return mUserIdLineEdit->text(); } QStringList ACLEntryDialog::userIds() const { return KPIMUtils::splitAddressList( mUserIdLineEdit->text() ); } KIMAP::Acl::Rights ACLEntryDialog::permissions() const { QAbstractButton* button = mButtonGroup->checkedButton(); if( !button ) return static_cast(-1); // hm ? return static_cast( mButtonGroup->id( button ) ); } class CollectionAclPage::ListViewItem : public QTreeWidgetItem { public: ListViewItem( QTreeWidget* listview ) : QTreeWidgetItem( listview ), mModified( false ), mNew( false ) {} void load( const QByteArray &id, KIMAP::Acl::Rights rights ); QString userId() const { return text( 0 ); } void setUserId( const QString& userId ) { setText( 0, userId ); } KIMAP::Acl::Rights permissions() const { return mPermissions; } void setPermissions( KIMAP::Acl::Rights permissions ); bool isModified() const { return mModified; } void setModified( bool b ) { mModified = b; } // The fact that an item is new doesn't matter much. // This bool is only used to handle deletion differently bool isNew() const { return mNew; } void setNew( bool b ) { mNew = b; } private: KIMAP::Acl::Rights mPermissions; QString mInternalRightsList; ///< protocol-dependent string (e.g. IMAP rights list) bool mModified; bool mNew; }; // internalRightsList is only used if permissions doesn't match the standard set static QString permissionsToUserString( int permissions, const QString& internalRightsList ) { for ( int i = 0; i < sizeof( standardPermissions ) / sizeof( *standardPermissions ); ++i ) { if ( permissions == standardPermissions[i].permissions ) return i18nc( "Permissions", standardPermissions[i].userString ); } if ( internalRightsList.isEmpty() ) return i18n( "Custom Permissions" ); // not very helpful, but should not happen else return i18n( "Custom Permissions (%1)", internalRightsList ); } void CollectionAclPage::ListViewItem::setPermissions( KIMAP::Acl::Rights permissions ) { mPermissions = permissions; setText( 1, permissionsToUserString( permissions, QString() ) ); } void CollectionAclPage::ListViewItem::load( const QByteArray &id, KIMAP::Acl::Rights rights ) { // Don't allow spaces in userids. If you need this, fix the slave->app communication, // since it uses space as a separator (imap4.cc, look for GETACL) // It's ok in distribution list names though, that's why this check is only done here // and also why there's no validator on the lineedit. if ( id.contains( ' ' ) ) { kWarning() << "Userid contains a space:" << id; } setUserId( id ); mPermissions = rights; mInternalRightsList = KIMAP::Acl::rightsToString( rights ); setText( 1, permissionsToUserString( mPermissions, mInternalRightsList ) ); mModified = true; // for dimap, so that earlier changes are still marked as changes } //// CollectionAclPage::CollectionAclPage( QWidget* parent ) : CollectionPropertiesPage( parent ), mUserRights( KIMAP::Acl::None ), mChanged( false ) { setPageTitle( i18n("Access Control") ); init(); } void CollectionAclPage::init() { QVBoxLayout* topLayout = new QVBoxLayout( this ); // We need a widget stack to show either a label ("no acl support", "please wait"...) // or a listview. mStack = new QStackedWidget( this ); topLayout->addWidget( mStack ); mLabel = new QLabel( mStack ); mLabel->setAlignment( Qt::AlignHCenter | Qt::AlignVCenter ); mLabel->setWordWrap( true ); mStack->addWidget( mLabel ); mACLWidget = new KHBox( mStack ); mACLWidget->setSpacing( KDialog::spacingHint() ); mListView = new QTreeWidget( mACLWidget ); QStringList headerNames; headerNames << i18n("User Id") << i18n("Permissions"); mListView->setHeaderItem( new QTreeWidgetItem( headerNames ) ); mListView->setAllColumnsShowFocus( true ); mListView->setAlternatingRowColors( true ); mListView->setSortingEnabled( false ); mListView->setRootIsDecorated( false ); mStack->addWidget( mACLWidget ); connect( mListView, SIGNAL( itemActivated( QTreeWidgetItem*, int ) ), SLOT( slotEditACL( QTreeWidgetItem* ) ) ); connect( mListView, SIGNAL( itemSelectionChanged() ), SLOT( slotSelectionChanged() ) ); KVBox* buttonBox = new KVBox( mACLWidget ); buttonBox->setSpacing( KDialog::spacingHint() ); mAddACL = new KPushButton( i18n( "Add Entry..." ), buttonBox ); mEditACL = new KPushButton( i18n( "Modify Entry..." ), buttonBox ); mRemoveACL = new KPushButton( i18n( "Remove Entry" ), buttonBox ); QWidget *spacer = new QWidget( buttonBox ); spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding ); connect( mAddACL, SIGNAL( clicked() ), SLOT( slotAddACL() ) ); connect( mEditACL, SIGNAL( clicked() ), SLOT( slotEditACL() ) ); connect( mRemoveACL, SIGNAL( clicked() ), SLOT( slotRemoveACL() ) ); mEditACL->setEnabled( false ); mRemoveACL->setEnabled( false ); mStack->setCurrentWidget( mACLWidget ); } bool CollectionAclPage::canHandle( const Akonadi::Collection &collection ) const { return collection.hasAttribute(); } void CollectionAclPage::load(const Akonadi::Collection & col) { Akonadi::ImapAclAttribute *acls = col.attribute(); QMap rights = acls->rights(); mListView->clear(); foreach ( const QByteArray &id, rights.keys() ) { ListViewItem* item = new ListViewItem( mListView ); item->load( id, rights[id] ); if ( !col.isValid() ) // new collection? everything is new then item->setModified( true ); } OrgKdeAkonadiImapSettingsInterface *imapSettingsInterface = KMail::Util::createImapSettingsInterface( col.resource() ); if ( imapSettingsInterface->isValid() ) { QDBusReply reply = imapSettingsInterface->userName(); if ( reply.isValid() ) { mImapUserName = reply; } } delete imapSettingsInterface; mUserRights = rights[mImapUserName.toUtf8()]; mStack->setCurrentWidget( mACLWidget ); slotSelectionChanged(); } void CollectionAclPage::save(Akonadi::Collection & col) { if ( !mChanged ) { return; } Akonadi::ImapAclAttribute *acls = col.attribute(); QMap rights; QTreeWidgetItemIterator it( mListView ); while ( QTreeWidgetItem* item = *it ) { ListViewItem* ACLitem = static_cast( item ); // we can use job->exec() here, it is not a hot path Akonadi::ContactGroupSearchJob *searchJob = new Akonadi::ContactGroupSearchJob( this ); searchJob->setQuery( Akonadi::ContactGroupSearchJob::Name, ACLitem->userId() ); searchJob->setLimit( 1 ); if ( !searchJob->exec() ) { ++it; continue; } if ( !searchJob->contactGroups().isEmpty() ) { // it has been a distribution list Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob( searchJob->contactGroups().first(), this ); if ( expandJob->exec() ) { foreach ( const KABC::Addressee &contact, expandJob->contacts() ) { const QByteArray rawEmail = KPIMUtils::extractEmailAddress( contact.preferredEmail().toUtf8() ); if ( !rawEmail.isEmpty() ) rights[ rawEmail ] = ACLitem->permissions(); } } } else { // it has been a normal contact const QByteArray rawEmail = KPIMUtils::extractEmailAddress( ACLitem->userId().toUtf8() ); if ( !rawEmail.isEmpty() ) rights[ rawEmail ] = ACLitem->permissions(); } ++it; } acls->setRights( rights ); new Akonadi::CollectionModifyJob( col, this ); } void CollectionAclPage::slotEditACL(QTreeWidgetItem* item) { if ( !item ) return; bool canAdmin = ( mUserRights & KIMAP::Acl::Admin ); // Same logic as in slotSelectionChanged, but this is also needed for double-click IIRC if ( canAdmin && item ) { // Don't allow users to remove their own admin permissions - there's no way back ListViewItem* ACLitem = static_cast( item ); if ( mImapUserName == ACLitem->userId() && ( ACLitem->permissions() & KIMAP::Acl::Admin ) ) canAdmin = false; } if ( !canAdmin ) return; ListViewItem* ACLitem = static_cast( mListView->currentItem() ); MessageViewer::AutoQPointer dlg( new ACLEntryDialog( i18n( "Modify Permissions" ), this ) ); dlg->setValues( ACLitem->userId(), ACLitem->permissions() ); if ( dlg->exec() == QDialog::Accepted && dlg ) { QStringList userIds = dlg->userIds(); Q_ASSERT( !userIds.isEmpty() ); // impossible, the OK button is disabled in that case ACLitem->setUserId( dlg->userIds().front() ); ACLitem->setPermissions( dlg->permissions() ); ACLitem->setModified( true ); mChanged = true; if ( userIds.count() > 1 ) { // more emails were added, append them userIds.pop_front(); addACLs( userIds, dlg->permissions() ); } } } void CollectionAclPage::slotEditACL() { slotEditACL( mListView->currentItem() ); } void CollectionAclPage::addACLs( const QStringList& userIds, KIMAP::Acl::Rights permissions ) { for( QStringList::const_iterator it = userIds.constBegin(); it != userIds.constEnd(); ++it ) { ListViewItem* ACLitem = new ListViewItem( mListView ); ACLitem->setUserId( *it ); ACLitem->setPermissions( permissions ); ACLitem->setModified( true ); ACLitem->setNew( true ); } } void CollectionAclPage::slotAddACL() { MessageViewer::AutoQPointer dlg( new ACLEntryDialog( i18n( "Add Permissions" ), this ) ); if ( dlg->exec() == QDialog::Accepted && dlg ) { const QStringList userIds = dlg->userIds(); addACLs( dlg->userIds(), dlg->permissions() ); mChanged = true; } } void CollectionAclPage::slotSelectionChanged() { QTreeWidgetItem* item = mListView->currentItem(); bool canAdmin = ( mUserRights & KIMAP::Acl::Admin ); bool canAdminThisItem = canAdmin; if ( canAdmin && item ) { // Don't allow users to remove their own admin permissions - there's no way back ListViewItem* ACLitem = static_cast( item ); if ( mImapUserName == ACLitem->userId() && ( ACLitem->permissions() & KIMAP::Acl::Admin ) ) canAdminThisItem = false; } bool lvVisible = mStack->currentWidget() == mACLWidget; mAddACL->setEnabled( lvVisible && canAdmin ); mEditACL->setEnabled( item && lvVisible && canAdminThisItem ); mRemoveACL->setEnabled( item && lvVisible && canAdminThisItem ); } void CollectionAclPage::slotRemoveACL() { ListViewItem* ACLitem = static_cast( mListView->currentItem() ); if ( !ACLitem ) return; if ( !ACLitem->isNew() ) { if ( mImapUserName == ACLitem->userId() ) { if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( topLevelWidget(), i18n( "Do you really want to remove your own permissions for this folder? You will not be able to access it afterwards." ), i18n( "Remove" ) ) ) return; } mRemovedACLs.append( ACLitem->userId() ); } delete ACLitem; mChanged = true; } #include "collectionaclpage.moc"