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
2 changes: 1 addition & 1 deletion src/Domain/Core/Context/SessionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function getAuthCompleted();
*
* @return ?string
*/
public function getTemporaryMasterPass(): ?string;
public function takeTemporaryMasterPass(): ?string;

/**
* Return the public key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ protected function getEncryptionConfig(): DataTab
);

$template->assign('tempMasterAttempts', $tempMasterAttempts);
$template->assign('tempMasterPass', $this->session->getTemporaryMasterPass());
$template->assign('tempMasterPass', $this->session->takeTemporaryMasterPass());

$template->assign(
'userGroups',
Expand Down
22 changes: 19 additions & 3 deletions src/Infrastructure/Context/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,27 @@ public function getAuthCompleted(): bool
}

/**
* Returns the temporary master password
* Takes the temporary master password, and forgets it
*
* It is put here by the administrator who generated it, so that the page rendered after the
* generating request can show it once — there is nowhere else to keep it for that one hop, and
* it is deliberately not persisted anywhere in plaintext.
*
* Reading it clears it. It used to stay for the life of that administrator's session, and the
* Configuration page reads it on every load, so a value meant to be shown once was rendered
* into the HTML again on every later visit — after it had expired, after its recipients had
* used it, and after the master password had been rotated. Nothing anywhere removed it: there
* was no clear for it to call.
*/
public function getTemporaryMasterPass(): ?string
public function takeTemporaryMasterPass(): ?string
{
return $this->getContextKey('tempmasterpass');
$tempMasterPass = $this->getContextKey('tempmasterpass');

if ($tempMasterPass !== null) {
$this->setContextKey('tempmasterpass', null);
}

return $tempMasterPass;
}

/**
Expand Down
29 changes: 27 additions & 2 deletions tests/Unit/Infrastructure/Context/SessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,36 @@ public function theTemporaryMasterPasswordIsCarried()
{
$session = $this->givenAStartedSession();

self::assertNull($session->getTemporaryMasterPass());
self::assertNull($session->takeTemporaryMasterPass());

$session->setTemporaryMasterPass('a-temporary-password');

self::assertSame('a-temporary-password', $session->getTemporaryMasterPass());
self::assertSame('a-temporary-password', $session->takeTemporaryMasterPass());
}

/**
* And carried exactly once.
*
* It lives in the session only so that the page rendered after the generating request can show
* it — there is nowhere else to keep it for that one hop, and it is deliberately not persisted
* in plaintext anywhere. It used to stay for the life of the administrator's session, and
* ConfigManager's index reads it on every load, so a value meant to be shown once was rendered
* into the HTML again on every later visit to the Configuration page: after it had expired,
* after its recipients had used it, and after the master password had been rotated. Nothing
* removed it, because there was nothing that could — no clear existed.
*
* @throws ContextException
* @throws SPException
*/
#[Test]
public function theTemporaryMasterPasswordIsCarriedOnlyOnce()
{
$session = $this->givenAStartedSession();

$session->setTemporaryMasterPass('a-temporary-password');

self::assertSame('a-temporary-password', $session->takeTemporaryMasterPass());
self::assertNull($session->takeTemporaryMasterPass(), 'it must not survive being read');
}

/**
Expand Down