github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/auth/htpasswd/access.go (about) 1 // Package htpasswd provides a simple authentication scheme that checks for the 2 // user credential hash in an htpasswd formatted file in a configuration-determined 3 // location. 4 // 5 // This authentication method MUST be used under TLS, as simple token-replay attack is possible. 6 package htpasswd 7 8 import ( 9 "errors" 10 "fmt" 11 "net/http" 12 "os" 13 14 "github.com/docker/distribution/context" 15 "github.com/docker/distribution/registry/auth" 16 ) 17 18 var ( 19 // ErrInvalidCredential is returned when the auth token does not authenticate correctly. 20 ErrInvalidCredential = errors.New("invalid authorization credential") 21 22 // ErrAuthenticationFailure returned when authentication failure to be presented to agent. 23 ErrAuthenticationFailure = errors.New("authentication failured") 24 ) 25 26 type accessController struct { 27 realm string 28 htpasswd *htpasswd 29 } 30 31 var _ auth.AccessController = &accessController{} 32 33 func newAccessController(options map[string]interface{}) (auth.AccessController, error) { 34 realm, present := options["realm"] 35 if _, ok := realm.(string); !present || !ok { 36 return nil, fmt.Errorf(`"realm" must be set for htpasswd access controller`) 37 } 38 39 path, present := options["path"] 40 if _, ok := path.(string); !present || !ok { 41 return nil, fmt.Errorf(`"path" must be set for htpasswd access controller`) 42 } 43 44 f, err := os.Open(path.(string)) 45 if err != nil { 46 return nil, err 47 } 48 defer f.Close() 49 50 h, err := newHTPasswd(f) 51 if err != nil { 52 return nil, err 53 } 54 55 return &accessController{realm: realm.(string), htpasswd: h}, nil 56 } 57 58 func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) { 59 req, err := context.GetRequest(ctx) 60 if err != nil { 61 return nil, err 62 } 63 64 username, password, ok := req.BasicAuth() 65 if !ok { 66 return nil, &challenge{ 67 realm: ac.realm, 68 err: ErrInvalidCredential, 69 } 70 } 71 72 if err := ac.htpasswd.authenticateUser(username, password); err != nil { 73 context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err) 74 return nil, &challenge{ 75 realm: ac.realm, 76 err: ErrAuthenticationFailure, 77 } 78 } 79 80 return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil 81 } 82 83 // challenge implements the auth.Challenge interface. 84 type challenge struct { 85 realm string 86 err error 87 } 88 89 var _ auth.Challenge = challenge{} 90 91 // SetHeaders sets the basic challenge header on the response. 92 func (ch challenge) SetHeaders(w http.ResponseWriter) { 93 w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm)) 94 } 95 96 func (ch challenge) Error() string { 97 return fmt.Sprintf("basic authentication challenge for realm %q: %s", ch.realm, ch.err) 98 } 99 100 func init() { 101 auth.Register("htpasswd", auth.InitFunc(newAccessController)) 102 }