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 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 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 79 void PinEntryDialog::slotTimeout() 80 { 81 _timed_out = true; 82 reject(); 83 } 84 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 233 void PinEntryDialog::showEvent(QShowEvent *event) 234 { 235 QDialog::showEvent(event); 236 _edit->setFocus(); 237 } 238 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 250 QString PinEntryDialog::description() const 251 { 252 return _desc->text(); 253 } 254 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 267 QString PinEntryDialog::error() const 268 { 269 return _error->text(); 270 } 271 272 void PinEntryDialog::setPin(const QString &txt) 273 { 274 _edit->setText(txt); 275 } 276 277 QString PinEntryDialog::pin() const 278 { 279 return _edit->text(); 280 } 281 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 290 QString PinEntryDialog::prompt() const 291 { 292 return _prompt->text(); 293 } 294 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 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 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 323 void PinEntryDialog::setQualityBarTT(const QString &txt) 324 { 325 if (_have_quality_bar) { 326 _quality_bar->setToolTip(txt); 327 } 328 } 329 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 343 void PinEntryDialog::setGenpinTT(const QString &txt) 344 { 345 if (mGenerateActionEdit) { 346 mGenerateActionEdit->setToolTip(txt); 347 } 348 } 349 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 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 394 void PinEntryDialog::setPinentryInfo(pinentry_t peinfo) 395 { 396 _pinentry_info = peinfo; 397 } 398 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 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 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 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 482 QString PinEntryDialog::repeatedPin() const 483 { 484 if (mRepeat) { 485 return mRepeat->text(); 486 } 487 return QString(); 488 } 489 490 bool PinEntryDialog::timedOut() const 491 { 492 return _timed_out; 493 } 494 495 void PinEntryDialog::setRepeatErrorText(const QString &err) 496 { 497 mRepeatError = err; 498 } 499 #include "pinentrydialog.moc" 500