Skip to content

Commit 655b8a1

Browse files
committed
Make authentication more configurable and extendable.
Signed-off-by: Jason Lewis <jason.lewis1991@gmail.com>
1 parent b6fc35b commit 655b8a1

11 files changed

+351
-50
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"illuminate/support": "4.1.*"
1515
},
1616
"require-dev": {
17+
"dingo/oauth2-server": "0.2.*",
1718
"illuminate/routing": "4.1.*",
1819
"illuminate/events": "4.1.*",
1920
"illuminate/auth": "4.1.*",

src/ApiServiceProvider.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace Dingo\Api;
22

3+
use Closure;
34
use Dingo\Api\Http\Response;
45
use Dingo\Api\Routing\Router;
56
use Dingo\Api\Auth\AuthManager;
@@ -135,13 +136,25 @@ protected function registerAuthentication()
135136
$providers = [];
136137

137138
$resolvers = [
138-
'basic' => function($app) { return new BasicProvider($app['auth']); },
139-
'oauth2' => function($app) { return new OAuth2Provider($app['dingo.oauth.resource']); }
139+
'basic' => function($app, $options) { return new BasicProvider($app['auth'], $options); },
140+
'oauth2' => function($app, $options) { return new OAuth2Provider($app['dingo.oauth.resource'], $options); }
140141
];
141142

142-
foreach ($app['config']['api::auth'] as $provider)
143+
foreach ($app['config']['api::auth'] as $key => $value)
143144
{
144-
$providers[$provider] = $resolvers[$provider]($app);
145+
list ($provider, $options) = [$key, $value];
146+
147+
if ( ! is_string($key))
148+
{
149+
list ($provider, $options) = [$value, []];
150+
}
151+
152+
if ($options instanceof Closure)
153+
{
154+
$options = call_user_func($options, $app);
155+
}
156+
157+
$providers[$provider] = $resolvers[$provider]($app, $options);
145158
}
146159

147160
return new Authentication($app['router'], $app['auth'], $providers);

src/Auth/Provider.php renamed to src/Auth/AuthorizationProvider.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
use Exception;
44
use Illuminate\Http\Request;
55

6-
abstract class Provider {
6+
abstract class AuthorizationProvider implements ProviderInterface {
7+
8+
/**
9+
* Array of provider speicifc options.
10+
*
11+
* @var array
12+
*/
13+
protected $options = [];
714

815
/**
916
* Validate the requests authorization header for the provider.
@@ -19,14 +26,6 @@ public function validateAuthorizationHeader(Request $request)
1926
}
2027
}
2128

22-
/**
23-
* Authenticate request.
24-
*
25-
* @param array $scopes
26-
* @return int
27-
*/
28-
abstract public function authenticate(array $scopes);
29-
3029
/**
3130
* Get the providers authorization method.
3231
*

src/Auth/BasicProvider.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,51 @@
11
<?php namespace Dingo\Api\Auth;
22

3+
use Illuminate\Http\Request;
34
use Illuminate\Auth\AuthManager;
45
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
56

6-
class BasicProvider extends Provider {
7+
class BasicProvider extends AuthorizationProvider {
8+
9+
/**
10+
* Illuminate authentication manager.
11+
*
12+
* @var \Illuminate\Auth\AuthManager
13+
*/
14+
protected $auth;
15+
16+
/**
17+
* Array of provider speicifc options.
18+
*
19+
* @var array
20+
*/
21+
protected $options = ['identifier' => 'email'];
722

823
/**
924
* Create a new Dingo\Api\Auth\BasicProvider instance.
1025
*
1126
* @param \Illuminate\Auth\AuthManager $auth
27+
* @param array $options
1228
* @return void
1329
*/
14-
public function __construct(AuthManager $auth)
30+
public function __construct(AuthManager $auth, array $options)
1531
{
1632
$this->auth = $auth;
33+
$this->options = array_merge($this->options, $options);
1734
}
1835

1936
/**
2037
* Authenticate request with Basic.
2138
*
22-
* @param array $scopes
39+
* @param \Illuminate\Http\Request $request
2340
* @return int
2441
*/
25-
public function authenticate(array $scopes)
42+
public function authenticate(Request $request)
2643
{
27-
if ($response = $this->auth->onceBasic() and $response->getStatusCode() === 401)
44+
$this->validateAuthorizationHeader($request);
45+
46+
if ($response = $this->auth->onceBasic($this->options['identifier']) and $response->getStatusCode() === 401)
2847
{
29-
throw new UnauthorizedHttpException('Basic', 'Invalid credentials.');
48+
throw new UnauthorizedHttpException('Basic', 'Invalid authentication credentials.');
3049
}
3150

3251
return $this->auth->user()->id;

src/Auth/OAuth2Provider.php

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,52 @@
11
<?php namespace Dingo\Api\Auth;
22

3+
use Illuminate\Http\Request;
34
use Dingo\OAuth2\Server\Resource;
45
use Dingo\OAuth2\Exception\InvalidTokenException;
56
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
67

7-
class OAuth2Provider extends Provider {
8+
class OAuth2Provider extends AuthorizationProvider {
9+
10+
/**
11+
* OAuth 2.0 resource server instance.
12+
*
13+
* @var \Dingo\OAuth2\Server\Resource
14+
*/
15+
protected $resource;
16+
17+
/**
18+
* Array of request scopes.
19+
*
20+
* @var array
21+
*/
22+
protected $scopes = [];
823

924
/**
1025
* Create a new Dingo\Api\Auth\OAuth2Provider instance.
1126
*
12-
* @param \Illuminate\Auth\Guard $auth
1327
* @param \Dingo\OAuth2\Server\Resource $resource
28+
* @param array $options
1429
* @return void
1530
*/
16-
public function __construct(Resource $resource)
31+
public function __construct(Resource $resource, array $options)
1732
{
1833
$this->resource = $resource;
34+
$this->options = $options;
1935
}
2036

2137
/**
2238
* Authenticate request with OAuth2.
2339
*
24-
* @param array $scopes
40+
* @param \Illuminate\Http\Request $request
2541
* @return int
2642
*/
27-
public function authenticate(array $scopes)
43+
public function authenticate(Request $request)
2844
{
45+
$this->validateAuthorizationHeader($request);
46+
2947
try
3048
{
31-
$token = $this->resource->validateRequest($scopes);
49+
$token = $this->resource->validateRequest($this->scopes);
3250

3351
return $token->getUserId();
3452
}
@@ -48,4 +66,17 @@ public function getAuthorizationMethod()
4866
return 'bearer';
4967
}
5068

69+
/**
70+
* Set the OAuth 2.0 request scopes.
71+
*
72+
* @param array $scopes
73+
* @return \Dingo\Api\Auth\OAuth2Provider
74+
*/
75+
public function setScopes(array $scopes)
76+
{
77+
$this->scopes = $scopes;
78+
79+
return $this;
80+
}
81+
5182
}

src/Auth/ProviderInterface.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php namespace Dingo\Api\Auth;
2+
3+
use Illuminate\Http\Request;
4+
5+
interface ProviderInterface {
6+
7+
/**
8+
* Authenticate the request.
9+
*
10+
* @param \Illuminate\Http\Request $request
11+
* @return int
12+
*/
13+
public function authenticate(Request $request);
14+
15+
}

src/Authentication.php

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Illuminate\Routing\Route;
77
use Illuminate\Auth\AuthManager;
88
use Dingo\Api\Http\InternalRequest;
9+
use Dingo\Api\Auth\ProviderInterface;
10+
use Dingo\Api\Auth\AuthorizationProvider;
911
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
1012

1113
class Authentication {
@@ -81,17 +83,20 @@ public function authenticate()
8183
// If authenticating via OAuth2 a route can be protected by defining its scopes.
8284
// We'll grab the scopes for this route and pass them through to the
8385
// authentication providers.
84-
$scopes = $this->getRouteScopes($route);
86+
if (isset($this->providers['oauth2']))
87+
{
88+
$scopes = $this->getRouteScopes($route);
89+
90+
$this->providers['oauth2']->setScopes($scopes);
91+
}
8592

8693
// Spin through each of the registered authentication providers and attempt to
87-
// authenticate when one of them.
94+
// authenticate through one of them.
8895
foreach ($this->providers as $provider)
8996
{
9097
try
9198
{
92-
$provider->validateAuthorizationHeader($request);
93-
94-
return $this->userId = $provider->authenticate($scopes);
99+
return $this->userId = $provider->authenticate($request);
95100
}
96101
catch (UnauthorizedHttpException $exception)
97102
{
@@ -109,7 +114,7 @@ public function authenticate()
109114

110115
if ($exception === null)
111116
{
112-
$exception = new UnauthorizedHttpException(null, 'Failed to authenticate because of an invalid or missing authorization header.');
117+
$exception = new UnauthorizedHttpException(null, 'Failed to authenticate because of bad credentials or an invalid authorization header.');
113118
}
114119

115120
throw $exception;
@@ -185,4 +190,18 @@ public function setUser($user)
185190
return $this;
186191
}
187192

193+
/**
194+
* Extend the authentication layer by registering a custom provider.
195+
*
196+
* @param string $key
197+
* @param \Dingo\Api\Auth\ProviderInterface $provider
198+
* @return \Dingo\Api\Authentication
199+
*/
200+
public function extend($key, ProviderInterface $provider)
201+
{
202+
$this->providers[$key] = $provider;
203+
204+
return $this;
205+
}
206+
188207
}

tests/AuthProviderTest.php renamed to tests/AuthAuthorizationProviderTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<?php
22

3-
class AuthProviderTest extends PHPUnit_Framework_TestCase {
3+
class AuthAuthorizationProviderTest extends PHPUnit_Framework_TestCase {
44

55

66
/**
77
* @expectedException \Exception
88
*/
99
public function testValidatingAuthorizationHeaderFailsWhenInvalidAndThrowsException()
1010
{
11-
$provider = new ProviderStub;
11+
$provider = new AuthorizationProviderStub;
1212
$request = Illuminate\Http\Request::create('/', 'GET');
1313
$request->headers->set('authorization', 'bar');
1414

@@ -18,7 +18,7 @@ public function testValidatingAuthorizationHeaderFailsWhenInvalidAndThrowsExcept
1818

1919
public function testValidatingAuthorizationHeaderSucceedsAndReturnsNull()
2020
{
21-
$provider = new ProviderStub;
21+
$provider = new AuthorizationProviderStub;
2222
$request = Illuminate\Http\Request::create('/', 'GET');
2323
$request->headers->set('authorization', 'foo');
2424

@@ -28,9 +28,9 @@ public function testValidatingAuthorizationHeaderSucceedsAndReturnsNull()
2828

2929
}
3030

31-
class ProviderStub extends Dingo\Api\Auth\Provider {
31+
class AuthorizationProviderStub extends Dingo\Api\Auth\AuthorizationProvider {
3232

33-
public function authenticate(array $scopes) {}
33+
public function authenticate(Illuminate\Http\Request $request) {}
3434

3535
public function getAuthorizationMethod() { return 'foo'; }
3636

0 commit comments

Comments
 (0)