Skip to content

Commit 5d0b295

Browse files
committed
Merge branch 'release/2.1'
2 parents b16c58b + 9ec5442 commit 5d0b295

19 files changed

+580
-87
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## 2.1 (released 2013-05-10)
4+
5+
* Moved zetacomponents/database to "suggest" in composer.json. If you rely on this feature you now need to include " zetacomponents/database" into "require" key in your own composer.json. (Issue #51)
6+
* New method in Refresh grant called `rotateRefreshTokens()`. Pass in `true` to issue a new refresh token each time an access token is refreshed. This parameter needs to be set to true in order to request reduced scopes with the new access token. (Issue #47)
7+
* Rename `key` column in oauth_scopes table to `scope` as `key` is a reserved SQL word. (Issue #45)
8+
* The `scope` parameter is no longer required by default as per the RFC. (Issue #43)
9+
* You can now set multiple default scopes by passing an array into `setDefaultScope()`. (Issue #42)
10+
* The password and client credentials grants now allow for multiple sessions per user. (Issue #32)
11+
* Scopes associated to authorization codes are not held in their own table (Issue #44)
12+
* Database schema updates.
13+
314
## 2.0.5 (released 2013-05-09)
415

516
* Fixed `oauth_session_token_scopes` table primary key

composer.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
{
22
"name": "league/oauth2-server",
33
"description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.",
4-
"version": "2.0.5",
4+
"version": "2.1",
55
"homepage": "https://github.com/php-loep/oauth2-server",
66
"license": "MIT",
77
"require": {
8-
"php": ">=5.3.0",
9-
"zetacomponents/database": "dev-master"
8+
"php": ">=5.3.0"
109
},
1110
"require-dev": {
1211
"mockery/mockery": ">=0.7.2"
@@ -43,5 +42,7 @@
4342
"League\\OAuth2\\Server": "src/"
4443
}
4544
},
46-
"suggest": {}
47-
}
45+
"suggest": {
46+
"zetacomponents/database": "Allows use of the build in PDO storage classes"
47+
}
48+
}

sql/mysql.sql

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ CREATE TABLE `oauth_session_access_tokens` (
3838
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3939

4040
CREATE TABLE `oauth_session_authcodes` (
41+
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
4142
`session_id` int(10) unsigned NOT NULL,
4243
`auth_code` char(40) NOT NULL,
4344
`auth_code_expires` int(10) unsigned NOT NULL,
44-
`scope_ids` char(255) DEFAULT NULL,
45-
PRIMARY KEY (`session_id`),
46-
CONSTRAINT `f_oaseau_seid` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
45+
PRIMARY KEY (`id`),
46+
KEY `session_id` (`session_id`),
47+
CONSTRAINT `oauth_session_authcodes_ibfk_1` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`) ON DELETE CASCADE
4748
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4849

4950
CREATE TABLE `oauth_session_redirects` (
@@ -65,13 +66,13 @@ CREATE TABLE `oauth_session_refresh_tokens` (
6566
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6667

6768
CREATE TABLE `oauth_scopes` (
68-
`id` SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
69-
`key` VARCHAR(255) NOT NULL,
70-
`name` VARCHAR(255) NOT NULL,
71-
`description` VARCHAR(255) DEFAULT NULL,
69+
`id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
70+
`scope` varchar(255) NOT NULL,
71+
`name` varchar(255) NOT NULL,
72+
`description` varchar(255) DEFAULT NULL,
7273
PRIMARY KEY (`id`),
73-
UNIQUE KEY `u_oasc_sc` (`key`)
74-
) ENGINE=INNODB DEFAULT CHARSET=utf8;
74+
UNIQUE KEY `u_oasc_sc` (`scope`)
75+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
7576

7677
CREATE TABLE `oauth_session_token_scopes` (
7778
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
@@ -82,4 +83,13 @@ CREATE TABLE `oauth_session_token_scopes` (
8283
KEY `f_oasetosc_scid` (`scope_id`),
8384
CONSTRAINT `f_oasetosc_scid` FOREIGN KEY (`scope_id`) REFERENCES `oauth_scopes` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
8485
CONSTRAINT `f_oasetosc_setoid` FOREIGN KEY (`session_access_token_id`) REFERENCES `oauth_session_access_tokens` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
86+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
87+
88+
CREATE TABLE `oauth_session_authcode_scopes` (
89+
`oauth_session_authcode_id` int(10) unsigned NOT NULL,
90+
`scope_id` smallint(5) unsigned NOT NULL,
91+
KEY `oauth_session_authcode_id` (`oauth_session_authcode_id`),
92+
KEY `scope_id` (`scope_id`),
93+
CONSTRAINT `oauth_session_authcode_scopes_ibfk_2` FOREIGN KEY (`scope_id`) REFERENCES `oauth_scopes` (`id`) ON DELETE CASCADE,
94+
CONSTRAINT `oauth_session_authcode_scopes_ibfk_1` FOREIGN KEY (`oauth_session_authcode_id`) REFERENCES `oauth_session_authcodes` (`id`) ON DELETE CASCADE
8595
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

src/League/OAuth2/Server/Authorization.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ class Authorization
5959
* Require the "scope" parameter to be in checkAuthoriseParams()
6060
* @var boolean
6161
*/
62-
protected $requireScopeParam = true;
62+
protected $requireScopeParam = false;
6363

6464
/**
65-
* Default scope to be used if none is provided and requireScopeParam is false
66-
* @var string
65+
* Default scope(s) to be used if none is provided
66+
* @var string|array
6767
*/
6868
protected $defaultScope = null;
6969

@@ -271,7 +271,7 @@ public function getResponseTypes()
271271
* @param boolean $require
272272
* @return void
273273
*/
274-
public function requireScopeParam($require = true)
274+
public function requireScopeParam($require = false)
275275
{
276276
$this->requireScopeParam = $require;
277277
}
@@ -287,7 +287,7 @@ public function scopeParamRequired()
287287

288288
/**
289289
* Default scope to be used if none is provided and requireScopeParam is false
290-
* @var string
290+
* @var string|array
291291
*/
292292
public function setDefaultScope($default = null)
293293
{

src/League/OAuth2/Server/Grant/AuthCode.php

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,14 @@ public function checkAuthoriseParams($inputParams = array())
152152
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
153153
}
154154

155-
if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
155+
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
156156
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
157-
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
158-
$scopes = array($this->authServer->getDefaultScope());
157+
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
158+
if (is_array($this->authServer->getDefaultScope())) {
159+
$scopes = $this->authServer->getDefaultScope();
160+
} else {
161+
$scopes = array($this->authServer->getDefaultScope());
162+
}
159163
}
160164

161165
$authParams['scopes'] = array();
@@ -189,21 +193,19 @@ public function newAuthoriseRequest($type, $typeId, $authParams = array())
189193
// Remove any old sessions the user might have
190194
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], $type, $typeId);
191195

192-
// List of scopes IDs
193-
$scopeIds = array();
194-
foreach ($authParams['scopes'] as $scope)
195-
{
196-
$scopeIds[] = $scope['id'];
197-
}
198-
199196
// Create a new session
200197
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], $type, $typeId);
201198

202199
// Associate a redirect URI
203200
$this->authServer->getStorage('session')->associateRedirectUri($sessionId, $authParams['redirect_uri']);
204201

205202
// Associate the auth code
206-
$this->authServer->getStorage('session')->associateAuthCode($sessionId, $authCode, time() + $this->authTokenTTL, implode(',', $scopeIds));
203+
$authCodeId = $this->authServer->getStorage('session')->associateAuthCode($sessionId, $authCode, time() + $this->authTokenTTL);
204+
205+
// Associate the scopes to the auth code
206+
foreach ($authParams['scopes'] as $scope) {
207+
$this->authServer->getStorage('session')->associateAuthCodeScope($authCodeId, $scope['id']);
208+
}
207209

208210
return $authCode;
209211
}
@@ -245,30 +247,30 @@ public function completeFlow($inputParams = null)
245247
}
246248

247249
// Verify the authorization code matches the client_id and the request_uri
248-
$session = $this->authServer->getStorage('session')->validateAuthCode($authParams['client_id'], $authParams['redirect_uri'], $authParams['code']);
250+
$authCodeDetails = $this->authServer->getStorage('session')->validateAuthCode($authParams['client_id'], $authParams['redirect_uri'], $authParams['code']);
249251

250-
if ( ! $session) {
252+
if ( ! $authCodeDetails) {
251253
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_grant'), 'code'), 9);
252254
}
253255

254-
// A session ID was returned so update it with an access token and remove the authorisation code
256+
// Get any associated scopes
257+
$scopes = $this->authServer->getStorage('session')->getAuthCodeScopes($authCodeDetails['authcode_id']);
255258

259+
// A session ID was returned so update it with an access token and remove the authorisation code
256260
$accessToken = SecureKey::make();
257261
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
258262
$accessTokenExpires = time() + $accessTokenExpiresIn;
259263

260264
// Remove the auth code
261-
$this->authServer->getStorage('session')->removeAuthCode($session['id']);
265+
$this->authServer->getStorage('session')->removeAuthCode($authCodeDetails['session_id']);
262266

263267
// Create an access token
264-
$accessTokenId = $this->authServer->getStorage('session')->associateAccessToken($session['id'], $accessToken, $accessTokenExpires);
268+
$accessTokenId = $this->authServer->getStorage('session')->associateAccessToken($authCodeDetails['session_id'], $accessToken, $accessTokenExpires);
265269

266270
// Associate scopes with the access token
267-
if ( ! is_null($session['scope_ids'])) {
268-
$scopeIds = explode(',', $session['scope_ids']);
269-
270-
foreach ($scopeIds as $scopeId) {
271-
$this->authServer->getStorage('session')->associateScope($accessTokenId, $scopeId);
271+
if (count($scopes) > 0) {
272+
foreach ($scopes as $scope) {
273+
$this->authServer->getStorage('session')->associateScope($accessTokenId, $scope['scope_id']);
272274
}
273275
}
274276

src/League/OAuth2/Server/Grant/ClientCredentials.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,14 @@ public function completeFlow($inputParams = null)
122122
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
123123
}
124124

125-
if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
125+
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
126126
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
127-
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
128-
$scopes = array($this->authServer->getDefaultScope());
127+
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
128+
if (is_array($this->authServer->getDefaultScope())) {
129+
$scopes = $this->authServer->getDefaultScope();
130+
} else {
131+
$scopes = array($this->authServer->getDefaultScope());
132+
}
129133
}
130134

131135
$authParams['scopes'] = array();
@@ -145,9 +149,6 @@ public function completeFlow($inputParams = null)
145149
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
146150
$accessTokenExpires = time() + $accessTokenExpiresIn;
147151

148-
// Delete any existing sessions just to be sure
149-
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], 'client', $authParams['client_id']);
150-
151152
// Create a new session
152153
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], 'client', $authParams['client_id']);
153154

src/League/OAuth2/Server/Grant/Password.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,14 @@ public function completeFlow($inputParams = null)
166166
if ($scopes[$i] === '') unset($scopes[$i]); // Remove any junk scopes
167167
}
168168

169-
if ($this->authServer->scopeParamRequired() === true && count($scopes) === 0) {
169+
if ($this->authServer->scopeParamRequired() === true && $this->authServer->getDefaultScope() === null && count($scopes) === 0) {
170170
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
171-
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope()) {
172-
$scopes = array($this->authServer->getDefaultScope());
171+
} elseif (count($scopes) === 0 && $this->authServer->getDefaultScope() !== null) {
172+
if (is_array($this->authServer->getDefaultScope())) {
173+
$scopes = $this->authServer->getDefaultScope();
174+
} else {
175+
$scopes = array($this->authServer->getDefaultScope());
176+
}
173177
}
174178

175179
$authParams['scopes'] = array();
@@ -189,9 +193,6 @@ public function completeFlow($inputParams = null)
189193
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
190194
$accessTokenExpires = time() + $accessTokenExpiresIn;
191195

192-
// Delete any existing sessions just to be sure
193-
$this->authServer->getStorage('session')->deleteSession($authParams['client_id'], 'user', $userId);
194-
195196
// Create a new session
196197
$sessionId = $this->authServer->getStorage('session')->createSession($authParams['client_id'], 'user', $userId);
197198

src/League/OAuth2/Server/Grant/RefreshToken.php

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ class RefreshToken implements GrantTypeInterface {
5454
*/
5555
protected $refreshTokenTTL = 604800;
5656

57+
/**
58+
* Rotate refresh tokens
59+
* @var boolean
60+
*/
61+
protected $rotateRefreshTokens = false;
62+
5763
/**
5864
* Constructor
5965
* @param Authorization $authServer Authorization server instance
@@ -111,6 +117,16 @@ public function getRefreshTokenTTL()
111117
return $this->refreshTokenTTL;
112118
}
113119

120+
/**
121+
* When a new access is token, expire the refresh token used and issue a new one.
122+
* @param boolean $rotateRefreshTokens Set to true to enable (default = false)
123+
* @return void
124+
*/
125+
public function rotateRefreshTokens($rotateRefreshTokens = false)
126+
{
127+
$this->rotateRefreshTokens = $rotateRefreshTokens;
128+
}
129+
114130
/**
115131
* Complete the refresh token grant
116132
* @param null|array $inputParams
@@ -119,7 +135,7 @@ public function getRefreshTokenTTL()
119135
public function completeFlow($inputParams = null)
120136
{
121137
// Get the required params
122-
$authParams = $this->authServer->getParam(array('client_id', 'client_secret', 'refresh_token'), 'post', $inputParams);
138+
$authParams = $this->authServer->getParam(array('client_id', 'client_secret', 'refresh_token', 'scope'), 'post', $inputParams);
123139

124140
if (is_null($authParams['client_id'])) {
125141
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'client_id'), 0);
@@ -159,24 +175,69 @@ public function completeFlow($inputParams = null)
159175
$accessToken = SecureKey::make();
160176
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
161177
$accessTokenExpires = time() + $accessTokenExpiresIn;
162-
$refreshToken = SecureKey::make();
163-
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();
164178

179+
// Associate the new access token with the session
165180
$newAccessTokenId = $this->authServer->getStorage('session')->associateAccessToken($accessTokenDetails['session_id'], $accessToken, $accessTokenExpires);
166181

167-
foreach ($scopes as $scope) {
168-
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']);
182+
if ($this->rotateRefreshTokens === true) {
183+
184+
// Generate a new refresh token
185+
$refreshToken = SecureKey::make();
186+
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();
187+
188+
// Revoke the old refresh token
189+
$this->authServer->getStorage('session')->removeRefreshToken($authParams['refresh_token']);
190+
191+
// Associate the new refresh token with the new access token
192+
$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);
169193
}
170194

171-
$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);
195+
// There isn't a request for reduced scopes so assign the original ones (or we're not rotating scopes)
196+
if ( ! isset($authParams['scope'])) {
172197

173-
return array(
198+
foreach ($scopes as $scope) {
199+
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']);
200+
}
201+
202+
} elseif ( isset($authParams['scope']) && $this->rotateRefreshTokens === true) {
203+
204+
// The request is asking for reduced scopes and rotate tokens is enabled
205+
$reqestedScopes = explode($this->authServer->getScopeDelimeter(), $authParams['scope']);
206+
207+
for ($i = 0; $i < count($reqestedScopes); $i++) {
208+
$reqestedScopes[$i] = trim($reqestedScopes[$i]);
209+
if ($reqestedScopes[$i] === '') unset($reqestedScopes[$i]); // Remove any junk scopes
210+
}
211+
212+
// Check that there aren't any new scopes being included
213+
$existingScopes = array();
214+
foreach ($scopes as $s) {
215+
$existingScopes[] = $s['scope'];
216+
}
217+
218+
foreach ($reqestedScopes as $reqScope) {
219+
if ( ! in_array($reqScope, $existingScopes)) {
220+
throw new Exception\ClientException(sprintf($this->authServer->getExceptionMessage('invalid_request'), 'scope'), 0);
221+
}
222+
223+
// Associate with the new access token
224+
$scopeDetails = $this->authServer->getStorage('scope')->getScope($reqScope, $authParams['client_id'], $this->identifier);
225+
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scopeDetails['id']);
226+
}
227+
}
228+
229+
$response = array(
174230
'access_token' => $accessToken,
175-
'refresh_token' => $refreshToken,
176231
'token_type' => 'bearer',
177232
'expires' => $accessTokenExpires,
178233
'expires_in' => $accessTokenExpiresIn
179234
);
235+
236+
if ($this->rotateRefreshTokens === true) {
237+
$response['refresh_token'] = $refreshToken;
238+
}
239+
240+
return $response;
180241
}
181242

182243
}

0 commit comments

Comments
 (0)