-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add input validation for Activation Code and Document Number #415
base: main
Are you sure you want to change the base?
Changes from all commits
e84e47f
aff9b0f
afe4604
d470e6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,47 +21,54 @@ | |
use JsonSerializable; | ||
use Surfnet\Stepup\Exception\InvalidArgumentException; | ||
|
||
use function preg_match; | ||
use function strval; | ||
|
||
final class DocumentNumber implements JsonSerializable | ||
{ | ||
/** | ||
* @var string | ||
* @var string|null | ||
*/ | ||
private $documentNumber; | ||
|
||
/** | ||
* @return self | ||
*/ | ||
public static function unknown() | ||
public static function unknown(): self | ||
{ | ||
return new self('—'); | ||
return new self(null); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this not cause problems when an identitie is forgotten (right to be forgotten) and the entry pops up in the audit log? The value that is set for an unknown document number is displayed then and there in the audit log. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really beyond my understanding... If you can get the regexp to work with this 'funny' character, we can leave this 'as-is'... |
||
} | ||
|
||
/** | ||
* @param string $documentNumber | ||
* @param string|null $documentNumber | ||
*/ | ||
public function __construct($documentNumber) | ||
public function __construct(?string $documentNumber) | ||
{ | ||
if (!is_string($documentNumber) || empty($documentNumber)) { | ||
if ($documentNumber === null) { | ||
// Created using the static ::unknown method | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some work remains to be done here, also see my previous comment about a forgotten Identity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a defense in depth measure. Since we do not know what the format is of the document numbers – these are whatever the issuer decided to put on the document – retroactively setting a limit in the middleware in this way can cause issues when there are existing document numbers in the event stream that do not validate anymore. Limiting what is allowed in is fine, but this should be done on entry. We could do this in the middleware, but as a defense in depth it is best placed in the RA when the form post is received, not in the middleware. SInce we do generate the activation codes, we know exactly what they look like so we could validate them in the middleware without additional risk. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did that at first, but Peter didn't want it there :-/ |
||
} elseif (empty($documentNumber)) { | ||
throw InvalidArgumentException::invalidType('non-empty string', 'documentNumber', $documentNumber); | ||
} elseif (!preg_match('/^([-]|[A-Z0-9-]{6})$/i', $documentNumber)) { | ||
throw InvalidArgumentException::invalidType('valid characters', 'documentNumber', $documentNumber); | ||
} | ||
|
||
$this->documentNumber = $documentNumber; | ||
} | ||
|
||
/** | ||
* @return string | ||
* @return string|null | ||
*/ | ||
public function getDocumentNumber() | ||
public function getDocumentNumber(): ?string | ||
{ | ||
return $this->documentNumber; | ||
} | ||
|
||
public function __toString() | ||
{ | ||
return $this->documentNumber; | ||
return strval($this->documentNumber); | ||
} | ||
|
||
public function equals(DocumentNumber $other) | ||
public function equals(DocumentNumber $other): bool | ||
{ | ||
return $this->documentNumber === $other->documentNumber; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -551,7 +551,7 @@ public function serializedDataProvider(){ | |
// Tests for changes in BC support for adding the VettingType in the SecondFactorVettedEvents in favour of the 'DocumentNumber' | ||
'SecondFactorVettedEvent:support-new-event-with-vetting-type' => [ | ||
'{"identity_id":"b260f10b-ce7c-4d09-b6a4-50a3923d637f","name_id":"urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1","identity_institution":"institution-d.example.com","second_factor_id":"512de1ff-0ae0-41b7-bb21-b71d77e570b8","second_factor_type":"yubikey","preferred_locale":"nl_NL"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","vetting_type":{"type":"on-premise","document_number":"012345678"}}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","vetting_type":{"type":"on-premise","document_number":"AB-123"}}', | ||
new SecondFactorVettedEvent( | ||
new IdentityId('b260f10b-ce7c-4d09-b6a4-50a3923d637f'), | ||
new NameId('urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1'), | ||
|
@@ -562,12 +562,12 @@ public function serializedDataProvider(){ | |
new CommonName('jane-d1 Institution-D.EXAMPLE.COM'), | ||
new Email('[email protected]'), | ||
new Locale('nl_NL'), | ||
new OnPremiseVettingType(new DocumentNumber('012345678')) | ||
new OnPremiseVettingType(new DocumentNumber('AB-123')) | ||
), | ||
], | ||
'SecondFactorVettedEvent:support-old-event-with-document-number' => [ | ||
'{"identity_id":"b260f10b-ce7c-4d09-b6a4-50a3923d637f","name_id":"urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1","identity_institution":"institution-d.example.com","second_factor_id":"512de1ff-0ae0-41b7-bb21-b71d77e570b8","second_factor_type":"yubikey","preferred_locale":"nl_NL"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","document_number":"012345678"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","document_number":"AB-123"}', | ||
new SecondFactorVettedEvent( | ||
new IdentityId('b260f10b-ce7c-4d09-b6a4-50a3923d637f'), | ||
new NameId('urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1'), | ||
|
@@ -578,12 +578,12 @@ public function serializedDataProvider(){ | |
new CommonName('jane-d1 Institution-D.EXAMPLE.COM'), | ||
new Email('[email protected]'), | ||
new Locale('nl_NL'), | ||
new OnPremiseVettingType(new DocumentNumber('012345678')) | ||
new OnPremiseVettingType(new DocumentNumber('AB-123')) | ||
), | ||
], | ||
'SecondFactorVettedWithoutTokenProofOfPossession:support-new-event-with-vetting-type' => [ | ||
'{"identity_id":"b260f10b-ce7c-4d09-b6a4-50a3923d637f","name_id":"urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1","identity_institution":"institution-d.example.com","second_factor_id":"512de1ff-0ae0-41b7-bb21-b71d77e570b8","second_factor_type":"yubikey","preferred_locale":"nl_NL"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","vetting_type":{"type":"on-premise","document_number":"012345678"}}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","vetting_type":{"type":"on-premise","document_number":"AB-123"}}', | ||
new SecondFactorVettedWithoutTokenProofOfPossession( | ||
new IdentityId('b260f10b-ce7c-4d09-b6a4-50a3923d637f'), | ||
new NameId('urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1'), | ||
|
@@ -594,12 +594,12 @@ public function serializedDataProvider(){ | |
new CommonName('jane-d1 Institution-D.EXAMPLE.COM'), | ||
new Email('[email protected]'), | ||
new Locale('nl_NL'), | ||
new OnPremiseVettingType(new DocumentNumber('012345678')) | ||
new OnPremiseVettingType(new DocumentNumber('AB-123')) | ||
), | ||
], | ||
'SecondFactorVettedWithoutTokenProofOfPossession:support-old-event-with-document-number' => [ | ||
'{"identity_id":"b260f10b-ce7c-4d09-b6a4-50a3923d637f","name_id":"urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1","identity_institution":"institution-d.example.com","second_factor_id":"512de1ff-0ae0-41b7-bb21-b71d77e570b8","second_factor_type":"yubikey","preferred_locale":"nl_NL"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","document_number":"012345678"}', | ||
'{"common_name":"jane-d1 Institution-D.EXAMPLE.COM","email":"[email protected]","second_factor_type":"yubikey","second_factor_identifier":"123465293846985","document_number":"AB-123"}', | ||
new SecondFactorVettedWithoutTokenProofOfPossession( | ||
new IdentityId('b260f10b-ce7c-4d09-b6a4-50a3923d637f'), | ||
new NameId('urn:collab:person:Institution-D.EXAMPLE.COM:jane-d1'), | ||
|
@@ -610,7 +610,7 @@ public function serializedDataProvider(){ | |
new CommonName('jane-d1 Institution-D.EXAMPLE.COM'), | ||
new Email('[email protected]'), | ||
new Locale('nl_NL'), | ||
new OnPremiseVettingType(new DocumentNumber('012345678')) | ||
new OnPremiseVettingType(new DocumentNumber('AB-123')) | ||
), | ||
], | ||
]; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love to have a more specific input validation here. It might be a good idea to introduce a registration code value object that is tasked with validating the code? That's more in line with the way the domain is set up. For now we kept it primitive because we did not realy bother with elaborate validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to go the extra mile, then please be my guest. I'm not going to solve the world's problems here, just the ones reported by our auditor ;)
Also not sure what you mean with 'more specific'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I think it's just a first guard on "sane" input as a defense-in-depth: does this even look like a registration code? The actual validity of the given code will be verified later obviously. So I can imagine that it's just fine to do the basic check that Tim implemented.