checkForExpiredPassword($request)) { return $response; } } catch (DatabaseException $e) { // Database isn't ready, carry on. } return $delegate($request); } /** * Check if the just authenticated member has the password expired. * Returns a response if the current request should not be * processed as usual. * * @param HTTPRequest $request * * @return HTTPResponse|null */ protected function checkForExpiredPassword(HTTPRequest $request): ?HTTPResponse { $session = $request->getSession(); if ($session && $session->get(static::SESSION_KEY_ALLOW_CURRENT_REQUEST)) { // allow current request and skip the expiration check, but for only the current // request, so we're deleting the flag from the session so it's not affecting other // requests. // This flag would usually be set from within $handler->authenticateRequest() $session->clear(static::SESSION_KEY_ALLOW_CURRENT_REQUEST); return null; } $user = Security::getCurrentUser(); if ($user && $user->isPasswordExpired()) { if ($response = $this->handleExpiredPassword($request)) { return $response; } } return null; } /** * Check if we have a redirect to a password change form registered * and redirect there if possible. * Otherwise, deauthenticate the user by resetting it for this request, * since we should treat ones with expired passwords as unauthorised. * * @param HTTPRequest $request * * @return HTTPResponse|null */ protected function handleExpiredPassword(HTTPRequest $request): ?HTTPResponse { $session = $request->getSession(); $sessionRedirectUrl = $session->get(static::SESSION_KEY_REDIRECT); $defaultRedirectUrl = static::config()->get('default_redirect'); if ($sessionRedirectUrl || $defaultRedirectUrl) { $redirectUrl = $this->absoluteUrl($sessionRedirectUrl ?? $defaultRedirectUrl); } else { $redirectUrl = null; } if (!$session || !$redirectUrl) { Security::setCurrentUser(null); return null; } $currentUrl = $this->absoluteUrl($request->getURL(true)); if ($currentUrl === $redirectUrl) { return null; } $allowedStartswith = static::config()->get('whitelisted_url_startswith'); if (is_array($allowedStartswith)) { foreach ($allowedStartswith as $pattern) { $startswith = $this->absoluteUrl($pattern); if (strncmp($currentUrl ?? '', $startswith ?? '', strlen($startswith ?? '')) === 0) { return null; } } } return $this->redirectOrForbid($request, $redirectUrl); } /** * Builds an absolute URL for the given path, adds base url * if the path configured as absolute * * @param string $url * * @return string */ protected static function absoluteUrl($url): string { if (substr($url ?? '', 0, 1) === '/' && substr($url ?? '', 1, 1) !== '/') { // add BASE_URL explicitly if not absolute $url = Controller::join_links(Director::absoluteBaseURL(), $url); } else { $url = Director::absoluteURL($url) ?: Controller::join_links(Director::absoluteBaseURL(), $url); } if (substr($url ?? '', -1) === '/') { $url = substr($url ?? '', 0, -1); } return $url; } /** * Returns a redirect to the URL if text/html is acceptable, otherwise * deauthenticates the current request by Security::setCurrentUser(null) * * @param HTTPRequest $request * @param string $redirectUrl * * @return HTTPResponse|null */ private function redirectOrForbid(HTTPRequest $request, $redirectUrl): ?HTTPResponse { $acceptableTypes = $request->getAcceptMimetypes(); $allowedTypes = static::config()->get('mimetypes_allowing_redirect') ?? []; if (count(array_intersect($allowedTypes ?? [], $acceptableTypes)) > 0) { $redirectAllowed = true; } else { // if browser didn't send the Accept header // with mimetypes, let's redirect anyway $redirectAllowed = count($acceptableTypes ?? []) === 0; } if ($redirectAllowed) { $response = new HTTPResponse(); $response->redirect($redirectUrl); return $response; } Security::setCurrentUser(null); return null; } /** * Preserve the password change URL in the session * That URL is to be redirected to to force users change expired passwords * * @param Session $session Session where we persist the redirect URL * @param string $url change password form address */ public static function setRedirect(Session $session, $url) { $session->set(static::SESSION_KEY_REDIRECT, $url); } /** * Allow the current request to be finished without password expiration check * * @param Session $session Session where we persist the redirect URL */ public static function allowCurrentRequest(Session $session) { $session->set(static::SESSION_KEY_ALLOW_CURRENT_REQUEST, true); } }