5 use BookStack\Activity\ActivityType;
6 use BookStack\Api\ApiToken;
8 use Illuminate\Support\Facades\Hash;
11 class UserApiTokenTest extends TestCase
13 protected array $testTokenData = [
14 'name' => 'My test API token',
15 'expires_at' => '2050-04-01',
18 public function test_tokens_section_not_visible_in_my_account_without_access_api_permission()
20 $user = $this->users->viewer();
22 $resp = $this->actingAs($user)->get('/my-account/auth');
23 $resp->assertDontSeeText('API Tokens');
25 $this->permissions->grantUserRolePermissions($user, ['access-api']);
27 $resp = $this->actingAs($user)->get('/my-account/auth');
28 $resp->assertSeeText('API Tokens');
29 $resp->assertSeeText('Create Token');
32 public function test_those_with_manage_users_can_view_other_user_tokens_but_not_create()
34 $viewer = $this->users->viewer();
35 $editor = $this->users->editor();
36 $this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
38 $resp = $this->actingAs($viewer)->get($editor->getEditUrl());
39 $resp->assertSeeText('API Tokens');
40 $resp->assertDontSeeText('Create Token');
43 public function test_create_api_token()
45 $editor = $this->users->editor();
47 $resp = $this->asAdmin()->get("/api-tokens/{$editor->id}/create");
48 $resp->assertStatus(200);
49 $resp->assertSee('Create API Token');
50 $resp->assertSee('Token Secret');
52 $resp = $this->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
53 $token = ApiToken::query()->latest()->first();
54 $resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
55 $this->assertDatabaseHas('api_tokens', [
56 'user_id' => $editor->id,
57 'name' => $this->testTokenData['name'],
58 'expires_at' => $this->testTokenData['expires_at'],
62 $this->assertSessionHas('api-token-secret:' . $token->id);
63 $secret = session('api-token-secret:' . $token->id);
64 $this->assertDatabaseMissing('api_tokens', [
67 $this->assertTrue(Hash::check($secret, $token->secret));
69 $this->assertTrue(strlen($token->token_id) === 32);
70 $this->assertTrue(strlen($secret) === 32);
72 $this->assertSessionHas('success');
73 $this->assertActivityExists(ActivityType::API_TOKEN_CREATE);
76 public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
78 $editor = $this->users->editor();
80 $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", ['name' => 'No expiry token', 'expires_at' => '']);
81 $resp->assertRedirect();
83 $token = ApiToken::query()->latest()->first();
85 $over = Carbon::now()->addYears(101);
86 $under = Carbon::now()->addYears(99);
88 ($token->expires_at < $over && $token->expires_at > $under),
89 'Token expiry set at 100 years in future'
93 public function test_created_token_displays_on_profile_page()
95 $editor = $this->users->editor();
96 $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
97 $resp->assertRedirect();
99 $token = ApiToken::query()->latest()->first();
101 $resp = $this->get($editor->getEditUrl());
102 $this->withHtml($resp)->assertElementExists('#api_tokens');
103 $this->withHtml($resp)->assertElementContains('#api_tokens', $token->name);
104 $this->withHtml($resp)->assertElementContains('#api_tokens', $token->token_id);
105 $this->withHtml($resp)->assertElementContains('#api_tokens', $token->expires_at->format('Y-m-d'));
108 public function test_secret_shown_once_after_creation()
110 $editor = $this->users->editor();
111 $resp = $this->asAdmin()->followingRedirects()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
112 $resp->assertSeeText('Token Secret');
114 $token = ApiToken::query()->latest()->first();
115 $this->assertNull(session('api-token-secret:' . $token->id));
117 $resp = $this->get("/api-tokens/{$editor->id}/{$token->id}");
119 $resp->assertDontSeeText('Client Secret');
122 public function test_token_update()
124 $editor = $this->users->editor();
125 $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
126 $token = ApiToken::query()->latest()->first();
128 'name' => 'My updated token',
129 'expires_at' => '2011-01-01',
132 $resp = $this->put("/api-tokens/{$editor->id}/{$token->id}", $updateData);
133 $resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
135 $this->assertDatabaseHas('api_tokens', array_merge($updateData, ['id' => $token->id]));
136 $this->assertSessionHas('success');
137 $this->assertActivityExists(ActivityType::API_TOKEN_UPDATE);
140 public function test_token_update_with_blank_expiry_sets_to_hundred_years_away()
142 $editor = $this->users->editor();
143 $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
144 $token = ApiToken::query()->latest()->first();
146 $this->put("/api-tokens/{$editor->id}/{$token->id}", [
147 'name' => 'My updated token',
149 ])->assertRedirect();
152 $over = Carbon::now()->addYears(101);
153 $under = Carbon::now()->addYears(99);
155 ($token->expires_at < $over && $token->expires_at > $under),
156 'Token expiry set at 100 years in future'
160 public function test_token_delete()
162 $editor = $this->users->editor();
163 $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
164 $token = ApiToken::query()->latest()->first();
166 $tokenUrl = "/api-tokens/{$editor->id}/{$token->id}";
168 $resp = $this->get($tokenUrl . '/delete');
169 $resp->assertSeeText('Delete Token');
170 $resp->assertSeeText($token->name);
171 $this->withHtml($resp)->assertElementExists('form[action$="' . $tokenUrl . '"]');
173 $resp = $this->delete($tokenUrl);
174 $resp->assertRedirect($editor->getEditUrl('#api_tokens'));
175 $this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
176 $this->assertActivityExists(ActivityType::API_TOKEN_DELETE);
179 public function test_user_manage_can_delete_token_without_api_permission_themselves()
181 $viewer = $this->users->viewer();
182 $editor = $this->users->editor();
183 $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
185 $this->asAdmin()->post("/api-tokens/{$viewer->id}/create", $this->testTokenData);
186 $token = ApiToken::query()->latest()->first();
188 $resp = $this->actingAs($editor)->get("/api-tokens/{$viewer->id}/{$token->id}");
189 $resp->assertStatus(200);
190 $resp->assertSeeText('Delete Token');
192 $resp = $this->actingAs($editor)->delete("/api-tokens/{$viewer->id}/{$token->id}");
193 $resp->assertRedirect($viewer->getEditUrl('#api_tokens'));
194 $this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
197 public function test_return_routes_change_depending_on_entry_context()
199 $user = $this->users->admin();
201 'settings' => url("/settings/users/{$user->id}/#api_tokens"),
202 'my-account' => url('/my-account/auth#api_tokens'),
205 foreach ($returnByContext as $context => $returnUrl) {
206 $resp = $this->actingAs($user)->get("/api-tokens/{$user->id}/create?context={$context}");
207 $this->withHtml($resp)->assertLinkExists($returnUrl, 'Cancel');
209 $this->post("/api-tokens/{$user->id}/create", $this->testTokenData);
210 $token = $user->apiTokens()->latest()->first();
212 $resp = $this->get($token->getUrl());
213 $this->withHtml($resp)->assertLinkExists($returnUrl, 'Back');
215 $resp = $this->delete($token->getUrl());
216 $resp->assertRedirect($returnUrl);
220 public function test_context_assumed_for_editing_tokens_of_another_user()
222 $user = $this->users->viewer();
224 $resp = $this->asAdmin()->get("/api-tokens/{$user->id}/create?context=my-account");
225 $this->withHtml($resp)->assertLinkExists($user->getEditUrl('#api_tokens'), 'Cancel');