Skip to content

Commit 23307a6

Browse files
Merge pull request #7836 from getkirby/v6/refact/auth-csrf
refact: `Kirby\Auth\Csrf`
2 parents b168ca8 + a4fb297 commit 23307a6

4 files changed

Lines changed: 110 additions & 43 deletions

File tree

src/Auth/Csrf.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Kirby\Auth;
4+
5+
use Kirby\Cms\App;
6+
7+
/**
8+
* Handler for the auth CSRF token
9+
*
10+
* @package Kirby Auth
11+
* @author Lukas Bestle <lukas@getkirby.com>
12+
* @link https://getkirby.com
13+
* @copyright Bastian Allgeier
14+
* @license https://getkirby.com/license
15+
* @since 6.0.0
16+
*/
17+
class Csrf
18+
{
19+
public function __construct(
20+
protected App $kirby
21+
) {
22+
}
23+
24+
/**
25+
* Returns csrf from the header
26+
*/
27+
public function fromHeader(): string|null
28+
{
29+
return $this->kirby->request()->csrf();
30+
}
31+
32+
/**
33+
* Returns either predefined csrf or the one from session
34+
*/
35+
public function fromSession(): string
36+
{
37+
$isDev = $this->kirby->option('panel.dev', false) !== false;
38+
$fallback = $isDev ? 'dev' : $this->kirby->csrf();
39+
return $this->kirby->option('api.csrf', $fallback);
40+
}
41+
42+
/**
43+
* Returns the csrf token if it exists and if it is valid
44+
*/
45+
public function get(): string|false
46+
{
47+
$header = $this->fromHeader();
48+
$session = $this->fromSession();
49+
50+
// compare both tokens
51+
if (hash_equals($session, (string)$header) !== true) {
52+
return false;
53+
}
54+
55+
return $session;
56+
}
57+
}

src/Cms/Auth.php

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Kirby\Cms;
44

5+
use Kirby\Auth\Csrf;
56
use Kirby\Auth\Exception\RateLimitException;
67
use Kirby\Auth\Limits;
78
use Kirby\Cms\Auth\Challenge;
@@ -35,6 +36,8 @@ class Auth
3536
*/
3637
public static array $challenges = [];
3738

39+
protected Csrf $csrf;
40+
3841
/**
3942
* Currently impersonated user
4043
*/
@@ -65,6 +68,7 @@ class Auth
6568
public function __construct(
6669
protected App $kirby
6770
) {
71+
$this->csrf = new Csrf($kirby);
6872
$this->limits = new Limits($kirby);
6973
}
7074

@@ -182,18 +186,7 @@ public function createChallenge(
182186
*/
183187
public function csrf(): string|false
184188
{
185-
// get the csrf from the header
186-
$fromHeader = $this->kirby->request()->csrf();
187-
188-
// check for a predefined csrf or use the one from session
189-
$fromSession = $this->csrfFromSession();
190-
191-
// compare both tokens
192-
if (hash_equals($fromSession, (string)$fromHeader) !== true) {
193-
return false;
194-
}
195-
196-
return $fromSession;
189+
return $this->csrf->get();
197190
}
198191

199192
/**
@@ -202,9 +195,7 @@ public function csrf(): string|false
202195
*/
203196
public function csrfFromSession(): string
204197
{
205-
$isDev = $this->kirby->option('panel.dev', false) !== false;
206-
$fallback = $isDev ? 'dev' : $this->kirby->csrf();
207-
return $this->kirby->option('api.csrf', $fallback);
198+
return $this->csrf->fromSession();
208199
}
209200

210201
/**
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<?php
22

3-
namespace Kirby\Cms;
3+
namespace Kirby\Auth;
44

5+
use Kirby\Cms\App;
56
use Kirby\Filesystem\Dir;
7+
use Kirby\TestCase;
68
use PHPUnit\Framework\Attributes\CoversClass;
79

8-
#[CoversClass(Auth::class)]
9-
class AuthCsrfTest extends TestCase
10+
#[CoversClass(Csrf::class)]
11+
class CsrfTest extends TestCase
1012
{
11-
public const string TMP = KIRBY_TMP_DIR . '/Cms.AuthCsrf';
13+
public const string TMP = KIRBY_TMP_DIR . '/Auth.Csrf';
1214

13-
protected Auth $auth;
15+
protected Csrf $csrf;
1416

1517
public function setUp(): void
1618
{
@@ -20,7 +22,7 @@ public function setUp(): void
2022
],
2123
]);
2224

23-
$this->auth = new Auth($this->app);
25+
$this->csrf = new Csrf($this->app);
2426
}
2527

2628
public function tearDown(): void
@@ -30,96 +32,96 @@ public function tearDown(): void
3032
$_GET = [];
3133
}
3234

33-
public function testCsrfFromSession1(): void
35+
public function testFromSession1(): void
3436
{
3537
$this->app->session()->set('kirby.csrf', 'session-csrf');
3638

3739
$_GET = [];
38-
$this->assertFalse($this->auth->csrf());
40+
$this->assertFalse($this->csrf->get());
3941
}
4042

41-
public function testCsrfFromSession2(): void
43+
public function testFromSession2(): void
4244
{
4345
$this->app->session()->set('kirby.csrf', 'session-csrf');
4446

4547
$_GET = ['csrf' => ''];
46-
$this->assertFalse($this->auth->csrf());
48+
$this->assertFalse($this->csrf->get());
4749
}
4850

49-
public function testCsrfFromSession3(): void
51+
public function testFromSession3(): void
5052
{
5153
$this->app->session()->set('kirby.csrf', 'session-csrf');
5254

5355
$_GET = ['csrf' => 'session-csrf'];
54-
$this->assertSame('session-csrf', $this->auth->csrf());
56+
$this->assertSame('session-csrf', $this->csrf->get());
5557
}
5658

57-
public function testCsrfFromSession4(): void
59+
public function testFromSession4(): void
5860
{
5961
$this->app->session()->set('kirby.csrf', 'session-csrf');
6062

6163
$_GET = ['csrf' => 'invalid-csrf'];
62-
$this->assertFalse($this->auth->csrf());
64+
$this->assertFalse($this->csrf->get());
6365
}
6466

65-
public function testCsrfFromOption1(): void
67+
public function testGet1(): void
6668
{
6769
$this->app = $this->app->clone([
6870
'options' => [
6971
'api.csrf' => 'option-csrf'
7072
]
7173
]);
72-
$this->auth = new Auth($this->app);
7374

75+
$this->csrf = new Csrf($this->app);
7476
$this->app->session()->set('kirby.csrf', 'session-csrf');
7577

7678
$_GET = [];
77-
$this->assertFalse($this->auth->csrf());
79+
$this->assertFalse($this->csrf->get());
7880
}
7981

80-
public function testCsrfFromOption2(): void
82+
public function testGet2(): void
8183
{
8284
$this->app = $this->app->clone([
8385
'options' => [
8486
'api.csrf' => 'option-csrf'
8587
]
8688
]);
87-
$this->auth = new Auth($this->app);
8889

90+
$this->csrf = new Csrf($this->app);
8991
$this->app->session()->set('kirby.csrf', 'session-csrf');
9092

9193
$_GET = ['csrf' => 'option-csrf'];
92-
$this->assertSame('option-csrf', $this->auth->csrf());
94+
$this->assertSame('option-csrf', $this->csrf->get());
9395
}
9496

95-
public function testCsrfFromOption3(): void
97+
public function testGet3(): void
9698
{
9799
$this->app = $this->app->clone([
98100
'options' => [
99101
'api.csrf' => 'option-csrf'
100102
]
101103
]);
102-
$this->auth = new Auth($this->app);
103104

105+
$this->csrf = new Csrf($this->app);
104106
$this->app->session()->set('kirby.csrf', 'session-csrf');
105107

106108
$_GET = ['csrf' => 'session-csrf'];
107-
$this->assertFalse($this->auth->csrf());
109+
$this->assertFalse($this->csrf->get());
108110
}
109111

110-
public function testCsrfFromOption4(): void
112+
public function testGet4(): void
111113
{
112114
$this->app = $this->app->clone([
113115
'options' => [
114116
'api.csrf' => 'option-csrf'
115117
]
116118
]);
117-
$this->auth = new Auth($this->app);
118119

120+
$this->csrf = new Csrf($this->app);
119121
$this->app->session()->set('kirby.csrf', 'session-csrf');
120122

121123
$_GET = ['csrf' => 'invalid-csrf'];
122-
$this->assertFalse($this->auth->csrf());
124+
$this->assertFalse($this->csrf->get());
123125
}
124126

125127
public function testCsrfFromSessionPanelDevOption(): void
@@ -129,7 +131,9 @@ public function testCsrfFromSessionPanelDevOption(): void
129131
'panel.dev' => true
130132
]
131133
]);
132-
$this->auth = new Auth($this->app);
133-
$this->assertSame('dev', $this->auth->csrfFromSession());
134+
135+
$this->csrf = new Csrf($this->app);
136+
137+
$this->assertFalse($this->csrf->get());
134138
}
135139
}

tests/Cms/Auth/AuthTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ public function tearDown(): void
6363
$this->app->session()->destroy();
6464
Dir::remove(static::TMP);
6565
App::destroy();
66+
$_GET = [];
67+
}
68+
69+
public function testCsrf(): void
70+
{
71+
$this->app->session()->set('kirby.csrf', 'session-csrf');
72+
$_GET = [];
73+
$this->assertFalse($this->auth->csrf());
74+
}
75+
76+
public function testCsrfFromSession(): void
77+
{
78+
$this->app->session()->set('kirby.csrf', 'session-csrf');
79+
$_GET = ['csrf' => 'session-csrf'];
80+
$this->assertSame('session-csrf', $this->auth->csrfFromSession());
6681
}
6782

6883
public function testImpersonate(): void

0 commit comments

Comments
 (0)