Documentation
¶
Overview ¶
Package storage is the file contract: what a tenant uploaded, and where it went.
The contract lives in the core and the drivers do not, for the same reason as data.Repository and jobs.Queue: every operation takes a security.Grant, and the path is prefixed by the tenant that Grant carries. A file is customer data, and a path without a tenant is a leak with a directory name.
There is no symlink into a document root. Publishing a storage directory that way makes every stored file world-readable by URL and turns authorization into "hope nobody guesses the name". Here a file is served by a route, and the route runs a Policy like any other.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrBadKey = errors.New("storage: the key escapes the tenant prefix")
ErrBadKey is returned for a key that would escape its tenant's prefix.
var ErrNoTenant = errors.New("storage: the Grant carries no tenant, and a file without one belongs to everybody")
ErrNoTenant is returned when the Grant carries no tenant.
var ErrNotFound = errors.New("storage: not found")
ErrNotFound is returned when a key does not exist for this tenant.
It is the same error whether the file is absent or belongs to somebody else, and that is deliberate: distinguishing them would tell a caller which keys exist in other tenants.
Functions ¶
func CleanKey ¶
CleanKey normalizes a key and refuses one that would escape.
This is the check that matters. A key is often a filename that came from an upload, and "../../../etc/passwd" is what an upload form eventually receives. Rejecting rather than sanitizing: a key that had to be rewritten to be safe is a key the caller did not mean, and silently storing it somewhere else is worse than an error.
Types ¶
type File ¶
type File struct {
Key string
Size int64
ContentType string
ModifiedAt time.Time
// Body is the content. The caller closes it.
Body io.ReadCloser
}
File is what a Get returns.
type Store ¶
type Store interface {
// Put writes a file under the tenant of the Grant.
Put(ctx context.Context, g security.Grant, key string, body io.Reader, contentType string) error
// Get reads one back.
Get(ctx context.Context, g security.Grant, key string) (File, error)
// Delete removes it. Removing what is not there is not an error.
Delete(ctx context.Context, g security.Grant, key string) error
// List returns the keys under a prefix, without the tenant part.
List(ctx context.Context, g security.Grant, prefix string) ([]string, error)
// Exists reports whether the key is there.
Exists(ctx context.Context, g security.Grant, key string) (bool, error)
}
Store is what a driver implements.
Every method takes a Grant. That is not ceremony: it is the only thing standing between "the application stores files" and "any handler can read any customer's files by building a string".