You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
4.4 KiB

<?php
declare(strict_types=1);
namespace authkit2\Http\Controllers;
use Illuminate\Http\Request;
use authkit2\Models\Token;
/**
* Methods for handling user authentication operations.
*/
class AuthenticationController extends Controller
{
/**
* OIDC flow to mediate the actual login process and exchanges.
* @var \authkit2\Oidc\Flows\UserFlow
*/
protected $user_flow;
public function __construct(\authkit2\Oidc\Flows\UserFlow $user_flow)
{
$this->user_flow = $user_flow;
}
/**
* Start the login flow for a user.
*
* Redirects the user to the SSO service
*
* @return mixed
*/
public function login()
{
// TODO: Pass in 'previous' URL from session so we can redirect back
// if we were redirected to login from a guard?
return redirect($this->user_flow->getRedirectUrl(config('authkit.authn.openid.redirect_uri'), config('authkit.authn.scopes')));
}
/**
* Handle the response from the SSO service.
*
* Exchange the code for a token and fetches basic user information.
* Attempts to log the user into this app, and creates them if they
* don't exist. Then redirects the user to the configured post_login url.
*
* @param Request $request
* @return mixed
*/
public function callback(Request $request)
{
$user_class = (string)config('auth.providers.users.model');
// Verify the passed in state value
$this->user_flow->validateState($request->state);
// TODO: Check for error response
// Exchange the code for a token
$oidc_token = $this->user_flow->exchangeCodeForToken($request->code, config('authkit.authn.openid.redirect_uri'));
$user_info = $oidc_token->getUserInfo();
// Try and find a token for that user id
$token = Token::where('id', $oidc_token->getUserId())->first();
// If that failed
if (!isset($token))
{
// No token for that user, either create them or migrate
$register_event_result = event(new \authkit2\Events\UserRegistration(null, $user_info));
if (sizeof($register_event_result) == 0)
{
// If there were no register event handlers then just assume we're using laravel default
// stuff and create a user for them.
$user = new $user_class();
$user->name = $user_info['name'];
$user->email = $user_info['email'];
$user->save();
}
else
{
// Otherwise, expect one returned by the event handlers.
$user = null;
foreach ($register_event_result as $result)
{
if (is_object($result))
{
$user = $result;
}
}
}
// TODO: Log a useful error message in these cases
if (!isset($user))
{
abort(500);
// We die() after each abort to help the static analyzer (phan)
// along, otherwise it doesn't realize that abort ends execution
// and is worried that we've hit the below code in an invalid state.
die();
}
elseif (!method_exists($user, 'save'))
{
abort(500);
die();
}
elseif (!method_exists($user, 'getAuthIdentifierName'))
{
abort(500);
die();
}
$token = new Token();
$token->id = $oidc_token->getUserId();
$token->access_token = $oidc_token->getAccessToken();
$refresh_token = $oidc_token->getRefreshToken();
if (!isset($refresh_token))
{
abort(500);
die();
}
$token->refresh_token = $refresh_token;
$token->user_id = $user->{$user->getAuthIdentifierName()};
$token->save();
}
else
{
$user = new $user_class();
$user = $user_class::where($user->getAuthIdentifierName(), $token->user_id)->first();
}
\Auth::login($user);
$login_event_result = event(new \authkit2\Events\UserLogin($user, $user_info));
if (!sizeof($login_event_result))
{
if (!isset($user) || !method_exists($user, 'save'))
{
// TODO: Log a useful error message
abort(500);
die();
}
// If nothing handled the login event, assume we're using laravel default
// everything and just go ahead and update the name/email
$user->name = $user_info['name'];
$user->email = $user_info['email'];
$user->save();
}
return redirect(url(config('authkit.authn.urls.post_login')));
}
/**
* Explicitly log out of this application and the SSO service.
*
* @return mixed
*/
public function logout()
{
event(new \authkit2\Events\UserLogout(\Auth::user()));
// Log out locally
\Auth::logout();
// Redirect to log out remotely as well
return redirect($this->user_flow->getLogoutUrl(url(config('authkit.authn.urls.post_logout'))));
}
}