2021-06-26 17:23:15 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Tests\User;
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-05-17 18:56:55 +02:00
|
|
|
use BookStack\Activity\ActivityType;
|
2019-12-29 20:46:46 +01:00
|
|
|
use BookStack\Api\ApiToken;
|
|
|
|
use Carbon\Carbon;
|
2023-10-19 15:18:42 +02:00
|
|
|
use Illuminate\Support\Facades\Hash;
|
2019-12-29 20:46:46 +01:00
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
class UserApiTokenTest extends TestCase
|
|
|
|
{
|
2023-10-19 15:18:42 +02:00
|
|
|
protected array $testTokenData = [
|
2021-06-26 17:23:15 +02:00
|
|
|
'name' => 'My test API token',
|
2019-12-29 21:18:37 +01:00
|
|
|
'expires_at' => '2050-04-01',
|
2019-12-29 20:46:46 +01:00
|
|
|
];
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
public function test_tokens_section_not_visible_in_my_account_without_access_api_permission()
|
2019-12-29 20:46:46 +01:00
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$user = $this->users->viewer();
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->actingAs($user)->get('/my-account/auth');
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertDontSeeText('API Tokens');
|
|
|
|
|
2023-01-21 12:08:34 +01:00
|
|
|
$this->permissions->grantUserRolePermissions($user, ['access-api']);
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->actingAs($user)->get('/my-account/auth');
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertSeeText('API Tokens');
|
|
|
|
$resp->assertSeeText('Create Token');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_those_with_manage_users_can_view_other_user_tokens_but_not_create()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$viewer = $this->users->viewer();
|
|
|
|
$editor = $this->users->editor();
|
|
|
|
$this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2019-12-30 20:42:46 +01:00
|
|
|
$resp = $this->actingAs($viewer)->get($editor->getEditUrl());
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertSeeText('API Tokens');
|
|
|
|
$resp->assertDontSeeText('Create Token');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_create_api_token()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->asAdmin()->get("/api-tokens/{$editor->id}/create");
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertStatus(200);
|
|
|
|
$resp->assertSee('Create API Token');
|
2019-12-29 21:07:28 +01:00
|
|
|
$resp->assertSee('Token Secret');
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
|
2019-12-29 20:46:46 +01:00
|
|
|
$this->assertDatabaseHas('api_tokens', [
|
2021-06-26 17:23:15 +02:00
|
|
|
'user_id' => $editor->id,
|
|
|
|
'name' => $this->testTokenData['name'],
|
2019-12-29 20:46:46 +01:00
|
|
|
'expires_at' => $this->testTokenData['expires_at'],
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Check secret token
|
|
|
|
$this->assertSessionHas('api-token-secret:' . $token->id);
|
|
|
|
$secret = session('api-token-secret:' . $token->id);
|
|
|
|
$this->assertDatabaseMissing('api_tokens', [
|
2019-12-29 21:07:28 +01:00
|
|
|
'secret' => $secret,
|
2019-12-29 20:46:46 +01:00
|
|
|
]);
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->assertTrue(Hash::check($secret, $token->secret));
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2019-12-29 21:07:28 +01:00
|
|
|
$this->assertTrue(strlen($token->token_id) === 32);
|
2019-12-29 20:46:46 +01:00
|
|
|
$this->assertTrue(strlen($secret) === 32);
|
|
|
|
|
|
|
|
$this->assertSessionHas('success');
|
2020-11-20 19:53:01 +01:00
|
|
|
$this->assertActivityExists(ActivityType::API_TOKEN_CREATE);
|
2019-12-29 20:46:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
|
|
|
|
$resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", ['name' => 'No expiry token', 'expires_at' => '']);
|
|
|
|
$resp->assertRedirect();
|
|
|
|
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
|
|
|
|
$over = Carbon::now()->addYears(101);
|
|
|
|
$under = Carbon::now()->addYears(99);
|
|
|
|
$this->assertTrue(
|
|
|
|
($token->expires_at < $over && $token->expires_at > $under),
|
2021-06-26 17:23:15 +02:00
|
|
|
'Token expiry set at 100 years in future'
|
2019-12-29 20:46:46 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_created_token_displays_on_profile_page()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
|
|
|
$resp->assertRedirect();
|
|
|
|
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
|
|
|
|
$resp = $this->get($editor->getEditUrl());
|
2022-07-23 16:10:18 +02:00
|
|
|
$this->withHtml($resp)->assertElementExists('#api_tokens');
|
|
|
|
$this->withHtml($resp)->assertElementContains('#api_tokens', $token->name);
|
|
|
|
$this->withHtml($resp)->assertElementContains('#api_tokens', $token->token_id);
|
|
|
|
$this->withHtml($resp)->assertElementContains('#api_tokens', $token->expires_at->format('Y-m-d'));
|
2019-12-29 20:46:46 +01:00
|
|
|
}
|
|
|
|
|
2019-12-29 21:07:28 +01:00
|
|
|
public function test_secret_shown_once_after_creation()
|
2019-12-29 20:46:46 +01:00
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->asAdmin()->followingRedirects()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
2019-12-29 21:07:28 +01:00
|
|
|
$resp->assertSeeText('Token Secret');
|
2019-12-29 20:46:46 +01:00
|
|
|
|
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
$this->assertNull(session('api-token-secret:' . $token->id));
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->get("/api-tokens/{$editor->id}/{$token->id}");
|
|
|
|
$resp->assertOk();
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertDontSeeText('Client Secret');
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_token_update()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
$updateData = [
|
2021-06-26 17:23:15 +02:00
|
|
|
'name' => 'My updated token',
|
2019-12-29 20:46:46 +01:00
|
|
|
'expires_at' => '2011-01-01',
|
|
|
|
];
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->put("/api-tokens/{$editor->id}/{$token->id}", $updateData);
|
|
|
|
$resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
|
2019-12-29 20:46:46 +01:00
|
|
|
|
|
|
|
$this->assertDatabaseHas('api_tokens', array_merge($updateData, ['id' => $token->id]));
|
|
|
|
$this->assertSessionHas('success');
|
2020-11-20 19:53:01 +01:00
|
|
|
$this->assertActivityExists(ActivityType::API_TOKEN_UPDATE);
|
2019-12-29 20:46:46 +01:00
|
|
|
}
|
|
|
|
|
2019-12-29 21:18:37 +01:00
|
|
|
public function test_token_update_with_blank_expiry_sets_to_hundred_years_away()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
2019-12-29 21:18:37 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->put("/api-tokens/{$editor->id}/{$token->id}", [
|
2021-06-26 17:23:15 +02:00
|
|
|
'name' => 'My updated token',
|
2019-12-29 21:18:37 +01:00
|
|
|
'expires_at' => '',
|
2023-10-19 15:18:42 +02:00
|
|
|
])->assertRedirect();
|
2019-12-29 21:18:37 +01:00
|
|
|
$token->refresh();
|
|
|
|
|
|
|
|
$over = Carbon::now()->addYears(101);
|
|
|
|
$under = Carbon::now()->addYears(99);
|
|
|
|
$this->assertTrue(
|
|
|
|
($token->expires_at < $over && $token->expires_at > $under),
|
2021-06-26 17:23:15 +02:00
|
|
|
'Token expiry set at 100 years in future'
|
2019-12-29 21:18:37 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-12-29 20:46:46 +01:00
|
|
|
public function test_token_delete()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$editor = $this->users->editor();
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$tokenUrl = "/api-tokens/{$editor->id}/{$token->id}";
|
2019-12-29 20:46:46 +01:00
|
|
|
|
|
|
|
$resp = $this->get($tokenUrl . '/delete');
|
|
|
|
$resp->assertSeeText('Delete Token');
|
|
|
|
$resp->assertSeeText($token->name);
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->withHtml($resp)->assertElementExists('form[action$="' . $tokenUrl . '"]');
|
2019-12-29 20:46:46 +01:00
|
|
|
|
|
|
|
$resp = $this->delete($tokenUrl);
|
|
|
|
$resp->assertRedirect($editor->getEditUrl('#api_tokens'));
|
|
|
|
$this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
|
2020-11-20 19:53:01 +01:00
|
|
|
$this->assertActivityExists(ActivityType::API_TOKEN_DELETE);
|
2019-12-29 20:46:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function test_user_manage_can_delete_token_without_api_permission_themselves()
|
|
|
|
{
|
2023-01-21 12:08:34 +01:00
|
|
|
$viewer = $this->users->viewer();
|
|
|
|
$editor = $this->users->editor();
|
|
|
|
$this->permissions->grantUserRolePermissions($editor, ['users-manage']);
|
2019-12-29 20:46:46 +01:00
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$this->asAdmin()->post("/api-tokens/{$viewer->id}/create", $this->testTokenData);
|
2019-12-29 20:46:46 +01:00
|
|
|
$token = ApiToken::query()->latest()->first();
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->actingAs($editor)->get("/api-tokens/{$viewer->id}/{$token->id}");
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertStatus(200);
|
|
|
|
$resp->assertSeeText('Delete Token');
|
|
|
|
|
2023-10-19 15:18:42 +02:00
|
|
|
$resp = $this->actingAs($editor)->delete("/api-tokens/{$viewer->id}/{$token->id}");
|
2019-12-29 20:46:46 +01:00
|
|
|
$resp->assertRedirect($viewer->getEditUrl('#api_tokens'));
|
|
|
|
$this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
|
|
|
|
}
|
2023-10-19 15:18:42 +02:00
|
|
|
|
|
|
|
public function test_return_routes_change_depending_on_entry_context()
|
|
|
|
{
|
|
|
|
$user = $this->users->admin();
|
|
|
|
$returnByContext = [
|
|
|
|
'settings' => url("/settings/users/{$user->id}/#api_tokens"),
|
|
|
|
'my-account' => url('/my-account/auth#api_tokens'),
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($returnByContext as $context => $returnUrl) {
|
|
|
|
$resp = $this->actingAs($user)->get("/api-tokens/{$user->id}/create?context={$context}");
|
|
|
|
$this->withHtml($resp)->assertLinkExists($returnUrl, 'Cancel');
|
|
|
|
|
|
|
|
$this->post("/api-tokens/{$user->id}/create", $this->testTokenData);
|
|
|
|
$token = $user->apiTokens()->latest()->first();
|
|
|
|
|
|
|
|
$resp = $this->get($token->getUrl());
|
|
|
|
$this->withHtml($resp)->assertLinkExists($returnUrl, 'Back');
|
|
|
|
|
|
|
|
$resp = $this->delete($token->getUrl());
|
|
|
|
$resp->assertRedirect($returnUrl);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_context_assumed_for_editing_tokens_of_another_user()
|
|
|
|
{
|
|
|
|
$user = $this->users->viewer();
|
|
|
|
|
|
|
|
$resp = $this->asAdmin()->get("/api-tokens/{$user->id}/create?context=my-account");
|
|
|
|
$this->withHtml($resp)->assertLinkExists($user->getEditUrl('#api_tokens'), 'Cancel');
|
|
|
|
}
|
2021-06-26 17:23:15 +02:00
|
|
|
}
|