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()
def builds(request, id=None): api = API(request) if id is None: if api.get: builds = Build.list() return api.respond( builds, template='builds/builds.html' ) elif api.post: build = Build.create( user=request.user, package=api.required('package'), flavour=api.required('flavour'), version=api.required('version'), commit=api.required('commit'), platform=api.optional('platform'), url=api.optional('url') ) return api.respond(build) else: if api.get: build = Build.objects.get( id=id ) return api.respond(build) raise API.MethodNotAllowedError( method=request.method )
def key_list(request): ''' For: GET /keys POST /keys ''' api = API(request) api.authenticated_or_raise() if api.get: keys = Key.list_user( user=request.user ) return api.respond( data=keys, paginate=True ) elif api.post: key = Key.create_one( user=request.user, address=api.required('address'), type=api.required('type'), action=api.required('action'), users=api.required('users') ) return api.respond( data=key ) else: return api.raise_method_not_allowed()
def snapshot(request, address=None): api = API(request) api.authenticated_or_raise() if address: if api.post: snapshot = Snapshot.create( address=address, user=request.user, file=request.FILES['file'] ) return api.respond(snapshot) elif api.get: snapshot_url = Snapshot.get( address=address, user=request.user ) if snapshot_url: return redirect(snapshot_url) else: raise Http404 else: if api.get: snapshots = Snapshot.list( user=request.user ) return api.respond(snapshots)
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()
def key_one(request, id): ''' For: GET /keys/{id} PATCH /keys/{id} DELETE /keys/{id} ''' api = API(request) api.authenticated_or_raise() if api.get: key = Key.read_one( id=id, user=request.user ) return api.respond( data=key ) elif api.patch: key = Key.update_one( id=id, user=request.user, data=api.data ) return api.respond( data=key ) elif api.delete: key = Key.delete_one( id=id, user=request.user ) return api.respond() else: api.raise_method_not_allowed()
def sessions(request, id=None): api = API(request) if id is None: if api.get: # Get a list of sessions sessions = Session.list(user=request.user, filter={"active": True}) return api.respond( sessions, template="sessions/list.html", context={ "sessions": sessions, "types": SessionType.objects.order_by("rank").all(), "images": SessionImage.objects.all(), }, ) elif api.post: # Launch a session for the user session = Session.launch(user=request.user, type_id=api.required("type"), image_name=api.require("image")) return api.respond(session) else: if api.get: # Get a session, updating it first session = Session.get(id=id, user=request.user) if session.active: session.update() return api.respond(session) return api.respond(status=405)
def address_one(request, id): ''' For: GET /addresses/{id} PATCH /addresses/{id} DELETE /addresses/{id} ''' api = API(request) api.authenticated_or_raise() if api.get: address = Address.read_one( id=id, user=request.user ) return api.respond( data=address ) elif api.patch: address = Address.update_one( id=id, user=request.user, data=api.data ) return api.respond( data=address ) elif api.delete: address = Address.delete_one( id=id, user=request.user ) return api.respond() else: api.raise_method_not_allowed()
def address_list(request): ''' For: GET /addresses POST /addresses ''' api = API(request) api.authenticated_or_raise() if api.get: addresses = Address.list( user=request.user ) return api.respond( data=addresses, paginate=100 ) elif api.post: address = Address.create_one( user=request.user, address=api.required('address'), type=api.required('type'), action=api.required('action'), users=api.required('users') ) return api.respond( data=address ) else: return api.raise_method_not_allowed()
def events(request): api = API(request) if api.post: UserEvent.create( user=request.user, name=api.required('name'), data=api.required('data') ) return api.respond() else: return api.respond(status=405)
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)
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()
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()
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()
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()
def test_user_agent(request): ''' For testing what django-user-agents and our internal request handler is returning for different, ummm, user agents ''' rh = API(request) ua = request.user_agent return rh.respond(OrderedDict([ ('rh_accept', rh.accept), ('rh_browser', rh.browser), ('ua_is_mobile', ua.is_mobile), ('ua_is_tablet', ua.is_tablet), ('ua_is_touch_capable', ua.is_touch_capable), ('ua_is_pc', ua.is_pc), ('ua_is_bot', ua.is_bot), ('ua_browser', ua.browser), ('ua_browser_family', ua.browser.family), ('ua_browser_version', ua.browser.version), ('ua_browser_version_string', ua.browser.version_string), ('ua_os', ua.os), ('ua_os_family', ua.os.family), ('ua_os_version', ua.os.version), ('ua_os_version_string', ua.os.version_string), ('ua_device', ua.device), ('ua_device_family', ua.device.family), ]))
def ping(request, id): """ Ping a session to keep it open Return `ready` value. Sessions that have not been pinged for sometime will be automatically stopped. """ api = API(request) if api.put: session = Session.get(id=id, user=request.user) if session.active: session.ping() return api.respond({"ready": session.ready}) else: return api.respond({"error": "session:inactive", "message": "Session is no longer active"}) else: raise API.MethodNotAllowedError(method=request.method)
def users_read(request, username): ''' Read a user ''' api = API(request) try: return api.respond(User.objects.get(username=username)) except User.DoesNotExist: raise Http404
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')
def stop(request, id): """ Stop a session """ api = API(request) if api.put: session = Session.get(id=id, user=request.user) session.stop(request.user) return api.respond(session) else: raise API.MethodNotAllowedError(method=request.method)
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()
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())
def tokens(request, id=None): ''' List, create, read, update, delete user tokens ''' api = API(request) if id is None: if api.get: # List all tokens for the user tokens = UserToken.objects.filter( user=request.user ) return api.respond(tokens) elif api.post: # Create a token for the user token = UserToken.objects.create( user=request.user, name=api.optional('name'), notes=api.optional('notes') ) return api.respond(token) else: # Get token, ensuring it is associated with the # request user token = UserToken.get_or_raise( id=id, user=request.user ) if api.get: # Read it return api.respond(token) elif api.patch: # Update it token.name = api.optional('name') token.notes = api.optional('notes') token.save() return api.respond(token) elif api.delete: # Delete it token.delete() return api.respond()
def wrap(request, *args, **kwargs): if request.user.details.tester: return function(request, *args, **kwargs) else: api = API(request) return api.respond( data={ 'error': 'user:not-tester', 'message': 'This page or API endpoint is only available to testers. See https://stenci.la/users/testers', }, template='users/testers.html', status=403 )
def snippets(request, id=None): api = API(request) if id is None: if api.get: snippets = Snippet.list() return api.respond( snippets, detail=0, template='snippets/list.html', context={ 'languages':['js','jl','py','r'] } ) else: if api.get: snippet = Snippet.get( id=id ) return api.respond( snippet, template='snippets/get.html' ) elif api.put: snippet = Snippet.put( user=request.user, id=id, spec=api.data ) return api.respond( snippet, detail=0 ) raise API.MethodNotAllowedError( method=request.method )
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()
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' ))
def new(request): """ List session types, create a new session, launch it and redirect the user to it's page """ api = API(request) if api.get: return api.respond( template="sessions/new.html", context={ "sessions": Session.list(user=request.user, filter={"active": True}).count(), "types": SessionType.objects.order_by("rank").all(), "images": SessionImage.objects.all(), }, ) elif api.post: session = Session.launch(user=request.user, type_id=api.required("type"), image_name=api.required("image")) return api.respond_created(url=session.url()) return api.respond_bad()
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()
def received(request, address): ''' Endpoint for the `curator` to ping when a component has received an update e.g. from `git push`. A token is used to protect access. But this endpoint only accepts the address of the component and then fetches an update from the `curator`, so a malicious user can not read or write data anyway. ''' api = API(request) # Check access token token = api.required('token') if token != settings.COMMS_TOKEN: return JsonResponse( {'error': 'invalid token'}, status=403 ) # Update the component component = Component.objects.get( address=address ) component.update() return api.respond()