Пример #1
0
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)
Пример #2
0
class MeetupsController(RedditController):
    def response_func(self, **kw):
        return self.sendstring(json.dumps(kw))

    @validate(VUser(),
              VCreateMeetup(),
              title=ValueOrBlank('title'),
              description=ValueOrBlank('description'),
              location=ValueOrBlank('location'),
              latitude=ValueOrBlank('latitude'),
              longitude=ValueOrBlank('longitude'),
              timestamp=ValueOrBlank('timestamp'),
              tzoffset=ValueOrBlank('tzoffset'))
    def GET_new(self, *a, **kw):
        return BoringPage(pagename='New Meetup',
                          content=NewMeetup(*a, **kw)).render()

    @Json
    @validate(VUser(),
              VCreateMeetup(),
              VModhash(),
              ip=ValidIP(),
              title=VRequired('title', errors.NO_TITLE),
              description=VRequired('description', errors.NO_DESCRIPTION),
              location=VRequired('location', errors.NO_LOCATION),
              latitude=VFloat('latitude', error=errors.NO_LOCATION),
              longitude=VFloat('longitude', error=errors.NO_LOCATION),
              timestamp=VTimestamp('timestamp'),
              tzoffset=VFloat('tzoffset', error=errors.INVALID_DATE))
    def POST_create(self, res, title, description, location, latitude,
                    longitude, timestamp, tzoffset, ip):
        if res._chk_error(errors.NO_TITLE):
            res._chk_error(errors.TITLE_TOO_LONG)
            res._focus('title')

        res._chk_errors((errors.NO_LOCATION, errors.NO_DESCRIPTION,
                         errors.INVALID_DATE, errors.NO_DATE))

        if res.error: return

        meetup = Meetup(author_id=c.user._id,
                        title=title,
                        description=description,
                        location=location,
                        latitude=latitude,
                        longitude=longitude,
                        timestamp=timestamp,
                        tzoffset=tzoffset)

        # Expire all meetups in the render cache
        g.rendercache.invalidate_key_group(Meetup.group_cache_key())

        meetup._commit()

        l = Link._submit(meetup_article_title(meetup),
                         meetup_article_text(meetup), 'self', c.user,
                         Subreddit._by_name('meetups'), ip, [])

        l.meetup = meetup._id36
        l._commit()
        meetup.assoc_link = l._id
        meetup._commit()

        when = datetime.now(g.tz) + timedelta(
            0,
            3600)  # Leave a short window of time before notification, in case
        # the meetup is edited/deleted soon after its creation
        PendingJob.store(when, 'process_new_meetup', {'meetup_id': meetup._id})

        #update the queries
        if g.write_query_queue:
            queries.new_link(l)

        res._redirect(url_for(action='show', id=meetup._id36))

    @Json
    @validate(VUser(),
              VModhash(),
              meetup=VEditMeetup('id'),
              title=VRequired('title', errors.NO_TITLE),
              description=VRequired('description', errors.NO_DESCRIPTION),
              location=VRequired('location', errors.NO_LOCATION),
              latitude=VFloat('latitude', error=errors.NO_LOCATION),
              longitude=VFloat('longitude', error=errors.NO_LOCATION),
              timestamp=VTimestamp('timestamp'),
              tzoffset=VFloat('tzoffset', error=errors.INVALID_DATE))
    def POST_update(self, res, meetup, title, description, location, latitude,
                    longitude, timestamp, tzoffset):
        if res._chk_error(errors.NO_TITLE):
            res._chk_error(errors.TITLE_TOO_LONG)
            res._focus('title')

        res._chk_errors((errors.NO_LOCATION, errors.NO_DESCRIPTION,
                         errors.INVALID_DATE, errors.NO_DATE))

        if res.error: return

        meetup.title = title
        meetup.description = description

        meetup.location = location
        meetup.latitude = latitude
        meetup.longitude = longitude

        meetup.timestamp = timestamp
        meetup.tzoffset = tzoffset

        # Expire all meetups in the render cache
        g.rendercache.invalidate_key_group(Meetup.group_cache_key())

        meetup._commit()

        # Update the linked article
        article = Link._byID(meetup.assoc_link)
        article._load()
        article_old_url = article.url
        article.title = meetup_article_title(meetup)
        article.article = meetup_article_text(meetup)
        article._commit()
        article.update_url_cache(article_old_url)

        res._redirect(url_for(action='show', id=meetup._id36))

    @validate(VUser(), meetup=VEditMeetup('id'))
    def GET_edit(self, meetup):
        return BoringPage(pagename='Edit Meetup',
                          content=EditMeetup(
                              meetup,
                              title=meetup.title,
                              description=meetup.description,
                              location=meetup.location,
                              latitude=meetup.latitude,
                              longitude=meetup.longitude,
                              timestamp=int(meetup.timestamp * 1000),
                              tzoffset=meetup.tzoffset)).render()

    # Show a meetup.  Most of this code was coped from GET_comments in front.py
    @validate(meetup=VMeetup('id'),
              sort=VMenu('controller', CommentSortMenu),
              num_comments=VMenu('controller', NumCommentsMenu))
    def GET_show(self, meetup, sort, num_comments):
        article = Link._byID(meetup.assoc_link)

        # figure out number to show based on the menu
        user_num = c.user.pref_num_comments or g.num_comments
        num = g.max_comments if num_comments == 'true' else user_num

        builder = CommentBuilder(article, CommentSortMenu.operator(sort), None,
                                 None)
        listing = NestedListing(builder,
                                num=num,
                                parent_name=article._fullname)
        displayPane = PaneStack()

        # insert reply box only for logged in user
        if c.user_is_loggedin:
            displayPane.append(CommentReplyBox())
            displayPane.append(CommentReplyBox(link_name=article._fullname))

        # finally add the comment listing
        displayPane.append(listing.listing())

        sort_menu = CommentSortMenu(default=sort, type='dropdown2')
        nav_menus = [
            sort_menu,
            NumCommentsMenu(article.num_comments, default=num_comments)
        ]

        content = CommentListing(
            content=displayPane,
            num_comments=article.num_comments,
            nav_menus=nav_menus,
        )

        # Update last viewed time, and return the previous last viewed time.  Actually tracked on the article
        lastViewed = None
        if c.user_is_loggedin:
            clicked = article._getLastClickTime(c.user)
            lastViewed = clicked._date if clicked else None
            article._click(c.user)

        res = ShowMeetup(meetup=meetup,
                         content=content,
                         fullname=article._fullname,
                         lastViewed=lastViewed)

        return BoringPage(pagename=meetup.title,
                          content=res,
                          body_class='meetup').render()
Пример #3
0
 def __init__(self, param=None, *a, **kw):
     VRequired.__init__(self, param, errors.OAUTH2_INVALID_CLIENT, *a, **kw)
Пример #4
0
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)
Пример #5
0
 def __init__(self, param=None, *a, **kw):
     VRequired.__init__(self, param, errors.OAUTH2_INVALID_CLIENT, *a, **kw)
Пример #6
0
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)
Пример #7
0
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)
Пример #8
0
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)