Skip to content

Commit c2bc925

Browse files
mrvanestvdijenmonkeyiq
committed
Add ProfileAuth authsource (simplesamlphp#2499)
In the cherry pick we might have to add a new method to the test class in DiscoControllerTest.php. * Add ProfileAuth authsource * Fix code quality * Remove unused code * Unrelated chore: use never return-type where appropriate * Fix login function description * put complete links into usersLinks and use that from twig * remove unused line --------- Co-authored-by: Tim van Dijen <tvdijen@gmail.com> Co-authored-by: Ben Martin <monkeyiq@users.sourceforge.net>
1 parent 49466d4 commit c2bc925

File tree

7 files changed

+479
-1
lines changed

7 files changed

+479
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,4 @@ typings/
235235

236236
# dotenv environment variables file
237237
.env
238+
cache
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
h2 {
2+
margin-bottom: 0;
3+
}
4+
5+
h2:hover {
6+
margin-bottom: 0;
7+
}
8+
9+
a {
10+
text-decoration: unset;
11+
}
12+
13+
a:hover {
14+
color: unset;
15+
}
16+
17+
table.profile {
18+
font-size: smaller;
19+
}
20+
21+
.profile td.key {
22+
padding-right: 10px;
23+
}

modules/exampleauth/routing/routes/routes.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,10 @@ exampleauth-resume:
2020
_controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::resume'
2121
}
2222
methods: [GET]
23+
24+
profileauth-login:
25+
path: /profileauth
26+
defaults: {
27+
_controller: 'SimpleSAML\Module\exampleauth\Controller\ProfileAuth::login'
28+
}
29+
methods: [GET, POST]
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\exampleauth\Auth\Source;
6+
7+
use Exception;
8+
use SimpleSAML\Auth;
9+
use SimpleSAML\Assert\Assert;
10+
use SimpleSAML\Error;
11+
use SimpleSAML\Logger;
12+
use SimpleSAML\Module;
13+
use SimpleSAML\Utils;
14+
use Symfony\Component\HttpFoundation\{Request, Response};
15+
16+
use function array_key_exists;
17+
use function sprintf;
18+
19+
/**
20+
* Profileauth authentication source.
21+
*
22+
* This class is an authentication source which stores all users in an array,
23+
* and authenticates users by clicking on their name.
24+
*
25+
* @package SimpleSAMLphp
26+
*/
27+
28+
class UserClick extends Auth\Source
29+
{
30+
/**
31+
* The string used to identify our states.
32+
*/
33+
public const STAGEID = '\SimpleSAML\Module\exampleauth\Auth\UserClick.state';
34+
35+
/**
36+
* The key of the AuthId field in the state.
37+
*/
38+
public const AUTHID = '\SimpleSAML\Module\exampleauth\Auth\UserClick.AuthId';
39+
40+
/**
41+
* Our users, stored in an associative array. The key of the array is "<id>",
42+
* while the value of each element is a new array with the attributes for each user.
43+
*
44+
* @var array
45+
*/
46+
public array $users = [];
47+
48+
49+
/**
50+
* @param array<mixed> $config
51+
* @return array<mixed>
52+
*/
53+
protected function getUsers(array $config): array
54+
{
55+
$users = [];
56+
$attrUtils = new Utils\Attributes();
57+
58+
// Validate and parse our configuration
59+
foreach ($config as $id => $attributes) {
60+
try {
61+
$attributes = $attrUtils->normalizeAttributesArray($attributes);
62+
} catch (Exception $e) {
63+
throw new Exception(sprintf(
64+
'Invalid attributes for user %s in authentication source %s: %s',
65+
$id,
66+
$this->authId,
67+
$e->getMessage(),
68+
));
69+
}
70+
71+
$users[$id] = $attributes;
72+
}
73+
74+
return $users;
75+
}
76+
77+
78+
/**
79+
* Constructor for this authentication source.
80+
*
81+
* @param array $info Information about this authentication source.
82+
* @param array $config Configuration.
83+
*/
84+
public function __construct(array $info, array $config)
85+
{
86+
// Call the parent constructor first, as required by the interface
87+
parent::__construct($info, $config);
88+
89+
if (array_key_exists('users', $config)) {
90+
$users = $config['users'];
91+
} else {
92+
Logger::warning(
93+
"Module exampleauth:UserClick misconfigured. Put users in 'users' key in your authsource.",
94+
);
95+
96+
throw new Error\Error(Error\ErrorCodes::WRONGUSERPASS);
97+
}
98+
99+
$this->users = $this->getUsers($users);
100+
}
101+
102+
103+
/**
104+
* Initialize login.
105+
*
106+
* This function saves the information about the login, and redirects to a
107+
* login page.
108+
*
109+
* @param array &$state Information about the current authentication.
110+
*/
111+
public function authenticate(array &$state): never
112+
{
113+
/*
114+
* Save the identifier of this authentication source, so that we can
115+
* retrieve it later. This allows us to call the login()-function on
116+
* the current object.
117+
*/
118+
$state[self::AUTHID] = $this->authId;
119+
120+
// Save the $state-array, so that we can restore it after a redirect
121+
$id = Auth\State::saveState($state, self::STAGEID);
122+
123+
/*
124+
* If there is only one user configured, skip the persona chooser
125+
*/
126+
if (count($this->users) === 1) {
127+
$this->handleLogin($id, 0);
128+
}
129+
130+
/*
131+
* Redirect to the login form. We include the identifier of the saved
132+
* state array as a parameter to the login form.
133+
*/
134+
$url = Module::getModuleURL('exampleauth/profileauth');
135+
$params = ['AuthState' => $id];
136+
$httpUtils = new Utils\HTTP();
137+
$httpUtils->redirectTrustedURL($url, $params);
138+
}
139+
140+
141+
/**
142+
* Attempt to log in using the given user id.
143+
*
144+
* On a successful login, this function should return the users attributes. On failure,
145+
* it should throw an exception. If the error was caused by the user entering the wrong
146+
* username or password, a \SimpleSAML\Error\Error(\SimpleSAML\Error\ErrorCodes::WRONGUSERPASS) should be thrown.
147+
*
148+
* Note that both the username and the password are UTF-8 encoded.
149+
*
150+
* @param int $id The userprofile the user clicked on.
151+
* @return array Associative array with the users attributes.
152+
*/
153+
protected function login(int $id): array
154+
{
155+
if (!array_key_exists($id, $this->users)) {
156+
throw new Error\Error(Error\ErrorCodes::WRONGUSERPASS);
157+
}
158+
159+
return $this->users[$id];
160+
}
161+
162+
163+
/**
164+
* Handle login request.
165+
*
166+
* This function is used by the login form (exampleauth/login) when the user
167+
* enters a username and password. On success, it will not return. On wrong
168+
* username/password failure, and other errors, it will throw an exception.
169+
*
170+
* @param string $authStateId The identifier of the authentication state.
171+
* @param string $id The username the user wrote.
172+
*/
173+
public static function handleLogin(string $authStateId, int $id): void
174+
{
175+
// Here we retrieve the state array we saved in the authenticate-function.
176+
$state = Auth\State::loadState($authStateId, self::STAGEID);
177+
178+
// Retrieve the authentication source we are executing.
179+
Assert::keyExists($state, self::AUTHID);
180+
181+
/** @var \SimpleSAML\Module\exampleauth\Auth\Source\UserClick|null $source */
182+
$source = Auth\Source::getById($state[self::AUTHID]);
183+
if ($source === null) {
184+
throw new Exception('Could not find authentication source with id ' . $state[self::AUTHID]);
185+
}
186+
187+
/*
188+
* $source now contains the authentication source on which authenticate()
189+
* was called. We should call login() on the same authentication source.
190+
*/
191+
192+
// Attempt to log in
193+
try {
194+
$attributes = $source->login($id);
195+
} catch (Exception $e) {
196+
Logger::stats(sprintf(
197+
'Unsuccessful login attempt from %s.',
198+
$_SERVER['REMOTE_ADDR'],
199+
));
200+
throw $e;
201+
}
202+
203+
Logger::stats(sprintf(
204+
"User '%s' successfully authenticated from %s",
205+
$id,
206+
$_SERVER['REMOTE_ADDR'],
207+
));
208+
209+
// Save the attributes we received from the login-function in the $state-array
210+
$state['Attributes'] = $attributes;
211+
212+
// Return control to SimpleSAMLphp after successful authentication.
213+
Auth\Source::completeAuth($state);
214+
}
215+
}

0 commit comments

Comments
 (0)