You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
17 KiB
482 lines
17 KiB
// -*- mode: C++; c-file-style: "gnu" -*- |
|
/** |
|
* |
|
* Copyright (c) 2004 David Faure <faure@kde.org> |
|
* |
|
* |
|
* 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 <khbox.h> |
|
#include <QStackedWidget> |
|
#include "messageviewer/autoqpointer.h" |
|
#include "imapaclattribute.h" |
|
#include "util.h" |
|
#include "imapsettings.h" |
|
|
|
#include <akonadi/collection.h> |
|
#include <akonadi/collectionmodifyjob.h> |
|
|
|
#include <akonadi/contact/contactgroupexpandjob.h> |
|
#include <akonadi/contact/contactgroupsearchjob.h> |
|
#include <akonadi/contact/emailaddressselectiondialog.h> |
|
#include <kabc/addresseelist.h> |
|
#include <kio/jobuidelegate.h> |
|
#include <kpushbutton.h> |
|
#include <kdebug.h> |
|
#include <klocale.h> |
|
#include <kconfiggroup.h> |
|
#include <KLineEdit> |
|
#include <kpimutils/email.h> |
|
|
|
#include <QDBusReply> |
|
#include <QLabel> |
|
#include <QRadioButton> |
|
#include <QGridLayout> |
|
#include <QVBoxLayout> |
|
#include <QButtonGroup> |
|
#include <QGroupBox> |
|
#include <QTreeWidget> |
|
|
|
#include <assert.h> |
|
#include <kmessagebox.h> |
|
#include <kvbox.h> |
|
|
|
// 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<Akonadi::EmailAddressSelectionDialog> 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<KIMAP::Acl::Rights>(-1); // hm ? |
|
return static_cast<KIMAP::Acl::Rights>( 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<Akonadi::ImapAclAttribute>(); |
|
} |
|
|
|
void CollectionAclPage::load(const Akonadi::Collection & col) |
|
{ |
|
Akonadi::ImapAclAttribute *acls = col.attribute<Akonadi::ImapAclAttribute>(); |
|
QMap<QByteArray, KIMAP::Acl::Rights> 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<QString> 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<Akonadi::ImapAclAttribute>(); |
|
QMap<QByteArray, KIMAP::Acl::Rights> rights; |
|
|
|
QTreeWidgetItemIterator it( mListView ); |
|
while ( QTreeWidgetItem* item = *it ) { |
|
ListViewItem* ACLitem = static_cast<ListViewItem *>( 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<ListViewItem *>( item ); |
|
if ( mImapUserName == ACLitem->userId() && ( ACLitem->permissions() & KIMAP::Acl::Admin ) ) |
|
canAdmin = false; |
|
} |
|
if ( !canAdmin ) return; |
|
|
|
ListViewItem* ACLitem = static_cast<ListViewItem *>( mListView->currentItem() ); |
|
MessageViewer::AutoQPointer<ACLEntryDialog> 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<ACLEntryDialog> 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<ListViewItem *>( 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<ListViewItem *>( 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"
|
|
|