diff --git a/database/migrations/2019_07_17_000001_make_client_secret_nullable.php b/database/migrations/2019_07_17_000001_make_client_secret_nullable.php new file mode 100644 index 000000000..4074a77b4 --- /dev/null +++ b/database/migrations/2019_07_17_000001_make_client_secret_nullable.php @@ -0,0 +1,32 @@ +string('secret', 100)->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('oauth_clients', function (Blueprint $table) { + $table->string('secret', 100)->change(); + }); + } +} diff --git a/resources/js/components/Clients.vue b/resources/js/components/Clients.vue index 3797faae9..beecfa0b9 100644 --- a/resources/js/components/Clients.vue +++ b/resources/js/components/Clients.vue @@ -125,6 +125,22 @@ + + +
+ + +
+
+ +
+ + Require the client to authenticate with a secret. + +
+
@@ -222,7 +238,8 @@ createForm: { errors: [], name: '', - redirect: '' + redirect: '', + confidential: true }, editForm: { diff --git a/src/Bridge/ClientRepository.php b/src/Bridge/ClientRepository.php index 7623d2da6..82077b6c9 100644 --- a/src/Bridge/ClientRepository.php +++ b/src/Bridge/ClientRepository.php @@ -37,7 +37,7 @@ public function getClientEntity($clientIdentifier) } return new Client( - $clientIdentifier, $record->name, $record->redirect, ! is_null($record->secret) + $clientIdentifier, $record->name, $record->redirect, $record->confidential() ); } @@ -52,7 +52,7 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType) return false; } - return hash_equals($record->secret, (string) $clientSecret); + return $record->confidential() && hash_equals($record->secret, (string) $clientSecret); } /** @@ -75,8 +75,6 @@ protected function handlesGrant($record, $grantType) return $record->personal_access_client; case 'password': return $record->password_client; - case 'client_credentials': - return ! empty($record->secret); default: return true; } diff --git a/src/Client.php b/src/Client.php index be4cdc1d4..1b094b66d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -82,4 +82,14 @@ public function firstParty() { return $this->personal_access_client || $this->password_client; } + + /** + * Determine if the client is a confidential client. + * + * @return bool + */ + public function confidential() + { + return ! empty($this->secret); + } } diff --git a/src/ClientRepository.php b/src/ClientRepository.php index ce0a19aae..0b595cf42 100644 --- a/src/ClientRepository.php +++ b/src/ClientRepository.php @@ -106,14 +106,19 @@ public function personalAccessClient() * @param string $redirect * @param bool $personalAccess * @param bool $password + * @param bool $confidential * @return \Laravel\Passport\Client */ - public function create($userId, $name, $redirect, $personalAccess = false, $password = false) + public function create($userId, $name, $redirect, $personalAccess = false, $password = false, $confidential = true) { + if ($personalAccess) { + $confidential = true; + } + $client = Passport::client()->forceFill([ 'user_id' => $userId, 'name' => $name, - 'secret' => Str::random(40), + 'secret' => $confidential ? Str::random(40) : null, 'redirect' => $redirect, 'personal_access_client' => $personalAccess, 'password_client' => $password, diff --git a/src/Http/Controllers/ClientController.php b/src/Http/Controllers/ClientController.php index 10895bf3e..255dbec69 100644 --- a/src/Http/Controllers/ClientController.php +++ b/src/Http/Controllers/ClientController.php @@ -73,10 +73,12 @@ public function store(Request $request) $this->validation->make($request->all(), [ 'name' => 'required|max:255', 'redirect' => ['required', $this->redirectRule], + 'confidential' => 'boolean', ])->validate(); return $this->clients->create( - $request->user()->getKey(), $request->name, $request->redirect + $request->user()->getKey(), $request->name, $request->redirect, + false, false, (bool) $request->input('confidential', true) )->makeVisible('secret'); } diff --git a/tests/BridgeClientRepositoryTest.php b/tests/BridgeClientRepositoryTest.php index b861a96c3..3c82bfbc4 100644 --- a/tests/BridgeClientRepositoryTest.php +++ b/tests/BridgeClientRepositoryTest.php @@ -153,4 +153,9 @@ public function firstParty() { return $this->personal_access_client || $this->password_client; } + + public function confidential() + { + return ! empty($this->secret); + } } diff --git a/tests/ClientControllerTest.php b/tests/ClientControllerTest.php index fdaac1bf2..5ae841059 100644 --- a/tests/ClientControllerTest.php +++ b/tests/ClientControllerTest.php @@ -48,7 +48,7 @@ public function test_clients_can_be_stored() $clients->shouldReceive('create') ->once() - ->with(1, 'client name', 'http://localhost') + ->with(1, 'client name', 'http://localhost', false, false, true) ->andReturn($client = new Client); $redirectRule = m::mock(RedirectRule::class); @@ -60,6 +60,46 @@ public function test_clients_can_be_stored() ], [ 'name' => 'required|max:255', 'redirect' => ['required', $redirectRule], + 'confidential' => 'boolean', + ])->andReturn($validator); + $validator->shouldReceive('validate')->once(); + + $controller = new ClientController( + $clients, $validator, $redirectRule + ); + + $this->assertEquals($client, $controller->store($request)); + } + + public function test_public_clients_can_be_stored() + { + $clients = m::mock(ClientRepository::class); + + $request = Request::create( + '/', + 'GET', + ['name' => 'client name', 'redirect' => 'http://localhost', 'confidential' => false] + ); + $request->setUserResolver(function () { + return new ClientControllerFakeUser; + }); + + $clients->shouldReceive('create') + ->once() + ->with(1, 'client name', 'http://localhost', false, false, false) + ->andReturn($client = new Client); + + $redirectRule = m::mock(RedirectRule::class); + + $validator = m::mock(Factory::class); + $validator->shouldReceive('make')->once()->with([ + 'name' => 'client name', + 'redirect' => 'http://localhost', + 'confidential' => false, + ], [ + 'name' => 'required|max:255', + 'redirect' => ['required', $redirectRule], + 'confidential' => 'boolean', ])->andReturn($validator); $validator->shouldReceive('validate')->once();