| // 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(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(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) |
| } |