class OAuth2AccessController(MinimalController): def pre(self): set_extension(request.environ, "json") MinimalController.pre(self) require_https() c.oauth2_client = self._get_client_auth() def _get_client_auth(self): auth = request.headers.get("Authorization") try: auth_scheme, auth_token = require_split(auth, 2) require(auth_scheme.lower() == "basic") try: auth_data = base64.b64decode(auth_token) except TypeError: raise RequirementException client_id, client_secret = require_split(auth_data, 2, ":") client = OAuth2Client.get_token(client_id) require(client) require(client.secret == client_secret) return client except RequirementException: abort(401, headers=[("WWW-Authenticate", 'Basic realm="reddit"')]) @validate(grant_type=VOneOf("grant_type", ("authorization_code", )), code=VRequired("code", errors.NO_TEXT), redirect_uri=VUrl("redirect_uri", allow_self=False, lookup=False)) def POST_access_token(self, grant_type, code, redirect_uri): resp = {} if not c.errors: auth_token = OAuth2AuthorizationCode.use_token( code, c.oauth2_client._id, redirect_uri) if auth_token: access_token = OAuth2AccessToken._new(auth_token.user_id, auth_token.scope) resp["access_token"] = access_token._id resp["token_type"] = access_token.token_type resp["expires_in"] = access_token._ttl resp["scope"] = auth_token.scope else: resp["error"] = "invalid_grant" else: if (errors.INVALID_OPTION, "grant_type") in c.errors: resp["error"] = "unsupported_grant_type" elif (errors.INVALID_OPTION, "scope") in c.errors: resp["error"] = "invalid_scope" else: resp["error"] = "invalid_request" return self.api_wrapper(resp)
class OAuth2FrontendController(RedditController): def pre(self): RedditController.pre(self) require_https() def _check_redirect_uri(self, client, redirect_uri): if not redirect_uri or not client or redirect_uri != client.redirect_uri: abort(403) def _error_response(self, resp): if (errors.OAUTH2_INVALID_CLIENT, "client_id") in c.errors: resp["error"] = "unauthorized_client" elif (errors.OAUTH2_ACCESS_DENIED, "authorize") in c.errors: resp["error"] = "access_denied" elif (errors.BAD_HASH, None) in c.errors: 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: resp["error"] = "invalid_scope" else: resp["error"] = "invalid_request" @validate(VUser(), response_type=VOneOf("response_type", ("code", )), client=VClientID(), redirect_uri=VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI), scope=VOneOf("scope", scope_info.keys()), state=VRequired("state", errors.NO_TEXT)) def GET_authorize(self, response_type, client, redirect_uri, scope, state): """ First step in [OAuth 2.0](http://oauth.net/2/) authentication. End users will be prompted for their credentials (username/password) and asked if they wish to authorize the application identified by the **client_id** parameter with the permissions specified by the **scope** parameter. They are then redirected to the endpoint on the client application's side specified by **redirect_uri**. If the user granted permission to the application, the response will contain a **code** parameter with a temporary authorization code which can be exchanged for an access token at [/api/v1/access_token](#api_method_access_token). **redirect_uri** must match the URI configured for the client in the [app preferences](/prefs/apps). If **client_id** or **redirect_uri** is not valid, or if the call does not take place over SSL, a 403 error will be returned. For all other errors, a redirect to **redirect_uri** will be returned, with a **error** parameter indicating why the request failed. """ self._check_redirect_uri(client, redirect_uri) resp = {} if not c.errors: c.deny_frames = True return OAuth2AuthorizationPage(client, redirect_uri, scope_info[scope], state).render() else: self._error_response(resp) return self.redirect(redirect_uri + "?" + urlencode(resp), code=302) @validate(VUser(), VModhash(fatal=False), client=VClientID(), redirect_uri=VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI), scope=VOneOf("scope", scope_info.keys()), state=VRequired("state", errors.NO_TEXT), authorize=VRequired("authorize", errors.OAUTH2_ACCESS_DENIED)) def POST_authorize(self, authorize, client, redirect_uri, scope, state): """Endpoint for OAuth2 authorization.""" self._check_redirect_uri(client, redirect_uri) resp = {} if state: resp["state"] = state if not c.errors: code = OAuth2AuthorizationCode._new(client._id, redirect_uri, c.user._id, scope) resp["code"] = code._id else: self._error_response(resp) return self.redirect(redirect_uri + "?" + urlencode(resp), code=302)
class OAuth2AccessController(MinimalController): def pre(self): set_extension(request.environ, "json") MinimalController.pre(self) require_https() c.oauth2_client = self._get_client_auth() def _get_client_auth(self): auth = request.headers.get("Authorization") try: auth_scheme, auth_token = require_split(auth, 2) require(auth_scheme.lower() == "basic") try: auth_data = base64.b64decode(auth_token) except TypeError: raise RequirementException client_id, client_secret = require_split(auth_data, 2, ":") client = OAuth2Client.get_token(client_id) require(client) require(client.secret == client_secret) return client except RequirementException: abort(401, headers=[("WWW-Authenticate", 'Basic realm="reddit"')]) @validate(grant_type=VOneOf("grant_type", ("authorization_code", )), code=VRequired("code", errors.NO_TEXT), redirect_uri=VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI)) def POST_access_token(self, grant_type, code, redirect_uri): """ Exchange an [OAuth 2.0](http://oauth.net/2/) authorization code (from [/api/v1/authorize](#api_method_authorize)) for an access token. On success, returns a URL-encoded dictionary containing **access_token**, **token_type**, **expires_in**, and **scope**. If there is a problem, an **error** parameter will be returned instead. Must be called using SSL, and must contain a HTTP `Authorization:` header which contains the application's client identifier as the username and client secret as the password. (The client id and secret are visible on the [app preferences page](/prefs/apps).) Per the OAuth specification, **grant_type** must be ``authorization_code`` and **redirect_uri** must exactly match the value that was used in the call to [/api/v1/authorize](#api_method_authorize). """ resp = {} if not c.errors: auth_token = OAuth2AuthorizationCode.use_token( code, c.oauth2_client._id, redirect_uri) if auth_token: access_token = OAuth2AccessToken._new(auth_token.user_id, auth_token.scope) resp["access_token"] = access_token._id resp["token_type"] = access_token.token_type resp["expires_in"] = access_token._ttl resp["scope"] = auth_token.scope else: resp["error"] = "invalid_grant" else: if (errors.INVALID_OPTION, "grant_type") in c.errors: resp["error"] = "unsupported_grant_type" elif (errors.INVALID_OPTION, "scope") in c.errors: resp["error"] = "invalid_scope" else: resp["error"] = "invalid_request" return self.api_wrapper(resp)
class OAuth2FrontendController(RedditController): def pre(self): RedditController.pre(self) require_https() def _check_redirect_uri(self, client, redirect_uri): if not redirect_uri or not client or redirect_uri != client.redirect_uri: abort(403) def _error_response(self, resp): if (errors.OAUTH2_INVALID_CLIENT, "client_id") in c.errors: resp["error"] = "unauthorized_client" elif (errors.OAUTH2_ACCESS_DENIED, "authorize") in c.errors: resp["error"] = "access_denied" elif (errors.BAD_HASH, None) in c.errors: 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: resp["error"] = "invalid_scope" else: resp["error"] = "invalid_request" @validate(VUser(), response_type=VOneOf("response_type", ("code", )), client=VClientID(), redirect_uri=VUrl("redirect_uri", allow_self=False, lookup=False), scope=VOneOf("scope", scope_info.keys()), state=VRequired("state", errors.NO_TEXT)) def GET_authorize(self, response_type, client, redirect_uri, scope, state): self._check_redirect_uri(client, redirect_uri) resp = {} if not c.errors: c.deny_frames = True return OAuth2AuthorizationPage(client, redirect_uri, scope_info[scope], state).render() else: self._error_response(resp) return self.redirect(redirect_uri + "?" + urlencode(resp), code=302) @validate(VUser(), VModhash(fatal=False), client=VClientID(), redirect_uri=VUrl("redirect_uri", allow_self=False, lookup=False), scope=VOneOf("scope", scope_info.keys()), state=VRequired("state", errors.NO_TEXT), authorize=VRequired("authorize", errors.OAUTH2_ACCESS_DENIED)) def POST_authorize(self, authorize, client, redirect_uri, scope, state): self._check_redirect_uri(client, redirect_uri) resp = {} if state: resp["state"] = state if not c.errors: code = OAuth2AuthorizationCode._new(client._id, redirect_uri, c.user._id, scope) resp["code"] = code._id else: self._error_response(resp) return self.redirect(redirect_uri + "?" + urlencode(resp), code=302)
class OAuth2AccessController(MinimalController): def pre(self): set_extension(request.environ, "json") MinimalController.pre(self) require_https() c.oauth2_client = self._get_client_auth() def _get_client_auth(self): auth = request.headers.get("Authorization") try: client_id, client_secret = parse_http_basic(auth) client = OAuth2Client.get_token(client_id) require(client) require(client.secret == client_secret) return client except RequirementException: abort(401, headers=[("WWW-Authenticate", 'Basic realm="reddit"')]) @validate(grant_type=VOneOf("grant_type", ("authorization_code", "refresh_token")), code=nop("code"), refresh_token=VOAuth2RefreshToken("refresh_token"), redirect_uri=VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI)) def POST_access_token(self, grant_type, code, refresh_token, redirect_uri): """ Exchange an [OAuth 2.0](http://oauth.net/2/) authorization code or refresh token (from [/api/v1/authorize](#api_method_authorize)) for an access token. On success, returns a URL-encoded dictionary containing **access_token**, **token_type**, **expires_in**, and **scope**. If an authorization code for a permanent grant was given, a **refresh_token** will be included. If there is a problem, an **error** parameter will be returned instead. Must be called using SSL, and must contain a HTTP `Authorization:` header which contains the application's client identifier as the username and client secret as the password. (The client id and secret are visible on the [app preferences page](/prefs/apps).) Per the OAuth specification, **grant_type** must be ``authorization_code`` for the initial access token or ``refresh_token`` for renewing the access token. In either case, **redirect_uri** must exactly match the value that was used in the call to [/api/v1/authorize](#api_method_authorize) that created this grant. """ resp = {} if not (code or refresh_token): c.errors.add("NO_TEXT", field=("code", "refresh_token")) if not c.errors: access_token = None if grant_type == "authorization_code": auth_token = OAuth2AuthorizationCode.use_token( code, c.oauth2_client._id, redirect_uri) if auth_token: if auth_token.refreshable: refresh_token = OAuth2RefreshToken._new( auth_token.client_id, auth_token.user_id, auth_token.scope) access_token = OAuth2AccessToken._new( auth_token.client_id, auth_token.user_id, auth_token.scope, refresh_token._id if refresh_token else None) elif grant_type == "refresh_token" and refresh_token: access_token = OAuth2AccessToken._new( refresh_token.client_id, refresh_token.user_id, refresh_token.scope, refresh_token=refresh_token._id) if access_token: resp["access_token"] = access_token._id resp["token_type"] = access_token.token_type resp["expires_in"] = access_token._ttl resp["scope"] = access_token.scope if refresh_token: resp["refresh_token"] = refresh_token._id else: resp["error"] = "invalid_grant" else: if (errors.INVALID_OPTION, "grant_type") in c.errors: resp["error"] = "unsupported_grant_type" elif (errors.INVALID_OPTION, "scope") in c.errors: resp["error"] = "invalid_scope" else: resp["error"] = "invalid_request" return self.api_wrapper(resp)