Exemple #1
0
def components(request, id=None, type=None):
    '''
    Standard API endpoint for components
    '''
    api = API(request)
    if api.get:
        filters = {}
        if type:
            type = type[:-1] # Remove the trailing 's' that is in the URL
            filters['type'] = type
        components = Component.list(
            user=request.user,
            address=api.optional('address'),
            published=api.optional('published', True, lambda value: bool(int(value))),
            filters=filters,
            sort=api.optional('sort', 'id')
        )
        return api.respond(
            data=components,
            paginate=25
        )
    elif api.post:
        api.authenticated_or_raise()
        component = Component.create_one(
            user=request.user,
            address=api.required('address'),
            type=api.required('type'),
        )
        return api.respond(
            data=component
        )
    else:
        api.raise_method_not_allowed()
Exemple #2
0
def component_one(request, id):
    # TODO integrate into the above
    api = API(request)
    if api.get:
        component = Component.read_one(
            id=id,
            user=request.user,
        )
        return api.respond(
            data=component
        )
    elif api.patch:
        # Currently, PATCH is not valid for components
        api.raise_method_not_allowed()
        # api.authenticated_or_raise()
        # component = Component.update_one(
        #    id=id,
        #    user=request.user,
        #    data=api.data
        # )
        # return api.respond(
        #    data=component
        # )
    elif api.delete:
        api.authenticated_or_raise()
        Component.delete_one(
            id=id,
            user=request.user
        )
        return api.respond()
    else:
        api.raise_method_not_allowed()
Exemple #3
0
def content(request, address):
    '''
    Get or set the content of a stencil
    '''
    api = API(request)
    if api.get:
        component = Component.get(
            id=None,
            user=request.user,
            action=READ,
            address=address
        )
        result = component.content_get(
            format=api.optional('format', 'html'),
        )
    elif api.put:
        component = Component.get(
            id=None,
            user=request.user,
            action=UPDATE,
            address=address
        )
        result = component.content_set(
            user=request.user,
            format=api.required('format'),
            content=api.required('content'),
            revision=api.required('revision')
        )
    return api.respond(result)
Exemple #4
0
    def test_tiny(self):
        id = 1000
        alphanum = "QI"
        self.assertEqual(Component.tiny_convert(id), alphanum)
        self.assertEqual(Component.tiny_convert(alphanum), id)

        com = Component(id=id)
        self.assertEqual(com.tiny_url, "https://stenci.la/%s~" % alphanum)
Exemple #5
0
def route(request, address=''):
    '''
    Route a request for component/s at an address

    If the address matches that of a single component then
    open in. Otherwise show a list of all components which have
    the address as a prefix
    '''
    # Remove any trailing slash for address matches
    if address.endswith('/'):
        address = address[:-1]
    # Check to see if there is a component matching this address
    # which the user is authorized to read
    component = Component.get(
        id=None,
        user=request.user,
        action=READ,
        address=address
    )
    if component:
        api = API(request)
        if api.accept == 'json':
            return api.respond(component)
        else:
            # Yes, return the components page or
            # redirect to the component's canonical URL if necessary
            if request.path == component.url():
                return page(request, address, component)
            else:
                return redirect(component.url(), permanent=True)
    else:
        # Is there an `all` stencil at the address?
        all = address+'/all'
        component = Component.get(request.user, READ, address=all)
        if component:
            # Yes, return that all stencil's page; ensure trailing slash
            if request.path.endswith('/'):
                return page(request, all, component)
            else:
                return redirect(request.path+'/')
        else:
            # Is there an `any` stencil at the parent address?
            parts = address.split('/')
            any = '/'.join(parts[:-1]) + '/any'
            component = Component.get(request.user, READ, address=any)
            if component:
                # Yes, return that any stencil's page; ensure trailing slash
                if request.path.endswith('/'):
                    return page(request, any, component)
                else:
                    return redirect(request.path+'/')
            else:
                # No, respond with stencil that provides a listing of
                # components at the address
                return page(request, '/core/stencils/index')
Exemple #6
0
def tiny(request, tiny):
    '''
    Redirect a tiny URL to the component's canonical URL
    '''
    id = Component.tiny_convert(tiny)
    component = Component.get(
        id=id,
        user=request.user,
        action=READ,
    )
    return redirect(component.url(), permanent=True)
Exemple #7
0
def ping(request, address):
    '''
    Ping the component's session.

    Returns the session id since that could change
    '''
    api = API(request)
    if api.put:
        if not request.user.is_authenticated():
            return api.respond_signin()
        else:
            component = Component.get(
                id=None,
                user=request.user,
                action=READ,
                address=address
            )
            session = component.activate(
                user=request.user
            )
            session.ping()
            return api.respond({
                'session': session.id
            })
    return api.respond_bad()
Exemple #8
0
def boot(request, address):
    '''
    Boot up a component
    '''
    api = API(request)
    if api.put:
        component = Component.get(
            id=None,
            user=request.user,
            action=READ,
            address=address
        )

        action, grantor = component.rights(request.user)
        if action >= EXECUTE:
            api.user_ensure()
            session = component.activate(
                user=request.user
            )
        else:
            session = None

        return api.respond({
            'rights': {
                'action': action_string(action),
                'grantor': grantor.serialize(user=request.user) if grantor else None
            },
            'session': session.serialize(user=request.user) if session else None
        })
    return api.respond_bad()
Exemple #9
0
def method(request, address, method):
    '''
    A general view for calling methods on components that are hosted
    in a session. Will launch a new session for a component if necessary.
    Not all component methods need an active session so views
    should be explicitly provided for those below.
    '''
    api = API(request)
    if api.put:
        if not request.user.is_authenticated():
            return api.respond_signin()
        else:
            component = Component.get(
                id=None,
                user=request.user,
                action=READ, # TODO : allow for alternative rights
                address=address
            )
            session = component.activate(
                user=request.user
            )
            status, body = session.request(
                resource=address,
                verb=request.method,
                method=method,
                data=request.body
            )
            return api.respond(
                data=body,
                raw=True,
                status=status
            )
    return api.respond_bad()
Exemple #10
0
def new(request, type):
    '''
    Create a new component for the user and redirect them
    to it's canonical page

    Guests should not be able to login. Can't use the `user_passes_test` decorators here
    to make use of `?next=/new/stencil` becuase for guest that
    results in a redirect loop (they need to be signed out first).
    This should be made better sometime.
    '''
    if request.user_agent.is_bot:
        return redirect('/', permanent=True)
    elif not request.user.is_authenticated():
        return redirect('/me/signup')
    elif request.user.details.guest:
        logout(request)
        return redirect('/me/signup')
    else:
        type = type[:-1] # Remove the trailing 's' that is in the URL
        component = Component.create(
            user=request.user,
            address=None,
            type=type
        )
        return redirect(component.url())
Exemple #11
0
def live_html(request, address):
    '''
    Get the HTML of a live copy of a document

    This should be generalised and/or merge wiith the `content` view (which also gets (and sets) HTML).
    '''
    api = API(request)
    if api.get:
        # First, check access rights
        component = Component.get(
            id=None,
            address=address,
            user=request.user,
            action=READ
        )
        # Get HTML from broker
        response = requests.get('http://10.0.1.75:7315/' + address + '@live?format=html')
        if response.status_code != 200:
            raise Exception(response.text)
        else:
            if api.browser:
                response = HttpResponse(response.json()['data'], content_type='text/html')
                response['Content-Disposition'] = 'attachment; filename="document.html"'
                return response
            else:
                return api.respond(data=response.json())

    return api.respond_bad()
Exemple #12
0
def live(request, address):
    '''
    Open the live collaboration clone for the component.
    '''
    api = API(request)
    if api.get:
        # First, check access rights
        component = Component.get(
            id=None,
            address=address,
            user=request.user,
            action=ANNOTATE
        )
        payload = {}
        # Then, see if a live collaboration clone already exists
        # Get the snapshot data
        # TODO: In the future there may be several collaboration servers
        response = requests.get('http://10.0.1.75:7315/' + address + '@live')
        if response.status_code == 404:
            response = requests.post('http://10.0.1.75:7315/', json = {
                'schemaName': 'stencila-document',
                'documentId': address + '@live',
                'format': 'html',
                'content': component.content_get('html')['content']
            })
            if response.status_code != 200:
                raise Exception(response.text)
        payload['snapshot'] = response.json()
        # Insert the collaboration websocket URL
        if settings.MODE == 'local':
            # It's not possible to do a Websocket redirect (without Nginx's 'X-Accel-Redirect') so
            # in development just give direct WS URL of collaboration server
            ws = 'ws://10.0.1.75:7315'
        else:
            ws = 'wss://stenci.la'
        ws += '/%s@collaborate?permit=%s' % (
            address,
            hmac.new(settings.SECRET_KEY, address + ':' + request.user.username).hexdigest()
        )
        payload['collabUrl'] = ws
        # Insert the user and access rights
        action, grantor = component.rights(request.user)
        payload['user'] = request.user.username
        payload['rights'] = action_string(action)
        # Render template with snapshot data embedded in it
        return api.respond(
            template = 'components/live.html',
            context = {
                'type': 'document',
                'payload': json.dumps(payload)
            }
        )

    return api.respond_bad()
Exemple #13
0
def explore(request):
    '''
    Explore components
    '''
    components = Component.list(
        user=request.user,
        published=True,
        sort='-id'
    )
    return render(request, 'explore.html', {
        'components': components
    })
Exemple #14
0
def commits(request, address):
    '''
    Get a list of commits for a component
    '''
    api = API(request)
    component = Component.get(
        id=None,
        user=request.user,
        action=READ,
        address=address
    )
    return api.respond(component.commits())
Exemple #15
0
def slugified(request, address, slug):
    '''
    View a component's page with a slugified URL. Checks the slug is correct.
    '''
    component = Component.get(
        id=None,
        user=request.user,
        action=READ,
        address=address
    )
    # If the slug is correct return the page
    if slug == component.slug():
        return page(request, address, component)
    # otherwise redirect to canonical URL with the correct slug
    else:
        return redirect(component.url(), permanent=True)
Exemple #16
0
def component_forks(request, id):
    '''
    Get a list of a component's forks
    For:
        GET /components/{id}/forks
    '''
    api = API(request)
    component = Component.one_if_authorized_or_raise(
        user=request.user,
        action=READ,
        id=id
    )
    if api.get:
        forks = component.forks(user)
        return api.respond(forks)
    else:
        api.raise_method_not_allowed()
Exemple #17
0
def commit(request, address):
    '''
    Commit the component

    This is a temporary stub implementation.
    It should robably be implemented as a "method"
    call above.
    '''
    api = API(request)
    component = Component.get(
        id=None,
        user=request.user,
        action=READ,
        address=address
    )
    return api.respond(dict(
        revision='xxxxxxxxx'
    ))
Exemple #18
0
def deactivate(request, address):
    '''
    Dectivate a component by stopping the currently active session for it
    '''
    api = API(request)
    if api.put:
        if not request.user.is_authenticated():
            return api.respond_signin()
        else:
            component = Component.get(
                id=None,
                user=request.user,
                action=READ,
                address=address
            )
            session = component.deactivate(
                user=request.user
            )
            return api.respond(session)
    return api.respond_bad()
Exemple #19
0
def component_fork(request, id):
    '''
    Fork a component
    For:
        POST /components/{id}/fork
    '''
    api = API(request)
    api.authenticated_or_raise()
    component = Component.get(
        id=id,
        user=request.user,
        action=READ,
    )
    if api.post:
        fork = component.fork(
            user=request.user,
            to=api.required('to')
        )
        return api.respond(fork)
    else:
        api.raise_method_not_allowed()
Exemple #20
0
def session(request, address):
    '''
    Get the session, if any, for a component
    '''
    api = API(request)
    if api.put:
        if not request.user.is_authenticated():
            return api.respond_signin()
        else:
            component = Component.get(
                id=None,
                user=request.user,
                action=READ,
                address=address
            )
            session = component.session(
                user=request.user,
                required=False
            )
            if session and session.active:
                session.update()
            return api.respond(session)
    return api.respond_bad()
Exemple #21
0
def component_star(request, id):
    '''
    Add/remove the component to/from the request user's starred list
    For:
        PUT /components/{id}/star
        DELETE /components/{id}/star
    '''
    api = API(request)
    api.authenticated_or_raise()
    component = Component.get(
        id=id,
        user=request.user,
        action=READ
    )
    if api.put:
        component.star(request.user)
    elif api.delete:
        component.unstar(request.user)
    else:
        api.raise_method_not_allowed()
    return api.respond({
        'stars': component.stars,
        'starred': component.starred(request.user)
    })
Exemple #22
0
def git(request, address):
    '''
    Serve a component repository.

    This view is used by Git clients to clone/pull/push to/from component respositories
      e.g. using the git shell program: git clone https://stenci.la/some/component
      e.g. using libgit2 integrated into stencila: stencil.pull()
    These commands results in requests to .git subdirectory of the component directory.

    Authentication will normally be done by UserToken via BasicAuth which uses the "Authorization"
    HTTP header. You can specify token (and no password) like this:

        git clone http://01sVr0jlc03M4qIBC1MQazbOlZvtcZZT5DkQ3Ir8uhMaEcoZhMo8dhHwvrFlKuVjRO:none@localhost:8010/235.git

    Note that Git does not supply the "Authorization" header until challenged to authenticate
    with a 401 header. So, a 401 response at first will be a normal part of the communications with Git
    for requests requiring UPDATE rights. If auth is not provided in the URL as above then
    the Git client will prompt for username/password.

    This method has to be csrf_exempt otherwise Git can not POST to it because it
    does not have a Django CRSF token.

    This view authorization and then gets `git-http-backend.go` to handle
    the request via an Nginx X-Accel-Redirect. `git-http-backend.go` is a Go implementation of
    the (C CGI backend)[http://git-scm.com/docs/git-http-backend].
    It is (should be?) faster and (for me) required less configuration to get working
    than the C CGI backend.
    '''
    # Authorize action
    service = request.GET.get('service')
    if request.method == 'GET':
        # A push involves a GET of git-receive-pack first.
        # So require UPDATE for this request
        if service == 'git-receive-pack':
            action = UPDATE
        else:
            action = READ
    else:
        # A pull, clone etc involves a POST to /foo/.git/git-upload-pack
        # So only require READ for this request
        if 'git-upload-pack' in request.path:
            action = READ
        else:
            action = UPDATE
    # If action requires UPDATE then the user must be authenticated first
    if action == UPDATE and not request.user.is_authenticated():
        return unauthenticated_response()
    # Get component thereby checking access rights
    Component.get(
        id=None,
        user=request.user,
        action=action,
        address=address
    )
    # Respond
    if settings.MODE == 'local':
        # Redirect to `curator.go` operating on this same machine
        # It is not possible to redirect to `http://10.0.1.50:7311` (i.e. using
        # the standard IP for the curator) because libgit2 complains with "Cross host redirect not allowed"
        # So instead we use `10.0.1.25`, the local IP of this, the director, but the port number
        # of the curator.
        url = 'http://10.0.1.25:7311' + request.get_full_path().replace('.git', '/.git')
        return HttpResponseRedirect(url)
    else:
        # Ask Nginx to do a proxy pass.
        # Replace '<address>.git' with '<address>/.git'
        url = '/internal-component-git' + request.get_full_path().replace('.git', '/.git')
        # For debugging purposes show the redirect header in the response content
        response = django.http.HttpResponse('Internal redirect to : %s' % url)
        response['X-Accel-Redirect'] = url
        return response