// Package auth defines a standard interface for request access controllers. // // An access controller has a simple interface with a single `Authorized` // method which checks that a given request is authorized to perform one or // more actions on one or more resources. This method should return a non-nil // error if the request is not authorized. // // An implementation registers its access controller by name with a constructor // which accepts an options map for configuring the access controller. // // options := map[string]interface{}{"sillySecret": "whysosilly?"} // accessController, _ := auth.GetAccessController("silly", options) // // This `accessController` can then be used in a request handler like so: // // func updateOrder(w http.ResponseWriter, r *http.Request) { // orderNumber := r.FormValue("orderNumber") // resource := auth.Resource{Type: "customerOrder", Name: orderNumber} // access := auth.Access{Resource: resource, Action: "update"} // // if ctx, err := accessController.Authorized(ctx, access); err != nil { // if challenge, ok := err.(auth.Challenge) { // // Let the challenge write the response. // challenge.SetHeaders(r, w) // w.WriteHeader(http.StatusUnauthorized) // return // } else { // // Some other error. // } // } // } package auth import ( "context" "errors" "fmt" "net/http" ) const ( // UserKey is used to get the user object from // a user context UserKey = "auth.user" // UserNameKey is used to get the user name from // a user context UserNameKey = "auth.user.name" ) var ( // ErrInvalidCredential is returned when the auth token does not authenticate correctly. ErrInvalidCredential = errors.New("invalid authorization credential") // ErrAuthenticationFailure returned when authentication fails. ErrAuthenticationFailure = errors.New("authentication failure") ) // UserInfo carries information about // an autenticated/authorized client. type UserInfo struct { Name string } // Resource describes a resource by type and name. type Resource struct { Type string Class string Name string } // Access describes a specific action that is // requested or allowed for a given resource. type Access struct { Resource Action string } // Challenge is a special error type which is used for HTTP 401 Unauthorized // responses and is able to write the response with WWW-Authenticate challenge // header values based on the error. type Challenge interface { error // SetHeaders prepares the request to conduct a challenge response by // adding the an HTTP challenge header on the response message. Callers // are expected to set the appropriate HTTP status code (e.g. 401) // themselves. SetHeaders(r *http.Request, w http.ResponseWriter) } // AccessController controls access to registry resources based on a request // and required access levels for a request. Implementations can support both // complete denial and http authorization challenges. type AccessController interface { // Authorized returns a non-nil error if the context is granted access and // returns a new authorized context. If one or more Access structs are // provided, the requested access will be compared with what is available // to the context. The given context will contain a "http.request" key with // a `*http.Request` value. If the error is non-nil, access should always // be denied. The error may be of type Challenge, in which case the caller // may have the Challenge handle the request or choose what action to take // based on the Challenge header or response status. The returned context // object should have a "auth.user" value set to a UserInfo struct. Authorized(ctx context.Context, access ...Access) (context.Context, error) } // CredentialAuthenticator is an object which is able to authenticate credentials type CredentialAuthenticator interface { AuthenticateUser(username, password string) error } // WithUser returns a context with the authorized user info. func WithUser(ctx context.Context, user UserInfo) context.Context { return userInfoContext{ Context: ctx, user: user, } } type userInfoContext struct { context.Context user UserInfo } func (uic userInfoContext) Value(key interface{}) interface{} { switch key { case UserKey: return uic.user case UserNameKey: return uic.user.Name } return uic.Context.Value(key) } // WithResources returns a context with the authorized resources. func WithResources(ctx context.Context, resources []Resource) context.Context { return resourceContext{ Context: ctx, resources: resources, } } type resourceContext struct { context.Context resources []Resource } type resourceKey struct{} func (rc resourceContext) Value(key interface{}) interface{} { if key == (resourceKey{}) { return rc.resources } return rc.Context.Value(key) } // AuthorizedResources returns the list of resources which have // been authorized for this request. func AuthorizedResources(ctx context.Context) []Resource { if resources, ok := ctx.Value(resourceKey{}).([]Resource); ok { return resources } return nil } // InitFunc is the type of an AccessController factory function and is used // to register the constructor for different AccesController backends. type InitFunc func(options map[string]interface{}) (AccessController, error) var accessControllers map[string]InitFunc func init() { accessControllers = make(map[string]InitFunc) } // Register is used to register an InitFunc for // an AccessController backend with the given name. func Register(name string, initFunc InitFunc) error { if _, exists := accessControllers[name]; exists { return fmt.Errorf("name already registered: %s", name) } accessControllers[name] = initFunc return nil } // GetAccessController constructs an AccessController // with the given options using the named backend. func GetAccessController(name string, options map[string]interface{}) (AccessController, error) { if initFunc, exists := accessControllers[name]; exists { return initFunc(options) } return nil, fmt.Errorf("no access controller registered with name: %s", name) }