Skip to content

Commit bd7d352

Browse files
authored
Merge pull request simplesamlphp#1502 from simplesamlphp/exampleauth-controllers
Exampleauth controllers
2 parents 31a49a8 + a1af994 commit bd7d352

9 files changed

Lines changed: 492 additions & 147 deletions

File tree

modules/exampleauth/lib/Auth/Process/RedirectTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* A simple processing filter for testing that redirection works as it should.
1414
*
1515
*/
16-
class RedirectTest extends \SimpleSAML\Auth\ProcessingFilter
16+
class RedirectTest extends Auth\ProcessingFilter
1717
{
1818
/**
1919
* Initialize processing of the redirect test.
@@ -29,7 +29,7 @@ public function process(array &$state): void
2929

3030
// Save state and redirect
3131
$id = Auth\State::saveState($state, 'exampleauth:redirectfilter-test');
32-
$url = Module::getModuleURL('exampleauth/redirecttest.php');
32+
$url = Module::getModuleURL('exampleauth/redirecttest');
3333

3434
$httpUtils = new Utils\HTTP();
3535
$httpUtils->redirectTrustedURL($url, ['StateId' => $id]);

modules/exampleauth/lib/Auth/Source/External.php

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use SimpleSAML\Error;
1010
use SimpleSAML\Module;
1111
use SimpleSAML\Utils;
12+
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
1213

1314
/**
1415
* Example external authentication source.
@@ -19,9 +20,8 @@
1920
* To adapt this to your own web site, you should:
2021
* 1. Create your own module directory.
2122
* 2. Enable to module in the config by adding '<module-dir>' => true to the $config['module.enable'] array.
22-
* 3. Copy this file and modules/exampleauth/www/resume.php to their corresponding
23-
* location in the new module.
24-
* 4. Replace all occurrences of "exampleauth" in this file and in resume.php with the name of your module.
23+
* 3. Copy this file to its corresponding location in the new module.
24+
* 4. Replace all occurrences of "exampleauth" in this file with the name of your module.
2525
* 5. Adapt the getUser()-function, the authenticate()-function and the logout()-function to your site.
2626
* 6. Add an entry in config/authsources.php referencing your module. E.g.:
2727
* 'myauth' => [
@@ -65,13 +65,12 @@ private function getUser(): ?array
6565
* stored in the users PHP session, but this could be replaced
6666
* with anything.
6767
*/
68-
69-
if (!session_id()) {
70-
// session_start not called before. Do it here
71-
session_start();
68+
$session = new SymfonySession();
69+
if (!$session->getId()) {
70+
$session->start();
7271
}
7372

74-
if (!isset($_SESSION['uid'])) {
73+
if (!$session->has('uid')) {
7574
// The user isn't authenticated
7675
return null;
7776
}
@@ -81,16 +80,15 @@ private function getUser(): ?array
8180
* Note that all attributes in SimpleSAMLphp are multivalued, so we need
8281
* to store them as arrays.
8382
*/
84-
8583
$attributes = [
86-
'uid' => [$_SESSION['uid']],
87-
'displayName' => [$_SESSION['name']],
88-
'mail' => [$_SESSION['mail']],
84+
'uid' => [$session->get('uid')],
85+
'displayName' => [$session->get('name')],
86+
'mail' => [$session->get('mail')],
8987
];
9088

9189
// Here we generate a multivalued attribute based on the account type
9290
$attributes['eduPersonAffiliation'] = [
93-
$_SESSION['type'], /* In this example, either 'student' or 'employee'. */
91+
$session->get('type'), /* In this example, either 'student' or 'employee'. */
9492
'member',
9593
];
9694

@@ -148,7 +146,7 @@ public function authenticate(array &$state): void
148146
* We assume that whatever authentication page we send the user to has an
149147
* option to return the user to a specific page afterwards.
150148
*/
151-
$returnTo = Module::getModuleURL('exampleauth/resume.php', [
149+
$returnTo = Module::getModuleURL('exampleauth/resume', [
152150
'State' => $stateId,
153151
]);
154152

@@ -159,7 +157,7 @@ public function authenticate(array &$state): void
159157
* is also part of this module, but in a real example, this would likely be
160158
* the absolute URL of the login page for the site.
161159
*/
162-
$authPage = Module::getModuleURL('exampleauth/authpage.php');
160+
$authPage = Module::getModuleURL('exampleauth/authpage');
163161

164162
/*
165163
* The redirect to the authentication page.
@@ -185,16 +183,18 @@ public function authenticate(array &$state): void
185183
* This function resumes the authentication process after the user has
186184
* entered his or her credentials.
187185
*
186+
* @param \Symfony\Component\HttpFoundation\Request $request
187+
*
188188
* @throws \SimpleSAML\Error\BadRequest
189189
* @throws \SimpleSAML\Error\Exception
190190
*/
191-
public static function resume(): void
191+
public static function resume(Request $request): void
192192
{
193193
/*
194194
* First we need to restore the $state-array. We should have the identifier for
195195
* it in the 'State' request parameter.
196196
*/
197-
if (!isset($_REQUEST['State'])) {
197+
if (!$request->has('State')) {
198198
throw new Error\BadRequest('Missing "State" parameter.');
199199
}
200200

@@ -203,7 +203,7 @@ public static function resume(): void
203203
* match the string we used in the saveState-call above.
204204
*/
205205
/** @var array $state */
206-
$state = Auth\State::loadState($_REQUEST['State'], 'exampleauth:External');
206+
$state = Auth\State::loadState($request->get('State'), 'exampleauth:External');
207207

208208
/*
209209
* Now we have the $state-array, and can use it to locate the authentication
@@ -266,15 +266,12 @@ public static function resume(): void
266266
*/
267267
public function logout(array &$state): void
268268
{
269-
if (!session_id()) {
270-
// session_start not called before. Do it here
271-
session_start();
269+
$session = new SymfonySession();
270+
if (!$session->getId()) {
271+
$session->start();
272272
}
273273

274-
/*
275-
* In this example we simply remove the 'uid' from the session.
276-
*/
277-
unset($_SESSION['uid']);
274+
$session->clear();
278275

279276
/*
280277
* If we need to do a redirect to a different page, we could do this
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\exampleauth\Controller;
6+
7+
use SimpleSAML\Auth;
8+
use SimpleSAML\Configuration;
9+
use SimpleSAML\Error;
10+
use SimpleSAML\HTTP\RunnableResponse;
11+
use SimpleSAML\Module\exampleauth\Auth\Source\External;
12+
use SimpleSAML\Session;
13+
use SimpleSAML\Utils;
14+
use SimpleSAML\XHTML\Template;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Session\Session as SymfonySession;
17+
18+
use function array_key_exists;
19+
use function preg_match;
20+
use function session_id;
21+
use function session_start;
22+
use function urldecode;
23+
24+
/**
25+
* Controller class for the exampleauth module.
26+
*
27+
* This class serves the different views available in the module.
28+
*
29+
* @package simplesamlphp/simplesamlphp
30+
*/
31+
class ExampleAuth
32+
{
33+
/** @var \SimpleSAML\Configuration */
34+
protected Configuration $config;
35+
36+
/** @var \SimpleSAML\Session */
37+
protected Session $session;
38+
39+
/**
40+
* @var \SimpleSAML\Auth\State|string
41+
* @psalm-var \SimpleSAML\Auth\State|class-string
42+
*/
43+
protected $authState = Auth\State::class;
44+
45+
46+
/**
47+
* Controller constructor.
48+
*
49+
* It initializes the global configuration and session for the controllers implemented here.
50+
*
51+
* @param \SimpleSAML\Configuration $config The configuration to use by the controllers.
52+
* @param \SimpleSAML\Session $session The session to use by the controllers.
53+
*
54+
* @throws \Exception
55+
*/
56+
public function __construct(
57+
Configuration $config,
58+
Session $session
59+
) {
60+
$this->config = $config;
61+
$this->session = $session;
62+
}
63+
64+
65+
/**
66+
* Inject the \SimpleSAML\Auth\State dependency.
67+
*
68+
* @param \SimpleSAML\Auth\State $authState
69+
*/
70+
public function setAuthState(Auth\State $authState): void
71+
{
72+
$this->authState = $authState;
73+
}
74+
75+
76+
/**
77+
* Auth testpage.
78+
*
79+
* @param \Symfony\Component\HttpFoundation\Request $request The current request.
80+
*
81+
* @return \SimpleSAML\XHTML\Template|\SimpleSAML\HTTP\RunnableResponse
82+
*/
83+
public function authpage(Request $request)
84+
{
85+
/**
86+
* This page serves as a dummy login page.
87+
*
88+
* Note that we don't actually validate the user in this example. This page
89+
* just serves to make the example work out of the box.
90+
*/
91+
$returnTo = $request->get('ReturnTo');
92+
if ($returnTo === null) {
93+
throw new Error\Exception('Missing ReturnTo parameter.');
94+
}
95+
96+
$httpUtils = new Utils\HTTP();
97+
$returnTo = $httpUtils->checkURLAllowed($returnTo);
98+
99+
/**
100+
* The following piece of code would never be found in a real authentication page. Its
101+
* purpose in this example is to make this example safer in the case where the
102+
* administrator of the IdP leaves the exampleauth-module enabled in a production
103+
* environment.
104+
*
105+
* What we do here is to extract the $state-array identifier, and check that it belongs to
106+
* the exampleauth:External process.
107+
*/
108+
if (!preg_match('@State=(.*)@', $returnTo, $matches)) {
109+
throw new Error\Exception('Invalid ReturnTo URL for this example.');
110+
}
111+
112+
/**
113+
* The loadState-function will not return if the second parameter does not
114+
* match the parameter passed to saveState, so by now we know that we arrived here
115+
* through the exampleauth:External authentication page.
116+
*/
117+
$this->authState::loadState(urldecode($matches[1]), 'exampleauth:External');
118+
119+
// our list of users.
120+
$users = [
121+
'student' => [
122+
'password' => 'student',
123+
'uid' => 'student',
124+
'name' => 'Student Name',
125+
'mail' => 'somestudent@example.org',
126+
'type' => 'student',
127+
],
128+
'admin' => [
129+
'password' => 'admin',
130+
'uid' => 'admin',
131+
'name' => 'Admin Name',
132+
'mail' => 'someadmin@example.org',
133+
'type' => 'employee',
134+
],
135+
];
136+
137+
// time to handle login responses; since this is a dummy example, we accept any data
138+
$badUserPass = false;
139+
if ($request->getMethod() === 'POST') {
140+
$username = $request->get('username');
141+
$password = $request->get('password');
142+
143+
if (!isset($users[$username]) || $users[$username]['password'] !== $password) {
144+
$badUserPass = true;
145+
} else {
146+
$user = $users[$username];
147+
148+
$session = new SymfonySession();
149+
if (!$session->getId()) {
150+
$session->start();
151+
}
152+
153+
$session->set('uid', $user['uid']);
154+
$session->set('name', $user['name']);
155+
$session->set('mail', $user['mail']);
156+
$session->set('type', $user['type']);
157+
158+
return new RunnableResponse([$httpUtils, 'redirectTrustedURL'], [$returnTo]);
159+
}
160+
}
161+
162+
// if we get this far, we need to show the login page to the user
163+
$t = new Template($this->config, 'exampleauth:authenticate.twig');
164+
$t->data['badUserPass'] = $badUserPass;
165+
$t->data['returnTo'] = $returnTo;
166+
167+
return $t;
168+
}
169+
170+
171+
/**
172+
* Redirect testpage.
173+
*
174+
* @param \Symfony\Component\HttpFoundation\Request $request The current request.
175+
*
176+
* @return \SimpleSAML\HTTP\RunnableResponse
177+
*/
178+
public function redirecttest(Request $request): RunnableResponse
179+
{
180+
/**
181+
* Request handler for redirect filter test.
182+
*/
183+
$stateId = $request->get('StateId');
184+
if ($stateId === null) {
185+
throw new Error\BadRequest('Missing required StateId query parameter.');
186+
}
187+
188+
/** @var array $state */
189+
$state = $this->authState::loadState($stateId, 'exampleauth:redirectfilter-test');
190+
$state['Attributes']['RedirectTest2'] = ['OK'];
191+
192+
return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
193+
}
194+
195+
196+
/**
197+
* Resume testpage.
198+
*
199+
* @param \Symfony\Component\HttpFoundation\Request $request The current request.
200+
*
201+
* @return \SimpleSAML\HTTP\RunnableResponse
202+
*/
203+
public function resume(Request $request): RunnableResponse
204+
{
205+
/**
206+
* This page serves as the point where the user's authentication
207+
* process is resumed after the login page.
208+
*
209+
* It simply passes control back to the class.
210+
*/
211+
return new RunnableResponse([External::class, 'resume'], [$request]);
212+
}
213+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
exampleauth-authpage:
2+
path: /authpage
3+
defaults: { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::authpage' }
4+
exampleauth-redirecttest:
5+
path: /redirecttest
6+
defaults: { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::redirecttest' }
7+
exampleauth-resume:
8+
path: /resume
9+
defaults: { _controller: 'SimpleSAML\Module\exampleauth\Controller\ExampleAuth::resume' }

0 commit comments

Comments
 (0)