-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #95 from TAMULib/development
SAML User support and test fixes
- Loading branch information
Showing
11 changed files
with
309 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
<?php | ||
namespace Pipit\Classes\Data; | ||
|
||
use Pipit\Classes\Exceptions\ConfigurationException; | ||
use OneLogin\Saml2\Auth; | ||
|
||
class UserSAML extends UserDB { | ||
use \Pipit\Traits\FileConfiguration; | ||
|
||
/** @var \Pipit\Interfaces\DataRepository $usersRepo A DataRepository representing the app's Users (assumes existence of 'username' and 'issaml' fields) */ | ||
private $usersRepo; | ||
|
||
/** @var mixed[] $settings An array of settings for onelogin configuration */ | ||
private $settings; | ||
|
||
private const CONFIG_FILE = "user.saml"; | ||
private const DEFAULT_USERNAME_MAPPING = "netid"; | ||
|
||
/** | ||
* Instantiates a new UserSAML by negotiating the login process with a configured SAML Server | ||
* @param mixed[] $inputData The input data from the request | ||
* @param \Pipit\Interfaces\DataRepository $usersRepo A DataRepository representing the app's Users (assumes existence of 'username' and 'issaml' fields) | ||
*/ | ||
public function __construct($inputData, $usersRepo) { | ||
|
||
$this->loadSettings(); | ||
|
||
parent::__construct(); | ||
|
||
$appConfig = $this->getAppConfiguration(); | ||
$redirectUrl = (array_key_exists('redirect', $this->settings) && is_string($this->settings['redirect'])) ? | ||
$this->settings['redirect']:$appConfig['PATH_HTTP']; | ||
|
||
if (!empty($inputData['SAMLResponse'])) { | ||
$this->usersRepo = $usersRepo; | ||
|
||
if (is_string($inputData['SAMLResponse']) && $this->processLogIn()) { | ||
header("Location:".$redirectUrl); | ||
} | ||
} elseif (!$this->isLoggedIn() && !isset($inputData['action'])) { | ||
$this->initiateLogIn(); | ||
} | ||
} | ||
|
||
/** | ||
* Verifies that all required settings have some value | ||
* @return boolean | ||
*/ | ||
protected function checkSettings() { | ||
return (is_array($this->settings) | ||
&& !empty($this->settings['sp']['entityId']) | ||
&& !empty($this->settings['sp']['assertionConsumerService']['url']) | ||
&& !empty($this->settings['sp']['singleLogoutService']['url']) | ||
&& !empty($this->settings['idp']['entityId']) | ||
&& !empty($this->settings['idp']['singleSignOnService']['url']) | ||
&& !empty($this->settings['idp']['singleLogoutService']['url']) | ||
&& !empty($this->settings['idp']['singleLogoutService']['responseUrl']) | ||
&& !empty($this->settings['idp']['x509cert'])); | ||
} | ||
|
||
/** | ||
* Loads the settings from Pipit configuration and merges them with the defaults from onelogin | ||
* @return void | ||
*/ | ||
protected function loadSettings() { | ||
$configurationFileName = self::CONFIG_FILE; | ||
$config = null; | ||
if ($this->configurationFileExists($configurationFileName)) { | ||
$config = $this->getConfigurationFromFileName($configurationFileName); | ||
} else { | ||
throw new ConfigurationException("SAML config file does not exist"); | ||
} | ||
$settings = []; | ||
$defaultSettingsFile = __DIR__."/../../../../../../onelogin/php-saml/settings_example.php"; | ||
if (is_file($defaultSettingsFile)) { | ||
require($defaultSettingsFile); | ||
} else { | ||
throw new ConfigurationException("Default Settings file is missing. Have you run composer install?"); | ||
} | ||
$this->settings = array_replace($settings, $config); | ||
|
||
if (!$this->checkSettings()) { | ||
throw new ConfigurationException("Invalid SAML settings. Please check ".$configurationFileName); | ||
} | ||
} | ||
|
||
/** | ||
* Processes the SAML response and uses it to login a user | ||
* @return boolean Returns true on successful login, false on everything else | ||
*/ | ||
public function processLogIn() { | ||
$auth = new Auth($this->settings); | ||
|
||
if (isset($_SESSION) && isset($_SESSION['AuthNRequestID'])) { | ||
$requestId = $_SESSION['AuthNRequestID']; | ||
} else { | ||
$requestId = null; | ||
} | ||
|
||
$auth->processResponse($requestId); | ||
unset($_SESSION['AuthNRequestID']); | ||
|
||
$errors = $auth->getErrors(); | ||
|
||
if (!empty($errors)) { | ||
throw new \RuntimeException("SAML error: ".implode(', ',$errors)); | ||
} | ||
|
||
if (!$auth->isAuthenticated()) { | ||
throw new \RuntimeException("SAML error: Not authenticated"); | ||
} | ||
|
||
$userNameField = (is_array($this->settings['claims']) && array_key_exists('username', $this->settings['claims'])) ? $this->settings['claims']['username'] : self::DEFAULT_USERNAME_MAPPING; | ||
|
||
if (!array_key_exists($userNameField, $auth->getAttributes())) { | ||
throw new \RuntimeException("SAML error: {$userNameField} claim not present in SAML response"); | ||
} | ||
|
||
$samlUserName = $auth->getAttributes()[$userNameField][0]; | ||
return $this->processUser($samlUserName); | ||
} | ||
|
||
/** | ||
* Uses the provided username to find/create a matching local user and initiate the session | ||
* @param string $userName | ||
* @return boolean Returns true on success, false for anything else | ||
*/ | ||
protected function processUser($userName) { | ||
$tusers = $this->usersRepo; | ||
//find an existing, active user or create a new one | ||
$user = $tusers->searchAdvanced(array("username"=>$userName)); | ||
if ($user && is_array($user)) { | ||
if ($user[0]['inactive'] == 0) { | ||
$userId = $user[0]['id']; | ||
} | ||
} elseif (!empty($userName)) { | ||
$userId = $tusers->add(array("username"=>$userName,"issaml"=>1)); | ||
} | ||
if (!empty($userId)) { | ||
session_regenerate_id(true); | ||
session_start(); | ||
$this->setSessionUserId($userId); | ||
$this->buildProfile(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Triggers the SAML login request | ||
* @return void | ||
*/ | ||
public function initiatelogIn() { | ||
$auth = new Auth($this->settings); | ||
$auth->login(); | ||
} | ||
|
||
/** | ||
* Terminates the local session and Initiates the SAML logout process | ||
*/ | ||
public function logOut() { | ||
parent::logOut(); | ||
$auth = new Auth($this->settings); | ||
$auth->logout(); | ||
} | ||
|
||
/** | ||
* Overrides the inherited UserDB login mechanism to guarantee no action/success | ||
*/ | ||
public function logIn($username,$password) { | ||
return false; | ||
} | ||
} | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
namespace TestFiles\Classes\Data; | ||
use Pipit\Interfaces as Interfaces; | ||
|
||
class TestUserSAML implements Interfaces\User { | ||
public function logOut() { | ||
return true; | ||
} | ||
|
||
public function logIn($username,$password) { | ||
return true; | ||
} | ||
|
||
public function isLoggedIn() { | ||
return true; | ||
} | ||
|
||
public function getProfileValue($field) { | ||
return null; | ||
} | ||
|
||
public function getProfile() { | ||
return []; | ||
} | ||
|
||
public function isAdmin() { | ||
return false; | ||
} | ||
} | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
[claims] | ||
;Optionally override the default 'netid' claim mapping | ||
;username = | ||
|
||
[sp] | ||
;Identifier of the SP entity (must be a URI) | ||
entityId = PATH_HTTP | ||
;Where the <AuthnResponse> message MUST be returned | ||
assertionConsumerService[url] = PATH_HTTP | ||
;Where the <Response> from the IdP will be returned | ||
singleLogoutService[url] = PATH_HTTP | ||
|
||
[idp] | ||
entityId = | ||
singleSignOnService[url] = | ||
singleLogoutService[url] = | ||
singleLogoutService[responseUrl] = PATH_HTTP | ||
x509cert = | ||
|
||
;The default post-login redirect behavior is to redirect to the application's home page | ||
;This can be overridden by defining a custom redirect path | ||
;redirect = PATH_HTTP |
Oops, something went wrong.