saml_idp-8.x-1.0-alpha1/src/Auth/Source/External.php
src/Auth/Source/External.php
<?php
// This class is not namespaced as simplesamlphp does not namespace module classes.
use Drupal\Core\DrupalKernel;
use Drupal\Core\Url;
use SimpleSAML\Utils\HTTP;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Request;
/**
* Drupalath authentication source for using Drupal's login page.
*
* Original author: SIL International, Steve Moitozo, <steve_moitozo@sil.org>, http://www.sil.org
* Modified by: Brad Jones, <brad@bradjonesllc.com>, http://bradjonesllc.com
*
* This class is an authentication source which is designed to
* more closely integrate with a Drupal site. It causes the user to be
* delivered to Drupal's login page, if they are not already authenticated.
*
* Original source: http://code.google.com/p/drupalauth/
*/
class sspmod_drupalauth_Auth_Source_External extends SimpleSAML_Auth_Source {
/**
* Dependency injection container
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
/**
* The Drupal application kernel
*
* @var DrupalKernel
*/
private $kernel;
/**
* Bootstrap Drupal, e.g., if we're being called from simplesamlphp.
* @see index.php
*/
protected function bootstrap() {
try {
$this->container = \Drupal::getContainer();
}
catch (Exception $e) {
$finder = new \DrupalFinder\DrupalFinder();
if ($finder->locateRoot(getcwd())) {
$classloader = require $finder->getVendorDir() . '/autoload.php';
}
else {
throw new Exception('Could not find Drupal root.');
}
DrupalKernel::bootEnvironment($finder->getDrupalRoot());
$request = Request::createFromGlobals();
$request->server->set('SCRIPT_FILENAME', '/index.php');
$this->kernel = DrupalKernel::createFromRequest($request, $classloader, 'prod');
$this->kernel->boot();
$this->kernel->preHandle($request);
$this->container = $this->kernel->getContainer();
}
return $this;
}
/**
* Constructor for this authentication source.
*
* @param array $info Information about this authentication source.
* @param array $config Configuration.
*/
public function __construct($info, $config) {
assert('is_array($info)');
assert('is_array($config)');
/* Call the parent constructor first, as required by the interface. */
parent::__construct($info, $config);
$this->bootstrap();
}
/**
* Retrieve attributes for the user.
*
* @param array $state
* The state, in case different attributes are needed for different
* service providers.
*
* @return array|NULL The user's attributes, or NULL if the user isn't authenticated.
*/
private function getUser(&$state) {
/* @var \Drupal\Core\Session\AccountInterface $user */
$user = $this->container->get('current_user')->getAccount();
if (!$user->isAnonymous()) {
$site_config = $this->container->get('config.factory')->get('system.site');
$user_entity = User::load($user->id());
$attributes = array(
'uid' => array($user->getDisplayName()),
// Return the UUID as it's guaranteed not to change and reduces clashes.
'uniqueIdentifier' => array('drupal:' . $user_entity->uuid()),
'displayName' => array($user->getDisplayName()),
'eduPersonPrincipalName' => array($user->getDisplayName() . '@drupal.' . $site_config->get('uuid')),
'mail' => array($user->getEmail()),
);
$this->container->get('module_handler')->alter('saml_idp_attributes', $attributes, $user_entity, $state);
return $attributes;
}
return NULL;
}
/**
* Log in using an external authentication helper.
*
* @param array &$state Information about the current authentication.
*/
public function authenticate(&$state) {
assert('is_array($state)');
if ($attributes = $this->getUser($state)) {
/*
* The user is already authenticated.
*
* Add the users attributes to the $state-array, and return control
* to the authentication process.
*/
$state['Attributes'] = $attributes;
return;
}
/*
* The user isn't authenticated. We therefore need to
* send the user to the login page.
*/
/*
* First we add the identifier of this authentication source
* to the state array, so that we know where to resume.
*/
$state['drupalauth:AuthID'] = $this->authId;
/*
* We need to save the $state-array, so that we can resume the
* login process after authentication.
*
* Note the second parameter to the saveState-function. This is a
* unique identifier for where the state was saved, and must be used
* again when we retrieve the state.
*
* The reason for it is to prevent attacks where the user takes a
* $state-array saved in one location and restores it in another location,
* and thus bypasses steps in the authentication process.
*/
$stateId = SimpleSAML_Auth_State::saveState($state, 'drupalauth:External', TRUE);
/*
* Now we generate an URL the user should return to after authentication.
* We assume that whatever authentication page we send the user to has an
* option to return the user to a specific page afterwards.
*
* Drupal will not redirect to an external URL. So, build a relative one.
*/
$returnTo = Url::fromRoute('saml_idp.resume', array('State' => $stateId))->toString();
/*
* Get the URL of the authentication page.
*/
$login = Url::fromRoute('user.login')->toString();
/*
* The redirect to the authentication page.
*
* Note the 'ReturnTo' parameter. This must most likely be replaced with
* the real name of the parameter for the login page.
*/
HTTP::redirectTrustedURL($login, array(
'destination' => $returnTo,
));
/*
* The redirect function never returns, so we never get this far.
*/
assert('FALSE');
}
/**
* Resume authentication process.
*
* This function resumes the authentication process after the user has
* entered his or her credentials.
*
* @see \Drupal\saml_idp\Resume::resume()
*
* @param array &$state The authentication state.
*/
public static function resume() {
/*
* First we need to restore the $state-array. We should have the identifier for
* it in the 'State' request parameter.
*
* @todo - Handle Request injection if this is on a CLI test?
*/
$container = \Drupal::getContainer();
$request = $container->get('request_stack')->getCurrentRequest();
if (!$stateId = $request->query->get('State')) {
throw new SimpleSAML_Error_BadRequest('Missing "State" parameter.');
}
/*
* Once again, note the second parameter to the loadState function. This must
* match the string we used in the saveState-call above.
*/
$state = SimpleSAML_Auth_State::loadState($stateId, 'drupalauth:External');
/*
* Now we have the $state-array, and can use it to locate the authentication
* source.
*/
$source = SimpleSAML_Auth_Source::getById($state['drupalauth:AuthID']);
if ($source === NULL) {
/*
* The only way this should fail is if we remove or rename the authentication source
* while the user is at the login page.
*/
throw new SimpleSAML_Error_Exception('Could not find authentication source.');
}
/*
* Make sure that we haven't switched the source type while the
* user was at the authentication page. This can only happen if we
* change config/authsources.php while an user is logging in.
*/
if (! ($source instanceof self)) {
throw new SimpleSAML_Error_Exception('Authentication source type changed.');
}
/*
* OK, now we know that our current state is sane. Time to actually log the user in.
*
* First we check that the user is actually logged in, and didn't simply skip the login page.
*/
$attributes = $source->getUser($state);
if ($attributes === NULL) {
/*
* The user isn't authenticated.
*
* Here we simply throw an exception, but we could also redirect the user back to the
* login page.
*/
throw new SimpleSAML_Error_Exception('User not authenticated after login attempt.');
}
/*
* So, we have a valid user. Time to resume the authentication process where we
* paused it in the authenticate()-function above.
*/
$state['Attributes'] = $attributes;
self::completeAuth($state);
/*
* The completeAuth-function never returns, so we never get this far.
*/
assert('FALSE');
}
/**
* {@inheritdoc}
*/
public function reauthenticate(array &$state) {
parent::reauthenticate($state);
// Fire hook for custom login actions.
\Drupal::getContainer()->get('module_handler')->invokeAll('saml_idp_reauthenticated', [$state]);
}
/**
* {@inheritdoc}
*/
public static function completeAuth(&$state) {
assert('is_array($state)');
assert('array_key_exists("LoginCompletedHandler", $state)');
SimpleSAML_Auth_State::deleteState($state);
$func = $state['LoginCompletedHandler'];
assert('is_callable($func)');
// Fire hook for custom login actions
// (This function is otherwise identical to the parent)
\Drupal::getContainer()->get('module_handler')->invokeAll('saml_idp_login_completed', [$state]);
call_user_func($func, $state);
assert(FALSE);
}
/**
* This function is called when the user start a logout operation, for example
* by logging out of a SP that supports single logout.
*
* @param array &$state The logout state array.
*/
public function logout(&$state) {
assert('is_array($state)');
if ($this->bootstrap()->getUser($state)) {
// We may not have a session started, but SessionManager will throw
// an error if it tries to destroy an uninitialized session.
$this->container->get('session_manager')->start();
user_logout();
}
}
}
