{
// Ensure user is has access-api permission and is the current user or has permission to manage the current user.
$this->checkPermission('access-api');
- $this->checkPermissionOrCurrentUser('manage-users', $userId);
+ $this->checkPermissionOrCurrentUser('users-manage', $userId);
$user = User::query()->findOrFail($userId);
return view('users.api-tokens.create', [
public function store(Request $request, int $userId)
{
$this->checkPermission('access-api');
- $this->checkPermissionOrCurrentUser('manage-users', $userId);
+ $this->checkPermissionOrCurrentUser('users-manage', $userId);
$this->validate($request, [
'name' => 'required|max:250',
}
$token->save();
- // TODO - Notification and activity?
+ $token->refresh();
+
session()->flash('api-token-secret:' . $token->id, $secret);
+ $this->showSuccessNotification(trans('settings.user_api_token_create_success'));
return redirect($user->getEditUrl('/api-tokens/' . $token->id));
}
[$user, $token] = $this->checkPermissionAndFetchUserToken($userId, $tokenId);
$token->fill($request->all())->save();
- // TODO - Notification and activity?
+ $this->showSuccessNotification(trans('settings.user_api_token_update_success'));
return redirect($user->getEditUrl('/api-tokens/' . $token->id));
}
[$user, $token] = $this->checkPermissionAndFetchUserToken($userId, $tokenId);
$token->delete();
- // TODO - Notification and activity?, Might have text in translations already (user_api_token_delete_success)
+ $this->showSuccessNotification(trans('settings.user_api_token_delete_success'));
return redirect($user->getEditUrl('#api_tokens'));
}
*/
protected function checkPermissionAndFetchUserToken(int $userId, int $tokenId): array
{
- $this->checkPermission('access-api');
- $this->checkPermissionOrCurrentUser('manage-users', $userId);
+ $this->checkPermissionOr('users-manage', function () use ($userId) {
+ return $userId === user()->id && userCan('access-api');
+ });
$user = User::query()->findOrFail($userId);
$token = ApiToken::query()->where('user_id', '=', $user->id)->where('id', '=', $tokenId)->firstOrFail();
--- /dev/null
+<?php namespace Test;
+
+use BookStack\Api\ApiToken;
+use Carbon\Carbon;
+use Tests\TestCase;
+
+class UserApiTokenTest extends TestCase
+{
+
+ protected $testTokenData = [
+ 'name' => 'My test API token',
+ 'expires_at' => '2099-04-01',
+ ];
+
+ public function test_tokens_section_not_visible_without_access_api_permission()
+ {
+ $user = $this->getEditor();
+
+ $resp = $this->actingAs($user)->get($user->getEditUrl());
+ $resp->assertDontSeeText('API Tokens');
+
+ $this->giveUserPermissions($user, ['access-api']);
+
+ $resp = $this->actingAs($user)->get($user->getEditUrl());
+ $resp->assertSeeText('API Tokens');
+ $resp->assertSeeText('Create Token');
+ }
+
+ public function test_those_with_manage_users_can_view_other_user_tokens_but_not_create()
+ {
+ $viewer = $this->getViewer();
+ $editor = $this->getEditor();
+ $this->giveUserPermissions($editor, ['users-manage']);
+
+ $resp = $this->actingAs($editor)->get($viewer->getEditUrl());
+ $resp->assertSeeText('API Tokens');
+ $resp->assertDontSeeText('Create Token');
+ }
+
+ public function test_create_api_token()
+ {
+ $editor = $this->getEditor();
+
+ $resp = $this->asAdmin()->get($editor->getEditUrl('/create-api-token'));
+ $resp->assertStatus(200);
+ $resp->assertSee('Create API Token');
+ $resp->assertSee('client secret');
+
+ $resp = $this->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+ $token = ApiToken::query()->latest()->first();
+ $resp->assertRedirect($editor->getEditUrl('/api-tokens/' . $token->id));
+ $this->assertDatabaseHas('api_tokens', [
+ 'user_id' => $editor->id,
+ 'name' => $this->testTokenData['name'],
+ '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', [
+ 'client_secret' => $secret,
+ ]);
+ $this->assertTrue(\Hash::check($secret, $token->client_secret));
+
+ $this->assertTrue(strlen($token->client_id) === 32);
+ $this->assertTrue(strlen($secret) === 32);
+
+ $this->assertSessionHas('success');
+ }
+
+ public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
+ {
+ $editor = $this->getEditor();
+ $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), ['name' => 'No expiry token']);
+ $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),
+ "Token expiry set at 100 years in future"
+ );
+ }
+
+ public function test_created_token_displays_on_profile_page()
+ {
+ $editor = $this->getEditor();
+ $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+ $token = ApiToken::query()->latest()->first();
+
+ $resp = $this->get($editor->getEditUrl());
+ $resp->assertElementExists('#api_tokens');
+ $resp->assertElementContains('#api_tokens', $token->name);
+ $resp->assertElementContains('#api_tokens', $token->client_id);
+ $resp->assertElementContains('#api_tokens', $token->expires_at->format('Y-m-d'));
+ }
+
+ public function test_client_secret_shown_once_after_creation()
+ {
+ $editor = $this->getEditor();
+ $resp = $this->asAdmin()->followingRedirects()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+ $resp->assertSeeText('Client Secret');
+
+ $token = ApiToken::query()->latest()->first();
+ $this->assertNull(session('api-token-secret:' . $token->id));
+
+ $resp = $this->get($editor->getEditUrl('/api-tokens/' . $token->id));
+ $resp->assertDontSeeText('Client Secret');
+ }
+
+ public function test_token_update()
+ {
+ $editor = $this->getEditor();
+ $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+ $token = ApiToken::query()->latest()->first();
+ $updateData = [
+ 'name' => 'My updated token',
+ 'expires_at' => '2011-01-01',
+ ];
+
+ $resp = $this->put($editor->getEditUrl('/api-tokens/' . $token->id), $updateData);
+ $resp->assertRedirect($editor->getEditUrl('/api-tokens/' . $token->id));
+
+ $this->assertDatabaseHas('api_tokens', array_merge($updateData, ['id' => $token->id]));
+ $this->assertSessionHas('success');
+ }
+
+ public function test_token_delete()
+ {
+ $editor = $this->getEditor();
+ $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+ $token = ApiToken::query()->latest()->first();
+
+ $tokenUrl = $editor->getEditUrl('/api-tokens/' . $token->id);
+
+ $resp = $this->get($tokenUrl . '/delete');
+ $resp->assertSeeText('Delete Token');
+ $resp->assertSeeText($token->name);
+ $resp->assertElementExists('form[action="'.$tokenUrl.'"]');
+
+ $resp = $this->delete($tokenUrl);
+ $resp->assertRedirect($editor->getEditUrl('#api_tokens'));
+ $this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
+ }
+
+ public function test_user_manage_can_delete_token_without_api_permission_themselves()
+ {
+ $viewer = $this->getViewer();
+ $editor = $this->getEditor();
+ $this->giveUserPermissions($editor, ['users-manage']);
+
+ $this->asAdmin()->post($viewer->getEditUrl('/create-api-token'), $this->testTokenData);
+ $token = ApiToken::query()->latest()->first();
+
+ $resp = $this->actingAs($editor)->get($viewer->getEditUrl('/api-tokens/' . $token->id));
+ $resp->assertStatus(200);
+ $resp->assertSeeText('Delete Token');
+
+ $resp = $this->actingAs($editor)->delete($viewer->getEditUrl('/api-tokens/' . $token->id));
+ $resp->assertRedirect($viewer->getEditUrl('#api_tokens'));
+ $this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
+ }
+
+}
\ No newline at end of file