コード例 #1
0
def do_search_extracts(request):
    uri = request.GET.get('uri', None)
    if not uri:
        raise HTTPClientError("Please specify a URI")
    view_def = request.GET.get('view') or 'default'
    discussion = request.context
    user_id = authenticated_userid(request)
    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    user_id = user_id or Everyone
    if not user_has_permission(discussion.id, user_id, P_READ):
        raise HTTPForbidden()
    permissions = [P_READ]

    if not uri:
        raise HTTPBadRequest("Please specify a search uri")
    content = Webpage.get_by(url=uri)
    if content:
        extracts = Extract.default_db.query(Extract).filter_by(
            content=content).all()
        rows = [
            extract.generic_json(view_def, user_id, permissions)
            for extract in extracts
        ]
        return {"total": len(extracts), "rows": rows}
    return {"total": 0, "rows": []}
コード例 #2
0
def delete_extract(request):
    user_id = request.authenticated_userid
    discussion_id = int(request.matchdict['discussion_id'])

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    user_id = user_id or Everyone

    extract_id = request.matchdict['id']
    extract = Extract.get_instance(extract_id)

    if not (user_has_permission(discussion_id, user_id, P_EDIT_EXTRACT) or
            (user_has_permission(discussion_id, user_id, P_EDIT_MY_EXTRACT)
             and user_id == extract.owner_id)):
        raise HTTPForbidden()

    if not extract:
        return HTTPNoContent()

    # TODO: Tombstonable extracts???
    extract.delete()
    return HTTPNoContent()
コード例 #3
0
def delete_extract(request):
    user_id = authenticated_userid(request)
    discussion = request.context

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
        user_id = user_id or Everyone

    extract_id = request.matchdict['id']
    extract = Extract.get_instance(extract_id)
    permissions = get_permissions(user_id, discussion.id, extract)
    if P_EDIT_EXTRACT not in permissions:
        raise HTTPForbidden()

    if not extract:
        return HTTPNoContent()

    # TODO: Tombstonable extracts???
    extract.delete()
    return HTTPNoContent()
コード例 #4
0
ファイル: changes_router.py プロジェクト: driver4567/assembl
 def on_message(self, msg):
     try:
         if getattr(self, 'socket', None):
             print("closing old socket")
             self.loop.add_callback(self.do_close)
             return
         if msg.startswith('discussion:') and self.valid:
             self.discussion = msg.split(':', 1)[1]
         if msg.startswith('token:') and self.valid:
             try:
                 self.token = decode_token(
                     msg.split(':', 1)[1], TOKEN_SECRET)
                 self.userId = 'local:AgentProfile/' + str(
                     self.token['userId'])
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             r = requests.get(
                 '%s/api/v1/discussion/%s/permissions/read/u/%s' %
                 (SERVER_URL, self.discussion, self.token['userId']))
             print(r.text)
             if r.text != 'true':
                 return
             self.socket = context.socket(zmq.SUB)
             self.socket.connect(INTERNAL_SOCKET)
             self.socket.setsockopt(zmq.SUBSCRIBE, '*')
             self.socket.setsockopt(zmq.SUBSCRIBE, str(self.discussion))
             self.loop = zmqstream.ZMQStream(self.socket, io_loop=io_loop)
             self.loop.on_recv(self.on_recv)
             print("connected")
             self.send('[{"@type":"Connection"}]')
     except Exception:
         capture_exception()
         self.do_close()
コード例 #5
0
ファイル: extract.py プロジェクト: Lornz-/assembl
def delete_extract(request):
    user_id = authenticated_userid(request)
    discussion_id = int(request.matchdict['discussion_id'])

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(
                token, request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    user_id = user_id or Everyone

    extract_id = request.matchdict['id']
    extract = Extract.get_instance(extract_id)

    if not (user_has_permission(discussion_id, user_id, P_EDIT_EXTRACT)
        or (user_has_permission(discussion_id, user_id, P_EDIT_MY_EXTRACT)
            and user_id == extract.owner_id)):
        raise HTTPForbidden()

    if not extract:
        return HTTPNoContent()

    with transaction.manager:
        # TODO: Tombstonable extracts???
        Extract.default_db.delete(extract)
    request.response.status = HTTPNoContent.code
    return HTTPNoContent()
コード例 #6
0
ファイル: changes_router.py プロジェクト: mydigilife/assembl
 def on_message(self, msg):
     try:
         if getattr(self, "socket", None):
             print "closing old socket"
             self.loop.add_callback(self.do_close)
             return
         if msg.startswith("discussion:") and self.valid:
             self.discussion = msg.split(":", 1)[1]
         if msg.startswith("token:") and self.valid:
             try:
                 self.token = decode_token(msg.split(":", 1)[1], TOKEN_SECRET)
                 self.userId = "local:AgentProfile/" + str(self.token["userId"])
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             r = requests.get(
                 "http://%s:%d/api/v1/discussion/%s/permissions/read/u/%s"
                 % (SERVER_HOST, SERVER_PORT, self.discussion, self.token["userId"])
             )
             print r.text
             if r.text != "true":
                 return
             self.socket = context.socket(zmq.SUB)
             self.socket.connect(INTERNAL_SOCKET)
             self.socket.setsockopt(zmq.SUBSCRIBE, "*")
             self.socket.setsockopt(zmq.SUBSCRIBE, str(self.discussion))
             self.loop = zmqstream.ZMQStream(self.socket, io_loop=io_loop)
             self.loop.on_recv(self.on_recv)
             print "connected"
             self.send('[{"@type":"Connection"}]')
     except Exception:
         if raven_client:
             raven_client.captureException()
         raise
コード例 #7
0
 async def on_message(self, msg):
     try:
         if msg.startswith('discussion:') and self.valid:
             self.discussion = msg.split(':', 1)[1]
             log.debug('discussion_id: %s', self.discussion)
         if msg.startswith('token:') and self.valid:
             try:
                 self.raw_token = msg.split(':', 1)[1]
                 self.token = decode_token(self.raw_token,
                                           self.token_secret)
                 if self.token['userId'] != Everyone:
                     self.userId = 'local:Agent/' + str(
                         self.token['userId'])
                 else:
                     self.userId = Everyone
                 log.info('userId: %s', self.userId)
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             async with self.http_client.get(
                     '%s/api/v1/discussion/%s/permissions/Conversation.R/u/%s'
                     %
                 (self.server_url, self.discussion, self.token['userId']),
                     headers={"Accept": "application/json"}) as resp:
                 text = await resp.text()
             log.debug(text)
             if text != 'true':
                 return
             log.info("connected")
             if self.userId == Everyone:
                 self.roles = {Everyone}
             else:
                 async with self.http_client.get(
                         '%s/api/v1/discussion/%s/roles/allfor/%s' %
                     (self.server_url, self.discussion,
                      self.token['userId']),
                         headers={"Accept": "application/json"}) as resp:
                     text = await resp.text()
                     self.roles = set(json.loads(text))
                     self.roles.add(Everyone)
                     self.roles.add(Authenticated)
                     self.roles.add('local:Agent/' +
                                    str(self.token['userId']))
             self.task = self.loop.create_task(self.connect())
             self.session.send('[{"@type":"Connection"}]')
             if self.token and self.raw_token and self.discussion and self.userId != Everyone:
                 async with self.http_client.post(
                         '%s/data/Discussion/%s/all_users/%d/connecting' %
                     (self.server_url, self.discussion,
                      self.token['userId']),
                         data={'token': self.raw_token}) as resp:
                     await resp.text()
     except Exception:
         capture_exception()
         await self.close()
コード例 #8
0
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = authenticated_userid(request)
    discussion = request.context

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
        user_id = user_id or Everyone

    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)
    permissions = get_permissions(user_id, discussion.id, extract)

    if P_EDIT_EXTRACT not in permissions:
        raise HTTPForbidden()

    updated_extract_data = json.loads(request.body)

    extract.owner_id = user_id or AgentProfile.get_database_id(
        extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    extract.important = updated_extract_data.get('important',
                                                 extract.important)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if (idea.discussion != extract.discussion):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion."
                % extract.get_discussion_id())
        if not idea.has_permission_req(P_ASSOCIATE_EXTRACT):
            raise HTTPForbidden("Cannot associate extact with this idea")
        extract.idea = idea
    else:
        extract.idea = None

    Extract.default_db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
コード例 #9
0
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = request.authenticated_userid
    discussion_id = int(request.matchdict['discussion_id'])

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    user_id = user_id or Everyone

    updated_extract_data = json.loads(request.body)
    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)

    if not (user_has_permission(discussion_id, user_id, P_EDIT_EXTRACT) or
            (user_has_permission(discussion_id, user_id, P_EDIT_MY_EXTRACT)
             and user_id == extract.owner_id)):
        return HTTPForbidden()

    extract.owner_id = user_id or get_database_id("User", extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    extract.important = updated_extract_data.get('important',
                                                 extract.important)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if (idea.discussion != extract.discussion):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion."
                % extract.get_discussion_id())
        extract.idea = idea
    else:
        extract.idea = None

    Extract.default_db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
コード例 #10
0
ファイル: extract.py プロジェクト: iilab/assembl
def put_extract(request):
    """
    Updating an Extract
    """
    extract_id = request.matchdict['id']
    user_id = authenticated_userid(request)
    discussion_id = int(request.matchdict['discussion_id'])

    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(
                token, request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    if not user_id:
        user_id = Everyone

    updated_extract_data = json.loads(request.body)
    extract = Extract.get_instance(extract_id)
    if not extract:
        raise HTTPNotFound("Extract with id '%s' not found." % extract_id)

    if not (user_has_permission(discussion_id, user_id, P_EDIT_EXTRACT)
        or (user_has_permission(discussion_id, user_id, P_EDIT_MY_EXTRACT)
            and user_id == extract.owner_id)):
        return HTTPForbidden()

    extract.owner_id = user_id or get_database_id("User", extract.owner_id)
    extract.order = updated_extract_data.get('order', extract.order)
    extract.important = updated_extract_data.get('important', extract.important)
    idea_id = updated_extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if(idea.discussion != extract.discussion):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion." % extract.get_discussion_id())
        extract.idea = idea
    else:
        extract.idea = None

    Extract.db.add(extract)
    #TODO: Merge ranges. Sigh.

    return {'ok': True}
コード例 #11
0
 def on_message(self, msg):
     try:
         if getattr(self, 'socket', None):
             log.info("closing old socket")
             self.loop.add_callback(self.do_close)
             return
         if msg.startswith('discussion:') and self.valid:
             self.discussion = msg.split(':', 1)[1]
         if msg.startswith('token:') and self.valid:
             try:
                 self.raw_token = msg.split(':', 1)[1]
                 self.token = decode_token(self.raw_token, TOKEN_SECRET)
                 if self.token['userId'] != Everyone:
                     self.userId = 'local:Agent/' + str(
                         self.token['userId'])
                 else:
                     self.userId = Everyone
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             r = requests.get(
                 '%s/api/v1/discussion/%s/permissions/read/u/%s' %
                 (SERVER_URL, self.discussion, self.token['userId']),
                 headers={"Accept": "application/json"})
             log.debug(r.text)
             if r.text != 'true':
                 return
             self.socket = context.socket(zmq.SUB)
             self.socket.connect(INTERNAL_SOCKET)
             self.socket.setsockopt(zmq.SUBSCRIBE, b'*')
             self.socket.setsockopt(
                 zmq.SUBSCRIBE, native_str_to_bytes(str(self.discussion)))
             self.loop = zmqstream.ZMQStream(self.socket, io_loop=io_loop)
             self.loop.on_recv(self.on_recv)
             log.info("connected")
             self.send('[{"@type":"Connection"}]')
             if self.token and self.raw_token and self.discussion and self.userId != Everyone:
                 requests.post(
                     '%s/data/Discussion/%s/all_users/%d/connecting' %
                     (SERVER_URL, self.discussion, self.token['userId']),
                     data={'token': self.raw_token})
     except Exception:
         capture_exception()
         self.do_close()
コード例 #12
0
ファイル: auth.py プロジェクト: cimadure/idealoom
def set_user_dis_connected(request, connecting):
    ctx = request.context
    discussion_id = ctx.get_discussion_id()
    if not discussion_id:
        # This view should only exist in discussion+user context
        raise HTTPNotFound()
    token = request.POST.get('token')
    # see if token corresponds to user
    user = ctx.get_instance_of_class(User)
    try:
        token = decode_token(token, TOKEN_SECRET)
        assert token['userId'] == user.id
    except TokenInvalid:
        raise HTTPUnauthorized()

    status = user.get_status_in_discussion(discussion_id)
    assert status
    if connecting:
        status.last_connected = datetime.now()
    else:
        status.last_disconnected = datetime.now()
    return HTTPOk()
コード例 #13
0
ファイル: changes_router.py プロジェクト: hypnotics/assembl
 def on_message(self, msg):
     try:
         if getattr(self, 'socket', None):
             print "closing old socket"
             self.loop.stop_on_recv()
             self.loop.close()
             self.socket = None
             self.loop = None
         if msg.startswith('discussion:') and self.valid:
             self.discussion = msg.split(':', 1)[1]
         if msg.startswith('token:') and self.valid:
             try:
                 self.token = decode_token(
                     msg.split(':', 1)[1], TOKEN_SECRET)
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             r = requests.get(
                 'http://%s:%d/api/v1/discussion/%s/permissions/read/u/%s' %
                 (SERVER_HOST, SERVER_PORT, self.discussion,
                     self.token['userId']))
             print r.text
             if r.text != 'true':
                 return
             self.socket = context.socket(zmq.SUB)
             self.socket.connect(INTERNAL_SOCKET)
             self.socket.setsockopt(zmq.SUBSCRIBE, '*')
             self.socket.setsockopt(zmq.SUBSCRIBE, str(self.discussion))
             self.loop = zmqstream.ZMQStream(self.socket, io_loop=io_loop)
             self.loop.on_recv(self.on_recv)
             print "connected"
             self.send('[{"@type":"Connection"}]')
     except Exception:
         if raven_client:
             raven_client.captureException()
         raise
コード例 #14
0
ファイル: changes_router.py プロジェクト: assembl/assembl
 def on_message(self, msg):
     try:
         if getattr(self, 'socket', None):
             print "closing old socket"
             self.loop.add_callback(self.do_close)
             return
         if msg.startswith('discussion:') and self.valid:
             self.discussion = msg.split(':', 1)[1]
         if msg.startswith('token:') and self.valid:
             try:
                 self.token = decode_token(
                     msg.split(':', 1)[1], TOKEN_SECRET)
                 self.userId = 'local:AgentProfile/' + str(
                     self.token['userId'])
             except TokenInvalid:
                 pass
         if self.token and self.discussion:
             # Check if token authorizes discussion
             r = requests.get(
                 '%s/api/v1/discussion/%s/permissions/read/u/%s' %
                 (SERVER_URL, self.discussion,
                     self.token['userId']))
             print r.text
             if r.text != 'true':
                 return
             self.socket = context.socket(zmq.SUB)
             self.socket.connect(INTERNAL_SOCKET)
             self.socket.setsockopt(zmq.SUBSCRIBE, '*')
             self.socket.setsockopt(zmq.SUBSCRIBE, str(self.discussion))
             self.loop = zmqstream.ZMQStream(self.socket, io_loop=io_loop)
             self.loop.on_recv(self.on_recv)
             print "connected"
             self.send('[{"@type":"Connection"}]')
     except Exception:
         capture_exception()
         self.do_close()
コード例 #15
0
def post_extract(request):
    """
    Create a new extract.
    """
    extract_data = json.loads(request.body)
    discussion = request.context
    db = discussion.db
    user_id = authenticated_userid(request)
    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(token,
                                 request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
        user_id = user_id or Everyone
        permissions = get_permissions(user_id, discussion_id)
    else:
        permissions = request.permissions
    if P_ADD_EXTRACT not in permissions:
        #TODO: maparent:  restore this code once it works:
        #raise HTTPForbidden(result=ACLDenied(permission=P_ADD_EXTRACT))
        raise HTTPForbidden()
    if not user_id or user_id == Everyone:
        # TODO: Create an anonymous user.
        raise HTTPServerError("Anonymous extracts are not implemeted yet.")
    content = None
    uri = extract_data.get('uri')
    important = extract_data.get('important', False)
    annotation_text = extract_data.get('text')
    target = extract_data.get('target')
    if not uri:
        # Extract from an internal post
        if not target:
            raise HTTPBadRequest("No target")

        target_class = sqla.get_named_class(target.get('@type'))
        if issubclass(target_class, Post):
            post_id = target.get('@id')
            post = Post.get_instance(post_id)
            if not post:
                raise HTTPNotFound("Post with id '%s' not found." % post_id)
            content = post
        elif issubclass(target_class, Webpage):
            uri = target.get('url')
    if uri and not content:
        content = Webpage.get_instance(uri)
        if not content:
            # TODO: maparent:  This is actually a singleton pattern, should be
            # handled by the AnnotatorSource now that it exists...
            source = db.query(AnnotatorSource).filter_by(
                discussion=discussion).filter(
                    cast(AnnotatorSource.name, Unicode) ==
                    'Annotator').first()
            if not source:
                source = AnnotatorSource(name='Annotator',
                                         discussion=discussion)
                db.add(source)
            content = Webpage(url=uri, discussion=discussion)
            db.add(content)
    extract_body = extract_data.get('quote', None)

    idea_id = extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if (idea.discussion.id != discussion.id):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion."
                % extract.get_discussion_id())
        if not idea.has_permission_req(P_ASSOCIATE_EXTRACT):
            raise HTTPForbidden("Cannot associate extact with this idea")
    else:
        idea = None

    new_extract = Extract(creator_id=user_id,
                          owner_id=user_id,
                          discussion=discussion,
                          idea=idea,
                          important=important,
                          annotation_text=annotation_text,
                          content=content)
    db.add(new_extract)

    for range_data in extract_data.get('ranges', []):
        range = TextFragmentIdentifier(extract=new_extract,
                                       body=extract_body,
                                       xpath_start=range_data['start'],
                                       offset_start=range_data['startOffset'],
                                       xpath_end=range_data['end'],
                                       offset_end=range_data['endOffset'])
        db.add(range)
    db.flush()

    return {'ok': True, '@id': new_extract.uri()}
コード例 #16
0
ファイル: extract.py プロジェクト: Lornz-/assembl
def post_extract(request):
    """
    Create a new extract.
    """
    extract_data = json.loads(request.body)
    discussion_id = int(request.matchdict['discussion_id'])
    user_id = authenticated_userid(request)
    if not user_id:
        # Straight from annotator
        token = request.headers.get('X-Annotator-Auth-Token')
        if token:
            token = decode_token(
                token, request.registry.settings['session.secret'])
            if token:
                user_id = token['userId']
    user_id = user_id or Everyone
    if not user_has_permission(discussion_id, user_id, P_ADD_EXTRACT):
        #TODO: maparent:  restore this code once it works:
        #return HTTPForbidden(result=ACLDenied(permission=P_ADD_EXTRACT))
        return HTTPForbidden()
    if not user_id or user_id == Everyone:
        # TODO: Create an anonymous user.
        raise HTTPServerError("Anonymous extracts are not implemeted yet.")
    content = None
    uri = extract_data.get('uri')
    important = extract_data.get('important', False)
    annotation_text = None
    if uri:
        # Straight from annotator
        annotation_text = extract_data.get('text')
    else:
        target = extract_data.get('target')
        if not (target or uri):
            raise HTTPBadRequest("No target")

        target_class = sqla.get_named_class(target.get('@type'))
        if issubclass(target_class, Post):
            post_id = target.get('@id')
            post = Post.get_instance(post_id)
            if not post:
                raise HTTPNotFound(
                    "Post with id '%s' not found." % post_id)
            content = post
        elif issubclass(target_class, Webpage):
            uri = target.get('url')
    if uri and not content:
        content = Webpage.get_instance(uri)
        if not content:
            # TODO: maparent:  This is actually a singleton pattern, should be
            # handled by the AnnotatorSource now that it exists...
            source = AnnotatorSource.default_db.query(AnnotatorSource).filter_by(
                discussion_id=discussion_id).filter(
                cast(AnnotatorSource.name, Unicode) == 'Annotator').first()
            if not source:
                source = AnnotatorSource(
                    name='Annotator', discussion_id=discussion_id,
                    type='source')
            content = Webpage(url=uri, discussion_id=discussion_id)
    extract_body = extract_data.get('quote', '')

    idea_id = extract_data.get('idIdea', None)
    if idea_id:
        idea = Idea.get_instance(idea_id)
        if(idea.discussion.id != discussion_id):
            raise HTTPBadRequest(
                "Extract from discussion %s cannot be associated with an idea from a different discussion." % extract.get_discussion_id())
    else:
        idea = None


    new_extract = Extract(
        creator_id=user_id,
        owner_id=user_id,
        discussion_id=discussion_id,
        body=extract_body,
        idea=idea,
        important=important,
        annotation_text=annotation_text,
        content=content
    )
    Extract.default_db.add(new_extract)

    for range_data in extract_data.get('ranges', []):
        range = TextFragmentIdentifier(
            extract=new_extract,
            xpath_start=range_data['start'],
            offset_start=range_data['startOffset'],
            xpath_end=range_data['end'],
            offset_end=range_data['endOffset'])
        TextFragmentIdentifier.default_db.add(range)
    Extract.default_db.flush()

    return {'ok': True, '@id': new_extract.uri()}