Skip to content

Commit f83bf89

Browse files
committed
LoginButtonHook: additional buttons to be displayed below the login form
1 parent 70f2982 commit f83bf89

File tree

6 files changed

+133
-5
lines changed

6 files changed

+133
-5
lines changed

application/controllers/AuthenticationController.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@
33

44
namespace Icinga\Controllers;
55

6+
use GuzzleHttp\Psr7\ServerRequest;
7+
use Icinga\Application\ClassLoader;
68
use Icinga\Application\Hook\AuthenticationHook;
9+
use Icinga\Application\Hook\LoginButton\LoginButton;
10+
use Icinga\Application\Hook\LoginButtonHook;
711
use Icinga\Application\Icinga;
812
use Icinga\Application\Logger;
913
use Icinga\Common\Database;
1014
use Icinga\Exception\AuthenticationException;
15+
use Icinga\Forms\Authentication\ButtonForm;
1116
use Icinga\Forms\Authentication\LoginForm;
1217
use Icinga\Web\Controller;
1318
use Icinga\Web\Helper\CookieHelper;
1419
use Icinga\Web\RememberMe;
1520
use Icinga\Web\Url;
1621
use RuntimeException;
22+
use Throwable;
1723

1824
/**
1925
* Application wide controller for authentication
@@ -93,7 +99,33 @@ public function loginAction()
9399
}
94100
$form->handleRequest();
95101
}
102+
103+
$loginButtons = [];
104+
$request = ServerRequest::fromGlobals();
105+
106+
foreach (LoginButtonHook::all() as $class => $hook) {
107+
try {
108+
foreach ($hook->getButtons() as $index => $button) {
109+
assert($button instanceof LoginButton);
110+
111+
$loginButtons[] = (new ButtonForm(
112+
"$class!$index",
113+
$button,
114+
ClassLoader::classBelongsToModule($class) ? ClassLoader::extractModuleName($class) : null
115+
))
116+
->on(ButtonForm::ON_SUCCESS, function () use ($button): void {
117+
($button->onClick)();
118+
})
119+
->handleRequest($request);
120+
}
121+
} catch (Throwable $e) {
122+
Logger::error('Failed to execute login button hook: %s', $e);
123+
continue;
124+
}
125+
}
126+
96127
$this->view->form = $form;
128+
$this->view->loginButtons = $loginButtons;
97129
$this->view->defaultTitle = $this->translate('Icinga Web 2 Login');
98130
$this->view->requiresSetup = $requiresSetup;
99131
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/* Icinga Web 2 | (c) 2025 Icinga GmbH | GPLv2+ */
3+
4+
namespace Icinga\Forms\Authentication;
5+
6+
use Icinga\Application\Hook\LoginButton\LoginButton;
7+
use Icinga\Web\Session;
8+
use ipl\Html\Form;
9+
use ipl\Html\FormElement\SubmitButtonElement;
10+
use ipl\Html\Html;
11+
use ipl\Web\Common\CsrfCounterMeasure;
12+
use ipl\Web\Common\FormUid;
13+
14+
/**
15+
* Form for user authentication via external identity providers
16+
*/
17+
class ButtonForm extends Form
18+
{
19+
use FormUid;
20+
use CsrfCounterMeasure;
21+
22+
public function __construct(
23+
string $name,
24+
protected readonly LoginButton $button,
25+
protected readonly ?string $moduleName = null
26+
) {
27+
$this->defaultAttributes['name'] = $name;
28+
}
29+
30+
protected function assemble(): void
31+
{
32+
$button = new SubmitButtonElement('btn_submit', $this->button->attributes);
33+
34+
if ($this->moduleName) {
35+
$button->addAttributes(['class' => "icinga-module module-$this->moduleName"]);
36+
}
37+
38+
$this->addElement($button->addHtml($this->button->content));
39+
$this->addHtml($this->createUidElement());
40+
$this->addElement($this->createCsrfCounterMeasure(Session::getSession()->getId()));
41+
}
42+
}

application/views/scripts/authentication/login.phtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
) ?></p>
2323
<?php endif ?>
2424
<?= $this->form ?>
25+
<?= implode('', $this->loginButtons) ?>
2526
<div id="login-footer">
2627
<p>Icinga Web 2 &copy; 2013-<?= date('Y') ?></p>
2728
<?= $this->qlink($this->translate('icinga.com'), 'https://icinga.com') ?>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/* Icinga Web 2 | (c) 2025 Icinga GmbH | GPLv2+ */
3+
4+
namespace Icinga\Application\Hook\LoginButton;
5+
6+
use Closure;
7+
use ipl\Html\Attributes;
8+
use ipl\Html\ValidHtml;
9+
10+
readonly class LoginButton
11+
{
12+
/**
13+
* Create an additional button to be displayed below the login form
14+
*
15+
* @param Closure $onClick What to do if the button is pressed
16+
* @param ValidHtml $content What to show in the button, see also {@link Html::wantHtml()}
17+
* @param ?Attributes $attributes Additional <button> attributes, e.g. title
18+
*/
19+
public function __construct(
20+
public Closure $onClick,
21+
public ValidHtml $content,
22+
public ?Attributes $attributes = null
23+
) {
24+
}
25+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/* Icinga Web 2 | (c) 2025 Icinga GmbH | GPLv2+ */
3+
4+
namespace Icinga\Application\Hook;
5+
6+
use Icinga\Application\Hook;
7+
use Icinga\Application\Hook\LoginButton\LoginButton;
8+
9+
abstract class LoginButtonHook
10+
{
11+
/**
12+
* @return LoginButton[]
13+
*/
14+
abstract public function getButtons(): array;
15+
16+
/**
17+
* @return static[]
18+
*/
19+
public static function all(): array
20+
{
21+
return Hook::all('LoginButton');
22+
}
23+
24+
public static function register(): void
25+
{
26+
Hook::register('LoginButton', static::class, static::class, true);
27+
}
28+
}

public/css/icinga/login.less

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@
7474
}
7575
}
7676

77-
input[type="submit"]:focus {
78-
outline: 3px solid;
79-
outline-color: @icinga-blue-light;
80-
}
77+
input[type="submit"], button {
78+
&:focus {
79+
outline: 3px solid;
80+
outline-color: @icinga-blue-light;
81+
}
8182

82-
input[type=submit] {
8383
border-radius: .25em;
8484
background: @icinga-secondary;
8585
color: white;

0 commit comments

Comments
 (0)