1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
4     SPDX-FileCopyrightText: 2007 Olivier Goffart <ogoffart at kde.org>
5     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
6 
7     SPDX-License-Identifier: LGPL-2.0-only
8 */
9 #include "kpassworddialog.h"
10 
11 #include <QCheckBox>
12 #include <QComboBox>
13 #include <QLabel>
14 #include <QLayout>
15 #include <QPushButton>
16 #include <QScreen>
17 #include <QStyleOption>
18 #include <QTimer>
19 
20 #include <ktitlewidget.h>
21 
22 #include "ui_kpassworddialog.h"
23 
24 /** @internal */
25 class KPasswordDialogPrivate
26 {
27 public:
KPasswordDialogPrivate(KPasswordDialog * qq)28     KPasswordDialogPrivate(KPasswordDialog *qq)
29         : q(qq)
30     {
31     }
32 
33     void actuallyAccept();
34     void activated(const QString &userName);
35 
36     void updateFields();
37     void init();
38 
39     KPasswordDialog *const q;
40     Ui_KPasswordDialog ui;
41     QMap<QString, QString> knownLogins;
42     QComboBox *userEditCombo = nullptr;
43     QIcon icon;
44     KPasswordDialog::KPasswordDialogFlags m_flags;
45     unsigned int commentRow = 0;
46 };
47 
KPasswordDialog(QWidget * parent,const KPasswordDialogFlags & flags)48 KPasswordDialog::KPasswordDialog(QWidget *parent, const KPasswordDialogFlags &flags)
49     : QDialog(parent)
50     , d(new KPasswordDialogPrivate(this))
51 {
52     setWindowTitle(tr("Password", "@title:window"));
53     setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"), windowIcon()));
54     d->m_flags = flags;
55     d->init();
56 }
57 
58 KPasswordDialog::~KPasswordDialog() = default;
59 
updateFields()60 void KPasswordDialogPrivate::updateFields()
61 {
62     if (m_flags & KPasswordDialog::UsernameReadOnly) {
63         ui.userEdit->setReadOnly(true);
64         ui.credentialsGroup->setFocusProxy(ui.passEdit);
65     }
66     ui.domainEdit->setReadOnly((m_flags & KPasswordDialog::DomainReadOnly));
67     ui.credentialsGroup->setEnabled(!q->anonymousMode());
68 }
69 
init()70 void KPasswordDialogPrivate::init()
71 {
72     ui.setupUi(q);
73     ui.buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
74     ui.errorMessage->setHidden(true);
75 
76     ui.userEditContextHelpButton->hide();
77     ui.userEditContextHelpButton->setFlat(true);
78     ui.userEditContextHelpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
79     ui.userEditContextHelpButton->setText(QString());
80     const QString description = QApplication::translate("KPasswordDialog", "Show Contextual Help");
81     ui.userEditContextHelpButton->setAccessibleName(description);
82     ui.userEditContextHelpButton->setToolTip(description);
83     QObject::connect(ui.userEditContextHelpButton, &QPushButton::released, q, [this] {
84         QEvent ev(QEvent::WhatsThis);
85         qApp->sendEvent(ui.userEdit, &ev);
86     });
87 
88     // Row 4: Username field
89     if (m_flags & KPasswordDialog::ShowUsernameLine) {
90         ui.userEdit->setFocus();
91         ui.credentialsGroup->setFocusProxy(ui.userEdit);
92         QObject::connect(ui.userEdit, &QLineEdit::returnPressed, ui.passEdit, qOverload<>(&QWidget::setFocus));
93     } else {
94         ui.userNameLabel->hide();
95         ui.userEdit->hide();
96         ui.domainLabel->hide();
97         ui.domainEdit->hide();
98         ui.passEdit->setFocus();
99         ui.credentialsGroup->setFocusProxy(ui.passEdit);
100         ui.prompt->setText(QApplication::translate("KPasswordDialog", "Supply a password below."));
101     }
102 
103     if (!(m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
104         ui.anonymousRadioButton->hide();
105         ui.usePasswordButton->hide();
106     }
107 
108     if (!(m_flags & KPasswordDialog::ShowDomainLine)) {
109         ui.domainLabel->hide();
110         ui.domainEdit->hide();
111     }
112 
113     if (!(m_flags & KPasswordDialog::ShowKeepPassword)) {
114         ui.keepCheckBox->hide();
115     }
116 
117     updateFields();
118 
119     QRect desktop = q->topLevelWidget()->screen()->geometry();
120     q->setMinimumWidth(qMin(1000, qMax(q->sizeHint().width(), desktop.width() / 4)));
121     q->setIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
122 }
123 
setIcon(const QIcon & icon)124 void KPasswordDialog::setIcon(const QIcon &icon)
125 {
126     d->icon = icon;
127 
128     QStyleOption option;
129     option.initFrom(this);
130     const int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this);
131     d->ui.pixmapLabel->setPixmap(icon.pixmap(iconSize));
132 }
133 
icon() const134 QIcon KPasswordDialog::icon() const
135 {
136     return d->icon;
137 }
138 
139 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 63)
setPixmap(const QPixmap & pixmap)140 void KPasswordDialog::setPixmap(const QPixmap &pixmap)
141 {
142     d->ui.pixmapLabel->setPixmap(pixmap);
143 }
144 
pixmap() const145 QPixmap KPasswordDialog::pixmap() const
146 {
147     return d->ui.pixmapLabel->pixmap(Qt::ReturnByValue);
148 }
149 #endif
150 
setUsername(const QString & user)151 void KPasswordDialog::setUsername(const QString &user)
152 {
153     d->ui.userEdit->setText(user);
154     if (user.isEmpty()) {
155         return;
156     }
157 
158     d->activated(user);
159     if (d->ui.userEdit->isVisibleTo(this)) {
160         d->ui.passEdit->setFocus();
161     }
162 }
163 
username() const164 QString KPasswordDialog::username() const
165 {
166     return d->ui.userEdit->text();
167 }
168 
password() const169 QString KPasswordDialog::password() const
170 {
171     return d->ui.passEdit->password();
172 }
173 
setDomain(const QString & domain)174 void KPasswordDialog::setDomain(const QString &domain)
175 {
176     d->ui.domainEdit->setText(domain);
177 }
178 
domain() const179 QString KPasswordDialog::domain() const
180 {
181     return d->ui.domainEdit->text();
182 }
183 
setAnonymousMode(bool anonymous)184 void KPasswordDialog::setAnonymousMode(bool anonymous)
185 {
186     if (anonymous && !(d->m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
187         // This is an error case, but we can at least let user see what's about
188         // to happen if they proceed.
189         d->ui.anonymousRadioButton->setVisible(true);
190 
191         d->ui.usePasswordButton->setVisible(true);
192         d->ui.usePasswordButton->setEnabled(false);
193     }
194 
195     d->ui.anonymousRadioButton->setChecked(anonymous);
196 }
197 
anonymousMode() const198 bool KPasswordDialog::anonymousMode() const
199 {
200     return d->ui.anonymousRadioButton->isChecked();
201 }
202 
setKeepPassword(bool b)203 void KPasswordDialog::setKeepPassword(bool b)
204 {
205     d->ui.keepCheckBox->setChecked(b);
206 }
207 
keepPassword() const208 bool KPasswordDialog::keepPassword() const
209 {
210     return d->ui.keepCheckBox->isChecked();
211 }
212 
addCommentLine(const QString & label,const QString & comment)213 void KPasswordDialog::addCommentLine(const QString &label, const QString &comment)
214 {
215     int gridMarginLeft;
216     int gridMarginTop;
217     int gridMarginRight;
218     int gridMarginBottom;
219     d->ui.formLayout->getContentsMargins(&gridMarginLeft, &gridMarginTop, &gridMarginRight, &gridMarginBottom);
220 
221     int spacing = d->ui.formLayout->horizontalSpacing();
222     if (spacing < 0) {
223         // same inter-column spacing for all rows, see comment in qformlayout.cpp
224         spacing = style()->combinedLayoutSpacing(QSizePolicy::Label, QSizePolicy::LineEdit, Qt::Horizontal, nullptr, this);
225     }
226 
227     QLabel *c = new QLabel(comment, this);
228     c->setWordWrap(true);
229 
230     d->ui.formLayout->insertRow(d->commentRow, label, c);
231     ++d->commentRow;
232 
233     // cycle through column 0 widgets and see the max width so we can set the minimum height of
234     // column 2 wordwrapable labels
235     int firstColumnWidth = 0;
236     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
237         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::LabelRole);
238         if (li) {
239             QWidget *w = li->widget();
240             if (w && !w->isHidden()) {
241                 firstColumnWidth = qMax(firstColumnWidth, w->sizeHint().width());
242             }
243         }
244     }
245     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
246         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::FieldRole);
247         if (li) {
248             QLabel *l = qobject_cast<QLabel *>(li->widget());
249             if (l && l->wordWrap()) {
250                 auto *style = this->style();
251                 const int leftMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
252                 const int rightMargin = style->pixelMetric(QStyle::PM_LayoutRightMargin);
253                 int w = sizeHint().width() - firstColumnWidth - leftMargin - rightMargin - gridMarginLeft - gridMarginRight - spacing;
254                 l->setMinimumSize(w, l->heightForWidth(w));
255             }
256         }
257     }
258 }
259 
showErrorMessage(const QString & message,const ErrorType type)260 void KPasswordDialog::showErrorMessage(const QString &message, const ErrorType type)
261 {
262     d->ui.errorMessage->setText(message, KTitleWidget::ErrorMessage);
263 
264     QFont bold = font();
265     bold.setBold(true);
266     switch (type) {
267     case PasswordError:
268         d->ui.passwordLabel->setFont(bold);
269         d->ui.passEdit->clear();
270         d->ui.passEdit->setFocus();
271         break;
272     case UsernameError:
273         if (d->ui.userEdit->isVisibleTo(this)) {
274             d->ui.userNameLabel->setFont(bold);
275             d->ui.userEdit->setFocus();
276         }
277         break;
278     case DomainError:
279         if (d->ui.domainEdit->isVisibleTo(this)) {
280             d->ui.domainLabel->setFont(bold);
281             d->ui.domainEdit->setFocus();
282         }
283         break;
284     case FatalError:
285         d->ui.userNameLabel->setEnabled(false);
286         d->ui.userEdit->setEnabled(false);
287         d->ui.passwordLabel->setEnabled(false);
288         d->ui.passEdit->setEnabled(false);
289         d->ui.keepCheckBox->setEnabled(false);
290         d->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
291         break;
292     default:
293         break;
294     }
295     adjustSize();
296 }
297 
setPrompt(const QString & prompt)298 void KPasswordDialog::setPrompt(const QString &prompt)
299 {
300     d->ui.prompt->setText(prompt);
301     d->ui.prompt->setWordWrap(true);
302     auto *style = this->style();
303     const int leftMarginHint = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
304     const int rightMarginHint = style->pixelMetric(QStyle::PM_LayoutRightMargin);
305     d->ui.prompt->setMinimumHeight(d->ui.prompt->heightForWidth(width() - leftMarginHint - rightMarginHint));
306 }
307 
prompt() const308 QString KPasswordDialog::prompt() const
309 {
310     return d->ui.prompt->text();
311 }
312 
setPassword(const QString & p)313 void KPasswordDialog::setPassword(const QString &p)
314 {
315     d->ui.passEdit->setPassword(p);
316 }
317 
setUsernameReadOnly(bool readOnly)318 void KPasswordDialog::setUsernameReadOnly(bool readOnly)
319 {
320     d->ui.userEdit->setReadOnly(readOnly);
321 
322     if (readOnly && d->ui.userEdit->hasFocus()) {
323         d->ui.passEdit->setFocus();
324     }
325 }
326 
setKnownLogins(const QMap<QString,QString> & knownLogins)327 void KPasswordDialog::setKnownLogins(const QMap<QString, QString> &knownLogins)
328 {
329     const int nr = knownLogins.count();
330     if (nr == 0) {
331         return;
332     }
333 
334     if (nr == 1) {
335         d->ui.userEdit->setText(knownLogins.begin().key());
336         setPassword(knownLogins.begin().value());
337         return;
338     }
339 
340     Q_ASSERT(!d->ui.userEdit->isReadOnly());
341     if (!d->userEditCombo) {
342         int row = -1;
343         QFormLayout::ItemRole userEditRole = QFormLayout::FieldRole;
344 
345         d->ui.formLayout->getWidgetPosition(d->ui.userEdit, &row, &userEditRole);
346         d->ui.formLayout->removeWidget(d->ui.userEdit);
347         delete d->ui.userEdit;
348         d->userEditCombo = new QComboBox(d->ui.credentialsGroup);
349         d->userEditCombo->setEditable(true);
350         d->ui.userEdit = d->userEditCombo->lineEdit();
351         d->ui.userNameLabel->setBuddy(d->userEditCombo);
352         d->ui.formLayout->setWidget(row > -1 ? row : 0, userEditRole, d->userEditCombo);
353 
354         setTabOrder(d->ui.userEdit, d->ui.anonymousRadioButton);
355         setTabOrder(d->ui.anonymousRadioButton, d->ui.domainEdit);
356         setTabOrder(d->ui.domainEdit, d->ui.passEdit);
357         setTabOrder(d->ui.passEdit, d->ui.keepCheckBox);
358         connect(d->ui.userEdit, &QLineEdit::returnPressed, d->ui.passEdit, qOverload<>(&QWidget::setFocus));
359     }
360 
361     d->knownLogins = knownLogins;
362     d->userEditCombo->addItems(knownLogins.keys());
363     d->userEditCombo->setFocus();
364 
365     connect(d->userEditCombo, &QComboBox::textActivated, this, [this](const QString &text) {
366         d->activated(text);
367     });
368 }
369 
setRevealPasswordAvailable(bool reveal)370 void KPasswordDialog::setRevealPasswordAvailable(bool reveal)
371 {
372     d->ui.passEdit->setRevealPasswordAvailable(reveal);
373 }
374 
isRevealPasswordAvailable() const375 bool KPasswordDialog::isRevealPasswordAvailable() const
376 {
377     return d->ui.passEdit->isRevealPasswordAvailable();
378 }
379 
activated(const QString & userName)380 void KPasswordDialogPrivate::activated(const QString &userName)
381 {
382     QMap<QString, QString>::ConstIterator it = knownLogins.constFind(userName);
383     if (it != knownLogins.constEnd()) {
384         q->setPassword(it.value());
385     }
386 }
387 
accept()388 void KPasswordDialog::accept()
389 {
390     if (!d->ui.errorMessage->isHidden()) {
391         d->ui.errorMessage->setText(QString());
392     }
393 
394     // reset the font in case we had an error previously
395     if (!d->ui.passwordLabel->isHidden()) {
396         d->ui.passwordLabel->setFont(font());
397         d->ui.userNameLabel->setFont(font());
398     }
399 
400     // we do this to allow the error message, if any, to go away
401     // checkPassword() may block for a period of time
402     QTimer::singleShot(0, this, [this] {
403         d->actuallyAccept();
404     });
405 }
406 
actuallyAccept()407 void KPasswordDialogPrivate::actuallyAccept()
408 {
409     if (!q->checkPassword()) {
410         return;
411     }
412 
413     bool keep = ui.keepCheckBox->isVisibleTo(q) && ui.keepCheckBox->isChecked();
414     Q_EMIT q->gotPassword(q->password(), keep);
415 
416     if (ui.userEdit->isVisibleTo(q)) {
417         Q_EMIT q->gotUsernameAndPassword(q->username(), q->password(), keep);
418     }
419 
420     q->QDialog::accept();
421 }
422 
checkPassword()423 bool KPasswordDialog::checkPassword()
424 {
425     return true;
426 }
427 
buttonBox() const428 QDialogButtonBox *KPasswordDialog::buttonBox() const
429 {
430     return d->ui.buttonBox;
431 }
432 
setUsernameContextHelp(const QString & help)433 void KPasswordDialog::setUsernameContextHelp(const QString &help)
434 {
435     d->ui.userEditContextHelpButton->setVisible(true);
436     d->ui.userEdit->setWhatsThis(help);
437 }
438 
439 #include "moc_kpassworddialog.cpp"
440