mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-26 18:30:50 +08:00
Added testing coverage to user API token interfaces
This commit is contained in:
parent
dccb279c84
commit
832fbd65af
|
@ -6,6 +6,6 @@ class ApiToken extends Model
|
|||
{
|
||||
protected $fillable = ['name', 'expires_at'];
|
||||
protected $casts = [
|
||||
'expires_at' => 'datetime:Y-m-d'
|
||||
'expires_at' => 'date:Y-m-d'
|
||||
];
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class UserApiTokenController extends Controller
|
|||
{
|
||||
// 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', [
|
||||
|
@ -31,7 +31,7 @@ class UserApiTokenController extends Controller
|
|||
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',
|
||||
|
@ -55,8 +55,10 @@ class UserApiTokenController extends Controller
|
|||
}
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
|
@ -89,7 +91,7 @@ class UserApiTokenController extends Controller
|
|||
[$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));
|
||||
}
|
||||
|
||||
|
@ -113,7 +115,7 @@ class UserApiTokenController extends Controller
|
|||
[$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'));
|
||||
}
|
||||
|
||||
|
@ -124,8 +126,9 @@ class UserApiTokenController extends Controller
|
|||
*/
|
||||
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();
|
||||
|
|
|
@ -22,7 +22,7 @@ class AddApiAuth extends Migration
|
|||
$table->string('client_id')->unique();
|
||||
$table->string('client_secret');
|
||||
$table->integer('user_id')->unsigned()->index();
|
||||
$table->timestamp('expires_at')->index();
|
||||
$table->date('expires_at')->index();
|
||||
$table->nullableTimestamps();
|
||||
});
|
||||
|
||||
|
|
|
@ -164,6 +164,8 @@ return [
|
|||
'user_api_token_expiry' => 'Expiry Date',
|
||||
'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.',
|
||||
'user_api_token_create_secret_message' => 'Immediately after creating this token a "client id"" & "client secret" will be generated and displayed. The client secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.',
|
||||
'user_api_token_create_success' => 'API token successfully created',
|
||||
'user_api_token_update_success' => 'API token successfully updated',
|
||||
'user_api_token' => 'API Token',
|
||||
'user_api_token_client_id' => 'Client ID',
|
||||
'user_api_token_client_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.',
|
||||
|
|
|
@ -88,8 +88,7 @@
|
|||
</section>
|
||||
@endif
|
||||
|
||||
{{-- TODO - Review Control--}}
|
||||
@if(($currentUser->id === $user->id && userCan('access-api')) || userCan('manage-users'))
|
||||
@if(($currentUser->id === $user->id && userCan('access-api')) || userCan('users-manage'))
|
||||
<section class="card content-wrap auto-height" id="api_tokens">
|
||||
<div class="grid half">
|
||||
<div><h2 class="list-heading">{{ trans('settings.users_api_tokens') }}</h2></div>
|
||||
|
|
165
tests/User/UserApiTokenTest.php
Normal file
165
tests/User/UserApiTokenTest.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?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]);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user