Skip to content

Commit

Permalink
Pass Through Provisioning (CFM-463)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benn Oshrin committed Mar 28, 2026
1 parent 96b4f23 commit 02d9929
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,8 @@ msgid "result.pwexpired"
# Note we swap the rendering order to make it more obvious, but the _parameter_ order is unchanged
msgstr "Password expired {1}, Principal active (expires: {0})"

msgid "result.unlocked-p"
msgstr "Principal {0} is unlocked"

msgid "result.synced"
msgstr "Synced existing principal {0}"
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,19 @@ public function provision(
if(!empty($password)) {
$action = 'update';
} else {
// If we don't find a Password we lock the principal. We will typically get here
// if the Authenticator is Locked (Person is Active but Password is Locked), so we
// don't need to check the Authenticator Status specifically. There may be other
// edge cases that get us here as well.
// If Pass Through Provisioning is enabled, we may have various scenarios where
// we will get a blank password for an Eligible Person, including reprovisioning
// or Authenticator lock/unlock. We'll need to look at the Authenticator Status
// for more information, but we'll default to locking.

$action = 'lock';

// There should be at least one entry with an authenticator status

if(!empty($data->passwords[0]->authenticator_status)
&& !$data->passwords[0]->authenticator_status->locked) {
$action = 'unlock';
}
}
} elseif($eligibility == ProvisioningEligibilityEnum::Ineligible) {
// Check to see if the principal exists in the KDC, and if so lock it
Expand Down Expand Up @@ -250,6 +257,25 @@ public function provision(
'comment' => __d('kerberos_connector', 'result.locked-p', [$principal]),
'identifier' => $principal
];
} elseif($action == 'unlock') {
// We only end up here if Pass Through Provisioning is enabled, in which case
// we have an authenticator with no Password (but presumably a disabled password
// in the KDC).

$attributes = $curprinc->getAttributes();

if($attributes & 64) {
// Remove the locked bit
$curprinc->setAttributes($curprinc->getAttributes() ^ 64);
$curprinc->save();
}
// else the principal is already unlocked

return [
'status' => ProvisioningStatusEnum::Provisioned,
'comment' => __d('kerberos_connector', 'result.unlocked-p', [$principal]),
'identifier' => $principal
];
} elseif($action == 'update') {
// Make sure we aren't currently DISALLOWING_ALL_TIX -- if we are clear the flag.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"max_length": { "type": "integer" },
"format_crypt_php": { "type": "boolean" },
"format_plaintext": { "type": "boolean" },
"format_sha1_ldap": { "type": "boolean" }
"format_sha1_ldap": { "type": "boolean" },
"prevent_reuse": { "type": "boolean" },
"use_hard_delete": { "type": "boolean" }
},
"indexes": {
"password_authenticators_i1": { "columns": [ "authenticator_id" ]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ msgstr "Crypt"
msgid "enumeration.PasswordEncodingEnum.EX"
msgstr "External"

msgid "enumeration.PasswordEncodingEnum.MT"
msgstr "Empty"

msgid "enumeration.PasswordEncodingEnum.NO"
msgstr "Plain"

Expand Down Expand Up @@ -61,20 +64,8 @@ msgstr "Password must be at least {0} characters"
msgid "error.Passwords.match"
msgstr "New passwords do not match"

msgid "field.PasswordAuthenticators.source_mode"
msgstr "Password Source"

msgid "field.PasswordAuthenticators.min_length"
msgstr "Minimum Password Length"

msgid "field.PasswordAuthenticators.min_length.desc"
msgstr "Must be between 8 and 64 characters (inclusive), default is 8"

msgid "field.PasswordAuthenticators.max_length"
msgstr "Maximum Password Length"

msgid "field.PasswordAuthenticators.max_length.desc"
msgstr "Must be between 8 and 64 characters (inclusive), default is 64 for Self Select and 16 for Autogenerate"
msgid "error.Passwords.reuse"
msgstr "Password was previously used, please pick a new one"

msgid "field.PasswordAuthenticators.format_crypt_php"
msgstr "Store as Crypt"
Expand All @@ -94,6 +85,30 @@ msgstr "Store as Salted SHA 1"
msgid "field.PasswordAuthenticators.format_sha1_ldap.desc"
msgstr "If enabled, the password will be stored in Salted SHA 1 format"

msgid "field.PasswordAuthenticators.min_length"
msgstr "Minimum Password Length"

msgid "field.PasswordAuthenticators.min_length.desc"
msgstr "Must be between 8 and 64 characters (inclusive), default is 8"

msgid "field.PasswordAuthenticators.max_length"
msgstr "Maximum Password Length"

msgid "field.PasswordAuthenticators.max_length.desc"
msgstr "Must be between 8 and 64 characters (inclusive), default is 64 for Self Select and 16 for Autogenerate"

msgid "field.PasswordAuthenticators.prevent_reuse"
msgstr "Prevent Password Reuse"

msgid "field.PasswordAuthenticators.prevent_reuse.desc"
msgstr "Require a new Password to be different than any prior Password (requires Store as Crypt, cannot be used with Hard Delete)"

msgid "field.PasswordAuthenticators.source_mode"
msgstr "Password Source"

msgid "field.PasswordAuthenticators.use_hard_delete"
msgstr "Use Hard Delete"

msgid "field.Passwords.password2"
msgstr "Password (Again)"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

class PasswordEncodingEnum extends StandardEnum {
const Crypt = 'CR'; // Crypt/bcrypt/etc as implemented by php's password_hash
const Empty = 'MT'; // "Empty" type used to track mod time when PTP enabled
const External = 'EX'; // Externally defined (ie: managed outside of Registry)
const Plain = 'NO'; // Not hashed
const SSHA = 'SH'; // Salted SHA 1 as intended for LDAP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,16 @@ public function initialize(array $config): void {

public function beforeMarshal(EventInterface $event, \ArrayObject $data, \ArrayObject $options) {
// PAR-PasswordAuthenticator-1 When the Password Source is Self Select, the password
// must be stored in PHP Crypt format
// must be stored in PHP Crypt format, unless the Authenticator is configured in Pass
// Through Provisioning mode.

if(!empty($data['source_mode']) && $data['source_mode'] == PasswordSourceEnum::SelfSelect) {
$authenticator = $this->Authenticators->get($data['authenticator_id']);

if($authenticator->enable_ptp) {
$data['format_crypt_php'] = false;
$data['format_sha1_ldap'] = false;
$data['format_plaintext'] = false;
} elseif(!empty($data['source_mode']) && $data['source_mode'] == PasswordSourceEnum::SelfSelect) {
$data['format_crypt_php'] = true;
}
}
Expand All @@ -134,6 +141,13 @@ public function marshalProvisioningData(
\App\Model\Entity\Authenticator $cfg,
int $personId
): array {
// If the Authenticator is in Pass Through Provisioning mode, do not pull any existing
// records from the database.

if($cfg->enable_ptp) {
return [];
}

// Retrieve any Passwords associated with this Person and the requested configuration.
// We'll include all available Password types (encodings) since we don't know which types
// any specific Provisioner will be interested in.
Expand Down Expand Up @@ -207,6 +221,16 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->allowEmptyString('format_sha1_ldap');

$validator->add('prevent_reuse', [
'content' => ['rule' => ['boolean']]
]);
$validator->allowEmptyString('prevent_reuse');

$validator->add('use_hard_delete', [
'content' => ['rule' => ['boolean']]
]);
$validator->allowEmptyString('use_hard_delete');

return $validator;
}
}
Loading

0 comments on commit 02d9929

Please sign in to comment.