/* * Copyright (c) 2012-2015 Christian Surlykke * 2017 bwLehrpool project * * Based on qt-lightdm-greeter, stripped down to fit * our specific needs. * It is distributed under the LGPL 2.1 or later license. * Please refer to the LICENSE file for a copy of the license. */ #include #include "x11util.h" #include "loginform.h" #include "ui_loginform.h" #include "settings.h" #include "global.h" #include "namereplace.h" #include "loginrpc.h" #include "qrlogin.h" #undef KeyPress #undef KeyRelease #undef FocusIn #undef FocusOut #include #include #include #include #include #include #include #include #include #include "webview.h" #include #include void createSimpleBackground(); LoginForm::LoginForm(QWidget *parent) : QWidget(parent), ui(new Ui::LoginForm), browser(nullptr), clearMsg(false), capsOn(-1), pageCount(0), qrcode(nullptr), qrlogin(nullptr) { ui->setupUi(this); origSize = shibSize = sizeHint(); if (this->parentWidget() != nullptr) { shibSize.setWidth(qMin(1000, int(this->parentWidget()->width() * .75f) )); shibSize.setHeight(qMin(700, int(this->parentWidget()->height() * .75f) )); } else { shibSize.rwidth() += 350; shibSize.rheight() += 250; } if (!Settings::shibSessionEnabled() && !Settings::guestSessionEnabled()) { origSize.rheight() -= 64; } initialize(); this->updateGeometry(); int port = Settings::rpcPort(); if (port != 0) { auto *car = new LoginRpc(port, this); connect(car, &LoginRpc::loginRequest, [this](const QString &username, const QString &password, const QString &resolution) { if (username.isEmpty() || password.isEmpty()) return; ui->userInput->setText(username); ui->passwordInput->setText(password); this->startFormBasedAuthentication(); }); } connect(ui->loginChooser, &QStackedWidget::currentChanged, this, &LoginForm::setBrowserSize); } LoginForm::~LoginForm() { delete ui; } void LoginForm::setFocus(Qt::FocusReason reason) { if (ui->userInput->text().isEmpty()) { ui->userInput->setFocus(reason); } else { ui->passwordInput->setFocus(reason); } } void LoginForm::resizeEvent(QResizeEvent *e) { if (this->parentWidget() != nullptr) { shibSize.setWidth(qMin(1000, int(this->parentWidget()->width() * .75f) )); shibSize.setHeight(qMin(700, int(this->parentWidget()->height() * .75f) )); } const QSize *size = nullptr; if (ui->loginChooser->currentWidget() == ui->shibPage) { size = &shibSize; } else { size = &origSize; } if (*size != e->size()) { e->ignore(); setMinimumSize(*size); setFixedSize(*size); setBaseSize(*size); int pw = 0, ph = 0; if (this->parentWidget() != nullptr) { this->parentWidget()->pos(); pw = (this->parentWidget()->width() - size->width()) / 2; ph = (this->parentWidget()->height() - size->height()) / 2; } setGeometry(pw, pw, size->width(), size->height()); emit resized(); setBrowserSize(); return; } QWidget::resizeEvent(e); } void LoginForm::initialize() { QString path = Settings::miniIconFile(); QPixmap pixmap; if (!path.isEmpty()) { // Try to get the default size, in case this is an SVG QSize size = QSvgRenderer(path).defaultSize(); if (!size.isValid()) { // if not, use maximum of destination size = ui->iconLabel->maximumSize(); } else { size = size.boundedTo(ui->iconLabel->maximumSize()).expandedTo(ui->iconLabel->minimumSize()); } pixmap = QIcon(path).pixmap(size); } if (pixmap.isNull() || pixmap.width() < 10) { // fallback to built-in bwlp logo pixmap = QIcon(QLatin1String(":/resources/bwlp.svg")).pixmap(ui->iconLabel->size()); } ui->iconLabel->setPixmap(pixmap); ui->iconLabel->setFixedSize(pixmap.size()); ui->frame->setFixedHeight(pixmap.height() + 2); ui->leaveComboBox->setView(new QListView()); // This is required to get the stylesheet to apply cancelLoginTimer.setInterval(20000); cancelLoginTimer.setSingleShot(true); connect(&cancelLoginTimer, &QTimer::timeout, this, &LoginForm::cancelLogin); hideMessageTimer.setInterval(10000); hideMessageTimer.setSingleShot(true); connect(&hideMessageTimer, &QTimer::timeout, this, [this]() { this->hideMessage(); this->capsOn = -1; this->checkCaps(); }); // timer to reset the form to its original state if (Settings::resetForm() > 0) { connect(&resetFormTimer, &QTimer::timeout, [this]() { if (ui->loginChooser->currentWidget() == ui->qrPage) return; int idleTime = static_cast(getIdleTime(QX11Info::display())); int remaining = Settings::resetForm() * 1000 - idleTime; if (remaining <= 0) { resetForm(); remaining = Settings::resetForm() * 1000; } resetFormTimer.start(remaining + 100); }); resetFormTimer.start(Settings::resetForm() * 1000); } ui->backButton->hide(); if (Settings::guestSessionEnabled()) { pageCount++; if (!Settings::guestSessionButtonText().isEmpty()) { ui->guestButton->setText(Settings::guestSessionButtonText()); } if (!Settings::userSessionButtonText().isEmpty()) { ui->loginButton->setText(Settings::userSessionButtonText()); } if (!Settings::guestSessionStartText().isEmpty()) { ui->guestStartLabel->setText(Settings::guestSessionStartText()); } if (!Settings::guestSessionStartButtonText().isEmpty()) { ui->guestStartButton->setText(Settings::guestSessionStartButtonText()); } connect(ui->guestButton, &QAbstractButton::released, this, [this]() { ui->loginChooser->setCurrentWidget(ui->guestPage); }); } else { ui->guestButton->hide(); } if (Settings::shibSessionEnabled()) { pageCount += 2; // Fake this so we always return on timeout; // otherwise, the browser session could expire after some time, breaking // the login process. if (!Settings::shibSessionButtonText().isEmpty()) { ui->shibButton->setText(Settings::shibSessionButtonText()); } connect(ui->shibButton, &QAbstractButton::released, this, &LoginForm::showShibWindow); // Reduce minimum size of hostname/icon bar ui->frame->setMinimumSize(10, 30); ui->frame->setMaximumSize(99999, ui->iconLabel->height()); ui->frame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); ui->loginChooser->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); } else { ui->shibButton->hide(); } if (Settings::qrSessionEnabled()) { pageCount += 2; // Fake this so we always return on timeout; // otherwise, the qr code could expire after some time, breaking // the login process. if (!Settings::qrSessionButtonText().isEmpty()) { ui->qrButton->setText(Settings::qrSessionButtonText()); } connect(ui->qrButton, &QAbstractButton::released, this, &LoginForm::showQrWindow); } else { ui->qrButton->hide(); } if (Settings::userSessionEnabled()) { pageCount++; } else { ui->loginButton->hide(); } if (pageCount > 1) { connect(ui->backButton, &QAbstractButton::released, this, [this]() { resetForm(); }); ui->loginChooser->setCurrentWidget(ui->welcomePage); connect(ui->loginChooser, &QStackedWidget::currentChanged, this, [this]() { if (ui->loginChooser->currentWidget() == ui->welcomePage) { ui->backButton->hide(); } else { ui->backButton->show(); } }); connect(ui->loginButton, &QAbstractButton::released, this, [this]() { ui->loginChooser->setCurrentWidget(ui->loginPage); ui->userInput->setFocus(); }); } else if (Settings::shibSessionEnabled()) { this->showShibWindow(); } else if (Settings::guestSessionEnabled()) { ui->loginChooser->setCurrentWidget(ui->guestPage); } else { ui->loginChooser->setCurrentWidget(ui->loginPage); } if (!Global::testMode()) { ui->hostnameLabel->setText(Global::greeter()->hostname()); if(!Settings::usernamePlaceholder().isEmpty()) { ui->userInput->setPlaceholderText(Settings::usernamePlaceholder()); } if(!Settings::passwordPlaceholder().isEmpty()) { ui->passwordInput->setPlaceholderText(Settings::passwordPlaceholder()); } addLeaveEntry(Global::power()->canShutdown(), "system-shutdown", tr("Shutdown"), "shutdown"); addLeaveEntry(Global::power()->canRestart(), "system-reboot", tr("Restart"), "restart"); connect(ui->leaveComboBox, QOverload::of(&QComboBox::activated), this, &LoginForm::leaveDropDownActivated); connect(Global::greeter(), &QLightDM::Greeter::showPrompt, this, &LoginForm::onPrompt); connect(Global::greeter(), &QLightDM::Greeter::showMessage, this, &LoginForm::onMessage); connect(Global::greeter(), &QLightDM::Greeter::authenticationComplete, this, &LoginForm::onAuthenticationComplete); if (Settings::guestSessionEnabled()) { connect(ui->guestStartButton, &QAbstractButton::released, this, []() { if (!Global::autoLoginGuest()) { std::cerr << "Guest login failed..." << std::endl; // TODO warn user about it } }); } } // Load regexp for name substitution NameReplace::loadSubs(); ui->leaveComboBox->setDisabled(ui->leaveComboBox->count() <= 1); ui->passwordInput->clear(); this->layout()->setSizeConstraint(QLayout::SetFixedSize); this->installEventFilter(this); checkCaps(); } void LoginForm::showShibWindow() { if (browser == nullptr) { browser = new WebView(ui->shibPage); ui->verticalLayout_5->addWidget(browser); connect(browser, &WebView::triggerReset, [this](const QString &message) { this->showMessage(message, true); if (pageCount == 1) { showShibWindow(); } else { ui->loginChooser->setCurrentWidget(ui->welcomePage); } }); connect(browser, &WebView::startAuthentication, this, &LoginForm::startAuthAs); } browser->reset(Settings::shibUrl()); ui->loginChooser->setCurrentWidget(ui->shibPage); setBrowserSize(); } void LoginForm::showQrWindow() { if (qrcode == nullptr) { qrcode = new QLabel(this); ui->vlQrCode->addWidget(qrcode); qrcode->setBackgroundRole(QPalette::Shadow); //connect(browser, &WebView::startAuthentication, this, &LoginForm::startAuthAs); } if (qrlogin != nullptr) { disconnect(qrlogin); qrlogin->deleteLater(); } qrlogin = new QrLogin(this); connect(qrlogin, &QrLogin::startAuthentication, this, &LoginForm::startAuthAs); connect(qrlogin, &QrLogin::updateStatus, this, &LoginForm::showLowPrioMessage); connect(qrlogin, &QrLogin::triggerReset, [this](const QString &message) { this->showMessage(message, true); ui->loginChooser->setCurrentWidget(ui->welcomePage); }); QPixmap pm(20, 20); pm.fill(Qt::red); qrcode->setPixmap(pm); qrlogin->loadQrCode(qrcode); ui->loginChooser->setCurrentWidget(ui->qrPage); } void LoginForm::checkCaps() { unsigned int mask = getKeyMask(QX11Info::display()); int caps = (mask & 1) == 1; if (caps != capsOn) { capsOn = caps; QString message(tr("!! CAPS LOCK ACTIVE !!")); if (caps) { ui->messageLabel->setProperty("caps", message); showMessage(message, false); } else if (ui->messageLabel->property("caps").toString() == message) { hideMessage(); } } } void LoginForm::startFormBasedAuthentication() { QString username(ui->userInput->text().trimmed()); NameReplace::replace(username); std::cerr << "Logging in as " << username.toStdString() << std::endl; if (ui->userInput->text().isEmpty()) { ui->userInput->setFocus(); return; } if (ui->passwordInput->text().isEmpty()) { ui->passwordInput->setFocus(); return; } startAuthAs(username, ui->passwordInput->text()); ui->passwordInput->setText("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); ui->passwordInput->clear(); } void LoginForm::startAuthAs(const QString &user, const QString &pass) { if (Global::testMode()) { showMessage(QLatin1String("Test mode..."), true); return; } if (Global::greeter()->inAuthentication()) { Global::greeter()->cancelAuthentication(); } showMessage(tr("Logging in..."), false); clearMsg = false; enableInputs(false); cancelLoginTimer.start(); password = pass; Global::greeter()->authenticate(user); } void LoginForm::onPrompt(QString prompt, QLightDM::Greeter::PromptType /* promptType */) { std::cerr << "Prompt: " << prompt.toStdString() << std::endl; Global::greeter()->respond(password); password = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"; password.clear(); } void LoginForm::leaveDropDownActivated(int index) { QString actionName = ui->leaveComboBox->itemData(index).toString(); if (actionName == "shutdown") Global::power()->shutdown(); else if (actionName == "restart") Global::power()->restart(); else if (actionName == "hibernate") Global::power()->hibernate(); else if (actionName == "suspend") Global::power()->suspend(); } void LoginForm::onMessage(QString message, QLightDM::Greeter::MessageType type) { if (type == QLightDM::Greeter::MessageType::MessageTypeError) { ui->passwordInput->clear(); } std::cerr << "Message: " << message.toStdString() << std::endl; showMessage(message, false); clearMsg = true; } void LoginForm::addLeaveEntry(bool canDo, QString iconName, QString text, QString actionName) { if (canDo) { ui->leaveComboBox->addItem(QIcon::fromTheme(iconName), text, actionName); } } void LoginForm::onAuthenticationComplete() { if (Global::greeter()->isAuthenticated()) { std::cerr << "Auth complete, start session" << std::endl; showMessage(tr("Starting session..."), false); createSimpleBackground(); if (Global::startSession()) { cancelLoginTimer.stop(); } else { showMessage(tr("Cannot open session"), true); clearMsg = true; } } else { std::cerr << "Auth failed, cancelling..." << std::endl; cancelLogin(); } } void LoginForm::cancelLogin() { std::cerr << "Cancel login" << std::endl; if (Global::greeter()->inAuthentication()) { std::cerr << "Was in authentication" << std::endl; Global::greeter()->cancelAuthentication(); } if (ui->loginChooser->currentWidget() != ui->loginPage && pageCount > 1) { ui->loginChooser->setCurrentWidget(ui->welcomePage); } cancelLoginTimer.stop(); ui->passwordInput->clear(); enableInputs(true); if (!clearMsg) { showMessage(tr("Login failed"), true); } else { ui->messageLabel->setStyleSheet("color:red"); } ui->passwordInput->setFocus(); clearMsg = true; } void LoginForm::enableInputs(bool enable) { ui->userInput->setEnabled(enable); ui->passwordInput->setEnabled(enable); ui->backButton->setEnabled(enable); ui->guestButton->setEnabled(enable); ui->shibButton->setEnabled(enable); ui->qrButton->setEnabled(enable); if (browser != nullptr) { browser->setEnabled(enable); } } void LoginForm::showLowPrioMessage(QString message) { if (!ui->messageLabel->text().isEmpty() && !ui->messageLabel->styleSheet().isEmpty()) return; showMessage(message, false); } void LoginForm::showMessage(QString message, bool error) { hideMessageTimer.stop(); ui->messageLabel->setText(message); if (error) { ui->messageLabel->setStyleSheet("color:red"); hideMessageTimer.start(); } else { ui->messageLabel->setStyleSheet(""); } } void LoginForm::hideMessage() { hideMessageTimer.stop(); ui->messageLabel->clear(); } void LoginForm::keyPressEvent(QKeyEvent *event) { if (clearMsg) { clearMsg = false; hideMessage(); } if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { if (!ui->userInput->isEnabled()) { // Ignore if auth in progress return; } if (ui->userInput->hasFocus()) { ui->passwordInput->setFocus(); return; } if (ui->passwordInput->hasFocus()) { startFormBasedAuthentication(); return; } } if (Settings::guestSessionEnabled() && event->key() == Qt::Key_Escape) { resetForm(); } // Fallback: Passthrough QWidget::keyPressEvent(event); } void LoginForm::resetForm() { std::cerr << "PageCount: " << pageCount << std::endl; if (ui->loginChooser->currentWidget() == ui->qrPage && qrlogin != nullptr) { qrlogin->abort(); hideMessage(); } if (pageCount > 1) { ui->loginChooser->setCurrentWidget(ui->welcomePage); } ui->passwordInput->clear(); ui->userInput->clear(); if (pageCount == 1) { if (Settings::userSessionEnabled()) { ui->userInput->setFocus(); } } } bool LoginForm::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::KeyRelease) { checkCaps(); } return false; } void LoginForm::setBrowserSize() { auto s = ui->shibPage->size(); auto *f = &shibSize; if (ui->loginChooser->currentWidget() != ui->shibPage) { f = &origSize; s = QSize(50, 50); } if (browser != nullptr) { browser->setFixedSize(s); browser->setGeometry(QRect(QPoint(0, 0), s)); } QTimer::singleShot(10, [this, f]() { int pw = 0, ph = 0; if (this->parentWidget() != nullptr) { this->parentWidget()->pos(); pw = (this->parentWidget()->width() - f->width()) / 2; ph = (this->parentWidget()->height() - f->height()) / 2; } this->resize(*f); this->setFixedSize(*f); this->setGeometry(pw, ph, f->width(), f->height()); auto s = ui->shibPage->size(); std::cerr << "Delayed resize to " << s.width() << " " << s.height() << std::endl; if (browser != nullptr) { browser->setFixedSize(s); browser->setGeometry(QRect(QPoint(0, 0), s)); } emit resized(); }); }