oauth2-server-php
oauth2-server-php copied to clipboard
Checking for one of two scopes
I've done a bit of looking around but I can't seem to find if this was addressed anywhere. I apologize if I missed it.
I have a collection of resources that belong to a user but can also be managed by an administrator. I'm looking to try and check a user against one of two possible scopes- "individual" (if you're the actual owner) or "admin" (if you have administrative privileges within the system). At the moment, I believe that checking for a required scope will error out when it isn't met.
Is there any way to do an if, elseif, else for multiple scopes? Thanks for the help!
Hi Brock, great question!
You're requesting some "or" logic. This would be done on the side of the ResourceController
when the API in question is requested.
It would be a great idea if we defined some kind of ACL-logic for the API. Currently, there is only the one $scope
parameter, and they are all required. So to get around this, you could do something like this:
// verify without any scope checking
if ($server->verifyResourceRequest($request)) {
$token = $server->getResourceController()->getToken();
$scopeUtil = $server->getScopeUtil();
if ($scopeUtil->checkScope('individual', $token['scope'])
|| $scopeUtil->checkScope('admin', $token['scope']))
{
// serve up resource
} else {
// throw "insufficient scope" error
}
} else {
// throw response error
$server->getResponse()->send();
}
This is pretty clunky, so I would love your thoughts on how we could better implement this. I am sure this is a common use case
I think that access control logic should be left to the API. The oauth2 server should provide to the API only the tools for checking easily the available scopes. For example it can be something like this:
function checkScope($scope) {
$token = $server->getResourceController()->getToken();
$scopeUtil = $server->getScopeUtil();
return $scopeUtil->checkScope($scope, $token['scope']);
}
Then the API can check like this:
if (!checkScope('inidividual') and !checkScope('admin')) return;
// else continue with serving the resource
Another option is to override the ScopeUtil
class... This is a little tricky because you don't want the $scope
string to be interpreted too differently, as it's used elsewhere in the library... for instance in the error response WWW-Authenticate
header.
Something like this:
namespace OAuth2;
class MyScope extends Scope
{
public function checkScope($required_scope, $available_scope)
{
$requred_scopes = explode(' OR ', $required_scope);
foreach ($required_scopes as $scope) {
if (parent::checkScope($scope, $available_scope)) {
return true;
}
}
return false;
}
}
Then you could make the call like so:
// add custom scope class
$myScope = new OAuth2\MyScope($server->getStorage('scope'));
$server->setScopeUtil($myScope);
// verify the request
if ($server->verifyResourceRequest($request, null, 'individual OR admin')) {
// serve the request
} else {
// throw response error
$server->getResponse()->send();
}
This is a lot cleaner, but you would want to ensure your scope class is only set for the ResourceController
, otherwise this may have some unexpected issues in other controllers
Why not add checkScope to the Oauth2\Storage\ScopeInterface? (and modify the ScopeClass to call it through the storage same as scopeExists)
That way you don't have to extend Scope and can rely on the interface definitions. Verifying scope can still be done the same
if (!App::$server->verifyResourceRequest($oAuthRequest, $response, $requiredScope)) {
$response->send();
die;
}
but because you have control over what you pass in as $requiredScope and now also implement checkScope() you could do something like:
public function checkScope($required_scope, $available_scope)
{
$available_scope = explode(' ', trim($available_scope));
if (is_array($required_scope)) {
return (count(array_intersect($required_scope, $available_scope)) > 0);
} else {
$required_scope = explode(' ', trim($required_scope));
return (count(array_diff($required_scope, $available_scope)) == 0);
}
}
So it will treat $requiredScope passed as an array like ['scope1', 'scope2'] as OR and also keep the regular AND for $scope passed as a string. Should be easy to change for any other required logic.
Small additions:
- you would have to make sure $required_scope is translated to a string properly to be compatible with default ResourceController. This sets a scope parameters for requests with incorrect scope in the WWW-Authenticate response header.
- Proposed moving checkScope is simple but does break current ScopeInterface (important for existing implementations).
- Maybe best is just to implement the ResourceControllerInterface and implement verifyResourceRequest as well when doing this?