/* * Copyright (C) 2011 Emweb bv, Herent, Belgium. * * See the LICENSE file for terms of use. */ #include "Wt/Auth/AbstractUserDatabase.h" #include "Wt/Auth/AuthModel.h" #include "Wt/Auth/AuthService.h" #include "Wt/Auth/LostPasswordWidget.h" #include "Wt/Auth/OAuthWidget.h" #include "Wt/Auth/PasswordPromptDialog.h" #include "Wt/Auth/RegistrationWidget.h" #include "Wt/Auth/UpdatePasswordWidget.h" #include "Wt/Auth/OAuthService.h" #ifdef WT_HAS_SAML #include "Wt/Auth/Saml/Process.h" #include "Wt/Auth/Saml/Service.h" #include "Wt/Auth/Saml/Widget.h" #endif // WT_HAS_SAML #include "Wt/WApplication.h" #include "Wt/WAnchor.h" #include "Wt/WCheckBox.h" #include "Wt/WContainerWidget.h" #include "Wt/WDialog.h" #include "Wt/WEnvironment.h" #include "Wt/WImage.h" #include "Wt/WLineEdit.h" #include "Wt/WLogger.h" #include "Wt/WMessageBox.h" #include "Wt/WPushButton.h" #include "Wt/WText.h" #include "Wt/WTheme.h" #include "Login.h" #include "AuthWidget.h" #include "web/WebUtils.h" #include "Wt/WDllDefs.h" #include namespace Wt { LOGGER("Auth.AuthWidget"); namespace Auth { AuthWidget::AuthWidget(const AuthService& baseAuth, AbstractUserDatabase& users, Login& login) : WTemplateFormView(WString::Empty), model_(std::make_shared(baseAuth, users)), login_(login) { init(); } AuthWidget::AuthWidget(Login& login) : WTemplateFormView(WString::Empty), login_(login) { init(); } AuthWidget::~AuthWidget() { dialog_.reset(); messageBox_.reset(); } void AuthWidget::init() { setWidgetIdMode(TemplateWidgetIdMode::SetObjectName); registrationEnabled_ = false; created_ = false; WApplication *app = WApplication::instance(); app->internalPathChanged().connect(this, &AuthWidget::onPathChange); app->theme()->apply(this, this, AuthWidgets); login_.changed().connect(this, &AuthWidget::onLoginChange); } void AuthWidget::setModel(std::unique_ptr model) { model_ = std::move(model); } void AuthWidget::setRegistrationEnabled(bool enabled) { registrationEnabled_ = enabled; } void AuthWidget::setInternalBasePath(const std::string& basePath) { basePath_ = Utils::append(Utils::prepend(basePath, '/'), '/');; } void AuthWidget::onPathChange(const std::string& path) { handleRegistrationPath(path); } bool AuthWidget::handleRegistrationPath(const std::string& path) { if (!basePath_.empty()) { WApplication *app = WApplication::instance(); if (app->internalPathMatches(basePath_)) { std::string ap = app->internalSubPath(basePath_); if (ap == "register/") { registerNewUser(); return true; } } } return false; } void AuthWidget::registerNewUser() { registerNewUser(Identity::Invalid); } void AuthWidget::registerNewUser(const Identity& oauth) { showDialog(tr("Wt.Auth.registration"), createRegistrationView(oauth)); } WDialog *AuthWidget::showDialog(const WString& title, std::unique_ptr contents) { if (contents) { dialog_.reset(new WDialog(title)); dialog_->contents()->addWidget(std::move(contents)); dialog_->contents()->childrenChanged() .connect(this, &AuthWidget::closeDialog); dialog_->footer()->hide(); if (!WApplication::instance()->environment().ajax()) { /* * try to center it better, we need to set the half width and * height as negative margins. */ dialog_->setMargin(WLength("-21em"), Side::Left); // .Wt-form width dialog_->setMargin(WLength("-200px"), Side::Top); // ??? } dialog_->show(); } return dialog_.get(); } void AuthWidget::closeDialog() { if (dialog_) { #ifdef WT_TARGET_JAVA delete dialog_.release(); #endif dialog_.reset(); } else { #ifdef WT_TARGET_JAVA delete messageBox_.release(); #endif messageBox_.reset(); } /* Reset internal path */ if (!basePath_.empty()) { WApplication *app = WApplication::instance(); if (app->internalPathMatches(basePath_)) { std::string ap = app->internalSubPath(basePath_); if (ap == "register/") { app->setInternalPath(basePath_, false); } } } } std::unique_ptr AuthWidget::createRegistrationModel() { auto result = std::unique_ptr (new RegistrationModel(*model_->baseAuth(), model_->users(), login_)); if (model_->passwordAuth()) result->addPasswordAuth(model_->passwordAuth()); result->addOAuth(model_->oAuth()); #ifdef WT_HAS_SAML result->addSaml(model_->saml()); #endif // WT_HAS_SAML return result; } std::unique_ptr AuthWidget::createRegistrationView(const Identity& id) { auto model = createRegistrationModel(); if (id.isValid()) model->registerIdentified(id); std::unique_ptr w(new RegistrationWidget(this)); w->setModel(std::move(model)); return std::move(w); } void AuthWidget::handleLostPassword() { showDialog(tr("Wt.Auth.lostpassword"), createLostPasswordView()); } std::unique_ptr AuthWidget::createLostPasswordView() { return std::unique_ptr (new LostPasswordWidget(model_->users(), *model_->baseAuth())); } void AuthWidget::letUpdatePassword(const User& user, bool promptPassword) { std::unique_ptr updatePasswordView = createUpdatePasswordView(user, promptPassword); UpdatePasswordWidget *defaultUpdatePasswordWidget = dynamic_cast(updatePasswordView.get()); showDialog(tr("Wt.Auth.updatepassword"), std::move(updatePasswordView)); if (defaultUpdatePasswordWidget) { defaultUpdatePasswordWidget->updated().connect(this, &AuthWidget::closeDialog); defaultUpdatePasswordWidget->canceled().connect(this, &AuthWidget::closeDialog); } } std::unique_ptr AuthWidget ::createUpdatePasswordView(const User& user, bool promptPassword) { return std::unique_ptr (new UpdatePasswordWidget (user, createRegistrationModel(), promptPassword ? model_ : std::shared_ptr())); } std::unique_ptr AuthWidget::createPasswordPromptDialog(Login& login) { return std::make_unique(login, model_); } void AuthWidget::logout() { model_->logout(login_); } void AuthWidget::displayError(const WString& m) { messageBox_.reset(new WMessageBox(tr("Wt.Auth.error"), m, Icon::None, StandardButton::Ok)); messageBox_->buttonClicked().connect(this, &AuthWidget::closeDialog); messageBox_->show(); } void AuthWidget::displayInfo(const WString& m) { messageBox_.reset(new WMessageBox(tr("Wt.Auth.notice"), m, Icon::None, StandardButton::Ok)); messageBox_->buttonClicked().connect(this, &AuthWidget::closeDialog); messageBox_->show(); } void AuthWidget::render(WFlags flags) { if (!created_) { create(); created_ = true; } WTemplateFormView::render(flags); } void AuthWidget::create() { if (created_) return; onLoginChange(); created_ = true; } void AuthWidget::onLoginChange() { if (!(isRendered() || created_)) return; clear(); if (login_.loggedIn()) { #ifndef WT_TARGET_JAVA if (created_) // do not do this if onLoginChange() is called from create() WApplication::instance()->changeSessionId(); #endif // WT_TARGET_JAVA createLoggedInView(); } else { if (login_.state() != LoginState::Disabled) { if (model_->baseAuth()->authTokensEnabled()) { WApplication::instance()->removeCookie (model_->baseAuth()->authTokenCookieName()); } model_->reset(); createLoginView(); } else { createLoginView(); } } } void AuthWidget::createLoginView() { setTemplateText(tr("Wt.Auth.template.login")); createPasswordLoginView(); createOAuthLoginView(); #ifdef WT_HAS_SAML createSamlLoginView(); #endif // WT_HAS_SAML_ } void AuthWidget::createPasswordLoginView() { updatePasswordLoginView(); } std::unique_ptr AuthWidget::createFormWidget(WFormModel::Field field) { std::unique_ptr result; if (field == AuthModel::LoginNameField) { result.reset(new WLineEdit()); result->setFocus(true); } else if (field == AuthModel::PasswordField) { WLineEdit *p = new WLineEdit(); p->enterPressed().connect(this, &AuthWidget::attemptPasswordLogin); p->setEchoMode(EchoMode::Password); result.reset(p); } else if (field == AuthModel::RememberMeField) { result.reset(new WCheckBox()); } return std::move(result); } void AuthWidget::updatePasswordLoginView() { if (model_->passwordAuth()) { setCondition("if:passwords", true); updateView(model_.get()); WInteractWidget *login = resolve("login"); if (!login) { login = bindWidget("login", std::make_unique(tr("Wt.Auth.login"))); login->clicked().connect(this, &AuthWidget::attemptPasswordLogin); model_->configureThrottling(login); if (model_->baseAuth()->emailVerificationEnabled()) { WText *text = bindWidget("lost-password", std::make_unique(tr("Wt.Auth.lost-password"))); text->clicked().connect(this, &AuthWidget::handleLostPassword); } else bindEmpty("lost-password"); if (registrationEnabled_) { if (!basePath_.empty()) { bindWidget("register", std::make_unique (WLink(LinkType::InternalPath, basePath_ + "register"), tr("Wt.Auth.register"))); } else { WText *t = bindWidget("register", std::make_unique(tr("Wt.Auth.register"))); t->clicked().connect(this, &AuthWidget::registerNewUser); } } else bindEmpty("register"); if (model_->baseAuth()->emailVerificationEnabled() && registrationEnabled_) bindString("sep", " | "); else bindEmpty("sep"); } model_->updateThrottling(login); } else { bindEmpty("lost-password"); bindEmpty("sep"); bindEmpty("register"); bindEmpty("login"); } } void AuthWidget::createOAuthLoginView() { if (!model_->oAuth().empty()) { setCondition("if:oauth", true); WContainerWidget *icons = bindWidget("icons", std::make_unique()); icons->setInline(isInline()); for (unsigned i = 0; i < model_->oAuth().size(); ++i) { const OAuthService *service = model_->oAuth()[i]; OAuthWidget *w = icons->addWidget(std::make_unique(*service)); w->authenticated().connect(this, &AuthWidget::oAuthDone); } } } #ifdef WT_HAS_SAML void AuthWidget::createSamlLoginView() { if (!model_->saml().empty()) { setCondition("if:oauth", true); WContainerWidget *icons = resolve("icons"); if (!icons) { icons = bindWidget("icons", std::make_unique()); icons->setInline(isInline()); } for (const Saml::Service *saml : model()->saml()) { Saml::Widget *w = icons->addNew(*saml); w->authenticated().connect(this, &AuthWidget::samlDone); } } } #endif // WT_HAS_SAML void AuthWidget::oAuthDone(OAuthProcess *oauth, const Identity& identity) { /* * FIXME: perhaps consider moving this to the model with signals or * by passing the Login object ? */ if (identity.isValid()) { LOG_SECURE(oauth->service().name() << ": identified: as " << identity.id() << ", " << identity.name() << ", " << identity.email()); std::unique_ptr t(model_->users().startTransaction()); User user = model_->baseAuth()->identifyUser(identity, model_->users()); if (user.isValid()) model_->loginUser(login_, user); else registerNewUser(identity); if (t.get()) t->commit(); } else { LOG_SECURE(oauth->service().name() << ": error: " << oauth->error()); displayError(oauth->error()); } } #ifdef WT_HAS_SAML void AuthWidget::samlDone(Saml::Process *process, const Identity &identity) { if (identity.isValid()) { LOG_SECURE(process->service().name() << ": identified: as " << identity.id() << ", " << identity.name() << ", " << identity.email()); std::unique_ptr t(model_->users().startTransaction()); User user = model_->baseAuth()->identifyUser(identity, model_->users()); if (user.isValid()) model_->loginUser(login_, user); else registerNewUser(identity); if (t.get()) t->commit(); } else { LOG_SECURE(process->service().name() << ": error: " << process->error()); displayError(process->error()); } } #endif // WT_HAS_SAML void AuthWidget::attemptPasswordLogin() { updateModel(model_.get()); if (model_->validate()) { if (!model_->login(login_)) updatePasswordLoginView(); } else updatePasswordLoginView(); } void AuthWidget::createLoggedInView() { setTemplateText(tr("Wt.Auth.template.logged-in")); bindString("user-name", login_.user().identity(Identity::LoginName)); WPushButton *logout = bindWidget("logout", std::make_unique(tr("Wt.Auth.logout"))); logout->clicked().connect(this, &AuthWidget::logout); } void AuthWidget::processEnvironment() { const WEnvironment& env = WApplication::instance()->environment(); if (registrationEnabled_) if (handleRegistrationPath(env.internalPath())) return; std::string emailToken = model_->baseAuth()->parseEmailToken(env.internalPath()); if (!emailToken.empty()) { EmailTokenResult result = model_->processEmailToken(emailToken); switch (result.state()) { case EmailTokenState::Invalid: displayError(tr("Wt.Auth.error-invalid-token")); break; case EmailTokenState::Expired: displayError(tr("Wt.Auth.error-token-expired")); break; case EmailTokenState::UpdatePassword: letUpdatePassword(result.user(), false); break; case EmailTokenState::EmailConfirmed: displayInfo(tr("Wt.Auth.info-email-confirmed")); User user = result.user(); model_->loginUser(login_, user); } /* * In progressive bootstrap mode, this would cause a redirect w/o * session ID, losing the dialog. */ if (WApplication::instance()->environment().ajax()) WApplication::instance()->setInternalPath("/"); return; } User user = model_->processAuthToken(); model_->loginUser(login_, user, LoginState::Weak); } } }