]> BookStack Code Mirror - bookstack/blob - tests/User/UserApiTokenTest.php
Updated translator & dependency attribution before release v25.05.1
[bookstack] / tests / User / UserApiTokenTest.php
1 <?php
2
3 namespace Tests\User;
4
5 use BookStack\Activity\ActivityType;
6 use BookStack\Api\ApiToken;
7 use Carbon\Carbon;
8 use Illuminate\Support\Facades\Hash;
9 use Tests\TestCase;
10
11 class UserApiTokenTest extends TestCase
12 {
13     protected array $testTokenData = [
14         'name'       => 'My test API token',
15         'expires_at' => '2050-04-01',
16     ];
17
18     public function test_tokens_section_not_visible_in_my_account_without_access_api_permission()
19     {
20         $user = $this->users->viewer();
21
22         $resp = $this->actingAs($user)->get('/my-account/auth');
23         $resp->assertDontSeeText('API Tokens');
24
25         $this->permissions->grantUserRolePermissions($user, ['access-api']);
26
27         $resp = $this->actingAs($user)->get('/my-account/auth');
28         $resp->assertSeeText('API Tokens');
29         $resp->assertSeeText('Create Token');
30     }
31
32     public function test_those_with_manage_users_can_view_other_user_tokens_but_not_create()
33     {
34         $viewer = $this->users->viewer();
35         $editor = $this->users->editor();
36         $this->permissions->grantUserRolePermissions($viewer, ['users-manage']);
37
38         $resp = $this->actingAs($viewer)->get($editor->getEditUrl());
39         $resp->assertSeeText('API Tokens');
40         $resp->assertDontSeeText('Create Token');
41     }
42
43     public function test_create_api_token()
44     {
45         $editor = $this->users->editor();
46
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');
51
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'],
59         ]);
60
61         // Check secret token
62         $this->assertSessionHas('api-token-secret:' . $token->id);
63         $secret = session('api-token-secret:' . $token->id);
64         $this->assertDatabaseMissing('api_tokens', [
65             'secret' => $secret,
66         ]);
67         $this->assertTrue(Hash::check($secret, $token->secret));
68
69         $this->assertTrue(strlen($token->token_id) === 32);
70         $this->assertTrue(strlen($secret) === 32);
71
72         $this->assertSessionHas('success');
73         $this->assertActivityExists(ActivityType::API_TOKEN_CREATE);
74     }
75
76     public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
77     {
78         $editor = $this->users->editor();
79
80         $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", ['name' => 'No expiry token', 'expires_at' => '']);
81         $resp->assertRedirect();
82
83         $token = ApiToken::query()->latest()->first();
84
85         $over = Carbon::now()->addYears(101);
86         $under = Carbon::now()->addYears(99);
87         $this->assertTrue(
88             ($token->expires_at < $over && $token->expires_at > $under),
89             'Token expiry set at 100 years in future'
90         );
91     }
92
93     public function test_created_token_displays_on_profile_page()
94     {
95         $editor = $this->users->editor();
96         $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
97         $resp->assertRedirect();
98
99         $token = ApiToken::query()->latest()->first();
100
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'));
106     }
107
108     public function test_secret_shown_once_after_creation()
109     {
110         $editor = $this->users->editor();
111         $resp = $this->asAdmin()->followingRedirects()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
112         $resp->assertSeeText('Token Secret');
113
114         $token = ApiToken::query()->latest()->first();
115         $this->assertNull(session('api-token-secret:' . $token->id));
116
117         $resp = $this->get("/api-tokens/{$editor->id}/{$token->id}");
118         $resp->assertOk();
119         $resp->assertDontSeeText('Client Secret');
120     }
121
122     public function test_token_update()
123     {
124         $editor = $this->users->editor();
125         $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
126         $token = ApiToken::query()->latest()->first();
127         $updateData = [
128             'name'       => 'My updated token',
129             'expires_at' => '2011-01-01',
130         ];
131
132         $resp = $this->put("/api-tokens/{$editor->id}/{$token->id}", $updateData);
133         $resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
134
135         $this->assertDatabaseHas('api_tokens', array_merge($updateData, ['id' => $token->id]));
136         $this->assertSessionHas('success');
137         $this->assertActivityExists(ActivityType::API_TOKEN_UPDATE);
138     }
139
140     public function test_token_update_with_blank_expiry_sets_to_hundred_years_away()
141     {
142         $editor = $this->users->editor();
143         $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
144         $token = ApiToken::query()->latest()->first();
145
146         $this->put("/api-tokens/{$editor->id}/{$token->id}", [
147             'name'       => 'My updated token',
148             'expires_at' => '',
149         ])->assertRedirect();
150         $token->refresh();
151
152         $over = Carbon::now()->addYears(101);
153         $under = Carbon::now()->addYears(99);
154         $this->assertTrue(
155             ($token->expires_at < $over && $token->expires_at > $under),
156             'Token expiry set at 100 years in future'
157         );
158     }
159
160     public function test_token_delete()
161     {
162         $editor = $this->users->editor();
163         $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
164         $token = ApiToken::query()->latest()->first();
165
166         $tokenUrl = "/api-tokens/{$editor->id}/{$token->id}";
167
168         $resp = $this->get($tokenUrl . '/delete');
169         $resp->assertSeeText('Delete Token');
170         $resp->assertSeeText($token->name);
171         $this->withHtml($resp)->assertElementExists('form[action$="' . $tokenUrl . '"]');
172
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);
177     }
178
179     public function test_user_manage_can_delete_token_without_api_permission_themselves()
180     {
181         $viewer = $this->users->viewer();
182         $editor = $this->users->editor();
183         $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
184
185         $this->asAdmin()->post("/api-tokens/{$viewer->id}/create", $this->testTokenData);
186         $token = ApiToken::query()->latest()->first();
187
188         $resp = $this->actingAs($editor)->get("/api-tokens/{$viewer->id}/{$token->id}");
189         $resp->assertStatus(200);
190         $resp->assertSeeText('Delete Token');
191
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]);
195     }
196
197     public function test_return_routes_change_depending_on_entry_context()
198     {
199         $user = $this->users->admin();
200         $returnByContext = [
201             'settings' => url("/settings/users/{$user->id}/#api_tokens"),
202             'my-account' => url('/my-account/auth#api_tokens'),
203         ];
204
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');
208
209             $this->post("/api-tokens/{$user->id}/create", $this->testTokenData);
210             $token = $user->apiTokens()->latest()->first();
211
212             $resp = $this->get($token->getUrl());
213             $this->withHtml($resp)->assertLinkExists($returnUrl, 'Back');
214
215             $resp = $this->delete($token->getUrl());
216             $resp->assertRedirect($returnUrl);
217         }
218     }
219
220     public function test_context_assumed_for_editing_tokens_of_another_user()
221     {
222         $user = $this->users->viewer();
223
224         $resp = $this->asAdmin()->get("/api-tokens/{$user->id}/create?context=my-account");
225         $this->withHtml($resp)->assertLinkExists($user->getEditUrl('#api_tokens'), 'Cancel');
226     }
227 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.