def access_token(request): """ Provides an access token based on a valid verifier and request token """ data = request.headers parsed_tokens = decode_authorization_header(data) if parsed_tokens == dict() or "oauth_token" not in parsed_tokens: error = "Missing required parameter." return json_response({"error": error}, status=400) request.resource_owner_key = parsed_tokens["oauth_consumer_key"] request.oauth_token = parsed_tokens["oauth_token"] request_validator = GMGRequestValidator(data) # Check that the verifier is valid verifier_valid = request_validator.validate_verifier( token=request.oauth_token, verifier=parsed_tokens["oauth_verifier"] ) if not verifier_valid: error = "Verifier code or token incorrect" return json_response({"error": error}, status=401) av = AccessTokenEndpoint(request_validator) tokens = av.create_access_token(request, {}) return form_response(tokens)
def authorize(request, client): # TODO: Get rid of the JSON responses in this view, it's called by the # user-agent, not the client. user_client_relation = OAuthUserClient.query.filter( (OAuthUserClient.user == request.user) & (OAuthUserClient.client == client)) if user_client_relation.filter( OAuthUserClient.state == u'approved').count(): redirect_uri = None if client.type == u'public': if not client.redirect_uri: return json_response( { 'status': 400, 'errors': [ u'Public clients should have a redirect_uri pre-set.' ] }, _disable_cors=True) redirect_uri = client.redirect_uri if client.type == u'confidential': redirect_uri = request.GET.get('redirect_uri', client.redirect_uri) if not redirect_uri: return json_response( { 'status': 400, 'errors': [u'No redirect_uri supplied!'] }, _disable_cors=True) code = OAuthCode() code.user = request.user code.client = client code.save() redirect_uri = ''.join( [redirect_uri, '?', urlencode({'code': code.code})]) _log.debug('Redirecting to {0}'.format(redirect_uri)) return redirect(request, location=redirect_uri) else: # Show prompt to allow client to access data # - on accept: send the user agent back to the redirect_uri with the # code parameter # - on deny: send the user agent back to the redirect uri with error # information form = AuthorizationForm(request.form) form.client_id.data = client.id form.next.data = request.url return render_to_response(request, 'oauth/authorize.html', { 'form': form, 'client': client })
def wrapper(request, *args, **kwargs): data = request.headers authorization = decode_authorization_header(data) if authorization == dict(): error = "Missing required parameter." return json_response({"error": error}, status=400) request_validator = GMGRequestValidator() resource_endpoint = ResourceEndpoint(request_validator) valid, r = resource_endpoint.validate_protected_resource_request( uri=request.url, http_method=request.method, body=request.data, headers=dict(request.headers), ) if not valid: error = "Invalid oauth prarameter." return json_response({"error": error}, status=400) # Fill user if not already token = authorization[u"oauth_token"] request.access_token = AccessToken.query.filter_by(token=token).first() if request.access_token is not None and request.user is None: user_id = request.access_token.actor request.user = LocalUser.query.filter_by(id=user_id).first() return controller(request, *args, **kwargs)
def authorize(request, client): # TODO: Get rid of the JSON responses in this view, it's called by the # user-agent, not the client. user_client_relation = OAuthUserClient.query.filter( (OAuthUserClient.user == request.user) & (OAuthUserClient.client == client)) if user_client_relation.filter(OAuthUserClient.state == u'approved').count(): redirect_uri = None if client.type == u'public': if not client.redirect_uri: return json_response({ 'status': 400, 'errors': [u'Public clients should have a redirect_uri pre-set.']}, _disable_cors=True) redirect_uri = client.redirect_uri if client.type == u'confidential': redirect_uri = request.GET.get('redirect_uri', client.redirect_uri) if not redirect_uri: return json_response({ 'status': 400, 'errors': [u'No redirect_uri supplied!']}, _disable_cors=True) code = OAuthCode() code.user = request.user code.client = client code.save() redirect_uri = ''.join([ redirect_uri, '?', urlencode({'code': code.code})]) _log.debug('Redirecting to {0}'.format(redirect_uri)) return redirect(request, location=redirect_uri) else: # Show prompt to allow client to access data # - on accept: send the user agent back to the redirect_uri with the # code parameter # - on deny: send the user agent back to the redirect uri with error # information form = AuthorizationForm(request.form) form.client_id.data = client.id form.next.data = request.url return render_to_response( request, 'oauth/authorize.html', {'form': form, 'client': client})
def post_entry(request): _log.debug('Posting entry') if request.method == 'OPTIONS': return json_response({'status': 200}) if request.method != 'POST': _log.debug('Must POST against post_entry') raise BadRequest() if not check_file_field(request, 'file'): _log.debug('File field not found') raise BadRequest() upload_limit, max_file_size = get_upload_file_limits(request.user) callback_url = request.form.get('callback_url') if callback_url: callback_url = unicode(callback_url) try: entry = submit_media(mg_app=request.app, user=request.user, submitted_file=request.files['file'], filename=request.files['file'].filename, title=unicode(request.form.get('title')), description=unicode( request.form.get('description')), license=unicode(request.form.get('license', '')), tags_string=unicode(request.form.get('tags', '')), upload_limit=upload_limit, max_file_size=max_file_size, callback_url=callback_url) return json_response(get_entry_serializable(entry, request.urlgen)) # Handle upload limit issues except FileUploadLimit: raise BadRequest(_(u'Sorry, the file size is too big.')) except UserUploadLimit: raise BadRequest( _('Sorry, uploading this file will put you over your' ' upload limit.')) except UserPastUploadLimit: raise BadRequest(_('Sorry, you have reached your upload limit.')) except Exception as e: ''' This section is intended to catch exceptions raised in mediagoblin.media_types ''' if isinstance(e, InvalidFileType) or \ isinstance(e, FileTypeNotSupported): raise BadRequest(unicode(e)) else: raise
def post_entry(request): _log.debug('Posting entry') if request.method == 'OPTIONS': return json_response({'status': 200}) if request.method != 'POST': _log.debug('Must POST against post_entry') raise BadRequest() if not check_file_field(request, 'file'): _log.debug('File field not found') raise BadRequest() upload_limit, max_file_size = get_upload_file_limits(request.user) callback_url = request.form.get('callback_url') if callback_url: callback_url = unicode(callback_url) try: entry = submit_media( mg_app=request.app, user=request.user, submitted_file=request.files['file'], filename=request.files['file'].filename, title=unicode(request.form.get('title')), description=unicode(request.form.get('description')), license=unicode(request.form.get('license', '')), upload_limit=upload_limit, max_file_size=max_file_size, callback_url=callback_url) return json_response(get_entry_serializable(entry, request.urlgen)) # Handle upload limit issues except FileUploadLimit: raise BadRequest( _(u'Sorry, the file size is too big.')) except UserUploadLimit: raise BadRequest( _('Sorry, uploading this file will put you over your' ' upload limit.')) except UserPastUploadLimit: raise BadRequest( _('Sorry, you have reached your upload limit.')) except Exception as e: ''' This section is intended to catch exceptions raised in mediagoblin.media_types ''' if isinstance(e, InvalidFileType) or \ isinstance(e, FileTypeNotSupported): raise BadRequest(unicode(e)) else: raise
def post_entry(request): _log.debug('Posting entry') if request.method == 'OPTIONS': return json_response({'status': 200}) if request.method != 'POST': _log.debug('Must POST against post_entry') raise BadRequest() if not check_file_field(request, 'file'): _log.debug('File field not found') raise BadRequest() media_file = request.files['file'] media_type, media_manager = sniff_media(media_file) entry = new_upload_entry(request.user) entry.media_type = unicode(media_type) entry.title = unicode( request.form.get('title') or splitext(media_file.filename)[0]) entry.description = unicode(request.form.get('description')) entry.license = unicode(request.form.get('license', '')) entry.generate_slug() # queue appropriately queue_file = prepare_queue_task(request.app, entry, media_file.filename) with queue_file: queue_file.write(request.files['file'].stream.read()) # Save now so we have this data before kicking off processing entry.save() if request.form.get('callback_url'): metadata = request.db.ProcessingMetaData() metadata.media_entry = entry metadata.callback_url = unicode(request.form['callback_url']) metadata.save() # Pass off to processing # # (... don't change entry after this point to avoid race # conditions with changes to the document via processing code) feed_url = request.urlgen('mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username) run_process_media(entry, feed_url) return json_response(get_entry_serializable(entry, request.urlgen))
def post_entry(request): _log.debug('Posting entry') if request.method == 'OPTIONS': return json_response({'status': 200}) if request.method != 'POST': _log.debug('Must POST against post_entry') raise BadRequest() if not check_file_field(request, 'file'): _log.debug('File field not found') raise BadRequest() media_file = request.files['file'] media_type, media_manager = sniff_media(media_file) entry = new_upload_entry(request.user) entry.media_type = unicode(media_type) entry.title = unicode(request.form.get('title') or splitext(media_file.filename)[0]) entry.description = unicode(request.form.get('description')) entry.license = unicode(request.form.get('license', '')) entry.generate_slug() # queue appropriately queue_file = prepare_queue_task(request.app, entry, media_file.filename) with queue_file: queue_file.write(request.files['file'].stream.read()) # Save now so we have this data before kicking off processing entry.save() if request.form.get('callback_url'): metadata = request.db.ProcessingMetaData() metadata.media_entry = entry metadata.callback_url = unicode(request.form['callback_url']) metadata.save() # Pass off to processing # # (... don't change entry after this point to avoid race # conditions with changes to the document via processing code) feed_url = request.urlgen( 'mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username) run_process_media(entry, feed_url) return json_response(get_entry_serializable(entry, request.urlgen))
def object_comments(request): """ Looks up for the comments on a object """ public_id = request.urlgen( "mediagoblin.api.object", object_type=request.matchdict["object_type"], id=request.matchdict["id"], qualified=True ) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error("Can't find '{0}' with ID '{1}'".format( request.matchdict["object_type"], request.matchdict["id"] ), 404) comments = media.serialize(request) comments = comments.get("replies", { "totalItems": 0, "items": [], "url": request.urlgen( "mediagoblin.api.object.comments", object_type=media.object_type, id=media.id, qualified=True ) }) comments["displayName"] = "Replies to {0}".format(comments["url"]) comments["links"] = { "first": comments["url"], "self": comments["url"], } return json_response(comments)
def object_endpoint(request): """ Lookup for a object type """ object_type = request.matchdict["object_type"] try: object_id = request.matchdict["id"] except ValueError: error = "Invalid object ID '{0}' for '{1}'".format( request.matchdict["id"], object_type ) return json_error(error) if object_type not in ["image"]: # not sure why this is 404, maybe ask evan. Maybe 400? return json_error( "Unknown type: {0}".format(object_type), status=404 ) public_id = request.urlgen( "mediagoblin.api.object", object_type=object_type, id=object_id, qualified=True ) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error( "Can't find '{0}' with ID '{1}'".format(object_type, object_id), status=404 ) return json_response(media.serialize(request))
def wrapper(request, *args, **kwargs): if not request.user.has_privilege(privilege_name): error = "User '{}' needs '{}' privilege".format( request.user.username, privilege_name) return json_response({"error": error}, status=403) return controller(request, *args, **kwargs)
def object_endpoint(request): """ Lookup for a object type """ object_type = request.matchdict["object_type"] try: object_id = request.matchdict["id"] except ValueError: error = "Invalid object ID '{0}' for '{1}'".format( request.matchdict["id"], object_type) return json_error(error) if object_type not in ["image"]: # not sure why this is 404, maybe ask evan. Maybe 400? return json_error("Unknown type: {0}".format(object_type), status=404) public_id = request.urlgen("mediagoblin.api.object", object_type=object_type, id=object_id, qualified=True) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error("Can't find '{0}' with ID '{1}'".format( object_type, object_id), status=404) return json_response(media.serialize(request))
def object_comments(request): """ Looks up for the comments on a object """ media = object(request, raw_obj=True) response = media if isinstance(response, MediaEntry): comments = response.serialize(request) comments = comments.get("replies", { "totalItems": 0, "items": [], "url": request.urlgen( "mediagoblin.federation.object.comments", objectType=media.objectType, uuid=media.id, qualified=True ) }) comments["displayName"] = "Replies to {0}".format(comments["url"]) comments["links"] = { "first": comments["url"], "self": comments["url"], } response = json_response(comments) return response
def host_meta(request): """ This provides the host-meta URL information that is outlined in RFC6415. By default this should provide XRD+XML however if the client accepts JSON we will provide that over XRD+XML. The 'Accept' header is used to decude this. A client should use this endpoint to determine what URLs to use for OAuth endpoints. """ links = [ { "rel": "lrdd", "type": "application/json", "href": request.urlgen("mediagoblin.webfinger.well-known.webfinger", qualified=True) }, { "rel": "registration_endpoint", "href": request.urlgen("mediagoblin.oauth.client_register", qualified=True), }, { "rel": "http://apinamespace.org/oauth/request_token", "href": request.urlgen("mediagoblin.oauth.request_token", qualified=True), }, { "rel": "http://apinamespace.org/oauth/authorize", "href": request.urlgen("mediagoblin.oauth.authorize", qualified=True), }, { "rel": "http://apinamespace.org/oauth/access_token", "href": request.urlgen("mediagoblin.oauth.access_token", qualified=True), }, { "rel": "http://apinamespace.org/activitypub/whoami", "href": request.urlgen("mediagoblin.webfinger.whoami", qualified=True), }, ] if "application/json" in request.accept_mimetypes: return json_response({"links": links}) # provide XML+XRD return render_to_response(request, "mediagoblin/api/host-meta.xml", {"links": links}, mimetype="application/xrd+xml")
def object_comments(request): """ Looks up for the comments on a object """ public_id = request.urlgen("mediagoblin.api.object", object_type=request.matchdict["object_type"], id=request.matchdict["id"], qualified=True) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_error( "Can't find '{0}' with ID '{1}'".format( request.matchdict["object_type"], request.matchdict["id"]), 404) comments = media.serialize(request) comments = comments.get( "replies", { "totalItems": 0, "items": [], "url": request.urlgen("mediagoblin.api.object.comments", object_type=media.object_type, id=media.id, qualified=True) }) comments["displayName"] = "Replies to {0}".format(comments["url"]) comments["links"] = { "first": comments["url"], "self": comments["url"], } return json_response(comments)
def host_meta(request): """ /.well-known/host-meta - provide URLs to resources """ links = [] links.append({ "ref": "registration_endpoint", "href": request.urlgen( "mediagoblin.oauth.client_register", qualified=True ), }) links.append({ "ref": "http://apinamespace.org/oauth/request_token", "href": request.urlgen( "mediagoblin.oauth.request_token", qualified=True ), }) links.append({ "ref": "http://apinamespace.org/oauth/authorize", "href": request.urlgen( "mediagoblin.oauth.authorize", qualified=True ), }) links.append({ "ref": "http://apinamespace.org/oauth/access_token", "href": request.urlgen( "mediagoblin.oauth.access_token", qualified=True ), }) return json_response({"links": links})
def object(request, raw_obj=False): """ Lookup for a object type """ object_type = request.matchdict["objectType"] try: object_id = int(request.matchdict["id"]) except ValueError: error = "Invalid object ID '{0}' for '{1}'".format( request.matchdict["id"], object_type ) return json_error(error) if object_type not in ["image"]: # not sure why this is 404, maybe ask evan. Maybe 400? return json_error( "Unknown type: {0}".format(object_type), status=404 ) media = MediaEntry.query.filter_by(id=object_id).first() if media is None: error = "Can't find '{0}' with ID '{1}'".format( object_type, object_id ) return json_error( "Can't find '{0}' with ID '{1}'".format(object_type, object_id), status=404 ) if raw_obj: return media return json_response(media.serialize(request))
def request_token(request): """ Returns request token """ try: data = decode_request(request) except ValueError: error = "Could not decode data." return json_response({"error": error}, status=400) if data == "": error = "Unknown Content-Type" return json_response({"error": error}, status=400) if not data and request.headers: data = request.headers data = dict(data) # mutableifying authorization = decode_authorization_header(data) if authorization == dict() or u"oauth_consumer_key" not in authorization: error = "Missing required parameter." return json_response({"error": error}, status=400) # check the client_id client_id = authorization[u"oauth_consumer_key"] client = Client.query.filter_by(id=client_id).first() if client == None: # client_id is invalid error = "Invalid client_id" return json_response({"error": error}, status=400) # make request token and return to client request_validator = GMGRequestValidator(authorization) rv = RequestTokenEndpoint(request_validator) tokens = rv.create_request_token(request, authorization) # store the nonce & timestamp before we return back nonce = authorization[u"oauth_nonce"] timestamp = authorization[u"oauth_timestamp"] timestamp = datetime.datetime.fromtimestamp(float(timestamp)) nc = NonceTimestamp(nonce=nonce, timestamp=timestamp) nc.save() return form_response(tokens)
def wrapper(request, *args, **kw): if not request.GET.get('client_id'): return json_response({ 'status': 400, 'errors': [u'No client identifier in URL']}, _disable_cors=True) client = OAuthClient.query.filter( OAuthClient.identifier == request.GET.get('client_id')).first() if not client: return json_response({ 'status': 400, 'errors': [u'No such client identifier']}, _disable_cors=True) return controller(request, client)
def wrapper(request, *args, **kwargs): if not request.user.has_privilege(privilege_name): error = "User '{0}' needs '{1}' privilege".format( request.user.username, privilege_name ) return json_response({"error": error}, status=403) return controller(request, *args, **kwargs)
def profile_endpoint(request): """ This is /api/user/<username>/profile - This will give profile info """ user, user_profile = get_profile(request) if user is None: username = request.matchdict["username"] return json_error("No such 'user' with username '{0}'".format(username), status=404) # user profiles are public so return information return json_response(user_profile)
def post_entry(request): _log.debug('Posting entry') if request.method == 'OPTIONS': return json_response({'status': 200}) if request.method != 'POST': _log.debug('Must POST against post_entry') raise BadRequest() if not check_file_field(request, 'file'): _log.debug('File field not found') raise BadRequest() callback_url = request.form.get('callback_url') if callback_url: callback_url = six.text_type(callback_url) try: entry = submit_media( mg_app=request.app, user=request.user, submitted_file=request.files['file'], filename=request.files['file'].filename, title=six.text_type(request.form.get('title')), description=six.text_type(request.form.get('description')), license=six.text_type(request.form.get('license', '')), tags_string=six.text_type(request.form.get('tags', '')), callback_url=callback_url) return json_response(get_entry_serializable(entry, request.urlgen)) # Handle upload limit issues except FileUploadLimit: raise BadRequest( _(u'Sorry, the file size is too big.')) except UserUploadLimit: raise BadRequest( _('Sorry, uploading this file will put you over your' ' upload limit.')) except UserPastUploadLimit: raise BadRequest( _('Sorry, you have reached your upload limit.')) except FileTypeNotSupported as e: raise BadRequest(e)
def profile_endpoint(request): """ This is /api/user/<username>/profile - This will give profile info """ user, user_profile = get_profile(request) if user is None: username = request.matchdict["username"] return json_error( "No such 'user' with username '{0}'".format(username), status=404) # user profiles are public so return information return json_response(user_profile)
def user(request): """ This is /api/user/<username> - This will get the user """ user, user_profile = profile(request, raw=True) data = { "nickname": user.username, "updated": user.created.isoformat(), "published": user.created.isoformat(), "profile": user_profile, } return json_response(data)
def wrapper(request, *args, **kw): if not request.GET.get('client_id'): return json_response( { 'status': 400, 'errors': [u'No client identifier in URL'] }, _disable_cors=True) client = OAuthClient.query.filter( OAuthClient.identifier == request.GET.get('client_id')).first() if not client: return json_response( { 'status': 400, 'errors': [u'No such client identifier'] }, _disable_cors=True) return controller(request, client)
def api_upload_request(request, file_data, entry): """ This handles a image upload request """ # Use the same kind of method from mediagoblin/submit/views:submit_start entry.title = file_data.filename entry.generate_slug() queue_file = prepare_queue_task(request.app, entry, file_data.filename) with queue_file: queue_file.write(request.data) entry.save() return json_response(entry.serialize(request))
def lrdd_lookup(request): """ This is the lrdd endpoint which can lookup a user (or other things such as activities). This is as specified by RFC6415. The cleint must provide a 'resource' as a GET parameter which should be the query to be looked up. """ if "resource" not in request.args: return json_error("No resource parameter", status=400) resource = request.args["resource"] if "@" in resource: # Lets pull out the username resource = resource[5:] if resource.startswith("acct:") else resource username, host = resource.split("@", 1) # Now lookup the user user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: return json_error( "Can't find 'user' with username '{}'".format(username)) return json_response([ { "rel": "http://webfinger.net/rel/profile-page", "href": user.url_for_self(request.urlgen), "type": "text/html" }, { "rel": "self", "href": request.urlgen( "mediagoblin.api.user", username=user.username, qualified=True ) }, { "rel": "activity-outbox", "href": request.urlgen( "mediagoblin.api.feed", username=user.username, qualified=True ) } ]) else: return json_error("Unrecognized resource parameter", status=404)
def lrdd_lookup(request): """ This is the lrdd endpoint which can lookup a user (or other things such as activities). This is as specified by RFC6415. The cleint must provide a 'resource' as a GET parameter which should be the query to be looked up. """ if "resource" not in request.args: return json_error("No resource parameter", status=400) resource = request.args["resource"] if "@" in resource: # Lets pull out the username resource = resource[5:] if resource.startswith("acct:") else resource username, host = resource.split("@", 1) # Now lookup the user user = LocalUser.query.filter(LocalUser.username==username).first() if user is None: return json_error( "Can't find 'user' with username '{0}'".format(username)) return json_response([ { "rel": "http://webfinger.net/rel/profile-page", "href": user.url_for_self(request.urlgen), "type": "text/html" }, { "rel": "self", "href": request.urlgen( "mediagoblin.api.user", username=user.username, qualified=True ) }, { "rel": "activity-outbox", "href": request.urlgen( "mediagoblin.api.feed", username=user.username, qualified=True ) } ]) else: return json_error("Unrecognized resource parameter", status=404)
def api_add_to_feed(request, entry): """ Add media to Feed """ if entry.title: entry.generate_slug() feed_url = request.urlgen( 'mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username ) run_process_media(entry, feed_url) add_comment_subscription(request.user, entry) return json_response(entry.serialize(request))
def wrapper(request, *args, **kwargs): data = request.headers authorization = decode_authorization_header(data) if authorization == dict(): error = "Missing required parameter." return json_response({"error": error}, status=400) request_validator = GMGRequestValidator() resource_endpoint = ResourceEndpoint(request_validator) valid, request = resource_endpoint.validate_protected_resource_request( uri=request.url, http_method=request.method, body=request.get_data(), headers=dict(request.headers), ) if not valid: error = "Invalid oauth prarameter." return json_response({"error": error}, status=400) return controller(request, *args, **kwargs)
def api_upload_request(request, file_data, entry): """ This handles a image upload request """ # Use the same kind of method from mediagoblin/submit/views:submit_start entry.title = file_data.filename # This will be set later but currently we just don't have enough information entry.slug = None queue_file = prepare_queue_task(request.app, entry, file_data.filename) with queue_file: queue_file.write(request.data) entry.save() return json_response(entry.serialize(request))
def upload_handler(request): if request.GET.get('url') and request.GET.get('title') and request.user: upload_limit, max_file_size = get_upload_file_limits(request.user) try: f = urlopen(request.GET.get('url')) fname = request.GET.get('url')[request.GET.get('url').rfind('/')+1:] tmpfile = tempfile.NamedTemporaryFile() tmpfile.write(f.read) tmpfile.flush() local_file = open(tmpfile.name, "r") try: entry = submit_media( mg_app = request.app, user=request.user, submitted_file=local_file, filename=fname, title=request.GET.get('title')) entryinfo = get_entry_serializable(entry, request.urlgen) os.unlink(f.name) return json_response({'status':200, 'permalink':entryinfo['permalink']}) except FileUploadLimit: return json_reponse({'status':400, 'error':'Past File size Upload Limit'}) except UserUploadLimit: return json_response({'status':400, 'error':'Past User upload limit'}) except UserPastUploadLimit: return json_response({'status':400, 'error':'Past upload limit'}) except Exception as e: return json_response({'status':400, 'error':'Unspecified error'}) except HTTPError as e: print("HTTP Error:", e.code, url) return json_response({'status':400, 'error':'unspecifice httperror'}) except URLError as e: print("URL Error:", e.reason, url) return json_response({'status':400, 'error':'unspecified url error'}) else: if not request.GET.get('url'): return json_response({'status':400, 'error':'No URL specified [GET, url]'}) elif not request.GET.get('title'): return json_response({'status':400, 'error':'No title specified [GET, title]'}) elif not request.user: return json_response({'status':401, 'error':'No user found'}); else: return json_reponse({'status':400, 'error':'Unknown Error Occured'})
def api_add_to_feed(request, entry): """ Add media to Feed """ feed_url = request.urlgen( 'mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username ) add_comment_subscription(request.user, entry) # Create activity create_activity("post", entry, entry.uploader) entry.save() run_process_media(entry, feed_url) return json_response(entry.serialize(request))
def access_token(request): """ Provides an access token based on a valid verifier and request token """ data = request.headers parsed_tokens = decode_authorization_header(data) if parsed_tokens == dict() or "oauth_token" not in parsed_tokens: error = "Missing required parameter." return json_response({"error": error}, status=400) request.resource_owner_key = parsed_tokens["oauth_consumer_key"] request.oauth_token = parsed_tokens["oauth_token"] request_validator = GMGRequestValidator(data) # Check that the verifier is valid verifier_valid = request_validator.validate_verifier( token=request.oauth_token, verifier=parsed_tokens["oauth_verifier"]) if not verifier_valid: error = "Verifier code or token incorrect" return json_response({"error": error}, status=401) av = AccessTokenEndpoint(request_validator) tokens = av.create_access_token(request, {}) return form_response(tokens)
def api_add_to_feed(request, entry): """ Add media to Feed """ if entry.title: # Shame we have to do this here but we didn't have the data in # api_upload_request as no filename is usually specified. entry.slug = None entry.generate_slug() feed_url = request.urlgen( 'mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username) run_process_media(entry, feed_url) add_comment_subscription(request.user, entry) return json_response(entry.serialize(request))
def user_endpoint(request): """ This is /api/user/<username> - This will get the user """ user, user_profile = get_profile(request) if user is None: username = request.matchdict["username"] return json_error( "No such 'user' with username '{0}'".format(username), status=404) return json_response({ "nickname": user.username, "updated": user.created.isoformat(), "published": user.created.isoformat(), "profile": user_profile, })
def api_add_to_feed(request, entry): """ Add media to Feed """ if entry.title: # Shame we have to do this here but we didn't have the data in # api_upload_request as no filename is usually specified. entry.slug = None entry.generate_slug() feed_url = request.urlgen('mediagoblin.user_pages.atom_feed', qualified=True, user=request.user.username) run_process_media(entry, feed_url) add_comment_subscription(request.user, entry) return json_response(entry.serialize(request))
def access_token(request): """ Provides an access token based on a valid verifier and request token """ data = request.headers parsed_tokens = decode_authorization_header(data) if parsed_tokens == dict() or "oauth_token" not in parsed_tokens: error = "Missing required parameter." return json_response({"error": error}, status=400) request.oauth_token = parsed_tokens["oauth_token"] request_validator = GMGRequestValidator(data) av = AccessTokenEndpoint(request_validator) tokens = av.create_access_token(request, {}) return form_response(tokens)
def oembed_with_media(request, media, maxheight=None, maxwidth=None, **kwargs): response = {} if media.media_type == IMAGE_MEDIA_TYPE: response['type'] = u'photo' elif media.media_type == VIDEO_MEDIA_TYPE: response['type'] = u'video' else: NotImplemented() response['version'] = u'1.0' media_dict = get_entry_serializable(media, request.urlgen) response['title'] = media.title response['author_name'] = media_dict['user'] response['author_url'] = media_dict['user_permalink'] response['provider_name'] = u'MediaGoblin' response['provider_url'] = request.host_url if media.media_type == IMAGE_MEDIA_TYPE: response['url'] = media_dict['media_files']['medium'] response['height'] = media.get_file_metadata('medium', 'height') response['width'] = media.get_file_metadata('medium', 'width') if ((maxheight and response['height'] > maxheight) or (maxwidth and response['width'] > maxwidth)): response['url'] = media_dict['media_files']['thumb'] response['height'] = media.get_file_metadata('thumb', 'height') response['width'] = media.get_file_metadata('thumb', 'width') elif media.media_type == VIDEO_MEDIA_TYPE: video_url = media_dict['media_files']['webm_video'] response['width'], response['height'] = media.get_file_metadata( 'webm_video', 'medium_size') if maxheight: response['height'] = maxheight if maxwidth: response['width'] = maxwidth response['html'] = (u'<video width="{0}" height="{1}" controls>' ' <source src="{2}" type="video/webm">' ' Your browser does not support the video tag.' '</video>').format(response['width'], response['height'], video_url) return json_response(response, _disable_cors=True)
def profile(request, raw=False): """ This is /api/user/<username>/profile - This will give profile info """ user = request.matchdict["username"] requested_user = User.query.filter_by(username=user).first() if user is None: return json_error( "No such 'user' with id '{0}'".format(user), status=404 ) if raw: return (requested_user.username, requested_user.serialize(request)) # user profiles are public so return information return json_response(requested_user.serialize(request))
def user_endpoint(request): """ This is /api/user/<username> - This will get the user """ user, user_profile = get_profile(request) if user is None: username = request.matchdict["username"] return json_error("No such 'user' with username '{0}'".format(username), status=404) return json_response( { "nickname": user.username, "updated": user.created.isoformat(), "published": user.created.isoformat(), "profile": user_profile, } )
def api_upload_request(request, file_data, entry): """ This handles a image upload request """ # Use the same kind of method from mediagoblin/submit/views:submit_start entry.title = file_data.filename # This will be set later but currently we just don't have enough information entry.slug = None # This is a MUST. entry.get_public_id(request.urlgen) queue_file = prepare_queue_task(request.app, entry, file_data.filename) with queue_file: queue_file.write(request.data) entry.save() return json_response(entry.serialize(request))
def get_entries(request): entries = request.db.MediaEntry.query # TODO: Make it possible to fetch unprocessed media, or media in-processing entries = entries.filter_by(state=u'processed') # TODO: Add sort order customization entries = entries.order_by(request.db.MediaEntry.created.desc()) # TODO: Fetch default and upper limit from config entries = entries.limit(int(request.GET.get('limit') or 10)) entries_serializable = [] for entry in entries: entries_serializable.append(get_entry_serializable(entry, request.urlgen)) return json_response(entries_serializable)
def host_meta(request): """ This provides the host-meta URL information that is outlined in RFC6415. By default this should provide XRD+XML however if the client accepts JSON we will provide that over XRD+XML. The 'Accept' header is used to decude this. A client should use this endpoint to determine what URLs to use for OAuth endpoints. """ links = [ { "rel": "lrdd", "type": "application/json", "href": request.urlgen("mediagoblin.webfinger.well-known.webfinger", qualified=True), }, {"rel": "registration_endpoint", "href": request.urlgen("mediagoblin.oauth.client_register", qualified=True)}, { "rel": "http://apinamespace.org/oauth/request_token", "href": request.urlgen("mediagoblin.oauth.request_token", qualified=True), }, { "rel": "http://apinamespace.org/oauth/authorize", "href": request.urlgen("mediagoblin.oauth.authorize", qualified=True), }, { "rel": "http://apinamespace.org/oauth/access_token", "href": request.urlgen("mediagoblin.oauth.access_token", qualified=True), }, { "rel": "http://apinamespace.org/activitypub/whoami", "href": request.urlgen("mediagoblin.webfinger.whoami", qualified=True), }, ] if "application/json" in request.accept_mimetypes: return json_response({"links": links}) # provide XML+XRD return render_to_response( request, "mediagoblin/api/host-meta.xml", {"links": links}, mimetype="application/xrd+xml" )
def feed_endpoint(request, outbox=None): """ Handles the user's outbox - /api/user/<username>/feed """ username = request.matchdict["username"] requested_user = LocalUser.query.filter( LocalUser.username == username).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) if request.data: data = json.loads(request.data.decode()) else: data = {"verb": None, "object": {}} if request.method in ["POST", "PUT"]: # Validate that the activity is valid if "verb" not in data or "object" not in data: return json_error("Invalid activity provided.") # Check that the verb is valid if data["verb"] not in ["post", "update", "delete"]: return json_error("Verb not yet implemented", 501) # We need to check that the user they're posting to is # the person that they are. if requested_user.id != request.user.id: return json_error("Not able to post to another users feed.", status=403) # Handle new posts if data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403) comment = TextComment(actor=request.user.id) comment.unserialize(data["object"], request) comment.save() # Create activity for comment generator = create_generator(request) activity = create_activity(verb="post", actor=request.user, obj=comment, target=comment.get_reply_to(), generator=generator) return json_response(activity.serialize(request)) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = extract_url_arguments( url=data["object"]["id"], urlmap=request.app.url_map)["id"] # Build public_id public_id = request.urlgen("mediagoblin.api.object", object_type=obj["objectType"], id=media_id, qualified=True) media = MediaEntry.query.filter_by(public_id=public_id).first() if media is None: return json_response( "No such 'image' with id '{0}'".format(media_id), status=404) if media.actor != request.user.id: return json_error( "Privilege 'commenter' required to comment.", status=403) if not media.unserialize(data["object"]): return json_error( "Invalid 'image' with id '{0}'".format(media_id)) # Add location if one exists if "location" in data: Location.create(data["location"], self) media.save() activity = api_add_to_feed(request, media) return json_response(activity.serialize(request)) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error( "Unknown object type '{0}'.".format(object_type)) # Updating existing objects if data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"] public_id = request.urlgen("mediagoblin.api.object", object_type=obj["objectType"], id=obj_id, qualified=True) # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403) comment = TextComment.query.filter_by( public_id=public_id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id)) # Check that the person trying to update the comment is # the author of the comment. if comment.actor != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403) if not comment.unserialize(data["object"], request): return json_error("Invalid 'comment' with id '{0}'".format( obj["id"])) comment.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=comment, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(public_id=public_id).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj["id"])) # Check that the person trying to update the comment is # the author of the comment. if image.actor != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403) if not image.unserialize(obj): return json_error( "Invalid 'image' with id '{0}'".format(obj_id)) image.generate_slug() image.save() # Create an update activity generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=image, generator=generator) return json_response(activity.serialize(request)) elif obj["objectType"] == "person": # check this is the same user if "id" not in obj or obj["id"] != requested_user.id: return json_error("Incorrect user id, unable to update") requested_user.unserialize(obj) requested_user.save() generator = create_generator(request) activity = create_activity(verb="update", actor=request.user, obj=requested_user, generator=generator) return json_response(activity.serialize(request)) elif data["verb"] == "delete": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") # Parse out the object ID obj_id = extract_url_arguments(url=obj["id"], urlmap=request.app.url_map)["id"] public_id = request.urlgen("mediagoblin.api.object", object_type=obj["objectType"], id=obj_id, qualified=True) if obj.get("objectType", None) == "comment": # Find the comment asked for comment = TextComment.query.filter_by( public_id=public_id, actor=request.user.id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id)) # Make a delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=comment, generator=generator) # Unfortunately this has to be done while hard deletion exists context = activity.serialize(request) # now we can delete the comment comment.delete() return json_response(context) if obj.get("objectType", None) == "image": # Find the image entry = MediaEntry.query.filter_by( public_id=public_id, actor=request.user.id).first() if entry is None: return json_error( "No such 'image' with id '{0}'.".format(obj_id)) # Make the delete activity generator = create_generator(request) activity = create_activity(verb="delete", actor=request.user, obj=entry, generator=generator) # This is because we have hard deletion context = activity.serialize(request) # Now we can delete the image entry.delete() return json_response(context) elif request.method != "GET": return json_error("Unsupported HTTP method {0}".format(request.method), status=501) feed = { "displayName": "Activities by {user}@{host}".format(user=request.user.username, host=request.host), "objectTypes": ["activity"], "url": request.base_url, "links": { "self": { "href": request.url } }, "author": request.user.serialize(request), "items": [], } # Create outbox if outbox is None: outbox = Activity.query.filter_by(actor=requested_user.id) else: outbox = outbox.filter_by(actor=requested_user.id) # We want the newest things at the top (issue: #1055) outbox = outbox.order_by(Activity.published.desc()) # Limit by the "count" (default: 20) limit = request.args.get("count", 20) try: limit = int(limit) except ValueError: limit = 20 # The upper most limit should be 200 limit = limit if limit < 200 else 200 # apply the limit outbox = outbox.limit(limit) # Offset (default: no offset - first <count> result) offset = request.args.get("offset", 0) try: offset = int(offset) except ValueError: offset = 0 outbox = outbox.offset(offset) # Build feed. for activity in outbox: try: feed["items"].append(activity.serialize(request)) except AttributeError: # This occurs because of how we hard-deletion and the object # no longer existing anymore. We want to keep the Activity # in case someone wishes to look it up but we shouldn't display # it in the feed. pass feed["totalItems"] = len(feed["items"]) return json_response(feed)
def client_register(request): """ Endpoint for client registration """ try: data = decode_request(request) except ValueError: error = "Could not decode data." return json_response({"error": error}, status=400) if data is "": error = "Unknown Content-Type" return json_response({"error": error}, status=400) if "type" not in data: error = "No registration type provided." return json_response({"error": error}, status=400) if data.get("application_type", None) not in CLIENT_TYPES: error = "Unknown application_type." return json_response({"error": error}, status=400) client_type = data["type"] if client_type == "client_update": # updating a client if "client_id" not in data: error = "client_id is requried to update." return json_response({"error": error}, status=400) elif "client_secret" not in data: error = "client_secret is required to update." return json_response({"error": error}, status=400) client = Client.query.filter_by(id=data["client_id"], secret=data["client_secret"]).first() if client is None: error = "Unauthorized." return json_response({"error": error}, status=403) client.application_name = data.get("application_name", client.application_name) client.application_type = data.get("application_type", client.application_type) app_name = ("application_type", client.application_name) if app_name in CLIENT_TYPES: client.application_name = app_name elif client_type == "client_associate": # registering if "client_id" in data: error = "Only set client_id for update." return json_response({"error": error}, status=400) elif "access_token" in data: error = "access_token not needed for registration." return json_response({"error": error}, status=400) elif "client_secret" in data: error = "Only set client_secret for update." return json_response({"error": error}, status=400) # generate the client_id and client_secret client_id = random_string(22, UNICODE_ASCII_CHARACTER_SET) client_secret = random_string(43, UNICODE_ASCII_CHARACTER_SET) expirey = 0 # for now, lets not have it expire expirey_db = None if expirey == 0 else expirey application_type = data["application_type"] # save it client = Client( id=client_id, secret=client_secret, expirey=expirey_db, application_type=application_type, ) else: error = "Invalid registration type" return json_response({"error": error}, status=400) logo_uri = data.get("logo_uri", client.logo_url) if logo_uri is not None and not validate_url(logo_uri): error = "Logo URI {0} is not a valid URI.".format(logo_uri) return json_response({"error": error}, status=400) else: client.logo_url = logo_uri client.application_name = data.get("application_name", None) contacts = data.get("contacts", None) if contacts is not None: if not isinstance(contacts, six.text_type): error = "Contacts must be a string of space-seporated email addresses." return json_response({"error": error}, status=400) contacts = contacts.split() for contact in contacts: if not validate_email(contact): # not a valid email error = "Email {0} is not a valid email.".format(contact) return json_response({"error": error}, status=400) client.contacts = contacts redirect_uris = data.get("redirect_uris", None) if redirect_uris is not None: if not isinstance(redirect_uris, six.text_type): error = "redirect_uris must be space-seporated URLs." return json_response({"error": error}, status=400) redirect_uris = redirect_uris.split() for uri in redirect_uris: if not validate_url(uri): # not a valid uri error = "URI {0} is not a valid URI".format(uri) return json_response({"error": error}, status=400) client.redirect_uri = redirect_uris client.save() expirey = 0 if client.expirey is None else client.expirey return json_response({ "client_id": client.id, "client_secret": client.secret, "expires_at": expirey, })
def access_token(request): ''' Access token endpoint provides access tokens to any clients that have the right grants/credentials ''' client = None user = None if request.GET.get('code'): # Validate the code arg, then get the client object from the db. code = OAuthCode.query.filter(OAuthCode.code == request.GET.get('code')).first() if not code: return json_response({ 'error': 'invalid_request', 'error_description': 'Invalid code.'}) client = code.client user = code.user elif request.args.get('refresh_token'): # Validate a refresh token, then get the client object from the db. refresh_token = OAuthRefreshToken.query.filter( OAuthRefreshToken.token == request.args.get('refresh_token')).first() if not refresh_token: return json_response({ 'error': 'invalid_request', 'error_description': 'Invalid refresh token.'}) client = refresh_token.client user = refresh_token.user if client: client_identifier = request.GET.get('client_id') if not client_identifier: return json_response({ 'error': 'invalid_request', 'error_description': 'Missing client_id in request.'}) if not client_identifier == client.identifier: return json_response({ 'error': 'invalid_client', 'error_description': 'Mismatching client credentials.'}) if client.type == u'confidential': client_secret = request.GET.get('client_secret') if not client_secret: return json_response({ 'error': 'invalid_request', 'error_description': 'Missing client_secret in request.'}) if not client_secret == client.secret: return json_response({ 'error': 'invalid_client', 'error_description': 'Mismatching client credentials.'}) access_token_data = create_token(client, user) return json_response(access_token_data, _disable_cors=True) return json_response({ 'error': 'invalid_request', 'error_description': 'Missing `code` or `refresh_token` parameter in request.'})
def feed(request): """ Handles the user's outbox - /api/user/<username>/feed """ user = request.matchdict["username"] requested_user = User.query.filter_by(username=user).first() # check if the user exists if requested_user is None: return json_error("No such 'user' with id '{0}'".format(user), 404) if request.data: data = json.loads(request.data) else: data = {"verb": None, "object": {}} # We need to check that the user they're posting to is # the person that they are. if request.method in ["POST", "PUT"] and \ requested_user.id != request.user.id: return json_error( "Not able to post to another users feed.", status=403 ) if request.method == "POST" and data["verb"] == "post": obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if obj.get("objectType", None) == "comment": # post a comment if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment(author=request.user.id) comment.unserialize(data["object"]) comment.save() data = {"verb": "post", "object": comment.serialize(request)} return json_response(data) elif obj.get("objectType", None) == "image": # Posting an image to the feed media_id = int(data["object"]["id"]) media = MediaEntry.query.filter_by(id=media_id).first() if media is None: return json_response( "No such 'image' with id '{0}'".format(id=media_id), status=404 ) if not media.unserialize(data["object"]): return json_error( "Invalid 'image' with id '{0}'".format(media_id) ) media.save() api_add_to_feed(request, media) return json_response({ "verb": "post", "object": media.serialize(request) }) elif obj.get("objectType", None) is None: # They need to tell us what type of object they're giving us. return json_error("No objectType specified.") else: # Oh no! We don't know about this type of object (yet) object_type = obj.get("objectType", None) return json_error("Unknown object type '{0}'.".format(object_type)) elif request.method in ["PUT", "POST"] and data["verb"] == "update": # Check we've got a valid object obj = data.get("object", None) if obj is None: return json_error("Could not find 'object' element.") if "objectType" not in obj: return json_error("No objectType specified.") if "id" not in obj: return json_error("Object ID has not been specified.") obj_id = obj["id"] # Now try and find object if obj["objectType"] == "comment": if not request.user.has_privilege(u'commenter'): return json_error( "Privilege 'commenter' required to comment.", status=403 ) comment = MediaComment.query.filter_by(id=obj_id).first() if comment is None: return json_error( "No such 'comment' with id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if comment.author != request.user.id: return json_error( "Only author of comment is able to update comment.", status=403 ) if not comment.unserialize(data["object"]): return json_error( "Invalid 'comment' with id '{0}'".format(obj_id) ) comment.save() activity = { "verb": "update", "object": comment.serialize(request), } return json_response(activity) elif obj["objectType"] == "image": image = MediaEntry.query.filter_by(id=obj_id).first() if image is None: return json_error( "No such 'image' with the id '{0}'.".format(obj_id) ) # Check that the person trying to update the comment is # the author of the comment. if image.uploader != request.user.id: return json_error( "Only uploader of image is able to update image.", status=403 ) if not image.unserialize(obj): return json_error( "Invalid 'image' with id '{0}'".format(obj_id) ) image.save() activity = { "verb": "update", "object": image.serialize(request), } return json_response(activity) elif request.method != "GET": return json_error( "Unsupported HTTP method {0}".format(request.method), status=501 ) feed_url = request.urlgen( "mediagoblin.federation.feed", username=request.user.username, qualified=True ) feed = { "displayName": "Activities by {user}@{host}".format( user=request.user.username, host=request.host ), "objectTypes": ["activity"], "url": feed_url, "links": { "first": { "href": feed_url, }, "self": { "href": request.url, }, "prev": { "href": feed_url, }, "next": { "href": feed_url, } }, "author": request.user.serialize(request), "items": [], } # Look up all the media to put in the feed (this will be changed # when we get real feeds/inboxes/outboxes/activites) for media in MediaEntry.query.all(): item = { "verb": "post", "object": media.serialize(request), "actor": request.user.serialize(request), "content": "{0} posted a picture".format(request.user.username), "id": 1, } item["updated"] = item["object"]["updated"] item["published"] = item["object"]["published"] item["url"] = item["object"]["url"] feed["items"].append(item) feed["totalItems"] = len(feed["items"]) return json_response(feed)
def inbox_endpoint(request, inbox=None): """ This is the user's inbox Currently because we don't have the ability to represent the inbox in the database this is not a "real" inbox in the pump.io/Activity streams 1.0 sense but instead just gives back all the data on the website inbox: allows you to pass a query in to limit inbox scope """ username = request.matchdict["username"] user = LocalUser.query.filter(LocalUser.username == username).first() if user is None: return json_error("No such 'user' with id '{0}'".format(username), 404) # Only the user who's authorized should be able to read their inbox if user.id != request.user.id: return json_error( "Only '{0}' can read this inbox.".format(user.username), 403) if inbox is None: inbox = Activity.query # Count how many items for the "totalItems" field total_items = inbox.count() # We want to make a query for all media on the site and then apply GET # limits where we can. inbox = inbox.order_by(Activity.published.desc()) # Limit by the "count" (default: 20) try: limit = int(request.args.get("count", 20)) except ValueError: limit = 20 # Prevent the count being too big (pump uses 200 so we shall) limit = limit if limit <= 200 else 200 # Apply the limit inbox = inbox.limit(limit) # Offset (default: no offset - first <count> results) inbox = inbox.offset(request.args.get("offset", 0)) # build the inbox feed feed = { "displayName": "Activities for {0}".format(user.username), "author": user.serialize(request), "objectTypes": ["activity"], "url": request.base_url, "links": { "self": { "href": request.url } }, "items": [], "totalItems": total_items, } for activity in inbox: try: feed["items"].append(activity.serialize(request)) except AttributeError: # As with the feed endpint this occurs because of how we our # hard-deletion method. Some activites might exist where the # Activity object and/or target no longer exist, for this case we # should just skip them. pass return json_response(feed)