|
| 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 | +} |
0 commit comments