Skip to content

Commit 904bf56

Browse files
authored
[9.x] Allow authorization responses to specify HTTP status codes (#43097)
* allow authorize response to specify http status * code style * add 404 helpers
1 parent ca490ca commit 904bf56

File tree

7 files changed

+373
-3
lines changed

7 files changed

+373
-3
lines changed

src/Illuminate/Auth/Access/AuthorizationException.php

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ class AuthorizationException extends Exception
1414
*/
1515
protected $response;
1616

17+
/**
18+
* The HTTP response status code.
19+
*
20+
* @var int|null
21+
*/
22+
protected $status;
23+
1724
/**
1825
* Create a new authorization exception instance.
1926
*
@@ -52,13 +59,56 @@ public function setResponse($response)
5259
return $this;
5360
}
5461

62+
/**
63+
* Set the HTTP response status code.
64+
*
65+
* @param int|null $status
66+
* @return $this
67+
*/
68+
public function withStatus($status)
69+
{
70+
$this->status = $status;
71+
72+
return $this;
73+
}
74+
75+
/**
76+
* Set the HTTP response status code to 404.
77+
*
78+
* @return $this
79+
*/
80+
public function asNotFound()
81+
{
82+
return $this->withStatus(404);
83+
}
84+
85+
/**
86+
* Determine if the HTTP status code has been set.
87+
*
88+
* @return bool
89+
*/
90+
public function hasStatus()
91+
{
92+
return $this->status !== null;
93+
}
94+
95+
/**
96+
* Get the HTTP status code.
97+
*
98+
* @return int|null
99+
*/
100+
public function status()
101+
{
102+
return $this->status;
103+
}
104+
55105
/**
56106
* Create a deny response object from this exception.
57107
*
58108
* @return \Illuminate\Auth\Access\Response
59109
*/
60110
public function toResponse()
61111
{
62-
return Response::deny($this->message, $this->code);
112+
return Response::deny($this->message, $this->code)->withStatus($this->status);
63113
}
64114
}

src/Illuminate/Auth/Access/HandlesAuthorization.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,29 @@ protected function deny($message = null, $code = null)
2727
{
2828
return Response::deny($message, $code);
2929
}
30+
31+
/**
32+
* Deny with a HTTP status code.
33+
*
34+
* @param int $status
35+
* @param ?string $message
36+
* @param ?int $code
37+
* @return \Illuminate\Auth\Access\Response
38+
*/
39+
public function denyWithStatus($status, $message = null, $code = null)
40+
{
41+
return Response::denyWithStatus($status, $message, $code);
42+
}
43+
44+
/**
45+
* Deny with a 404 HTTP status code.
46+
*
47+
* @param ?string $message
48+
* @param ?int $code
49+
* @return \Illuminate\Auth\Access\Response
50+
*/
51+
public function denyAsNotFound($message = null, $code = null)
52+
{
53+
return Response::denyWithStatus(404, $message, $code);
54+
}
3055
}

src/Illuminate/Auth/Access/Response.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class Response implements Arrayable
2727
*/
2828
protected $code;
2929

30+
/**
31+
* The HTTP response status code.
32+
*
33+
* @var int|null
34+
*/
35+
protected $status;
36+
3037
/**
3138
* Create a new response.
3239
*
@@ -66,6 +73,31 @@ public static function deny($message = null, $code = null)
6673
return new static(false, $message, $code);
6774
}
6875

76+
/**
77+
* Create a new "deny" Response with a HTTP status code.
78+
*
79+
* @param int $status
80+
* @param string|null $message
81+
* @param mixed $code
82+
* @return \Illuminate\Auth\Access\Response
83+
*/
84+
public static function denyWithStatus($status, $message = null, $code = null)
85+
{
86+
return static::deny($message, $code)->withStatus($status);
87+
}
88+
89+
/**
90+
* Create a new "deny" Response with a 404 HTTP status code.
91+
*
92+
* @param string|null $message
93+
* @param mixed $code
94+
* @return \Illuminate\Auth\Access\Response
95+
*/
96+
public static function denyAsNotFound($message = null, $code = null)
97+
{
98+
return static::denyWithStatus(404, $message, $code);
99+
}
100+
69101
/**
70102
* Determine if the response was allowed.
71103
*
@@ -117,12 +149,46 @@ public function authorize()
117149
{
118150
if ($this->denied()) {
119151
throw (new AuthorizationException($this->message(), $this->code()))
120-
->setResponse($this);
152+
->setResponse($this)
153+
->withStatus($this->status);
121154
}
122155

123156
return $this;
124157
}
125158

159+
/**
160+
* Set the HTTP response status code.
161+
*
162+
* @param null|int $status
163+
* @return $this
164+
*/
165+
public function withStatus($status)
166+
{
167+
$this->status = $status;
168+
169+
return $this;
170+
}
171+
172+
/**
173+
* Set the HTTP response status code to 404.
174+
*
175+
* @return $this
176+
*/
177+
public function asNotFound()
178+
{
179+
return $this->withStatus(404);
180+
}
181+
182+
/**
183+
* Get the HTTP status code.
184+
*
185+
* @return int|null
186+
*/
187+
public function status()
188+
{
189+
return $this->status;
190+
}
191+
126192
/**
127193
* Convert the response to an array.
128194
*

src/Illuminate/Foundation/Exceptions/Handler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ protected function prepareException(Throwable $e)
375375
return match (true) {
376376
$e instanceof BackedEnumCaseNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
377377
$e instanceof ModelNotFoundException => new NotFoundHttpException($e->getMessage(), $e),
378-
$e instanceof AuthorizationException => new AccessDeniedHttpException($e->getMessage(), $e),
378+
$e instanceof AuthorizationException && $e->hasStatus() => new HttpException($e->status(), $e->getMessage(), $e),
379+
$e instanceof AuthorizationException && ! $e->hasStatus() => new AccessDeniedHttpException($e->getMessage(), $e),
379380
$e instanceof TokenMismatchException => new HttpException(419, $e->getMessage(), $e),
380381
$e instanceof SuspiciousOperationException => new NotFoundHttpException('Bad hostname provided.', $e),
381382
$e instanceof RecordsNotFoundException => new NotFoundHttpException('Not found.', $e),

tests/Auth/AuthAccessResponseTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,82 @@ public function testDenyMethodWithNoMessageReturnsNull()
3535
$this->assertNull($response->message());
3636
}
3737

38+
public function testItSetsEmptyStatusOnExceptionWhenAuthorizing()
39+
{
40+
try {
41+
Response::deny('foo', 3)->authorize();
42+
$this->fail();
43+
} catch (AuthorizationException $e) {
44+
$this->assertNull($e->status());
45+
$this->assertFalse($e->hasStatus());
46+
$this->assertSame('foo', $e->getMessage());
47+
$this->assertSame(3, $e->getCode());
48+
}
49+
}
50+
51+
public function testItSetsStatusOnExceptionWhenAuthorizing()
52+
{
53+
try {
54+
Response::deny('foo', 3)->withStatus(418)->authorize();
55+
$this->fail();
56+
} catch (AuthorizationException $e) {
57+
$this->assertSame(418, $e->status());
58+
$this->assertTrue($e->hasStatus());
59+
$this->assertSame('foo', $e->getMessage());
60+
$this->assertSame(3, $e->getCode());
61+
}
62+
63+
try {
64+
Response::deny('foo', 3)->asNotFound()->authorize();
65+
$this->fail();
66+
} catch (AuthorizationException $e) {
67+
$this->assertSame(404, $e->status());
68+
$this->assertTrue($e->hasStatus());
69+
$this->assertSame('foo', $e->getMessage());
70+
$this->assertSame(3, $e->getCode());
71+
}
72+
73+
try {
74+
Response::denyWithStatus(444)->authorize();
75+
$this->fail();
76+
} catch (AuthorizationException $e) {
77+
$this->assertSame(444, $e->status());
78+
$this->assertTrue($e->hasStatus());
79+
$this->assertSame('This action is unauthorized.', $e->getMessage());
80+
$this->assertSame(0, $e->getCode());
81+
}
82+
83+
try {
84+
Response::denyWithStatus(444, 'foo', 3)->authorize();
85+
$this->fail();
86+
} catch (AuthorizationException $e) {
87+
$this->assertSame(444, $e->status());
88+
$this->assertTrue($e->hasStatus());
89+
$this->assertSame('foo', $e->getMessage());
90+
$this->assertSame(3, $e->getCode());
91+
}
92+
93+
try {
94+
Response::denyAsNotFound()->authorize();
95+
$this->fail();
96+
} catch (AuthorizationException $e) {
97+
$this->assertSame(404, $e->status());
98+
$this->assertTrue($e->hasStatus());
99+
$this->assertSame('This action is unauthorized.', $e->getMessage());
100+
$this->assertSame(0, $e->getCode());
101+
}
102+
103+
try {
104+
Response::denyAsNotFound('foo', 3)->authorize();
105+
$this->fail();
106+
} catch (AuthorizationException $e) {
107+
$this->assertSame(404, $e->status());
108+
$this->assertTrue($e->hasStatus());
109+
$this->assertSame('foo', $e->getMessage());
110+
$this->assertSame(3, $e->getCode());
111+
}
112+
}
113+
38114
public function testAuthorizeMethodThrowsAuthorizationExceptionWhenResponseDenied()
39115
{
40116
$response = Response::deny('Some message.', 'some_code');

0 commit comments

Comments
 (0)