1 /* pinentrydialog.cpp - A (not yet) secure Qt 4 dialog for PIN entry.
2  * Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
3  * Copyright 2007 Ingo Klöcker
4  * Copyright 2016 Intevation GmbH
5  *
6  * Written by Steffen Hansen <steffen@klaralvdalens-datakonsult.se>.
7  * Modified by Andre Heinecke <aheinecke@intevation.de>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see <https://www.gnu.org/licenses/>.
21  * SPDX-License-Identifier: GPL-2.0+
22  */
23 
24 #include "pinentrydialog.h"
25 #include <QGridLayout>
26 
27 #include <QProgressBar>
28 #include <QApplication>
29 #include <QFontMetrics>
30 #include <QStyle>
31 #include <QPainter>
32 #include <QPushButton>
33 #include <QDialogButtonBox>
34 #include <QKeyEvent>
35 #include <QLabel>
36 #include <QPalette>
37 #include <QLineEdit>
38 #include <QAction>
39 #include <QCheckBox>
40 #include "pinlineedit.h"
41 
42 #include <QDebug>
43 
44 #ifdef Q_OS_WIN
45 #include <windows.h>
46 #if QT_VERSION >= 0x050700
47 #include <QtPlatformHeaders/QWindowsWindowFunctions>
48 #endif
49 #endif
50 
raiseWindow(QWidget * w)51 void raiseWindow(QWidget *w)
52 {
53 #ifdef Q_OS_WIN
54 #if QT_VERSION >= 0x050700
55     QWindowsWindowFunctions::setWindowActivationBehavior(
56             QWindowsWindowFunctions::AlwaysActivateWindow);
57 #endif
58 #endif
59     w->setWindowState((w->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
60     w->activateWindow();
61     w->raise();
62 }
63 
icon(QStyle::StandardPixmap which)64 QPixmap icon(QStyle::StandardPixmap which)
65 {
66     QPixmap pm = qApp->windowIcon().pixmap(48, 48);
67 
68     if (which != QStyle::SP_CustomBase) {
69         const QIcon ic = qApp->style()->standardIcon(which);
70         QPainter painter(&pm);
71         const int emblemSize = 22;
72         painter.drawPixmap(pm.width() - emblemSize, 0,
73                            ic.pixmap(emblemSize, emblemSize));
74     }
75 
76     return pm;
77 }
78 
slotTimeout()79 void PinEntryDialog::slotTimeout()
80 {
81     _timed_out = true;
82     reject();
83 }
84 
PinEntryDialog(QWidget * parent,const char * name,int timeout,bool modal,bool enable_quality_bar,const QString & repeatString,const QString & visibilityTT,const QString & hideTT)85 PinEntryDialog::PinEntryDialog(QWidget *parent, const char *name,
86                                int timeout, bool modal, bool enable_quality_bar,
87                                const QString &repeatString,
88                                const QString &visibilityTT,
89                                const QString &hideTT)
90     : QDialog(parent),
91       mRepeat(NULL),
92       _grabbed(false),
93       _disable_echo_allowed(true),
94       mVisibilityTT(visibilityTT),
95       mHideTT(hideTT),
96       mVisiActionEdit(NULL),
97       mGenerateActionEdit(NULL),
98       mVisiCB(NULL)
99 {
100     _timed_out = false;
101 
102     if (modal) {
103         setWindowModality(Qt::ApplicationModal);
104     }
105 
106     _icon = new QLabel(this);
107     _icon->setPixmap(icon());
108 
109     _error = new QLabel(this);
110     QPalette pal;
111     pal.setColor(QPalette::WindowText, Qt::red);
112     _error->setPalette(pal);
113     _error->hide();
114 
115     _desc = new QLabel(this);
116     _desc->hide();
117 
118     _prompt = new QLabel(this);
119     _prompt->hide();
120 
121     _edit = new PinLineEdit(this);
122     _edit->setMaxLength(256);
123     _edit->setMinimumWidth(_edit->fontMetrics().averageCharWidth()*20 + 48);
124     _edit->setEchoMode(QLineEdit::Password);
125 
126     _prompt->setBuddy(_edit);
127 
128     if (enable_quality_bar) {
129         _quality_bar_label = new QLabel(this);
130         _quality_bar_label->setAlignment(Qt::AlignVCenter);
131         _quality_bar = new QProgressBar(this);
132         _quality_bar->setAlignment(Qt::AlignCenter);
133         _have_quality_bar = true;
134     } else {
135         _have_quality_bar = false;
136     }
137 
138     QDialogButtonBox *const buttons = new QDialogButtonBox(this);
139     buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
140     _ok = buttons->button(QDialogButtonBox::Ok);
141     _cancel = buttons->button(QDialogButtonBox::Cancel);
142 
143     _ok->setDefault(true);
144 
145     if (style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons)) {
146         _ok->setIcon(style()->standardIcon(QStyle::SP_DialogOkButton));
147         _cancel->setIcon(style()->standardIcon(QStyle::SP_DialogCancelButton));
148     }
149 
150     if (timeout > 0) {
151         _timer = new QTimer(this);
152         connect(_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
153         _timer->start(timeout * 1000);
154     } else {
155         _timer = NULL;
156     }
157 
158     connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
159     connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
160     connect(_edit, SIGNAL(textChanged(QString)),
161             this, SLOT(updateQuality(QString)));
162     connect(_edit, SIGNAL(textChanged(QString)),
163             this, SLOT(textChanged(QString)));
164     connect(_edit, SIGNAL(backspacePressed()),
165             this, SLOT(onBackspace()));
166 
167     QGridLayout *const grid = new QGridLayout(this);
168     int row = 1;
169     grid->addWidget(_error, row++, 1, 1, 2);
170     grid->addWidget(_desc,  row++, 1, 1, 2);
171     //grid->addItem( new QSpacerItem( 0, _edit->height() / 10, QSizePolicy::Minimum, QSizePolicy::Fixed ), 1, 1 );
172     grid->addWidget(_prompt, row, 1);
173     grid->addWidget(_edit, row++, 2);
174     if (!repeatString.isNull()) {
175         mRepeat = new QLineEdit;
176         mRepeat->setMaxLength(256);
177         mRepeat->setEchoMode(QLineEdit::Password);
178         connect(mRepeat, SIGNAL(textChanged(QString)),
179                 this, SLOT(textChanged(QString)));
180         QLabel *repeatLabel = new QLabel(repeatString);
181         repeatLabel->setBuddy(mRepeat);
182         grid->addWidget(repeatLabel, row, 1);
183         grid->addWidget(mRepeat, row++, 2);
184         setTabOrder(_edit, mRepeat);
185         setTabOrder(mRepeat, _ok);
186     }
187     if (enable_quality_bar) {
188         grid->addWidget(_quality_bar_label, row, 1);
189         grid->addWidget(_quality_bar, row++, 2);
190     }
191     /* Set up the show password action */
192     const QIcon visibilityIcon = QIcon::fromTheme(QLatin1String("visibility"));
193     const QIcon hideIcon = QIcon::fromTheme(QLatin1String("hint"));
194     const QIcon generateIcon = QIcon(); /* Disabled for now
195                                          QIcon::fromTheme(QLatin1String("password-generate")); */
196 #if QT_VERSION >= 0x050200
197     if (!generateIcon.isNull()) {
198         mGenerateActionEdit = _edit->addAction(generateIcon,
199                                                QLineEdit::LeadingPosition);
200         mGenerateActionEdit->setToolTip(mGenerateTT);
201         connect(mGenerateActionEdit, SIGNAL(triggered()), this, SLOT(generatePin()));
202     }
203     if (!visibilityIcon.isNull() && !hideIcon.isNull()) {
204         mVisiActionEdit = _edit->addAction(visibilityIcon, QLineEdit::TrailingPosition);
205         mVisiActionEdit->setVisible(false);
206         mVisiActionEdit->setToolTip(mVisibilityTT);
207         connect(mVisiActionEdit, SIGNAL(triggered()), this, SLOT(toggleVisibility()));
208     } else
209 #endif
210     {
211         if (!mVisibilityTT.isNull()) {
212             mVisiCB = new QCheckBox(mVisibilityTT);
213             connect(mVisiCB, SIGNAL(toggled(bool)), this, SLOT(toggleVisibility()));
214             grid->addWidget(mVisiCB, row++, 1, 1, 2, Qt::AlignLeft);
215         }
216     }
217     grid->addWidget(buttons, ++row, 0, 1, 3);
218 
219     grid->addWidget(_icon, 0, 0, row - 1, 1, Qt::AlignVCenter | Qt::AlignLeft);
220 
221     grid->setSizeConstraint(QLayout::SetFixedSize);
222 
223 
224     connect(qApp, SIGNAL(focusChanged(QWidget *, QWidget *)),
225             this, SLOT(focusChanged(QWidget *, QWidget *)));
226 
227     setWindowState(Qt::WindowMinimized);
228     QTimer::singleShot(0, this, [this] () {
229         raiseWindow (this);
230     });
231 }
232 
showEvent(QShowEvent * event)233 void PinEntryDialog::showEvent(QShowEvent *event)
234 {
235     QDialog::showEvent(event);
236     _edit->setFocus();
237 }
238 
setDescription(const QString & txt)239 void PinEntryDialog::setDescription(const QString &txt)
240 {
241     _desc->setVisible(!txt.isEmpty());
242     _desc->setText(txt);
243 #ifndef QT_NO_ACCESSIBILITY
244     _desc->setAccessibleDescription(txt);
245 #endif
246     _icon->setPixmap(icon());
247     setError(QString());
248 }
249 
description() const250 QString PinEntryDialog::description() const
251 {
252     return _desc->text();
253 }
254 
setError(const QString & txt)255 void PinEntryDialog::setError(const QString &txt)
256 {
257     if (!txt.isNull()) {
258         _icon->setPixmap(icon(QStyle::SP_MessageBoxCritical));
259     }
260     _error->setText(txt);
261 #ifndef QT_NO_ACCESSIBILITY
262     _error->setAccessibleDescription(txt);
263 #endif
264     _error->setVisible(!txt.isEmpty());
265 }
266 
error() const267 QString PinEntryDialog::error() const
268 {
269     return _error->text();
270 }
271 
setPin(const QString & txt)272 void PinEntryDialog::setPin(const QString &txt)
273 {
274     _edit->setText(txt);
275 }
276 
pin() const277 QString PinEntryDialog::pin() const
278 {
279     return _edit->text();
280 }
281 
setPrompt(const QString & txt)282 void PinEntryDialog::setPrompt(const QString &txt)
283 {
284     _prompt->setText(txt);
285     _prompt->setVisible(!txt.isEmpty());
286     if (txt.contains("PIN"))
287       _disable_echo_allowed = false;
288 }
289 
prompt() const290 QString PinEntryDialog::prompt() const
291 {
292     return _prompt->text();
293 }
294 
setOkText(const QString & txt)295 void PinEntryDialog::setOkText(const QString &txt)
296 {
297     _ok->setText(txt);
298 #ifndef QT_NO_ACCESSIBILITY
299     _ok->setAccessibleDescription(txt);
300 #endif
301     _ok->setVisible(!txt.isEmpty());
302 }
303 
setCancelText(const QString & txt)304 void PinEntryDialog::setCancelText(const QString &txt)
305 {
306     _cancel->setText(txt);
307 #ifndef QT_NO_ACCESSIBILITY
308     _cancel->setAccessibleDescription(txt);
309 #endif
310     _cancel->setVisible(!txt.isEmpty());
311 }
312 
setQualityBar(const QString & txt)313 void PinEntryDialog::setQualityBar(const QString &txt)
314 {
315     if (_have_quality_bar) {
316         _quality_bar_label->setText(txt);
317 #ifndef QT_NO_ACCESSIBILITY
318         _quality_bar_label->setAccessibleDescription(txt);
319 #endif
320     }
321 }
322 
setQualityBarTT(const QString & txt)323 void PinEntryDialog::setQualityBarTT(const QString &txt)
324 {
325     if (_have_quality_bar) {
326         _quality_bar->setToolTip(txt);
327     }
328 }
329 
setGenpinLabel(const QString & txt)330 void PinEntryDialog::setGenpinLabel(const QString &txt)
331 {
332     if (!mGenerateActionEdit) {
333         return;
334     }
335     if (txt.isEmpty()) {
336         mGenerateActionEdit->setVisible(false);
337     } else {
338         mGenerateActionEdit->setText(txt);
339         mGenerateActionEdit->setVisible(true);
340     }
341 }
342 
setGenpinTT(const QString & txt)343 void PinEntryDialog::setGenpinTT(const QString &txt)
344 {
345     if (mGenerateActionEdit) {
346         mGenerateActionEdit->setToolTip(txt);
347     }
348 }
349 
onBackspace()350 void PinEntryDialog::onBackspace()
351 {
352     if (_disable_echo_allowed) {
353         _edit->setEchoMode(QLineEdit::NoEcho);
354         if (mRepeat) {
355             mRepeat->setEchoMode(QLineEdit::NoEcho);
356         }
357     }
358 }
359 
updateQuality(const QString & txt)360 void PinEntryDialog::updateQuality(const QString &txt)
361 {
362     int length;
363     int percent;
364     QPalette pal;
365 
366     if (_timer) {
367         _timer->stop();
368     }
369 
370     _disable_echo_allowed = false;
371 
372     if (!_have_quality_bar || !_pinentry_info) {
373         return;
374     }
375     const QByteArray utf8_pin = txt.toUtf8();
376     const char *pin = utf8_pin.constData();
377     length = strlen(pin);
378     percent = length ? pinentry_inq_quality(_pinentry_info, pin, length) : 0;
379     if (!length) {
380         _quality_bar->reset();
381     } else {
382         pal = _quality_bar->palette();
383         if (percent < 0) {
384             pal.setColor(QPalette::Highlight, QColor("red"));
385             percent = -percent;
386         } else {
387             pal.setColor(QPalette::Highlight, QColor("green"));
388         }
389         _quality_bar->setPalette(pal);
390         _quality_bar->setValue(percent);
391     }
392 }
393 
setPinentryInfo(pinentry_t peinfo)394 void PinEntryDialog::setPinentryInfo(pinentry_t peinfo)
395 {
396     _pinentry_info = peinfo;
397 }
398 
focusChanged(QWidget * old,QWidget * now)399 void PinEntryDialog::focusChanged(QWidget *old, QWidget *now)
400 {
401     // Grab keyboard. It might be a little weird to do it here, but it works!
402     // Previously this code was in showEvent, but that did not work in Qt4.
403     if (!_pinentry_info || _pinentry_info->grab) {
404         if (_grabbed && old && (old == _edit || old == mRepeat)) {
405             old->releaseKeyboard();
406             _grabbed = false;
407         }
408         if (!_grabbed && now && (now == _edit || now == mRepeat)) {
409             now->grabKeyboard();
410             _grabbed = true;
411         }
412     }
413 
414 }
415 
textChanged(const QString & text)416 void PinEntryDialog::textChanged(const QString &text)
417 {
418     Q_UNUSED(text);
419     if (mRepeat && mRepeat->text() == _edit->text()) {
420         _ok->setEnabled(true);
421         _ok->setToolTip(QString());
422     } else if (mRepeat) {
423         _ok->setEnabled(false);
424         _ok->setToolTip(mRepeatError);
425     }
426 
427     if (mVisiActionEdit && sender() == _edit) {
428         mVisiActionEdit->setVisible(!_edit->text().isEmpty());
429     }
430     if (mGenerateActionEdit) {
431         mGenerateActionEdit->setVisible(_edit->text().isEmpty() &&
432                                         _pinentry_info->genpin_label);
433     }
434 }
435 
generatePin()436 void PinEntryDialog::generatePin()
437 {
438     const char *pin = pinentry_inq_genpin(_pinentry_info);
439     if (pin) {
440         if (_edit->echoMode() == QLineEdit::Password) {
441             toggleVisibility();
442         }
443         const auto pinStr = QString::fromUtf8(pin);
444         _edit->setText(pinStr);
445         mRepeat->setText(pinStr);
446     }
447 }
448 
toggleVisibility()449 void PinEntryDialog::toggleVisibility()
450 {
451     if (sender() != mVisiCB) {
452         if (_edit->echoMode() == QLineEdit::Password) {
453             mVisiActionEdit->setIcon(QIcon::fromTheme(QLatin1String("hint")));
454             mVisiActionEdit->setToolTip(mHideTT);
455             _edit->setEchoMode(QLineEdit::Normal);
456             if (mRepeat) {
457                 mRepeat->setEchoMode(QLineEdit::Normal);
458             }
459         } else {
460             mVisiActionEdit->setIcon(QIcon::fromTheme(QLatin1String("visibility")));
461             mVisiActionEdit->setToolTip(mVisibilityTT);
462             _edit->setEchoMode(QLineEdit::Password);
463             if (mRepeat) {
464                 mRepeat->setEchoMode(QLineEdit::Password);
465             }
466         }
467     } else {
468         if (mVisiCB->isChecked()) {
469             if (mRepeat) {
470                 mRepeat->setEchoMode(QLineEdit::Normal);
471             }
472             _edit->setEchoMode(QLineEdit::Normal);
473         } else {
474             if (mRepeat) {
475                 mRepeat->setEchoMode(QLineEdit::Password);
476             }
477             _edit->setEchoMode(QLineEdit::Password);
478         }
479     }
480 }
481 
repeatedPin() const482 QString PinEntryDialog::repeatedPin() const
483 {
484     if (mRepeat) {
485         return mRepeat->text();
486     }
487     return QString();
488 }
489 
timedOut() const490 bool PinEntryDialog::timedOut() const
491 {
492     return _timed_out;
493 }
494 
setRepeatErrorText(const QString & err)495 void PinEntryDialog::setRepeatErrorText(const QString &err)
496 {
497     mRepeatError = err;
498 }
499 #include "pinentrydialog.moc"
500