Skip to content

Commit

Permalink
Merge pull request #126 from silinternational/feature/secureuser
Browse files Browse the repository at this point in the history
Add SecureUser ID Store
  • Loading branch information
Baggerone authored Jan 18, 2022
2 parents db02509 + 1fa099b commit c1ec71c
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 31 deletions.
3 changes: 3 additions & 0 deletions application/common/components/IdStoreBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Sil\Idp\IdSync\common\components\adapters\GoogleSheetsIdStore;
use Sil\Idp\IdSync\common\components\adapters\SagePeopleIdStore;
use Sil\Idp\IdSync\common\components\adapters\WorkdayIdStore;
use Sil\Idp\IdSync\common\components\adapters\SecureUserIdStore;
use Sil\Idp\IdSync\common\components\adapters\fakes\FakeIdStore;
use Sil\Idp\IdSync\common\interfaces\IdStoreInterface;
use Sil\Idp\IdSync\common\models\User;
Expand All @@ -15,12 +16,14 @@ abstract class IdStoreBase extends Component implements IdStoreInterface
const ADAPTER_GOOGLE_SHEETS = 'googlesheets';
const ADAPTER_WORKDAY = 'workday';
const ADAPTER_SAGE_PEOPLE = 'sagepeople';
const ADAPTER_SECURE_USER = 'secureuser';

protected static $adapters = [
self::ADAPTER_FAKE => FakeIdStore::class,
self::ADAPTER_GOOGLE_SHEETS => GoogleSheetsIdStore::class,
self::ADAPTER_WORKDAY => WorkdayIdStore::class,
self::ADAPTER_SAGE_PEOPLE => SagePeopleIdStore::class,
self::ADAPTER_SECURE_USER => SecureUserIdStore::class,
];

public static function getAdapterClassFor($adapterName)
Expand Down
164 changes: 164 additions & 0 deletions application/common/components/adapters/SecureUserIdStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php
namespace Sil\Idp\IdSync\common\components\adapters;

use Exception;
use GuzzleHttp\Client;
use InvalidArgumentException;
use Sil\Idp\IdSync\common\components\IdStoreBase;
use Sil\Idp\IdSync\common\models\User;
use yii\helpers\Json;

class SecureUserIdStore extends IdStoreBase
{
public $apiUrl = null;
public $apiKey = null;
public $apiSecret = null;

public $timeout = 45; // Timeout in seconds (per call to ID Store API).

protected $httpClient = null;

public function init()
{
$requiredProperties = [
'apiUrl',
'apiKey',
'apiSecret',
];
foreach ($requiredProperties as $requiredProperty) {
if (empty($this->$requiredProperty)) {
throw new InvalidArgumentException(sprintf(
'No %s was provided.',
$requiredProperty
), 1642083101);
}
}

parent::init();
}

public static function getFieldNameMap(): array
{
return [
'employee_number' => User::EMPLOYEE_ID,
'first_name' => User::FIRST_NAME,
'last_name' => User::LAST_NAME,
'display_name' => User::DISPLAY_NAME,
'email' => User::EMAIL,
'username' => User::USERNAME,
'locked' => User::LOCKED,
'manager_email' => User::MANAGER_EMAIL,
];
}

/**
* Get the specified user's information. Note that inactive users will be
* treated as non-existent users.
*
* @param string $employeeId The Employee ID.
* @return User|null Information about the specified user, or null if no
* such active user was found.
* @throws Exception
*/
public function getActiveUser(string $employeeId)
{
$allUsers = $this->getAllActiveUsers();
foreach ($allUsers as $user) {
if ((string)$user->getEmployeeId() === (string)$employeeId) {
return $user;
}
}
return null;
}

/**
* Get a list of users' information (containing at least an Employee ID) for
* all users changed since the specified time.
*
* @param int $unixTimestamp The time (as a UNIX timestamp).
* @return User[]
* @throws Exception
*/
public function getUsersChangedSince(int $unixTimestamp)
{
throw new Exception(__FUNCTION__ . ' not yet implemented');
}

/**
* Get information about each of the (active) users.
*
* @return User[] A list of Users.
* @throws Exception
*/
public function getAllActiveUsers()
{
$client = $this->getHttpClient();

$api_sig = hash_hmac('sha256', time() . $this->apiKey, $this->apiSecret);

$response = $client->get($this->apiUrl, [
'connect_timeout' => $this->timeout,
'headers' => [
'Accept' => 'application/json',
'x-api-key' => $this->apiKey,
'x-auth-hmac-sha256' => $api_sig,
],
'http_errors' => false,
]);

$statusCode = (int)$response->getStatusCode();
if (($statusCode >= 200) && ($statusCode <= 299)) {
$allUsersInfo = Json::decode($response->getBody());
} else {
throw new Exception(sprintf(
'Unexpected response (%s %s): %s',
$response->getStatusCode(),
$response->getReasonPhrase(),
$response->getBody()
), 1642083102);
}

if (! is_array($allUsersInfo)) {
throw new Exception(sprintf(
'Unexpected result when getting all active users: %s',
var_export($allUsersInfo, true)
), 1642083103);
}

$allActiveUsersInfo = array_filter(
$allUsersInfo,
function ($user) {
return ($user[User::ACTIVE] === true);
}
);

return array_map(
function ($entry) {
// Unset 'active', since ID Stores only return active users.
unset($entry[User::ACTIVE]);

// Convert the resulting user info to a User.
return self::getAsUser($entry);
},
$allActiveUsersInfo
);
}

/**
* Get the HTTP client to use.
*
* @return Client
*/
protected function getHttpClient()
{
if ($this->httpClient === null) {
$this->httpClient = new Client();
}
return $this->httpClient;
}

public function getIdStoreName(): string
{
return 'SecureUser';
}
}
58 changes: 27 additions & 31 deletions application/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions application/features/behat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ default:
tags: '~@usersChangedSince&&~@canUpdateLastSynced'
paths: [ "%paths.base%/../features/id-store-integration.feature" ]
contexts: [ Sil\Idp\IdSync\Behat\Context\SagePeopleIntegrationContext ]
secure_user_integration_features:
filters:
# removing whitespace to silence faulty deprecation warning
# see https://github.com/Behat/Gherkin/pull/215#issuecomment-944115733
tags: '~@usersChangedSince&&~@canUpdateLastSynced'
paths: [ "%paths.base%/../features/id-store-integration.feature" ]
contexts: [ Sil\Idp\IdSync\Behat\Context\SecureUserIntegrationContext ]
notification_features:
paths: [ "%paths.base%/../features/notification.feature" ]
contexts: [ Sil\Idp\IdSync\Behat\Context\NotificationContext ]
Loading

0 comments on commit c1ec71c

Please sign in to comment.