Skip to content

[11.x] Custom authorization view response #1629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Contracts/AuthorizationViewResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Laravel\Passport\Contracts;

use Illuminate\Contracts\Support\Responsable;

interface AuthorizationViewResponse extends Responsable
{
/**
* @param array $parameters
* @return mixed
*/
public function withParameters($parameters = []);
}
14 changes: 7 additions & 7 deletions src/Http/Controllers/AuthorizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Laravel\Passport\Bridge\User;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Contracts\AuthorizationViewResponse;
use Laravel\Passport\Passport;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\AuthorizationServer;
Expand All @@ -28,9 +28,9 @@ class AuthorizationController
protected $server;

/**
* The response factory implementation.
* The response implementation.
*
* @var \Illuminate\Contracts\Routing\ResponseFactory
* @var \Laravel\Passport\Contracts\AuthorizationViewResponse
*/
protected $response;

Expand All @@ -45,12 +45,12 @@ class AuthorizationController
* Create a new controller instance.
*
* @param \League\OAuth2\Server\AuthorizationServer $server
* @param \Illuminate\Contracts\Routing\ResponseFactory $response
* @param \Laravel\Passport\Contracts\AuthorizationViewResponse $response
* @param \Illuminate\Contracts\Auth\StatefulGuard $guard
* @return void
*/
public function __construct(AuthorizationServer $server,
ResponseFactory $response,
AuthorizationViewResponse $response,
StatefulGuard $guard)
{
$this->server = $server;
Expand All @@ -65,7 +65,7 @@ public function __construct(AuthorizationServer $server,
* @param \Illuminate\Http\Request $request
* @param \Laravel\Passport\ClientRepository $clients
* @param \Laravel\Passport\TokenRepository $tokens
* @return \Illuminate\Http\Response
* @return \Laravel\Passport\Contracts\AuthorizationViewResponse|mixed|void
*/
public function authorize(ServerRequestInterface $psrRequest,
Request $request,
Expand Down Expand Up @@ -109,7 +109,7 @@ public function authorize(ServerRequestInterface $psrRequest,
$request->session()->put('authToken', $authToken = Str::random());
$request->session()->put('authRequest', $authRequest);

return $this->response->view('passport::authorize', [
return $this->response->withParameters([
'client' => $client,
'user' => $user,
'scopes' => $scopes,
Expand Down
68 changes: 68 additions & 0 deletions src/Http/Responses/AuthorizationViewResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Laravel\Passport\Http\Responses;

use Illuminate\Contracts\Support\Responsable;
use Laravel\Passport\Contracts\AuthorizationViewResponse as AuthorizationViewResponseContract;

class AuthorizationViewResponse implements AuthorizationViewResponseContract
{
/**
* The name of the view or the callable used to generate the view.
*
* @var string
*/
protected $view;

/**
* An array of arguments that may be passed to the view response and used in the view.
*
* @var string
*/
protected $parameters;

/**
* Create a new response instance.
*
* @param callable|string $view
* @return void
*/
public function __construct($view)
{
$this->view = $view;
}

/**
* Add parameters to response.
*
* @param $parameters
* @return $this
*/
public function withParameters($parameters = [])
{
$this->parameters = $parameters;

return $this;
}

/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function toResponse($request)
{
if (! is_callable($this->view) || is_string($this->view)) {
return view($this->view, [...$this->parameters]);
}

$response = call_user_func($this->view, ...$this->parameters);

if ($response instanceof Responsable) {
return $response->toResponse($request);
}

return $response;
}
}
15 changes: 15 additions & 0 deletions src/Passport.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use DateInterval;
use DateTimeInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Laravel\Passport\Contracts\AuthorizationViewResponse as AuthorizationViewResponseContract;
use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use League\OAuth2\Server\ResourceServer;
use Mockery;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -715,4 +717,17 @@ public static function withoutCookieEncryption()

return new static;
}

/**
* Specify which view should be used as the authorization view.
*
* @param callable|string $view
* @return void
*/
public static function authorizationView($view)
{
app()->singleton(AuthorizationViewResponseContract::class, function ($app) use ($view) {
return new AuthorizationViewResponse($view);
});
}
}
2 changes: 2 additions & 0 deletions src/PassportServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public function register()
$this->registerJWTParser();
$this->registerResourceServer();
$this->registerGuard();

Passport::authorizationView('passport::authorize');
}

/**
Expand Down
35 changes: 17 additions & 18 deletions tests/Unit/AuthorizationControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace Laravel\Passport\Tests\Unit;

use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Request;
use Laravel\Passport\Bridge\Scope;
use Laravel\Passport\Client;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\Exceptions\OAuthServerException;
use Laravel\Passport\Http\Controllers\AuthorizationController;
use Laravel\Passport\Http\Responses\AuthorizationViewResponse;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;
Expand All @@ -36,7 +36,7 @@ public function test_authorization_view_is_presented()
]);

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand All @@ -57,21 +57,20 @@ public function test_authorization_view_is_presented()

$clients = m::mock(ClientRepository::class);
$clients->shouldReceive('find')->with(1)->andReturn($client = m::mock(Client::class));

$client->shouldReceive('skipsAuthorization')->andReturn(false);

$response->shouldReceive('view')->once()->andReturnUsing(function ($view, $data) use ($client, $user) {
$this->assertSame('passport::authorize', $view);
$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')->with($user, $client)->andReturnNull();

$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
$this->assertEquals($request, $data['request']);
$this->assertSame('description', $data['scopes'][0]->description);

return 'view';
});

$tokens = m::mock(TokenRepository::class);
$tokens->shouldReceive('findValidToken')->with($user, $client)->andReturnNull();

$this->assertSame('view', $controller->authorize(
m::mock(ServerRequestInterface::class), $request, $clients, $tokens
));
Expand All @@ -80,7 +79,7 @@ public function test_authorization_view_is_presented()
public function test_authorization_exceptions_are_handled()
{
$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -108,7 +107,7 @@ public function test_request_is_approved_if_valid_token_exists()
]);

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -158,7 +157,7 @@ public function test_request_is_approved_if_client_can_skip_authorization()
]);

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -207,7 +206,7 @@ public function test_authorization_view_is_presented_if_request_has_prompt_equal
]);

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand All @@ -234,10 +233,10 @@ public function test_authorization_view_is_presented_if_request_has_prompt_equal
$tokens = m::mock(TokenRepository::class);
$tokens->shouldNotReceive('findValidToken');

$response->shouldReceive('view')->once()->andReturnUsing(function ($view, $data) use ($client, $user) {
$this->assertSame('passport::authorize', $view);
$response->shouldReceive('withParameters')->once()->andReturnUsing(function ($data) use ($client, $user, $request) {
$this->assertEquals($client, $data['client']);
$this->assertEquals($user, $data['user']);
$this->assertEquals($request, $data['request']);
$this->assertSame('description', $data['scopes'][0]->description);

return 'view';
Expand All @@ -257,7 +256,7 @@ public function test_authorization_denied_if_request_has_prompt_equals_to_none()
]);

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -299,7 +298,7 @@ public function test_authorization_denied_if_request_has_prompt_equals_to_none()
public function test_authorization_denied_if_unauthenticated_and_request_has_prompt_equals_to_none()
{
$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -340,7 +339,7 @@ public function test_logout_and_prompt_login_if_request_has_prompt_equals_to_log
$this->expectException('Illuminate\Auth\AuthenticationException');

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down Expand Up @@ -371,7 +370,7 @@ public function test_user_should_be_authenticated()
$this->expectException('Illuminate\Auth\AuthenticationException');

$server = m::mock(AuthorizationServer::class);
$response = m::mock(ResponseFactory::class);
$response = m::mock(AuthorizationViewResponse::class);
$guard = m::mock(StatefulGuard::class);

$controller = new AuthorizationController($server, $response, $guard);
Expand Down