Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions ldap/class/ldapmanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,56 @@ function () {
]
);
},
// 28 - the plugin's six foreign keys.
//
// fogproject ADR 0031 decision 8: sweep, then add. ADD CONSTRAINT
// validates the rows already in the table and answers 1452 if any
// of them point at a parent that is gone -- and applyConstraints()
// REPORTS a refusal rather than returning it, so an install that
// skipped the sweep would succeed while silently not creating the
// constraint. Both calls are filtered to this plugin's own group,
// so neither can reach another plugin's tables or core's.
//
// Steps 19-21 above are the same idea hand-written, and they are
// why this plugin needed it first: deleting a user, role or user
// group did not clear this plugin's rows, so an install upgraded
// from before LDAPDeleteMassItems existed holds mappings pointing
// at ids that are gone. They stay exactly as they are -- anyone
// past them has already run them -- but they only ever ran once,
// against a backlog. These constraints are what stops the backlog
// ever forming again, in the database rather than in a hook.
//
// The relationships are declared in fogproject's
// commons/schema-constraints.php:
// LDAPGroups.lgServerID -> LDAPServers.lsID
// ldapGroupRoleAssoc.lgraGroupID -> LDAPGroups.lgID
// ldapGroupRoleAssoc.lgraRoleID -> roles.rID
// ldapGroupUserGroupAssoc.lgugGroupID -> LDAPGroups.lgID
// ldapGroupUserGroupAssoc.lgugUserGroupID -> userGroups.ugID
// ldapUserGrant.lugUserID -> users.uId
// all CASCADE. LDAPGroups is a satellite of its server -- a group
// has no meaning without the directory it was read from -- and
// the rest are junctions.
//
// ldapUserGrant.lugTargetID is deliberately NOT among them. Its
// parent table is chosen by the sibling lugTargetType column, so
// there is no single table to reference; step 19 sweeps it by
// hand and LDAPDeleteMassItems keeps it clean. Same shape as
// core's scheduledTasks.stGroupHostID.
//
// No column change was needed: all six are int(11) NOT NULL
// against int(11) parents, and none carries a sentinel.
//
// Appended, never folded into an earlier step -- installdb()
// SKIPS the pSchema steps an install has already passed, which is
// the mistake steps 10-16 above exist to repair.
function () {
$res = \FOG\Db\SchemaReconciler::sweepOrphans('ldap');
if (is_string($res)) {
return $res;
}
return \FOG\Db\SchemaReconciler::applyConstraints('ldap');
},
];
}
/**
Expand Down
82 changes: 81 additions & 1 deletion location/class/locationmanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,18 @@ public function createSql()
'TINYINT(1)',
"ENUM('http', 'https')"
],
// Nullability, in field order. lStorageNodeID is the one true
// entry: the column is a tri-state, and NULL is how it spells
// "no specific node -- let the group choose". See step 4 of
// schema() for why that stopped being 0, and
// Location::getStorageNode() for the reader, which treats any
// falsy value the same way and so needed no change.
[
false,
false,
false,
false,
false,
true,
false,
false,
false,
Expand Down Expand Up @@ -159,6 +165,80 @@ function () {
]
);
},
// 4 - lStorageNodeID stops spelling "no node" as 0.
//
// fogproject ADR 0031. A foreign key accepts NULL for "no
// reference" and nothing else: 0 is a value, so a constraint
// over this column would demand a storage node with ngmID = 0
// and refuse every location that had not pinned one.
//
// The column is genuinely a tri-state and always has been --
// Location::getStorageNode() returns the named node when it is
// truthy and falls through to the group's optimal node when it
// is not -- so this changes how "none" is spelled, not what the
// column means. FOGController::get() reads through isset(), so
// NULL comes back as '' and is falsy exactly as 0 was; the
// reader needed no change, which is the point.
//
// Appended rather than folded into step 0 for the reason step 3
// gives: installdb() skips the pSchema steps an install has
// already passed. createSql() above now builds the column
// nullable for a fresh install; this is what an existing one
// gets.
function () {
$sql = sprintf(
'ALTER TABLE `%s` MODIFY COLUMN `lStorageNodeID`'
. ' INTEGER NULL DEFAULT NULL',
$this->tablename
);
if (false !== self::$DB->query($sql)->error) {
return self::$DB->error;
}
$sql = sprintf(
'UPDATE `%s` SET `lStorageNodeID` = NULL'
. ' WHERE `lStorageNodeID` = 0',
$this->tablename
);
if (false !== self::$DB->query($sql)->error) {
return self::$DB->error;
}
// Logged rather than silent: this rewrites rows, and the
// count is the only evidence it did.
error_log(
sprintf(
'%s: %s: %d %s',
_('Location schema'),
'lStorageNodeID',
(int)self::$DB->affectedRows(),
_('row(s) converted from 0 to NULL')
)
);
return true;
},
// 5 - the plugin's foreign keys.
//
// fogproject ADR 0031 decision 8: sweep, then add. ADD
// CONSTRAINT validates the rows already there and answers 1452
// if any of them point at a parent that is gone, so the sweep is
// the precondition for the statement rather than a policy
// choice. Both calls are filtered to this plugin's own group, so
// nothing here can reach another plugin's tables or core's.
//
// The relationships themselves are declared in fogproject's
// commons/schema-constraints.php, not here -- half of them point
// at core tables, and the map is meant to answer "what points at
// hosts?" from one file.
//
// Idempotent, and re-run by the unfiltered reconcile after every
// core schema update: planConstraints() skips a constraint whose
// declaration already matches the map.
function () {
$res = \FOG\Db\SchemaReconciler::sweepOrphans('location');
if (is_string($res)) {
return $res;
}
return \FOG\Db\SchemaReconciler::applyConstraints('location');
},
];
}
/**
Expand Down
53 changes: 53 additions & 0 deletions oidc/class/oidcmanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,59 @@ function () {
]
);
},
// 9 - the plugin's eight foreign keys.
//
// fogproject ADR 0031 decision 8: sweep, then add. ADD CONSTRAINT
// validates the rows already in the table and answers 1452 if any
// of them point at a parent that is gone -- and applyConstraints()
// REPORTS a refusal rather than returning it, so an install that
// skipped the sweep would succeed while silently not creating the
// constraint. Both calls are filtered to this plugin's own group,
// so neither can reach another plugin's tables or core's.
//
// All eight land here even though five of the tables belong to
// other managers, including oidcIdentity, whose own schema() runs
// separately at step 1 above. The calls are driven by the table
// names in fogproject's commons/schema-constraints.php rather than
// by whose manager is executing, so the plugin needs exactly one
// constraint step and it belongs in the orchestrator -- by which
// point every one of its tables exists.
//
// OIDCGroups.ogProviderID -> OIDCProviders.opID
// oidcIdentity.oiProviderID -> OIDCProviders.opID
// oidcIdentity.oiUserID -> users.uId
// oidcGroupRoleAssoc.ograGroupID -> OIDCGroups.ogID
// oidcGroupRoleAssoc.ograRoleID -> roles.rID
// oidcGroupUserGroupAssoc.ogugGroupID -> OIDCGroups.ogID
// oidcGroupUserGroupAssoc.ogugUserGroupID -> userGroups.ugID
// oidcUserGrant.ougUserID -> users.uId
//
// All CASCADE. OIDCGroups and oidcIdentity are satellites -- a
// group claim mapping and a subject-to-user binding both mean
// nothing without the provider they came from -- and the rest are
// junctions. oidcIdentity is the one worth being deliberate about:
// it is the record that this external subject IS this FOG user, so
// deleting either end has to take it. Leaving it would let the
// next user created with a recycled id inherit someone else's
// identity binding.
//
// ougTargetID is deliberately excluded and stays polymorphic: its
// parent table is chosen by the sibling ougTargetType column, so
// there is no single table to reference. Same shape as core's
// scheduledTasks.stGroupHostID.
//
// No column change was needed: all eight are int(11) NOT NULL
// against int(11) parents, and none carries a sentinel.
//
// Appended, never folded into an earlier step -- installdb() SKIPS
// the pSchema steps an install has already passed.
function () {
$res = \FOG\Db\SchemaReconciler::sweepOrphans('oidc');
if (is_string($res)) {
return $res;
}
return \FOG\Db\SchemaReconciler::applyConstraints('oidc');
},
];
}
/**
Expand Down
37 changes: 37 additions & 0 deletions ou/class/oumanager.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,43 @@ public function schema()
$this->createSql(),
// 1
self::getClass('OUAssociationManager')->createSql(),
// 2 - the plugin's foreign keys.
//
// fogproject ADR 0031 decision 8: sweep, then add. ADD CONSTRAINT
// validates the rows already in the table and answers 1452 if any
// of them point at a parent that is gone -- and applyConstraints()
// REPORTS a refusal rather than returning it, so an install that
// skipped the sweep would succeed while silently not creating the
// constraint. The sweep is the precondition for the statement, not
// a policy choice.
//
// Both calls are filtered to this plugin's own group, so neither
// can reach another plugin's tables or core's. The relationships
// themselves are declared in fogproject's
// commons/schema-constraints.php: ouAssoc.oaOUID CASCADE to `ou`,
// ouAssoc.oaHostID CASCADE to `hosts`. Half of them point at core
// tables, and that map is meant to answer "what points at hosts?"
// from one file.
//
// Appended rather than folded into an earlier step because
// installdb() SKIPS the pSchema steps an install has already
// passed instead of replaying them, so an edit to an earlier step
// is invisible to everyone already past it.
//
// No column change was needed: both columns are already
// int(11) NOT NULL against int(11) parents, and neither carries a
// sentinel -- an association row exists only to name both ends.
//
// Idempotent, and re-run by the unfiltered reconcile after every
// core schema update: planConstraints() skips a constraint whose
// declaration already matches the map.
function () {
$res = \FOG\Db\SchemaReconciler::sweepOrphans('ou');
if (is_string($res)) {
return $res;
}
return \FOG\Db\SchemaReconciler::applyConstraints('ou');
},
];
}
/**
Expand Down
Loading