-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.go
240 lines (214 loc) · 6.68 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Package rmsgo implements the remoteStorage protocol.
//
// Remote root refers to the URL path below which remote storage resources can be accessed.
// For example, with remote root set to "/storage", a document named
// "/Pictures/Kitten.avif" can be found via the URL
// "https://example.com/storage/Pictures/Kitten.avif".
//
// Storage root refers to the location on disk where the actual "Kitten.avif"
// file is stored (for example "/var/storage").
//
// Use the Configure function and the various Options to setup the server.
//
// err := rmsgo.Configure(RemoteRoot, StorageRoot, WithXXX(...), ...)
// if err != nil {
// log.Fatal(err)
// }
package rmsgo
import (
"fmt"
"log"
"net/http"
"path/filepath"
"time"
. "github.com/cvanloo/rmsgo/mock"
)
type (
// Option configures the Server.
// You're not supposed to create an Option yourself, but instead use the WithXXX functions.
Option func(*Server)
// Server holds the server configuration.
Server struct {
rroot, sroot string
allowAllOrigins bool
allowedOrigins []string
allowOrigin AllowOriginFunc
middleware Middleware
unhandled ErrorHandlerFunc
defaultUser User
authenticate AuthenticateFunc
}
// @todo: domain name (needed eg., for rfc9457 errors)
// ErrorHandlerFunc is passed any errors that the remoteStorage server
// doesn't know how to handle itself.
ErrorHandlerFunc func(err error)
// AllowOriginFunc decides whether the origin of request r is allowed
// (returns true) or forbidden (returns false).
AllowOriginFunc func(r *http.Request, origin string) bool
// AuthenticateFunc authenticates a request (usually based on the bearer token).
// If the request is correctly authenticated a valid User and true are returned.
// Is the authentication invalid, the returned values are nil and false.
AuthenticateFunc func(r *http.Request, bearer string) (User, bool)
)
const timeFormat = time.RFC1123
var g *Server
// Configure initializes the remote storage server.
// remoteRoot is the URL path below which remote storage is accessible.
// storageRoot is a folder on the server's file system where remoteStorage
// documents are written to and read from.
// It is recommended to properly configure authentication by using the
// WithAuthentication option.
func Configure(remoteRoot, storageRoot string, opts ...Option) error {
rroot := filepath.Clean(remoteRoot)
if rroot == "/" {
rroot = ""
}
sroot := filepath.Clean(storageRoot)
fi, err := FS.Stat(sroot)
if err != nil {
return err
}
if !fi.IsDir() {
return fmt.Errorf("storage root is not a directory: %s", sroot)
}
s := &Server{
rroot: rroot,
sroot: sroot,
allowAllOrigins: true,
allowedOrigins: []string{},
allowOrigin: func(r *http.Request, origin string) bool {
for _, o := range g.allowedOrigins {
if o == origin {
return true
}
}
return false
},
middleware: func(next http.Handler) http.Handler {
return next
},
unhandled: func(err error) {
log.Printf("rmsgo: unhandled error: %v\n", err)
},
defaultUser: UserReadOnly{},
authenticate: func(r *http.Request, bearer string) (User, bool) {
return g.defaultUser, true
},
}
for _, opt := range opts {
opt(s)
}
g = s
return nil
}
// WithErrorHandler configures the error handler to use.
func WithErrorHandler(h ErrorHandlerFunc) Option {
return func(s *Server) {
s.unhandled = h
}
}
// WithMiddleware configures middleware (e.g., for logging) in front of the
// remote storage server.
// The middleware is responsible for passing the request on to the rms server
// using next.ServeHTTP(w, r).
// If this is not done correctly, rmsgo won't be able to handle requests.
func WithMiddleware(m Middleware) Option {
return func(s *Server) {
s.middleware = m
}
}
// WithAllowAnyReadWrite allows even unauthenticated requests to create, read,
// and delete any documents on the server.
// This option has no effect if WithAuthentication is specified.
// Per default, i.e, if neither this nor any other auth related option
// is configured, read-only (GET and HEAD) requests are allowed for the
// unauthenticated user.
func WithAllowAnyReadWrite() Option {
return func(s *Server) {
s.defaultUser = UserReadWrite{}
}
}
// WithAuthentication configures the function to use for authenticating requests.
// The AuthenticateFunc authenticates a request and returns the associated user
// and true, or nil and false (unauthenticated).
// In the latter case, access is forbidden unless it is a read request going to
// a public document (a document whose path starts with "/public/").
// In case of an authenticated user, access rights are determined based on the
// user's Permission method.
func WithAuthentication(a AuthenticateFunc) Option {
return func(s *Server) {
s.authenticate = a
}
}
// WithAllowedOrigins configures a list of allowed origins.
// By default all origins are allowed.
// This option is ignored if WithAllowOrigin is called.
func WithAllowedOrigins(origins []string) Option {
return func(s *Server) {
s.allowAllOrigins = false
s.allowedOrigins = origins
}
}
// WithAllowOrigin configures the remote storage server to use f to decide
// whether an origin is allowed or not.
// If this option is set up, the list of origins set by WithAllowOrigins is ignored.
func WithAllowOrigin(f AllowOriginFunc) Option {
return func(s *Server) {
s.allowAllOrigins = false
s.allowOrigin = f
}
}
// Optionally use opt depending on cond.
func Optionally(cond bool, opt Option) Option {
return func(s *Server) {
if cond {
opt(s)
}
}
}
// Group multiple Options together into a single Option.
func Options(opts ...Option) Option {
return func(s *Server) {
for _, opt := range opts {
opt(s)
}
}
}
func handlePanic(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
var err error
switch t := r.(type) {
case error:
err = fmt.Errorf("recovered panic: %w", t)
default:
err = fmt.Errorf("recovered panic: %v", t)
}
g.unhandled(err)
}
}()
next.ServeHTTP(w, r)
})
}
func stripRoot(next http.Handler) http.Handler {
return http.StripPrefix(g.rroot /* don't strip slash */, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
}))
}
// Register the remote storage server (with middleware if configured via
// Options.UseMiddleware) to the mux using g.Rroot() + '/' as pattern.
// If mux is nil the http.DefaultServeMux is used.
func Register(mux *http.ServeMux) {
if mux == nil {
mux = http.DefaultServeMux
}
stack := MiddlewareStack(
handlePanic,
g.middleware,
stripRoot,
handleCORS,
handleAuthorization,
)
mux.Handle(g.rroot+"/", stack(RMSRouter()))
}