github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/auth/auth.go (about) 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(w) 25 // w.WriteHeader(http.StatusUnauthorized) 26 // return 27 // } else { 28 // // Some other error. 29 // } 30 // } 31 // } 32 // 33 package auth 34 35 import ( 36 "fmt" 37 "net/http" 38 39 "github.com/docker/distribution/context" 40 ) 41 42 // UserInfo carries information about 43 // an autenticated/authorized client. 44 type UserInfo struct { 45 Name string 46 } 47 48 // Resource describes a resource by type and name. 49 type Resource struct { 50 Type string 51 Name string 52 } 53 54 // Access describes a specific action that is 55 // requested or allowed for a given resource. 56 type Access struct { 57 Resource 58 Action string 59 } 60 61 // Challenge is a special error type which is used for HTTP 401 Unauthorized 62 // responses and is able to write the response with WWW-Authenticate challenge 63 // header values based on the error. 64 type Challenge interface { 65 error 66 67 // SetHeaders prepares the request to conduct a challenge response by 68 // adding the an HTTP challenge header on the response message. Callers 69 // are expected to set the appropriate HTTP status code (e.g. 401) 70 // themselves. 71 SetHeaders(w http.ResponseWriter) 72 } 73 74 // AccessController controls access to registry resources based on a request 75 // and required access levels for a request. Implementations can support both 76 // complete denial and http authorization challenges. 77 type AccessController interface { 78 // Authorized returns a non-nil error if the context is granted access and 79 // returns a new authorized context. If one or more Access structs are 80 // provided, the requested access will be compared with what is available 81 // to the context. The given context will contain a "http.request" key with 82 // a `*http.Request` value. If the error is non-nil, access should always 83 // be denied. The error may be of type Challenge, in which case the caller 84 // may have the Challenge handle the request or choose what action to take 85 // based on the Challenge header or response status. The returned context 86 // object should have a "auth.user" value set to a UserInfo struct. 87 Authorized(ctx context.Context, access ...Access) (context.Context, error) 88 } 89 90 // WithUser returns a context with the authorized user info. 91 func WithUser(ctx context.Context, user UserInfo) context.Context { 92 return userInfoContext{ 93 Context: ctx, 94 user: user, 95 } 96 } 97 98 type userInfoContext struct { 99 context.Context 100 user UserInfo 101 } 102 103 func (uic userInfoContext) Value(key interface{}) interface{} { 104 switch key { 105 case "auth.user": 106 return uic.user 107 case "auth.user.name": 108 return uic.user.Name 109 } 110 111 return uic.Context.Value(key) 112 } 113 114 // InitFunc is the type of an AccessController factory function and is used 115 // to register the constructor for different AccesController backends. 116 type InitFunc func(options map[string]interface{}) (AccessController, error) 117 118 var accessControllers map[string]InitFunc 119 120 func init() { 121 accessControllers = make(map[string]InitFunc) 122 } 123 124 // Register is used to register an InitFunc for 125 // an AccessController backend with the given name. 126 func Register(name string, initFunc InitFunc) error { 127 if _, exists := accessControllers[name]; exists { 128 return fmt.Errorf("name already registered: %s", name) 129 } 130 131 accessControllers[name] = initFunc 132 133 return nil 134 } 135 136 // GetAccessController constructs an AccessController 137 // with the given options using the named backend. 138 func GetAccessController(name string, options map[string]interface{}) (AccessController, error) { 139 if initFunc, exists := accessControllers[name]; exists { 140 return initFunc(options) 141 } 142 143 return nil, fmt.Errorf("no access controller registered with name: %s", name) 144 }