1 // Package auth defines a standard interface for request access controllers. 2 // 3 // An access controller has a simple interface with a single `Authorized` 4 // method which checks that a given request is authorized to perform one or 5 // more actions on one or more resources. This method should return a non-nil 6 // error if the request is not authorized. 7 // 8 // An implementation registers its access controller by name with a constructor 9 // which accepts an options map for configuring the access controller. 10 // 11 // options := map[string]interface{}{"sillySecret": "whysosilly?"} 12 // accessController, _ := auth.GetAccessController("silly", options) 13 // 14 // This `accessController` can then be used in a request handler like so: 15 // 16 // func updateOrder(w http.ResponseWriter, r *http.Request) { 17 // orderNumber := r.FormValue("orderNumber") 18 // resource := auth.Resource{Type: "customerOrder", Name: orderNumber} 19 // access := auth.Access{Resource: resource, Action: "update"} 20 // 21 // if ctx, err := accessController.Authorized(ctx, access); err != nil { 22 // if challenge, ok := err.(auth.Challenge) { 23 // // Let the challenge write the response. 24 // challenge.SetHeaders(r, w) 25 // w.WriteHeader(http.StatusUnauthorized) 26 // return 27 // } else { 28 // // Some other error. 29 // } 30 // } 31 // } 32 package auth 33 34 import ( 35 "context" 36 "errors" 37 "fmt" 38 "net/http" 39 ) 40 41 const ( 42 // UserKey is used to get the user object from 43 // a user context 44 UserKey = "auth.user" 45 46 // UserNameKey is used to get the user name from 47 // a user context 48 UserNameKey = "auth.user.name" 49 ) 50 51 var ( 52 // ErrInvalidCredential is returned when the auth token does not authenticate correctly. 53 ErrInvalidCredential = errors.New("invalid authorization credential") 54 55 // ErrAuthenticationFailure returned when authentication fails. 56 ErrAuthenticationFailure = errors.New("authentication failure") 57 ) 58 59 // UserInfo carries information about 60 // an autenticated/authorized client. 61 type UserInfo struct { 62 Name string 63 } 64 65 // Resource describes a resource by type and name. 66 type Resource struct { 67 Type string 68 Class string 69 Name string 70 } 71 72 // Access describes a specific action that is 73 // requested or allowed for a given resource. 74 type Access struct { 75 Resource 76 Action string 77 } 78 79 // Challenge is a special error type which is used for HTTP 401 Unauthorized 80 // responses and is able to write the response with WWW-Authenticate challenge 81 // header values based on the error. 82 type Challenge interface { 83 error 84 85 // SetHeaders prepares the request to conduct a challenge response by 86 // adding the an HTTP challenge header on the response message. Callers 87 // are expected to set the appropriate HTTP status code (e.g. 401) 88 // themselves. 89 SetHeaders(r *http.Request, w http.ResponseWriter) 90 } 91 92 // AccessController controls access to registry resources based on a request 93 // and required access levels for a request. Implementations can support both 94 // complete denial and http authorization challenges. 95 type AccessController interface { 96 // Authorized returns a non-nil error if the context is granted access and 97 // returns a new authorized context. If one or more Access structs are 98 // provided, the requested access will be compared with what is available 99 // to the context. The given context will contain a "http.request" key with 100 // a `*http.Request` value. If the error is non-nil, access should always 101 // be denied. The error may be of type Challenge, in which case the caller 102 // may have the Challenge handle the request or choose what action to take 103 // based on the Challenge header or response status. The returned context 104 // object should have a "auth.user" value set to a UserInfo struct. 105 Authorized(ctx context.Context, access ...Access) (context.Context, error) 106 } 107 108 // CredentialAuthenticator is an object which is able to authenticate credentials 109 type CredentialAuthenticator interface { 110 AuthenticateUser(username, password string) error 111 } 112 113 // WithUser returns a context with the authorized user info. 114 func WithUser(ctx context.Context, user UserInfo) context.Context { 115 return userInfoContext{ 116 Context: ctx, 117 user: user, 118 } 119 } 120 121 type userInfoContext struct { 122 context.Context 123 user UserInfo 124 } 125 126 func (uic userInfoContext) Value(key interface{}) interface{} { 127 switch key { 128 case UserKey: 129 return uic.user 130 case UserNameKey: 131 return uic.user.Name 132 } 133 134 return uic.Context.Value(key) 135 } 136 137 // WithResources returns a context with the authorized resources. 138 func WithResources(ctx context.Context, resources []Resource) context.Context { 139 return resourceContext{ 140 Context: ctx, 141 resources: resources, 142 } 143 } 144 145 type resourceContext struct { 146 context.Context 147 resources []Resource 148 } 149 150 type resourceKey struct{} 151 152 func (rc resourceContext) Value(key interface{}) interface{} { 153 if key == (resourceKey{}) { 154 return rc.resources 155 } 156 157 return rc.Context.Value(key) 158 } 159 160 // AuthorizedResources returns the list of resources which have 161 // been authorized for this request. 162 func AuthorizedResources(ctx context.Context) []Resource { 163 if resources, ok := ctx.Value(resourceKey{}).([]Resource); ok { 164 return resources 165 } 166 167 return nil 168 } 169 170 // InitFunc is the type of an AccessController factory function and is used 171 // to register the constructor for different AccesController backends. 172 type InitFunc func(options map[string]interface{}) (AccessController, error) 173 174 var accessControllers map[string]InitFunc 175 176 func init() { 177 accessControllers = make(map[string]InitFunc) 178 } 179 180 // Register is used to register an InitFunc for 181 // an AccessController backend with the given name. 182 func Register(name string, initFunc InitFunc) error { 183 if _, exists := accessControllers[name]; exists { 184 return fmt.Errorf("name already registered: %s", name) 185 } 186 187 accessControllers[name] = initFunc 188 189 return nil 190 } 191 192 // GetAccessController constructs an AccessController 193 // with the given options using the named backend. 194 func GetAccessController(name string, options map[string]interface{}) (AccessController, error) { 195 if initFunc, exists := accessControllers[name]; exists { 196 return initFunc(options) 197 } 198 199 return nil, fmt.Errorf("no access controller registered with name: %s", name) 200 } 201