github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/auth/auth.go (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package auth implements Camlistore authentication. 18 package auth 19 20 import ( 21 "crypto/rand" 22 "errors" 23 "fmt" 24 "net/http" 25 "os" 26 "strings" 27 "sync" 28 29 "camlistore.org/pkg/httputil" 30 ) 31 32 // Operation represents a bitmask of operations. See the OpX constants. 33 type Operation int 34 35 const ( 36 OpUpload Operation = 1 << iota 37 OpStat 38 OpGet 39 OpEnumerate 40 OpRemove 41 OpSign 42 OpDiscovery 43 OpRead = OpEnumerate | OpStat | OpGet | OpDiscovery 44 OpRW = OpUpload | OpEnumerate | OpStat | OpGet // Not Remove 45 OpVivify = OpUpload | OpStat | OpGet | OpDiscovery 46 OpAll = OpUpload | OpEnumerate | OpStat | OpRemove | OpGet | OpSign | OpDiscovery 47 ) 48 49 var ( 50 mode AuthMode // the auth logic depending on the choosen auth mechanism 51 ) 52 53 // An AuthMode is the interface implemented by diffent authentication 54 // schemes. 55 type AuthMode interface { 56 // AllowedAccess returns a bitmask of all operations 57 // this user/request is allowed to do. 58 AllowedAccess(req *http.Request) Operation 59 // AddAuthHeader inserts in req the credentials needed 60 // for a client to authenticate. 61 AddAuthHeader(req *http.Request) 62 } 63 64 // UnauthorizedSender may be implemented by AuthModes which want to 65 // handle sending unauthorized. 66 type UnauthorizedSender interface { 67 // SendUnauthorized sends an unauthorized response, 68 // and returns whether it handled it. 69 SendUnauthorized(http.ResponseWriter, *http.Request) (handled bool) 70 } 71 72 func FromEnv() (AuthMode, error) { 73 return FromConfig(os.Getenv("CAMLI_AUTH")) 74 } 75 76 // An AuthConfigParser parses a registered authentication type's option 77 // and returns an AuthMode. 78 type AuthConfigParser func(arg string) (AuthMode, error) 79 80 var authConstructor = map[string]AuthConfigParser{ 81 "none": newNoneAuth, 82 "localhost": newLocalhostAuth, 83 "userpass": newUserPassAuth, 84 "devauth": newDevAuth, 85 "basic": newBasicAuth, 86 } 87 88 // RegisterAuth registers a new authentication scheme. 89 func RegisterAuth(name string, ctor AuthConfigParser) { 90 if _, dup := authConstructor[name]; dup { 91 panic("Dup registration of auth mode " + name) 92 } 93 authConstructor[name] = ctor 94 } 95 96 func newNoneAuth(string) (AuthMode, error) { 97 return None{}, nil 98 } 99 100 func newLocalhostAuth(string) (AuthMode, error) { 101 return Localhost{}, nil 102 } 103 104 func newDevAuth(pw string) (AuthMode, error) { 105 // the vivify mode password is automatically set to "vivi" + Password 106 return &DevAuth{ 107 Password: pw, 108 VivifyPass: "vivi" + pw, 109 }, nil 110 } 111 112 func newUserPassAuth(arg string) (AuthMode, error) { 113 pieces := strings.Split(arg, ":") 114 if len(pieces) < 2 { 115 return nil, fmt.Errorf("Wrong userpass auth string; needs to be \"user:password\"") 116 } 117 username := pieces[0] 118 password := pieces[1] 119 mode := &UserPass{Username: username, Password: password} 120 for _, opt := range pieces[2:] { 121 switch { 122 case opt == "+localhost": 123 mode.OrLocalhost = true 124 case strings.HasPrefix(opt, "vivify="): 125 // optional vivify mode password: "userpass:joe:ponies:vivify=rainbowdash" 126 mode.VivifyPass = strings.Replace(opt, "vivify=", "", -1) 127 default: 128 return nil, fmt.Errorf("Unknown userpass option %q", opt) 129 } 130 } 131 return mode, nil 132 } 133 134 func newBasicAuth(arg string) (AuthMode, error) { 135 pieces := strings.Split(arg, ":") 136 if len(pieces) != 2 { 137 return nil, fmt.Errorf("invalid basic auth syntax. got %q, want \"username:password\"", arg) 138 } 139 return NewBasicAuth(pieces[0], pieces[1]), nil 140 } 141 142 // NewBasicAuth returns a UserPass Authmode, adequate to support HTTP 143 // basic authentication. 144 func NewBasicAuth(username, password string) AuthMode { 145 return &UserPass{ 146 Username: username, 147 Password: password, 148 } 149 } 150 151 // ErrNoAuth is returned when there is no configured authentication. 152 var ErrNoAuth = errors.New("auth: no configured authentication") 153 154 // FromConfig parses authConfig and accordingly sets up the AuthMode 155 // that will be used for all upcoming authentication exchanges. The 156 // supported modes are UserPass and DevAuth. UserPass requires an authConfig 157 // of the kind "userpass:joe:ponies". 158 // 159 // If the input string is empty, the error will be ErrNoAuth. 160 func FromConfig(authConfig string) (AuthMode, error) { 161 if authConfig == "" { 162 return nil, ErrNoAuth 163 } 164 pieces := strings.SplitN(authConfig, ":", 2) 165 if len(pieces) < 1 { 166 return nil, fmt.Errorf("Invalid auth string: %q", authConfig) 167 } 168 authType := pieces[0] 169 170 if fn, ok := authConstructor[authType]; ok { 171 arg := "" 172 if len(pieces) == 2 { 173 arg = pieces[1] 174 } 175 return fn(arg) 176 } 177 return nil, fmt.Errorf("Unknown auth type: %q", authType) 178 } 179 180 // SetMode sets the authentication mode for future requests. 181 func SetMode(m AuthMode) { 182 mode = m 183 } 184 185 // UserPass is used when the auth string provided in the config 186 // is of the kind "userpass:username:pass" 187 // Possible options appended to the config string are 188 // "+localhost" and "vivify=pass", where pass will be the 189 // alternative password which only allows the vivify operation. 190 type UserPass struct { 191 Username, Password string 192 OrLocalhost bool // if true, allow localhost ident auth too 193 // Alternative password used (only) for the vivify operation. 194 // It is checked when uploading, but Password takes precedence. 195 VivifyPass string 196 } 197 198 func (up *UserPass) AllowedAccess(req *http.Request) Operation { 199 user, pass, err := httputil.BasicAuth(req) 200 if err == nil { 201 if user == up.Username { 202 if pass == up.Password { 203 return OpAll 204 } 205 if pass == up.VivifyPass { 206 return OpVivify 207 } 208 } 209 } 210 211 if websocketTokenMatches(req) { 212 return OpAll 213 } 214 if up.OrLocalhost && httputil.IsLocalhost(req) { 215 return OpAll 216 } 217 218 return 0 219 } 220 221 func (up *UserPass) AddAuthHeader(req *http.Request) { 222 req.SetBasicAuth(up.Username, up.Password) 223 } 224 225 type None struct{} 226 227 func (None) AllowedAccess(req *http.Request) Operation { 228 return OpAll 229 } 230 231 func (None) AddAuthHeader(req *http.Request) { 232 // Nothing. 233 } 234 235 type Localhost struct { 236 None 237 } 238 239 func (Localhost) AllowedAccess(req *http.Request) (out Operation) { 240 if httputil.IsLocalhost(req) { 241 return OpAll 242 } 243 return 0 244 } 245 246 // DevAuth is used for development. It has one password and one vivify password, but 247 // also accepts all passwords from localhost. Usernames are ignored. 248 type DevAuth struct { 249 Password string 250 // Password for the vivify mode, automatically set to "vivi" + Password 251 VivifyPass string 252 } 253 254 func (da *DevAuth) AllowedAccess(req *http.Request) Operation { 255 _, pass, err := httputil.BasicAuth(req) 256 if err == nil { 257 if pass == da.Password { 258 return OpAll 259 } 260 if pass == da.VivifyPass { 261 return OpVivify 262 } 263 } 264 265 if websocketTokenMatches(req) { 266 return OpAll 267 } 268 269 // See if the local TCP port is owned by the same non-root user as this 270 // server. This check performed last as it may require reading from the 271 // kernel or exec'ing a program. 272 if httputil.IsLocalhost(req) { 273 return OpAll 274 } 275 276 return 0 277 } 278 279 func (da *DevAuth) AddAuthHeader(req *http.Request) { 280 req.SetBasicAuth("", da.Password) 281 } 282 283 func IsLocalhost(req *http.Request) bool { 284 return httputil.IsLocalhost(req) 285 } 286 287 // TODO(mpl): if/when we ever need it: 288 // func AllowedWithAuth(am AuthMode, req *http.Request, op Operation) bool 289 290 // Allowed returns whether the given request 291 // has access to perform all the operations in op. 292 func Allowed(req *http.Request, op Operation) bool { 293 if op&OpUpload != 0 { 294 // upload (at least from camput) requires stat and get too 295 op = op | OpVivify 296 } 297 return mode.AllowedAccess(req)&op == op 298 } 299 300 func websocketTokenMatches(req *http.Request) bool { 301 return req.Method == "GET" && 302 req.Header.Get("Upgrade") == "websocket" && 303 req.FormValue("authtoken") == ProcessRandom() 304 } 305 306 func TriedAuthorization(req *http.Request) bool { 307 // Currently a simple test just using HTTP basic auth 308 // (presumably over https); may expand. 309 return req.Header.Get("Authorization") != "" 310 } 311 312 func SendUnauthorized(rw http.ResponseWriter, req *http.Request) { 313 if us, ok := mode.(UnauthorizedSender); ok { 314 if us.SendUnauthorized(rw, req) { 315 return 316 } 317 } 318 realm := "camlistored" 319 if devAuth, ok := mode.(*DevAuth); ok { 320 realm = "Any username, password is: " + devAuth.Password 321 } 322 rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm)) 323 rw.WriteHeader(http.StatusUnauthorized) 324 fmt.Fprintf(rw, "<html><body><h1>Unauthorized</h1>") 325 } 326 327 type Handler struct { 328 http.Handler 329 } 330 331 // ServeHTTP serves only if this request and auth mode are allowed all Operations. 332 func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 333 h.serveHTTPForOp(w, r, OpAll) 334 } 335 336 // serveHTTPForOp serves only if op is allowed for this request and auth mode. 337 func (h Handler) serveHTTPForOp(w http.ResponseWriter, r *http.Request, op Operation) { 338 if Allowed(r, op) { 339 h.Handler.ServeHTTP(w, r) 340 } else { 341 SendUnauthorized(w, r) 342 } 343 } 344 345 // RequireAuth wraps a function with another function that enforces 346 // HTTP Basic Auth and checks if the operations in op are all permitted. 347 func RequireAuth(h http.Handler, op Operation) http.Handler { 348 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 349 if Allowed(req, op) { 350 h.ServeHTTP(rw, req) 351 } else { 352 SendUnauthorized(rw, req) 353 } 354 }) 355 } 356 357 var ( 358 processRand string 359 processRandOnce sync.Once 360 ) 361 362 func ProcessRandom() string { 363 processRandOnce.Do(genProcessRand) 364 return processRand 365 } 366 367 func genProcessRand() { 368 processRand = RandToken(20) 369 } 370 371 // RandToken genererates (with crypto/rand.Read) and returns a token 372 // that is the hex version (2x size) of size bytes of randomness. 373 func RandToken(size int) string { 374 buf := make([]byte, size) 375 if n, err := rand.Read(buf); err != nil || n != len(buf) { 376 panic("failed to get random: " + err.Error()) 377 } 378 return fmt.Sprintf("%x", buf) 379 }