Skip to content
Snippets Groups Projects
Unverified Commit 4602c708 authored by Marko Ivancic's avatar Marko Ivancic Committed by GitHub
Browse files

Add data store, tracker and provider (#4)

* Includes basic functionality to enable data tracking, persisting and fetching.
parent 9b028ae5
No related branches found
No related tags found
1 merge request!1Relate histories
Showing
with 855 additions and 336 deletions
# simplesamlphp-module-accounting
SimpleSAMLphp module providing user accounting functionality
SimpleSAMLphp module providing user accounting functionality.
**Module is in development and is not production ready.**
## TODO
- [ ] MySQL store
- [ ] MySQL Job Store
- [ ] Cron hooks
\ No newline at end of file
- [x] Data stores
- [ ] Cron hooks
- [ ] Profile page UI
- [ ] Translation
## Tests
To run phpcs, psalm and phpunit:
```shell
composer pre-commit
```
......@@ -27,7 +27,6 @@
"require": {
"php": "^7.4 || ^8.0",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-pdo_sqlite": "*",
"doctrine/dbal": "^3",
"psr/log": "^1|^2|^3",
......
This diff is collapsed.
......@@ -3,7 +3,9 @@
declare(strict_types=1);
use SimpleSAML\Module\accounting\ModuleConfiguration;
use SimpleSAML\Module\accounting\Providers;
use SimpleSAML\Module\accounting\Stores;
use SimpleSAML\Module\accounting\Trackers;
$config = [
/**
......@@ -12,42 +14,102 @@ $config = [
*
* Examples:
* urn:oasis:names:tc:SAML:attribute:subject-id
* eduPersonUniqueId
* eduPersonTargetedID
* eduPersonPrincipalName
* eduPersonUniqueID
*/
ModuleConfiguration::OPTION_USER_ID_ATTRIBUTE => 'urn:oasis:names:tc:SAML:attribute:subject-id',
ModuleConfiguration::OPTION_USER_ID_ATTRIBUTE_NAME => 'urn:oasis:names:tc:SAML:attribute:subject-id',
/**
* Default authentication source which will be used when authenticating users in SimpleSAMLphp Profile Page.
*/
ModuleConfiguration::OPTION_DEFAULT_AUTHENTICATION_SOURCE => 'default-sp',
/**
* Accounting processing type. There are two possible types: 'synchronous' and 'asynchronous'.
* - 'synchronous': accounting processing will be performed during authentication itself (slower)
* - 'asynchronous': for each authentication event a new job will be created for later processing (faster,
* but requires setting up job storage and a cron entry).
*/
ModuleConfiguration::OPTION_ACCOUNTING_PROCESSING_TYPE =>
/**
* Synchronous option, meaning accounting processing will be performed during authentication itself
* (slower authentication).
*/
ModuleConfiguration\AccountingProcessingType::VALUE_SYNCHRONOUS,
/**
* Asynchronous option, meaning for each authentication event a new job will be created for later processing
* (faster authentication, but requires setting up job storage and a cron entry).
*/
//ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS,
/**
* Jobs store. Determines which of the available stores will be used to store jobs in case the 'asynchronous'
* accounting processing type was set.
* Jobs store class. In case of the 'asynchronous' accounting processing type, this determines which class
* will be used to store jobs. The class must implement Stores\Interfaces\JobsStoreInterface.
*/
ModuleConfiguration::OPTION_JOBS_STORE => Stores\Jobs\DoctrineDbal\JobsStore::class,
ModuleConfiguration::OPTION_JOBS_STORE =>
/**
* Default jobs store class which expects Doctrine DBAL compatible connection to be set below.
*/
Stores\Jobs\DoctrineDbal\Store::class,
/**
* Default data tracker and provider to be used for accounting and as a source for data display in SSP UI.
* This class must implement Trackers\Interfaces\AuthenticationDataTrackerInterface and
* Providers\Interfaces\AuthenticationDataProviderInterface
*/
ModuleConfiguration::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER =>
/**
* Track each authentication event for idp / sp / user combination, and any change in idp / sp metadata or
* released user attributes. Each authentication event record will have data used and released at the
* time of the authentication event (versioned idp / sp / user data). This tracker can also be
* used as an authentication data provider. It expects Doctrine DBAL compatible connection
* to be set below. Internally it uses store class
* Stores\Data\DoctrineDbal\DoctrineDbal\Versioned\Store::class.
*/
Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class,
/**
* Store connection for particular store. Can be used to set different connections for different stores.
* Additional trackers to run besides default data tracker. These trackers will typically only process and
* persist authentication data to proper data store, and won't be used to display data in SSP UI.
* These tracker classes must implement Trackers\Interfaces\AuthenticationDataTrackerInterface.
*/
ModuleConfiguration::OPTION_STORE_TO_CONNECTION_KEY_MAP => [
Stores\Jobs\DoctrineDbal\JobsStore::class => 'doctrine_dbal_pdo_mysql',
ModuleConfiguration::OPTION_ADDITIONAL_TRACKERS => [
// TODO mivanci at least one more tracker and its connection
// tracker-class
],
/**
* Store connections and their parameters.
*
* Any compatible Doctrine DBAL implementation can be used:
* https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html
* There are additional parameters for: table prefix.
* Examples for mysql and sqlite are provided below.
* Map of classes (stores, trackers, providers, ...) and connection keys, which defines which connections will
* be used. Value for connection key can be string, or it can be an array with two connection types as keys:
* master or slave. Master connection is single connection which will be used to write data to, and it
* must be set. If no slave connections are set, master will also be used to read data from. Slave
* connections are defined as array of strings. If slave connections are set, random one will
* be picked to read data from.
*/
ModuleConfiguration::OPTION_CLASS_TO_CONNECTION_MAP => [
/**
* Connection key to be used by jobs store class.
*/
Stores\Jobs\DoctrineDbal\Store::class => 'doctrine_dbal_pdo_mysql',
/**
* Connection key to be used by this data tracker and provider.
*/
Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class => [
ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_mysql',
ModuleConfiguration\ConnectionType::SLAVE => [
'doctrine_dbal_pdo_mysql',
],
],
],
/**
* Connections and their parameters.
*/
ModuleConfiguration::OPTION_ALL_STORE_CONNECTIONS_AND_PARAMETERS => [
ModuleConfiguration::OPTION_CONNECTIONS_AND_PARAMETERS => [
/**
* Examples for Doctrine DBAL compatible mysql and sqlite connection parameters are provided below (more info
* on https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html).
* There are additional parameters for: table prefix.
*/
'doctrine_dbal_pdo_mysql' => [
'driver' => 'pdo_mysql', // (string): The built-in driver implementation to use
'user' => 'user', // (string): Username to use when connecting to the database.
......@@ -58,7 +120,7 @@ $config = [
//'unix_socket' => 'unix_socet', // (string): Name of the socket used to connect to the database.
'charset' => 'utf8', // (string): The charset used when connecting to the database.
//'url' => 'mysql://user:secret@localhost/mydb?charset=utf8', // ...alternative way of providing parameters.
// Additional parameters not originaly avaliable in Doctrine DBAL
// Additional parameters not originally available in Doctrine DBAL
'table_prefix' => '', // (string): Prefix for each table.
],
'doctrine_dbal_pdo_sqlite' => [
......@@ -69,7 +131,7 @@ $config = [
// Mutually exclusive with path. path takes precedence.
//'url' => 'sqlite:////path/to/db.sqlite // ...alternative way of providing path parameter.
//'url' => 'sqlite:///:memory:' // ...alternative way of providing memory parameter.
// Additional parameters not originaly avaliable in Doctrine DBAL
// Additional parameters not originally available in Doctrine DBAL
'table_prefix' => '', // (string): Prefix for each table.
],
],
......
......@@ -23,6 +23,7 @@
<errorLevel type="suppress">
<directory name="config-templates" />
<directory name="tests/config-templates" />
<directory name="tests/attributemap" />
</errorLevel>
</UnusedVariable>
......
accounting-test:
path: /test
defaults: { _controller: 'SimpleSAML\Module\accounting\Controller\Test::test' }
accounting-setup:
path: /setup
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\Test::setup' }
accounting-job-runner:
path: /job-runner
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\Test::jobRunner' }
accounting-admin-configuration-status:
path: /admin/configuration/status
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\Admin\Configuration::status' }
accounting-user-personal-data:
path: /user/personal-data
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\User\Profile::personalData' }
accounting-user-connected-organizations:
path: /user/connected-organizations
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\User\Profile::connectedOrganizations' }
accounting-user-activity:
path: /user/activity
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\User\Profile::activity' }
accounting-user-logout:
path: /user/logout
methods:
- POST
defaults: { _controller: 'SimpleSAML\Module\accounting\Http\Controllers\User\Profile::logout' }
services:
# default configuration for services in *this* file
_defaults:
autowire: true
public: false
bind:
Psr\Log\LoggerInterface: '@accounting.logger'
SimpleSAML\Module\accounting\ModuleConfiguration:
class: SimpleSAML\Module\accounting\ModuleConfiguration
\ No newline at end of file
# Services
SimpleSAML\Module\accounting\:
resource: '../../src/*'
exclude: '../../src/{Http/Controllers}'
# Service aliases
accounting.logger:
class: SimpleSAML\Module\accounting\Services\Logger
# Controllers
SimpleSAML\Module\accounting\Http\Controllers\:
resource: '../../src/Http/Controllers/*'
tags: ['controller.service_arguments']
\ No newline at end of file
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Auth\Process;
use Psr\Log\LoggerInterface;
use SimpleSAML\Auth\ProcessingFilter;
use SimpleSAML\Module\accounting\Entities\Authentication\Event;
use SimpleSAML\Module\accounting\Entities\Authentication\State;
use SimpleSAML\Module\accounting\Exceptions\StoreException;
use SimpleSAML\Module\accounting\ModuleConfiguration;
use SimpleSAML\Module\accounting\Services\Logger;
use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder;
use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder;
use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface;
class Accounting extends ProcessingFilter
{
protected ModuleConfiguration $moduleConfiguration;
protected JobsStoreBuilder $jobsStoreBuilder;
protected LoggerInterface $logger;
protected AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder;
/**
* @param array $config
* @param mixed $reserved
* @param ModuleConfiguration|null $moduleConfiguration
* @param LoggerInterface|null $logger
* @param JobsStoreBuilder|null $jobsStoreBuilder
*/
public function __construct(
array &$config,
$reserved,
ModuleConfiguration $moduleConfiguration = null,
LoggerInterface $logger = null,
JobsStoreBuilder $jobsStoreBuilder = null,
AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder = null
) {
parent::__construct($config, $reserved);
$this->moduleConfiguration = $moduleConfiguration ?? new ModuleConfiguration();
$this->logger = $logger ?? new Logger();
$this->jobsStoreBuilder = $jobsStoreBuilder ?? new JobsStoreBuilder($this->moduleConfiguration, $this->logger);
$this->authenticationDataTrackerBuilder = $authenticationDataTrackerBuilder ??
new AuthenticationDataTrackerBuilder($this->moduleConfiguration, $this->logger);
}
/**
* @throws StoreException
*/
public function process(array &$state): void
{
try {
$authenticationEvent = new Event(new State($state));
if ($this->isAccountingProcessingTypeAsynchronous()) {
// Only create authentication event job for later processing...
$this->createAuthenticationEventJob($authenticationEvent);
return;
}
// Accounting type is synchronous, so do the processing right away...
$configuredTrackers = array_merge(
[$this->moduleConfiguration->getDefaultDataTrackerAndProviderClass()],
$this->moduleConfiguration->getAdditionalTrackers()
);
/** @var string $tracker */
foreach ($configuredTrackers as $tracker) {
($this->authenticationDataTrackerBuilder->build($tracker))->process($authenticationEvent);
}
} catch (\Throwable $exception) {
$message = sprintf('Accounting error, skipping... Error was: %s.', $exception->getMessage());
$this->logger->error($message, $state);
}
}
protected function isAccountingProcessingTypeAsynchronous(): bool
{
return $this->moduleConfiguration->getAccountingProcessingType() ===
ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS;
}
/**
* @throws StoreException
*/
protected function createAuthenticationEventJob(Event $authenticationEvent): void
{
($this->jobsStoreBuilder->build($this->moduleConfiguration->getJobsStoreClass()))
->enqueue(new Event\Job($authenticationEvent));
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Controller;
use Exception;
use SimpleSAML\Configuration;
use SimpleSAML\Locale\Translate;
use SimpleSAML\Module\accounting\ModuleConfiguration;
use SimpleSAML\Session;
use SimpleSAML\XHTML\Template;
use Symfony\Component\HttpFoundation\Request;
class Test
{
protected Configuration $configuration;
protected Session $session;
protected ModuleConfiguration $moduleConfiguration;
/**
* @param Configuration $configuration
* @param Session $session The current user session.
* @param ModuleConfiguration $moduleConfiguration
*/
public function __construct(
Configuration $configuration,
Session $session,
ModuleConfiguration $moduleConfiguration
) {
$this->configuration = $configuration;
$this->session = $session;
$this->moduleConfiguration = $moduleConfiguration;
}
/**
* @param Request $request
* @return Template
* @throws Exception
*/
public function test(Request $request): Template
{
$template = new Template($this->configuration, 'accounting:configuration.twig');
$template->data = [
'test' => Translate::noop('Accounting'),
'test_config' => $this->moduleConfiguration->getConfiguration()->getString('test_config'),
];
return $template;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities;
use DateTimeImmutable;
class Activity
{
protected ServiceProvider $serviceProvider;
protected User $user;
protected DateTimeImmutable $happenedAt;
public function __construct(
ServiceProvider $serviceProvider,
User $user,
DateTimeImmutable $happenedAt
) {
$this->serviceProvider = $serviceProvider;
$this->user = $user;
$this->happenedAt = $happenedAt;
}
/**
* @return ServiceProvider
*/
public function getServiceProvider(): ServiceProvider
{
return $this->serviceProvider;
}
/**
* @return User
*/
public function getUser(): User
{
return $this->user;
}
/**
* @return DateTimeImmutable
*/
public function getHappenedAt(): DateTimeImmutable
{
return $this->happenedAt;
}
}
<?php
namespace SimpleSAML\Module\accounting\Entities\Activity;
use SimpleSAML\Module\accounting\Entities\Activity;
class Bag
{
protected array $activities = [];
public function add(Activity $activity): void
{
$this->activities[] = $activity;
}
public function getAll(): array
{
return $this->activities;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities\Authentication;
use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload;
class Event extends AbstractPayload
{
protected State $state;
protected \DateTimeImmutable $happenedAt;
public function __construct(State $state, \DateTimeImmutable $happenedAt = null)
{
$this->state = $state;
$this->happenedAt = $happenedAt ?? new \DateTimeImmutable();
}
public function getState(): State
{
return $this->state;
}
public function getHappenedAt(): \DateTimeImmutable
{
return $this->happenedAt;
}
}
......@@ -2,16 +2,16 @@
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities\AuthenticationEvent;
namespace SimpleSAML\Module\accounting\Entities\Authentication\Event;
use SimpleSAML\Module\accounting\Entities\AuthenticationEvent;
use SimpleSAML\Module\accounting\Entities\Authentication\Event;
use SimpleSAML\Module\accounting\Entities\Bases\AbstractJob;
use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload;
use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
class Job extends AbstractJob
{
public function getPayload(): AuthenticationEvent
public function getPayload(): Event
{
return $this->validatePayload($this->payload);
}
......@@ -21,10 +21,10 @@ class Job extends AbstractJob
$this->payload = $this->validatePayload($payload);
}
protected function validatePayload(AbstractPayload $payload): AuthenticationEvent
protected function validatePayload(AbstractPayload $payload): Event
{
if (! ($payload instanceof AuthenticationEvent)) {
throw new UnexpectedValueException('AuthenticationEvent Job payload must be of type AuthenticationEvent.');
if (! ($payload instanceof Event)) {
throw new UnexpectedValueException('Event Job payload must be of type Event.');
}
return $payload;
......
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities\Authentication;
use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider;
use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
use SimpleSAML\Module\accounting\Helpers\NetworkHelper;
class State
{
public const KEY_ATTRIBUTES = 'Attributes';
public const KEY_AUTHENTICATION_INSTANT = 'AuthnInstant';
public const KEY_IDENTITY_PROVIDER_METADATA = 'IdPMetadata';
public const KEY_SOURCE = 'Source';
public const KEY_SERVICE_PROVIDER_METADATA = 'SPMetadata';
public const KEY_DESTINATION = 'Destination';
public const KEY_ACCOUNTING = 'accounting';
public const ACCOUNTING_KEY_CLIENT_IP_ADDRESS = 'client_ip_address';
protected string $identityProviderEntityId;
protected string $serviceProviderEntityId;
protected array $attributes;
protected \DateTimeImmutable $createdAt;
protected ?\DateTimeImmutable $authenticationInstant;
protected array $identityProviderMetadata;
protected array $serviceProviderMetadata;
protected ?string $clientIpAddress;
public function __construct(array $state, \DateTimeImmutable $createdAt = null)
{
$this->createdAt = $createdAt ?? new \DateTimeImmutable();
$this->identityProviderMetadata = $this->resolveIdentityProviderMetadata($state);
$this->identityProviderEntityId = $this->resolveIdentityProviderEntityId();
$this->serviceProviderMetadata = $this->resolveServiceProviderMetadata($state);
$this->serviceProviderEntityId = $this->resolveServiceProviderEntityId();
$this->attributes = $this->resolveAttributes($state);
$this->authenticationInstant = $this->resolveAuthenticationInstant($state);
$this->clientIpAddress = $this->resolveClientIpAddress($state);
}
protected function resolveIdentityProviderEntityId(): string
{
if (
!empty($this->identityProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID]) &&
is_string($this->identityProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID])
) {
return $this->identityProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID];
}
throw new UnexpectedValueException('IdP metadata array does not contain entity ID.');
}
protected function resolveServiceProviderEntityId(): string
{
if (
!empty($this->serviceProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID]) &&
is_string($this->serviceProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID])
) {
return $this->serviceProviderMetadata[AbstractProvider::METADATA_KEY_ENTITY_ID];
}
throw new UnexpectedValueException('SP metadata array does not contain entity ID.');
}
protected function resolveAttributes(array $state): array
{
if (empty($state[self::KEY_ATTRIBUTES]) || !is_array($state[self::KEY_ATTRIBUTES])) {
throw new UnexpectedValueException('State array does not contain user attributes.');
}
return $state[self::KEY_ATTRIBUTES];
}
public function getIdentityProviderEntityId(): string
{
return $this->identityProviderEntityId;
}
public function getServiceProviderEntityId(): string
{
return $this->serviceProviderEntityId;
}
public function getAttributes(): array
{
return $this->attributes;
}
public function getAttributeValue(string $attributeName): ?string
{
if (!empty($this->attributes[$attributeName]) && is_array($this->attributes[$attributeName])) {
return (string)reset($this->attributes[$attributeName]);
}
return null;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
protected function resolveAuthenticationInstant(array $state): ?\DateTimeImmutable
{
if (empty($state[self::KEY_AUTHENTICATION_INSTANT])) {
return null;
}
$authInstant = (string)$state[self::KEY_AUTHENTICATION_INSTANT];
try {
return new \DateTimeImmutable('@' . $authInstant);
} catch (\Throwable $exception) {
$message = sprintf(
'Unable to create DateTimeImmutable using AuthInstant value \'%s\'. Error was: %s.',
$authInstant,
$exception->getMessage()
);
throw new UnexpectedValueException($message);
}
}
public function getAuthenticationInstant(): ?\DateTimeImmutable
{
return $this->authenticationInstant;
}
protected function resolveIdentityProviderMetadata(array $state): array
{
if (
!empty($state[self::KEY_IDENTITY_PROVIDER_METADATA]) &&
is_array($state[self::KEY_IDENTITY_PROVIDER_METADATA])
) {
return $state[self::KEY_IDENTITY_PROVIDER_METADATA];
} elseif (!empty($state[self::KEY_SOURCE]) && is_array($state[self::KEY_SOURCE])) {
return $state[self::KEY_SOURCE];
}
throw new UnexpectedValueException('State array does not contain IdP metadata.');
}
protected function resolveServiceProviderMetadata(array $state): array
{
if (
!empty($state[self::KEY_SERVICE_PROVIDER_METADATA]) &&
is_array($state[self::KEY_SERVICE_PROVIDER_METADATA])
) {
return $state[self::KEY_SERVICE_PROVIDER_METADATA];
} elseif (!empty($state[self::KEY_DESTINATION]) && is_array($state[self::KEY_DESTINATION])) {
return $state[self::KEY_DESTINATION];
}
throw new UnexpectedValueException('State array does not contain SP metadata.');
}
/**
* @return array
*/
public function getIdentityProviderMetadata(): array
{
return $this->identityProviderMetadata;
}
/**
* @return array
*/
public function getServiceProviderMetadata(): array
{
return $this->serviceProviderMetadata;
}
protected function resolveClientIpAddress(array $state): ?string
{
return NetworkHelper::resolveClientIpAddress(
isset($state[self::KEY_ACCOUNTING][self::ACCOUNTING_KEY_CLIENT_IP_ADDRESS]) ?
(string)$state[self::KEY_ACCOUNTING][self::ACCOUNTING_KEY_CLIENT_IP_ADDRESS]
: null
);
}
/**
* @return string|null
*/
public function getClientIpAddress(): ?string
{
return $this->clientIpAddress;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities;
use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload;
class AuthenticationEvent extends AbstractPayload
{
protected array $state;
public function __construct(array $state)
{
$this->state = $state;
}
public function getState(): array
{
return $this->state;
}
}
......@@ -4,22 +4,23 @@ declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities\Bases;
use DateTimeImmutable;
use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface;
abstract class AbstractJob implements JobInterface
{
protected AbstractPayload $payload;
protected ?int $id;
protected \DateTimeImmutable $createdAt;
protected DateTimeImmutable $createdAt;
public function __construct(
AbstractPayload $payload,
int $id = null,
\DateTimeImmutable $createdAt = null
DateTimeImmutable $createdAt = null
) {
$this->setPayload($payload);
$this->id = $id;
$this->createdAt = $createdAt ?? new \DateTimeImmutable();
$this->createdAt = $createdAt ?? new DateTimeImmutable();
}
public function getId(): ?int
......@@ -37,7 +38,7 @@ abstract class AbstractJob implements JobInterface
$this->payload = $payload;
}
public function getCreatedAt(): \DateTimeImmutable
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
......
<?php
namespace SimpleSAML\Module\accounting\Entities\Bases;
use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
abstract class AbstractProvider
{
public const METADATA_KEY_NAME = 'name';
public const METADATA_KEY_ENTITY_ID = 'entityid';
protected array $metadata;
protected string $entityId;
public function __construct(array $metadata)
{
$this->metadata = $metadata;
$this->entityId = $this->resolveEntityId();
}
public function getMetadata(): array
{
return $this->metadata;
}
public function getName(string $locale = 'en'): ?string
{
if (
isset($this->metadata[self::METADATA_KEY_NAME]) &&
is_array($this->metadata[self::METADATA_KEY_NAME]) &&
!empty($this->metadata[self::METADATA_KEY_NAME][$locale]) &&
is_string($this->metadata[self::METADATA_KEY_NAME][$locale])
) {
return (string)$this->metadata[self::METADATA_KEY_NAME][$locale];
}
return null;
}
public function getEntityId(): string
{
return $this->entityId;
}
protected function resolveEntityId(): string
{
if (
!empty($this->metadata[self::METADATA_KEY_ENTITY_ID]) &&
is_string($this->metadata[self::METADATA_KEY_ENTITY_ID])
) {
return $this->metadata[self::METADATA_KEY_ENTITY_ID];
}
throw new UnexpectedValueException('Provider entity metadata does not contain entity ID.');
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities;
/**
* Represents a Service Provider to which a user has authenticated at least once.
*/
class ConnectedServiceProvider
{
protected ServiceProvider $serviceProvider;
protected int $numberOfAuthentications;
protected \DateTimeImmutable $lastAuthenticationAt;
protected \DateTimeImmutable $firstAuthenticationAt;
protected User $user;
/**
* TODO mivanci make sortable by name (or entity ID if not present), number of authns, last/first authn.
* @param ServiceProvider $serviceProvider
* @param int $numberOfAuthentications
* @param \DateTimeImmutable $lastAuthenticationAt
* @param \DateTimeImmutable $firstAuthenticationAt
* @param User $user
*/
public function __construct(
ServiceProvider $serviceProvider,
int $numberOfAuthentications,
\DateTimeImmutable $lastAuthenticationAt,
\DateTimeImmutable $firstAuthenticationAt,
User $user
) {
$this->serviceProvider = $serviceProvider;
$this->numberOfAuthentications = $numberOfAuthentications;
$this->lastAuthenticationAt = $lastAuthenticationAt;
$this->firstAuthenticationAt = $firstAuthenticationAt;
$this->user = $user;
}
/**
* @return ServiceProvider
*/
public function getServiceProvider(): ServiceProvider
{
return $this->serviceProvider;
}
/**
* @return int
*/
public function getNumberOfAuthentications(): int
{
return $this->numberOfAuthentications;
}
/**
* @return \DateTimeImmutable
*/
public function getLastAuthenticationAt(): \DateTimeImmutable
{
return $this->lastAuthenticationAt;
}
/**
* @return \DateTimeImmutable
*/
public function getFirstAuthenticationAt(): \DateTimeImmutable
{
return $this->firstAuthenticationAt;
}
/**
* @return User
*/
public function getUser(): User
{
return $this->user;
}
}
<?php
namespace SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider;
use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider;
class Bag
{
/**
* @var ConnectedServiceProvider[]
*/
protected array $connectedServiceProviders = [];
public function addOrReplace(ConnectedServiceProvider $connectedServiceProvider): void
{
$spEntityId = $connectedServiceProvider->getServiceProvider()->getEntityId();
$this->connectedServiceProviders[$spEntityId] = $connectedServiceProvider;
}
public function getAll(): array
{
return $this->connectedServiceProviders;
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Entities;
class GenericJob extends Bases\AbstractJob
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment