'Original-Pass-1!', 'new_password' => 'Updated-Pass-1!', ]); $this->assertSame(200, $update['status'], json_encode($update['body'])); $this->assertTrue($update['body']['success'] ?? false); // Old password no longer works, new one does $oldLogin = static::loginWithPassword('erin@example.test', 'Original-Pass-1!'); $this->assertSame('failed', $oldLogin['body']['status'] ?? null); $newLogin = static::loginWithPassword('erin@example.test', 'Updated-Pass-1!'); $this->assertSame('success', $newLogin['body']['status'] ?? null); } public function testUpdatePasswordRejectsWrongCurrentPassword(): void { static::createUser('frank@example.test', 'Original-Pass-2!'); $accessToken = static::loginWithPassword('frank@example.test', 'Original-Pass-2!')['accessToken']; $update = static::authorizedRequest($accessToken, 'POST', self::BASE . '/password/update', [ 'current_password' => 'not-the-current-password', 'new_password' => 'Updated-Pass-2!', ]); $this->assertSame(400, $update['status']); // Original password still works $login = static::loginWithPassword('frank@example.test', 'Original-Pass-2!'); $this->assertSame('success', $login['body']['status'] ?? null); } public function testUpdatePasswordRequiresAuthentication(): void { $update = static::request('POST', self::BASE . '/password/update', [ 'current_password' => 'irrelevant', 'new_password' => 'irrelevant', ]); $this->assertSame(401, $update['status']); } public function testAdminStatusReflectsEnrollment(): void { $roleId = static::createRoleWithPermissions(['authentication_provider_password.admin.view']); $enrolledUid = static::createUser('grace@example.test', 'Grace-Pass-1!', [$roleId]); $unenrolledUid = static::createUser('heidi@example.test'); $accessToken = static::loginWithPassword('grace@example.test', 'Grace-Pass-1!')['accessToken']; $enrolledStatus = static::authorizedRequest($accessToken, 'POST', self::BASE . '/status', ['uid' => $enrolledUid]); $unenrolledStatus = static::authorizedRequest($accessToken, 'POST', self::BASE . '/status', ['uid' => $unenrolledUid]); $this->assertTrue($enrolledStatus['body']['enrolled'] ?? null); $this->assertFalse($unenrolledStatus['body']['enrolled'] ?? null); } public function testAdminResetSetsAPasswordThatCanLogIn(): void { $uid = static::createUser('ivan@example.test'); // Bootstrap an authenticated actor with permission to call the admin endpoint $roleId = static::createRoleWithPermissions(['authentication_provider_password.admin.manage']); static::createUser('actor1@example.test', 'Actor-Pass-1!', [$roleId]); $accessToken = static::loginWithPassword('actor1@example.test', 'Actor-Pass-1!')['accessToken']; $reset = static::authorizedRequest($accessToken, 'POST', self::BASE . '/reset', [ 'uid' => $uid, 'password' => 'Reset-By-Admin-1!', ]); $this->assertSame(200, $reset['status'], json_encode($reset['body'])); $this->assertTrue($reset['body']['success'] ?? false); $login = static::loginWithPassword('ivan@example.test', 'Reset-By-Admin-1!'); $this->assertSame('success', $login['body']['status'] ?? null); } public function testAdminResetRejectsShortPasswords(): void { $uid = static::createUser('judy@example.test'); $roleId = static::createRoleWithPermissions(['authentication_provider_password.admin.manage']); static::createUser('actor2@example.test', 'Actor-Pass-2!', [$roleId]); $accessToken = static::loginWithPassword('actor2@example.test', 'Actor-Pass-2!')['accessToken']; $reset = static::authorizedRequest($accessToken, 'POST', self::BASE . '/reset', [ 'uid' => $uid, 'password' => 'short', ]); $this->assertSame(400, $reset['status']); } public function testAdminRemoveRevokesLogin(): void { $uid = static::createUser('kevin@example.test', 'Kevin-Pass-1!'); $roleId = static::createRoleWithPermissions(['authentication_provider_password.admin.manage']); static::createUser('actor3@example.test', 'Actor-Pass-3!', [$roleId]); $accessToken = static::loginWithPassword('actor3@example.test', 'Actor-Pass-3!')['accessToken']; $remove = static::authorizedRequest($accessToken, 'POST', self::BASE . '/remove', ['uid' => $uid]); $this->assertSame(200, $remove['status']); $this->assertTrue($remove['body']['success'] ?? false); $status = static::authorizedRequest($accessToken, 'POST', self::BASE . '/status', ['uid' => $uid]); $this->assertFalse($status['body']['enrolled'] ?? null); $login = static::loginWithPassword('kevin@example.test', 'Kevin-Pass-1!'); $this->assertSame('failed', $login['body']['status'] ?? null); } public function testAdminEndpointsRejectActorsWithoutPermission(): void { $targetUid = static::createUser('mallory-target@example.test', 'Mallory-Target-1!'); static::createUser('mallory@example.test', 'Mallory-Pass-1!'); $accessToken = static::loginWithPassword('mallory@example.test', 'Mallory-Pass-1!')['accessToken']; $status = static::authorizedRequest($accessToken, 'POST', self::BASE . '/status', ['uid' => $targetUid]); $this->assertSame(403, $status['status']); $reset = static::authorizedRequest($accessToken, 'POST', self::BASE . '/reset', [ 'uid' => $targetUid, 'password' => 'Should-Not-Apply-1!', ]); $this->assertSame(403, $reset['status']); $remove = static::authorizedRequest($accessToken, 'POST', self::BASE . '/remove', ['uid' => $targetUid]); $this->assertSame(403, $remove['status']); // None of the rejected calls should have taken effect $login = static::loginWithPassword('mallory-target@example.test', 'Mallory-Target-1!'); $this->assertSame('success', $login['body']['status'] ?? null); } }