mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-05 03:00:15 -04:00
Add support for multiple scopes per OAuth2 access token.
This commit is contained in:
@@ -93,6 +93,7 @@ error_list = dict((
|
||||
('BAD_FLAIR_TARGET', _('not a valid flair target')),
|
||||
('OAUTH2_INVALID_CLIENT', _('invalid client id')),
|
||||
('OAUTH2_INVALID_REDIRECT_URI', _('invalid redirect_uri parameter')),
|
||||
('OAUTH2_INVALID_SCOPE', _('invalid scope requested')),
|
||||
('OAUTH2_ACCESS_DENIED', _('access denied by the user')),
|
||||
('CONFIRM', _("please confirm the form")),
|
||||
('NO_API', _('cannot perform this action via the API')),
|
||||
|
||||
@@ -33,7 +33,7 @@ from r2.lib.db.thing import NotFound
|
||||
from r2.models import Account
|
||||
from r2.models.token import OAuth2Client, OAuth2AuthorizationCode, OAuth2AccessToken
|
||||
from r2.controllers.errors import ForbiddenError, errors
|
||||
from validator import validate, VRequired, VOneOf, VUser, VModhash, VOAuth2ClientID
|
||||
from validator import validate, VRequired, VOneOf, VUser, VModhash, VOAuth2ClientID, VOAuth2Scope
|
||||
from r2.lib.pages import OAuth2AuthorizationPage
|
||||
from r2.lib.require import RequirementException, require, require_split
|
||||
|
||||
@@ -67,7 +67,7 @@ class OAuth2FrontendController(RedditController):
|
||||
resp["error"] = "access_denied"
|
||||
elif (errors.INVALID_OPTION, "response_type") in c.errors:
|
||||
resp["error"] = "unsupported_response_type"
|
||||
elif (errors.INVALID_OPTION, "scope") in c.errors:
|
||||
elif (errors.OAUTH2_INVALID_SCOPE, "scope") in c.errors:
|
||||
resp["error"] = "invalid_scope"
|
||||
else:
|
||||
resp["error"] = "invalid_request"
|
||||
@@ -78,7 +78,7 @@ class OAuth2FrontendController(RedditController):
|
||||
response_type = VOneOf("response_type", ("code",)),
|
||||
client = VOAuth2ClientID(),
|
||||
redirect_uri = VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI),
|
||||
scope = VOneOf("scope", scope_info.keys()),
|
||||
scope = VOAuth2Scope(),
|
||||
state = VRequired("state", errors.NO_TEXT))
|
||||
def GET_authorize(self, response_type, client, redirect_uri, scope, state):
|
||||
"""
|
||||
@@ -106,7 +106,7 @@ class OAuth2FrontendController(RedditController):
|
||||
|
||||
if not c.errors:
|
||||
c.deny_frames = True
|
||||
return OAuth2AuthorizationPage(client, redirect_uri, scope_info[scope], state).render()
|
||||
return OAuth2AuthorizationPage(client, redirect_uri, scope, state).render()
|
||||
else:
|
||||
return self._error_response(state, redirect_uri)
|
||||
|
||||
@@ -114,7 +114,7 @@ class OAuth2FrontendController(RedditController):
|
||||
VModhash(fatal=False),
|
||||
client = VOAuth2ClientID(),
|
||||
redirect_uri = VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI),
|
||||
scope = VOneOf("scope", scope_info.keys()),
|
||||
scope = VOAuth2Scope(),
|
||||
state = VRequired("state", errors.NO_TEXT),
|
||||
authorize = VRequired("authorize", errors.OAUTH2_ACCESS_DENIED))
|
||||
def POST_authorize(self, authorize, client, redirect_uri, scope, state):
|
||||
@@ -219,7 +219,7 @@ class OAuth2ResourceController(MinimalController):
|
||||
if handler:
|
||||
oauth2_perms = getattr(handler, "oauth2_perms", None)
|
||||
if oauth2_perms:
|
||||
if access_token.scope not in oauth2_perms["allowed_scopes"]:
|
||||
if set(oauth2_perms["allowed_scopes"]).intersection(access_token.scope_list):
|
||||
self._auth_error(403, "insufficient_scope")
|
||||
else:
|
||||
self._auth_error(400, "invalid_request")
|
||||
|
||||
@@ -1855,3 +1855,18 @@ class VOAuth2ClientDeveloper(VOAuth2ClientID):
|
||||
if not client or not client.has_developer(c.user):
|
||||
return self.error()
|
||||
return client
|
||||
|
||||
class VOAuth2Scope(VRequired):
|
||||
default_param = "scope"
|
||||
def __init__(self, param=None, *a, **kw):
|
||||
VRequired.__init__(self, param, errors.OAUTH2_INVALID_SCOPE, *a, **kw)
|
||||
|
||||
def run(self, scope):
|
||||
from r2.controllers.oauth2 import scope_info
|
||||
scope = VRequired.run(self, scope)
|
||||
if scope:
|
||||
scope_list = scope.split(',')
|
||||
if all(scope in scope_info for scope in scope_list):
|
||||
return scope_list
|
||||
else:
|
||||
self.error()
|
||||
|
||||
@@ -824,10 +824,12 @@ class Register(Login):
|
||||
pass
|
||||
|
||||
class OAuth2AuthorizationPage(BoringPage):
|
||||
def __init__(self, client, redirect_uri, scope, state):
|
||||
def __init__(self, client, redirect_uri, scopes, state):
|
||||
from r2.controllers.oauth2 import scope_info
|
||||
scope_details = [scope_info[scope] for scope in scopes]
|
||||
content = OAuth2Authorization(client=client,
|
||||
redirect_uri=redirect_uri,
|
||||
scope=scope,
|
||||
scope_details=scope_details,
|
||||
state=state)
|
||||
BoringPage.__init__(self, _("request for permission"),
|
||||
show_sidebar=False, content=content)
|
||||
|
||||
@@ -245,7 +245,8 @@ class OAuth2AuthorizationCode(ConsumableToken):
|
||||
_connection_pool = "main"
|
||||
|
||||
@classmethod
|
||||
def _new(cls, client_id, redirect_uri, user_id, scope):
|
||||
def _new(cls, client_id, redirect_uri, user_id, scope_list):
|
||||
scope = ','.join(scope_list)
|
||||
return super(OAuth2AuthorizationCode, cls)._new(
|
||||
client_id=client_id,
|
||||
redirect_uri=redirect_uri,
|
||||
@@ -275,7 +276,8 @@ class OAuth2AccessToken(Token):
|
||||
_connection_pool = "main"
|
||||
|
||||
@classmethod
|
||||
def _new(cls, user_id, scope):
|
||||
def _new(cls, user_id, scope_list):
|
||||
scope = ','.join(scope_list)
|
||||
return super(OAuth2AccessToken, cls)._new(
|
||||
user_id=user_id,
|
||||
scope=scope)
|
||||
@@ -349,6 +351,10 @@ class OAuth2AccessToken(Token):
|
||||
|
||||
return tokens
|
||||
|
||||
@property
|
||||
def scope_list(self):
|
||||
return self.scope.split(',')
|
||||
|
||||
class OAuth2AccessTokensByUser(tdb_cassandra.View):
|
||||
"""Index listing the outstanding access tokens for an account."""
|
||||
|
||||
|
||||
@@ -31,13 +31,15 @@
|
||||
<div class="access">
|
||||
<h2>${_("Allow %(app_name)s to:") % dict(app_name=thing.client.name)}</h2>
|
||||
<ul>
|
||||
<li>${thing.scope["description"]}</li>
|
||||
%for scope_info in thing.scope_details:
|
||||
<li>${scope_info["description"]}</li>
|
||||
%endfor
|
||||
</ul>
|
||||
<p class="notice">${_("%(app_name)s will not be able to access your reddit password.") % dict(app_name=thing.client.name)}</p>
|
||||
<form method="post" action="/api/v1/authorize" class="pretty-form">
|
||||
<input type="hidden" name="client_id" value="${thing.client._id}" />
|
||||
<input type="hidden" name="redirect_uri" value="${thing.redirect_uri}" />
|
||||
<input type="hidden" name="scope" value="${thing.scope['id']}" />
|
||||
<input type="hidden" name="scope" value="${','.join(scope['id'] for scope in thing.scope_details)}" />
|
||||
<input type="hidden" name="state" value="${thing.state}" />
|
||||
<input type="hidden" name="uh" value="${c.modhash}"/>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user