Skip to content

Commit

Permalink
AuthenticationEvent MVC (CFM-132)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benn Oshrin committed Aug 14, 2022
1 parent 3b6e559 commit 96cbea9
Show file tree
Hide file tree
Showing 33 changed files with 1,096 additions and 183 deletions.
13 changes: 13 additions & 0 deletions app/config/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@
}
},

"authentication_events": {
"columns": {
"id": {},
"authenticated_identifier": { "type": "string", "size": 256 },
"authentication_event": { "type": "string", "size": 2 },
"remote_ip": { "type": "string", "size": 40 }
},
"indexes": {
"authentication_events_i1": { "columns": [ "authenticated_identifier" ] }
},
"changelog": false
},

"api_users": {
"columns": {
"id": {},
Expand Down
9 changes: 9 additions & 0 deletions app/resources/locales/en_US/command.po
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ msgstr "Setup appears to have already run"

msgid "se.salt"
msgstr "Generating salt file"

msgid "tm.epilog"
msgstr "An optional, space separated list of tables to transmogrify may be specified"

msgid "tm.login-identifier-copy"
msgstr "Copy any login Identifiers from External Identities to the associated Persons"

msgid "tm.login-identifier-type"
msgstr "Flag all Person Identifiers of this type as login identifiers"
3 changes: 3 additions & 0 deletions app/resources/locales/en_US/controller.po
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ msgstr "{0,plural,=1{Ad Hoc Attribute} other{Ad Hoc Attributes}}"
msgid "ApiUsers"
msgstr "{0,plural,=1{API User} other{API Users}}"

msgid "AuthenticationEvents"
msgstr "{0,plural,=1{Authentication Event} other{Authentication Events}}"

msgid "CoSettings"
msgstr "{0,plural,=1{CO Setting} other{CO Settings}}"

Expand Down
6 changes: 6 additions & 0 deletions app/resources/locales/en_US/enumeration.po
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@

# Enumerations

msgid "AuthenticationEventEnum.AI"
msgstr "API Login"

msgid "AuthenticationEventEnum.IN"
msgstr "Registry Login"

msgid "BooleanEnum.0"
msgstr "False"

Expand Down
6 changes: 6 additions & 0 deletions app/resources/locales/en_US/error.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ msgstr "Group cannot be nested into itself"
msgid "Groups.nested"
msgstr "Group is nested or has nestings, and cannot be suspended or deleted"

msgid "Identifiers.login"
msgstr "Only Identifiers attached to a Person may be flagged for login"

msgid "input.blank"
msgstr "Value cannot consist of only blank characters"

Expand All @@ -136,6 +139,9 @@ msgstr "The provided value is not a valid URL"
msgid "input.length"
msgstr "The provided value cannot be longer than {0} characters"

msgid "input.notprov"
msgstr "{0} must be provided"

msgid "invalid"
msgstr "Invalid value \"{0}\""

Expand Down
6 changes: 6 additions & 0 deletions app/resources/locales/en_US/field.po
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ msgstr "Area Code"
msgid "attribute"
msgstr "Attribute"

msgid "AuthenticationEvents.authenticated_identifier"
msgstr "Authenticated Identifier"

msgid "AuthenticationEvents.authentication_event"
msgstr "Authentication Event"

msgid "comment"
msgstr "Comment"

Expand Down
144 changes: 119 additions & 25 deletions app/src/Command/TransmogrifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ class TransmogrifyCommand extends Command {
'person_picker_display_types' => null
]
],
'authentication_events' => [
'source' => 'cm_authentication_events',
'displayField' => 'authenticated_identifier'
],
'api_users' => [
'source' => 'cm_api_users',
'displayField' => 'username',
Expand Down Expand Up @@ -270,7 +274,8 @@ class TransmogrifyCommand extends Command {
'co_department_id' => null,
'co_provisioning_target_id' => null,
'organization_id' => null
]
],
'preRow' => 'map_login_identifiers'
],
'telephone_numbers' => [
'source' => 'cm_telephone_numbers',
Expand Down Expand Up @@ -323,8 +328,8 @@ class TransmogrifyCommand extends Command {
// Cache the driver for ease of workarounds
protected $outdriver = null;

// Our noise level ('quiet', 'verbose', or 'default')
protected $noise = 'default';
// Shell arguments, for easier access
protected $args = null;
protected $io = null;

/**
Expand All @@ -336,7 +341,16 @@ class TransmogrifyCommand extends Command {
*/

protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {
$parser->setEpilog('An optional, space separated list of tables to transmogrify may be specified');
$parser->addOption('login-identifier-copy', [
'help' => __d('command', 'tm.login-identifier-copy'),
'boolean' => true
]);

$parser->addOption('login-identifier-type', [
'help' => __d('command', 'tm.login-identifier-type')
]);

$parser->setEpilog(__d('command', 'tm.epilog'));

return $parser;
}
Expand Down Expand Up @@ -418,6 +432,7 @@ protected function check_group_memberships(array $origRow, array $row) {
*/

public function execute(Arguments $args, ConsoleIo $io) {
$this->args = $args;
$this->io = $io;

// Load data from the inbound "transmogrify" database to a newly created
Expand Down Expand Up @@ -477,12 +492,6 @@ public function execute(Arguments $args, ConsoleIo $io) {
$this->outconn = DriverManager::getConnection($cargs, $outconfig);
$this->outdriver = $cargs['driver'];

if($args->getOption('quiet')) {
$this->noise = 'quiet';
} elseif($args->getOption('verbose')) {
$this->noise = 'verbose';
}

// We accept a list of table names, mostly for testing purposes
$atables = $args->getArguments();

Expand Down Expand Up @@ -551,7 +560,6 @@ public function execute(Arguments $args, ConsoleIo $io) {
$this->fixBooleans($t, $row);

$this->mapFields($t, $row);


$this->outconn->insert($schemaPrefix.$t, $row);

Expand All @@ -571,29 +579,37 @@ public function execute(Arguments $args, ConsoleIo $io) {
// did not load, perhaps because it was associated with an Org Identity
// not linked to a CO Person that was not migrated.
$warns++;
$io->warning("Skipping record " . $row['id'] . " due to invalid foreign key: " . $e->getMessage());
$io->warning("Skipping $t record " . $row['id'] . " due to invalid foreign key: " . $e->getMessage());
}
catch(\InvalidArgumentException $e) {
// If we can't find a value for mapping we skip the record
// (ie: mapFields basically requires a successful mapping)
$warns++;
$io->warning("Skipping record " . $row['id'] . ": " . $e->getMessage());
$io->warning("Skipping $t record " . $row['id'] . ": " . $e->getMessage());
}
catch(\Exception $e) {
$err++;
$io->error("Record " . $row['id'] . ": " . $e->getMessage());
$io->error("$t record " . $row['id'] . ": " . $e->getMessage());
}

$tally++;

if($this->noise == 'default') {
if(!$this->args->getOption('quiet') && !$this->args->getOption('verbose')) {
// We don't output the progress bar for quiet for obvious reasons,
// or for verbose so we don't interfere with the extra output
$this->cliLogPercentage($tally, $count);
}
}

$max = $this->inconn->fetchOne('SELECT MAX(id) FROM ' . $this->tables[$t]['source']);
// Run any post processing functions for the table.

if(!empty($this->tables[$t]['postTable'])) {
$p = $this->tables[$t]['postTable'];

$this->$p();
}

$max = $this->outconn->fetchOne('SELECT MAX(id) FROM ' . $t);
$max++;
$stdout_msg = "(New max: " . $max . ")";
if($warns > 0) {
Expand All @@ -605,6 +621,8 @@ public function execute(Arguments $args, ConsoleIo $io) {

$io->out($stdout_msg);

$this->io->info("Resetting sequence for $t to $max");

// Strictly speaking we should use prepared statements, but we control the
// data here, and also we're executing a maintenance operation (so query
// optimization is less important)
Expand All @@ -614,14 +632,6 @@ public function execute(Arguments $args, ConsoleIo $io) {
$outsql = "ALTER SEQUENCE " . $t . "_id_seq RESTART WITH " . $max;
}
$this->outconn->executeQuery($outsql);

// Run any post processing functions for the table.

if(!empty($this->tables[$t]['postTable'])) {
$p = $this->tables[$t]['postTable'];

$this->$p();
}
}
}

Expand Down Expand Up @@ -656,6 +666,21 @@ protected function findCoId(array $row) {
return $this->cache['groups']['id'][ $row['group_id'] ]['co_id'];
}
}
// We also support being called using the old keys for use in the preRow context
elseif(!empty($row['org_identity_id'])) {
// Map the OrgIdentity to a CO Person, then to the CO
if(!empty($this->cache['external_identities']['id'][ $row['org_identity_id'] ]['person_id'])) {
$personId = $this->cache['external_identities']['id'][ $row['org_identity_id'] ]['person_id'];

if(isset($this->cache['people']['id'][ $personId ]['co_id'])) {
return $this->cache['people']['id'][ $personId ]['co_id'];
}
}
} elseif(!empty($row['co_person_id'])) {
if(isset($this->cache['people']['id'][ $row['co_person_id'] ]['co_id'])) {
return $this->cache['people']['id'][ $row['co_person_id'] ]['co_id'];
}
}

throw new \InvalidArgumentException('CO not found for record');
}
Expand Down Expand Up @@ -861,6 +886,67 @@ protected function map_extended_type(array $row) {
return Inflector::pluralize($bits[0]) . "." . $bits[1];
}

/**
* Map login identifiers, in accordance with the configuration.
*
* @since COmanage Registry v5.0.0
* @param array $origRow Row of table data (original data)
* @param array $row Row of table data (post fixes)
* @throws InvalidArgumentException
*/

protected function map_login_identifiers(array $origRow, array $row) {
// There might be multiple reasons to copy the row, but we only want to
// copy it once.
$copyRow = false;

if(!empty($origRow['org_identity_id'])) {
if($this->args->getOption('login-identifier-copy')
&& $origRow['login']) {
$copyRow = true;
}

// Note the argument here is the old v4 string (eg "eppn") and not the
// PE foreign key
if($this->args->getOption('login-identifier-type')
&& $origRow['type'] == $this->args->getOption('login-identifier-type')) {
$copyRow = true;
}

// Identifiers attached to External Identities do not have login flags in PE
$row['login'] = false;
}

if($copyRow) {
// Find the Person ID associated with this External Identity ID

if(!empty($this->cache['external_identities']['id'][ $origRow['org_identity_id'] ]['person_id'])) {
// Insert a new row attached to the Person, leave the original record
// (ie: $row) untouched

$copiedRow = [
'person_id' => $this->map_org_identity_co_person_id(['id' => $origRow['org_identity_id']]),
'identifier' => $origRow['identifier'],
'type_id' => $this->map_identifier_type($origRow),
'status' => $origRow['status'],
'login' => true,
'created' => $origRow['created'],
'modified' => $origRow['modified']
];

// Set up changelog and fix booleans
$this->fixChangelog('identifiers', $copiedRow, true);
$this->fixBooleans('identifiers', $copiedRow);

try {
$this->outconn->insert('identifiers', $copiedRow);
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
$this->io->warning("record already exists: " . print_r($copiedRow, true));
}
}
}
}

/**
* Map an identifier type string to a foreign key.
*
Expand Down Expand Up @@ -941,7 +1027,15 @@ protected function map_org_identity_co_person_id(array $row) {

while($r = $stmt->fetch()) {
if(!empty($r['org_identity_id'])) {
$this->cache['org_identities']['co_people'][ $r['org_identity_id'] ][ $r['revision'] ] = $r['co_person_id'];
if(isset($this->cache['org_identities']['co_people'][ $r['org_identity_id'] ][ $r['revision'] ])) {
// If for some reason we already have a record, it's probably due to
// improper unpooling from a legacy deployment. We'll accept only the
// first record and throw warnings on the others.

$this->io->warning("Found existing CO Person for Org Identity " . $r['org_identity_id'] . ", skipping");
} else {
$this->cache['org_identities']['co_people'][ $r['org_identity_id'] ][ $r['revision'] ] = $r['co_person_id'];
}
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/Controller/ApiV2Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,23 @@ public function index() {
$query = $query->where([$this->$modelsName->getAlias().'.'.$link->attr => $link->value]);
}

if($modelsName == 'AuthenticationEvents') {
// Special case for filtering on authenticated identifier. There is a
// similar filter in AuthenticationEventsController::beforeFilter.
// If other special cases show up this should get refactored into a trait
// populated by the table (or something similar).

if($this->getRequest()->getQuery('authenticated_identifier')) {
$query = $query->where(['authenticated_identifier' => \App\Lib\Util\StringUtilities::urlbase64decode($this->getRequest()->getQuery('authenticated_identifier'))]);
} else {
// We only allow unfiltered queries for platform users

if(!$this->RegistryAuth->isPlatformAdmin()) {
throw new \InvalidArgumentException(__d('error', 'input.notprov', 'authenticated_identifier'));
}
}
}

// This magically makes REST calls paginated... can use eg direction=,
// sort=, limit=, page=
$this->set($this->tableName, $this->Paginator->paginate($query));
Expand Down
Loading

0 comments on commit 96cbea9

Please sign in to comment.