Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
93343d2
Refactor LDP API middleware
dmitrizagidulin Jun 12, 2018
165b413
Add LdpRequest.from() factory method
dmitrizagidulin Jun 12, 2018
a4362fe
Expand COPY docstring comments
dmitrizagidulin Jun 12, 2018
0c5dc4b
Add LdpRequest constructor() with docstring
dmitrizagidulin Jun 12, 2018
8bdf4e3
Rename LdpRequest to LdpOperation, use mapping instead of switch() in…
dmitrizagidulin Jun 12, 2018
a541d3a
Make explicit components passed to Operation.from()
dmitrizagidulin Jun 12, 2018
8997b2f
No need to pass in method to Operation.from()
dmitrizagidulin Jun 13, 2018
90ae8c0
Rename LdpApi to LdpHttpHandler
dmitrizagidulin Jun 13, 2018
77fd5dd
Assume a first-class Authentication object in request session
dmitrizagidulin Jun 13, 2018
5b0a3f2
Add the rest of the ldp operations to the map
dmitrizagidulin Jun 13, 2018
4bd804b
Add parseTargetUrl() to LdpOperation
dmitrizagidulin Jun 13, 2018
bb68760
Add an illustrative LdpGetOperation.from()
dmitrizagidulin Jun 13, 2018
239ac2b
Replace Operation.from() factory method with ldp instance factory
dmitrizagidulin Jun 13, 2018
513d861
Flesh out LdpGetOperation.from()
dmitrizagidulin Jun 13, 2018
7c96212
Add content-type and -length to common headers
dmitrizagidulin Jun 14, 2018
eeee098
Add dispatch logic for LdpRangeOperation
dmitrizagidulin Jun 14, 2018
842773c
Add LdpDeleteOperation.perform() logic
dmitrizagidulin Jun 14, 2018
fe9b82f
Update fs-extra to latest
dmitrizagidulin Jun 15, 2018
2595a15
Implement LdpTarget, refactor LdpHttpHandler accordingly
dmitrizagidulin Jun 15, 2018
906e289
Implement LdpFileStore and LdpFileResource
dmitrizagidulin Jun 15, 2018
00688f7
Implement deleteResource() & deleteContainer()
dmitrizagidulin Jun 15, 2018
127dde1
Pass in fs into LdpFileStore
dmitrizagidulin Jun 15, 2018
976dc87
Implement LdpPutOperation logic (and store logic)
dmitrizagidulin Jun 15, 2018
5d7d453
Add root and multiuser params to host config object
dmitrizagidulin Aug 5, 2018
2ec6ed2
Regenerate package-lock.json
dmitrizagidulin Oct 5, 2018
0f7bfc8
Refactor/expand SolidHost.parseTargetUrl()
dmitrizagidulin Oct 5, 2018
ed40c42
Refactor LDP Target, FileResource and Store
dmitrizagidulin Oct 5, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions lib/api/ldp/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use strict'

const { LdpOperation } = require('./ldp-operation')
const LdpTarget = require('./ldp-target')
const HttpError = require('standard-http-error')
const Negotiator = require('negotiator')

class LdpHttpHandler {
/**
* Usage:
* ```
* const mapperOptions = {
* rootUrl: host.serverUri,
* rootPath: host.root,
* includeHost: host.multiuser
* }
* const mapper = new LegacyResourceMapper(mapperOptions)
*
* const storeOptions = {
* host, mapper, suffixMeta, suffixAcl, dataBrowserPath, suppressDataBrowser
* }
* const store = new LdpFileStore(storeOptions)
*
* const acl = new AclChecker({host, fetch, store, ...})
*
* const ldp = new LdpHttpHandler({store, acl, host, fetch})
* ```
*
* @param options.store {LdpFileStore|LdpMemoryStore|LdpQuadStore} storage backend
* (either file system based, or in-memory, or SPARQL based quad store, etc)
*
* @param options.acl {AclChecker} ACL checker component
*
* @param options.host {SolidHost} Server config object
*/
constructor (options) {
this.store = options.store
this.acl = options.acl
this.host = options.host
}

/**
* Provides an Express handler for LDP API requests.
* Authentication has already happened earlier in the middleware stack, stored
* in session.
* Usage:
* ```
* app.use('/', ldp.handleRequest)
* ```
*
* @see https://github.com/solid/solid-architecture/blob/master/server/request-flow.md
*
* @throws {Error} 401 If request not authenticated but resource is non-public
*
* @throws {Error} 404 If resource is not found, OR if found and request
* is not authorized to access it (default behavior, can be overridden by
* owner of resource)
*
* @throws {Error} 403 If request is not authorized to access resource and
* user has enabled "Request permission" action
*
* @throws {Error} 400 If invalid parameters (or error parsing request body,
* for cases like PATCH requests)
*
* @throws {Error} 409 If PATCH operation results in conflict
*
* @throws {Error} 406 If no appropriate representation found (for content type)
*
* @throws {Error} 405 If HTTP method not allowed / not implemented
*/
async handleRequest (req, res, next) {
try {
const operation = await this.operationFrom(req)

// check that operation is permitted (throws error if not)
const permissions = await this.acl.allow(operation)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should take target and operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why so? The target is already contained in the operation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in the architecture's doc definition of “operation”, see #706 (comment)


// perform the operation and return a result
const result = await operation.perform({store: this.store})

// write both generic and op-specific headers
// note: you need `permissions` for the WAC-Allow header
operation.writeHeaders({res, result, permissions})

operation.writeResponse({res, result})
} catch (error) {
next(error)
}
}

/**
* LdpOperation factory method
*
* @param req {IncomingRequest}
*
* @returns {Promise<LdpOperation>}
*/
async operationFrom (req) {
const options = this.parseOperation(req)

const Operation = LdpOperation.BY_METHOD[options.method]
Comment thread
dmitrizagidulin marked this conversation as resolved.
if (!Operation) {
throw new HttpError(405, 'Method not supported')
}

return Operation.from(options)
}

/**
* @param req {IncomingRequest}
*
* @returns {object} Operation constructor params object
*/
parseOperation (req) {
const { method, headers, session: { authentication } } = req

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The knowledge about where and how the authentication is stored should probably live in another component.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, sure thing. I was only including it here cause it was mentioned in the architecture flow doc.

const target = this.targetFrom(req)

const bodyStream = req // TODO: create a lazy parser here
const store = this.store

// Note: store is needed for LdpGetOperation factory
return {method, target, headers, bodyStream, authentication, store}
}

targetFrom (req) {
return new LdpTarget(this.parseTarget(req))
}

parseTarget (req) {
const targetUrl = this.host.parseTargetUrl(req)
const conneg = new Negotiator(req)

return { name: req.path, url: targetUrl, conneg }
}
}

module.exports = LdpHttpHandler
Loading