summaryrefslogblamecommitdiffstats
path: root/src/loginform.cpp
blob: 3ca0ec47721e8372f115f7bed5727834b9674f24 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                            
                         
 

                                                   


                                                             
 


                      




                         
                     
                    




                 

                             
                  
                

                      
                    
                       
                   


                      

                   

                              
                                       

                          
                     
                    
               


                    
 
                      







                                                                                   


                                                                              
                 

                           



                                                                                                                                   

                                                        

                                                
                                                

           
                                                                                                 















                                                





























                                                                                   


                            


                                            
                                                              
                                                      
                                                                    

                                                
                                                                                                         

                                          
     






                                                                                            
                                                   
 
                                                                                                   
 
                                        
                                         
                                                                                
 

                                         




                                                                 
 
                                                    

                                                             

                                                                






                                                                              
           

                                                           
                           

                                          
                    






                                                                         
                                                                            



                                                                                   


                                                                             




                                         


                                                                                


                                                                       
                                                                                              








                                                                                               











                                                                                          






                                         


                                                                            
                                                            






                                                                                   



                                                                             
                                                
                               

                                                          

                                                          
     
 

                                                                  


                                                                               


                                                                                   
 

                                                                                                     
 



                                                                                                                           

                                              





                                                                                  
         

     


                                            
                                                                    
                               
                                                             



                                   


















                                                                                       
























                                                                                         













                                                                              

 
                                              
 
                                                          


                                                                             


                                          
     


                                              
     



                                                                       
 








                                                                     
                                            
                     
                        
                             

                                          

 
                                                                                        
 
                                                                     


                                                  




                                                                       



                                                                     

 
                                                                               
 

                                                                        

                                   
                                                                       
                                                          
                        

 






                                                                                             
                                          
 
                                               
                                                                 
                                                      
                                 
                                     

                                        
                                                             
                                
         
            
                                                               
                      


     
                             
 
                                                 
                                                
                                                                  
                                                          
         


                                                                                  

                                   
                           
                        


                                                             
         
                                      
                        

 





                                              

                                           




                                            






                                                                                             

















                                                             

                                               

                                 
                              
         
                                                                          








                                              
                                           

                       
     
                                                                            
                    
     

                                  

 
                           
 
                                                         



                                                                                
                        
                                                            
     
                               
                           




                                             

 






                                                           












                                                            
                                        








                                                                    





                                                                                         
                       

       
/*
* 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 <QTextStream>

#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 <QAbstractListModel>
#include <QModelIndex>
#include <QPixmap>
#include <QIcon>
#include <QMessageBox>
#include <QMenu>
#include <QListView>
#include <QSvgRenderer>
#include <QX11Info>
#include "webview.h"
#include <QSizePolicy>

#include <iostream>

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<int>(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<int>::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)
{
    bool err = type == QLightDM::Greeter::MessageType::MessageTypeError;
    if (err) {
        ui->passwordInput->clear();
    }
	std::cerr << "Message: " << message.toStdString() << std::endl;
	showMessage(QLatin1String("[G] ") + message, err);
	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();
    });
}