diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 4e7a46412a..eb0d7120eb 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -29,23 +29,28 @@ }, { "ImportPath": "github.com/docker/distribution/context", - "Comment": "v2.0.0-332-g79a4ca2", - "Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9" + "Comment": "v2.0.0-353-gfed58bd", + "Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b" }, { "ImportPath": "github.com/docker/distribution/health", - "Comment": "v2.0.0-332-g79a4ca2", - "Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9" + "Comment": "v2.0.0-353-gfed58bd", + "Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b" }, { "ImportPath": "github.com/docker/distribution/registry/auth", - "Comment": "v2.0.0-332-g79a4ca2", - "Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9" + "Comment": "v2.0.0-353-gfed58bd", + "Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b" + }, + { + "ImportPath": "github.com/docker/distribution/registry/api/errcode", + "Comment": "v2.0.0-353-gfed58bd", + "Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b" }, { "ImportPath": "github.com/docker/distribution/uuid", - "Comment": "v2.0.0-332-g79a4ca2", - "Rev": "79a4ca2abe6f8622039678ba85a42cb6c7fd8af9" + "Comment": "v2.0.0-353-gfed58bd", + "Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b" }, { "ImportPath": "github.com/docker/docker/pkg/term", diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors.go b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors.go new file mode 100644 index 0000000000..acdeb022a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors.go @@ -0,0 +1,259 @@ +package errcode + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ErrorCoder is the base interface for ErrorCode and Error allowing +// users of each to just call ErrorCode to get the real ID of each +type ErrorCoder interface { + ErrorCode() ErrorCode +} + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +var _ error = ErrorCode(0) + +// ErrorCode just returns itself +func (ec ErrorCode) ErrorCode() ErrorCode { + return ec +} + +// Error returns the ID/Value +func (ec ErrorCode) Error() string { + return ec.Descriptor().Value +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// WithDetail creates a new Error struct based on the passed-in info and +// set the Detail property appropriately +func (ec ErrorCode) WithDetail(detail interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithDetail(detail) +} + +// WithArgs creates a new Error struct and sets the Args slice +func (ec ErrorCode) WithArgs(args ...interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithArgs(args...) +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + + // TODO(duglin): See if we need an "args" property so we can do the + // variable substitution right before showing the message to the user +} + +var _ error = Error{} + +// ErrorCode returns the ID/Value of this Error +func (e Error) ErrorCode() ErrorCode { + return e.Code +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", + strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), + e.Code.Message()) +} + +// WithDetail will return a new Error, based on the current one, but with +// some Detail info added +func (e Error) WithDetail(detail interface{}) Error { + return Error{ + Code: e.Code, + Message: e.Message, + Detail: detail, + } +} + +// WithArgs uses the passed-in list of interface{} as the substitution +// variables in the Error's Message string, but returns a new Error +func (e Error) WithArgs(args ...interface{}) Error { + return Error{ + Code: e.Code, + Message: fmt.Sprintf(e.Code.Message(), args...), + Detail: e.Detail, + } +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCode provides the http status code that is associated with + // this error condition. + HTTPStatusCode int +} + +// ParseErrorCode returns the value by the string error code. +// `ErrorCodeUnknown` will be returned if the error is not known. +func ParseErrorCode(value string) ErrorCode { + ed, ok := idToDescriptors[value] + if ok { + return ed.Code + } + + return ErrorCodeUnknown +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors []error + +var _ error = Errors{} + +func (errs Errors) Error() string { + switch len(errs) { + case 0: + return "" + case 1: + return errs[0].Error() + default: + msg := "errors:\n" + for _, err := range errs { + msg += err.Error() + "\n" + } + return msg + } +} + +// Len returns the current number of errors. +func (errs Errors) Len() int { + return len(errs) +} + +// MarshalJSON converts slice of error, ErrorCode or Error into a +// slice of Error - then serializes +func (errs Errors) MarshalJSON() ([]byte, error) { + var tmpErrs struct { + Errors []Error `json:"errors,omitempty"` + } + + for _, daErr := range errs { + var err Error + + switch daErr.(type) { + case ErrorCode: + err = daErr.(ErrorCode).WithDetail(nil) + case Error: + err = daErr.(Error) + default: + err = ErrorCodeUnknown.WithDetail(daErr) + + } + + // If the Error struct was setup and they forgot to set the + // Message field (meaning its "") then grab it from the ErrCode + msg := err.Message + if msg == "" { + msg = err.Code.Message() + } + + tmpErrs.Errors = append(tmpErrs.Errors, Error{ + Code: err.Code, + Message: msg, + Detail: err.Detail, + }) + } + + return json.Marshal(tmpErrs) +} + +// UnmarshalJSON deserializes []Error and then converts it into slice of +// Error or ErrorCode +func (errs *Errors) UnmarshalJSON(data []byte) error { + var tmpErrs struct { + Errors []Error + } + + if err := json.Unmarshal(data, &tmpErrs); err != nil { + return err + } + + var newErrs Errors + for _, daErr := range tmpErrs.Errors { + // If Message is empty or exactly matches the Code's message string + // then just use the Code, no need for a full Error struct + if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { + // Error's w/o details get converted to ErrorCode + newErrs = append(newErrs, daErr.Code) + } else { + // Error's w/ details are untouched + newErrs = append(newErrs, Error{ + Code: daErr.Code, + Message: daErr.Message, + Detail: daErr.Detail, + }) + } + } + + *errs = newErrs + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors_test.go b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors_test.go new file mode 100644 index 0000000000..1f0aaf9119 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors_test.go @@ -0,0 +1,174 @@ +package errcode + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" +) + +// TestErrorCodes ensures that error code format, mappings and +// marshaling/unmarshaling. round trips are stable. +func TestErrorCodes(t *testing.T) { + if len(errorCodeToDescriptors) == 0 { + t.Fatal("errors aren't loaded!") + } + + for ec, desc := range errorCodeToDescriptors { + if ec != desc.Code { + t.Fatalf("error code in descriptor isn't correct, %q != %q", ec, desc.Code) + } + + if idToDescriptors[desc.Value].Code != ec { + t.Fatalf("error code in idToDesc isn't correct, %q != %q", idToDescriptors[desc.Value].Code, ec) + } + + if ec.Message() != desc.Message { + t.Fatalf("ec.Message doesn't mtach desc.Message: %q != %q", ec.Message(), desc.Message) + } + + // Test (de)serializing the ErrorCode + p, err := json.Marshal(ec) + if err != nil { + t.Fatalf("couldn't marshal ec %v: %v", ec, err) + } + + if len(p) <= 0 { + t.Fatalf("expected content in marshaled before for error code %v", ec) + } + + // First, unmarshal to interface and ensure we have a string. + var ecUnspecified interface{} + if err := json.Unmarshal(p, &ecUnspecified); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", ec, err) + } + + if _, ok := ecUnspecified.(string); !ok { + t.Fatalf("expected a string for error code %v on unmarshal got a %T", ec, ecUnspecified) + } + + // Now, unmarshal with the error code type and ensure they are equal + var ecUnmarshaled ErrorCode + if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", ec, err) + } + + if ecUnmarshaled != ec { + t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, ec) + } + } + +} + +// TestErrorsManagement does a quick check of the Errors type to ensure that +// members are properly pushed and marshaled. +var ErrorCodeTest1 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST1", + Message: "test error 1", + Description: `Just a test message #1.`, + HTTPStatusCode: http.StatusInternalServerError, +}) + +var ErrorCodeTest2 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST2", + Message: "test error 2", + Description: `Just a test message #2.`, + HTTPStatusCode: http.StatusNotFound, +}) + +var ErrorCodeTest3 = Register("v2.errors", ErrorDescriptor{ + Value: "TEST3", + Message: "Sorry %q isn't valid", + Description: `Just a test message #3.`, + HTTPStatusCode: http.StatusNotFound, +}) + +func TestErrorsManagement(t *testing.T) { + var errs Errors + + errs = append(errs, ErrorCodeTest1) + errs = append(errs, ErrorCodeTest2.WithDetail( + map[string]interface{}{"digest": "sometestblobsumdoesntmatter"})) + errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE")) + errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE").WithDetail("data")) + + p, err := json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + expectedJSON := `{"errors":[` + + `{"code":"TEST1","message":"test error 1"},` + + `{"code":"TEST2","message":"test error 2","detail":{"digest":"sometestblobsumdoesntmatter"}},` + + `{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid"},` + + `{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid","detail":"data"}` + + `]}` + + if string(p) != expectedJSON { + t.Fatalf("unexpected json:\ngot:\n%q\n\nexpected:\n%q", string(p), expectedJSON) + } + + // Now test the reverse + var unmarshaled Errors + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errs) { + t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs) + } + + // Test the arg substitution stuff + e1 := unmarshaled[3].(Error) + exp1 := `Sorry "BOOGIE" isn't valid` + if e1.Message != exp1 { + t.Fatalf("Wrong msg, got:\n%q\n\nexpected:\n%q", e1.Message, exp1) + } + + // Test again with a single value this time + errs = Errors{ErrorCodeUnknown} + expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" + p, err = json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } + + // Now test the reverse + unmarshaled = nil + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errs) { + t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs) + } + + // Verify that calling WithArgs() more than once does the right thing. + // Meaning creates a new Error and uses the ErrorCode Message + e1 = ErrorCodeTest3.WithArgs("test1") + e2 := e1.WithArgs("test2") + if &e1 == &e2 { + t.Fatalf("args: e2 and e1 should not be the same, but they are") + } + if e2.Message != `Sorry "test2" isn't valid` { + t.Fatalf("e2 had wrong message: %q", e2.Message) + } + + // Verify that calling WithDetail() more than once does the right thing. + // Meaning creates a new Error and overwrites the old detail field + e1 = ErrorCodeTest3.WithDetail("stuff1") + e2 = e1.WithDetail("stuff2") + if &e1 == &e2 { + t.Fatalf("detail: e2 and e1 should not be the same, but they are") + } + if e2.Detail != `stuff2` { + t.Fatalf("e2 had wrong detail: %q", e2.Detail) + } + +} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/handler.go b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/handler.go new file mode 100644 index 0000000000..49a64a86eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/handler.go @@ -0,0 +1,44 @@ +package errcode + +import ( + "encoding/json" + "net/http" +) + +// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err +// and sets the content-type header to 'application/json'. It will handle +// ErrorCoder and Errors, and if necessary will create an envelope. +func ServeJSON(w http.ResponseWriter, err error) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + var sc int + + switch errs := err.(type) { + case Errors: + if len(errs) < 1 { + break + } + + if err, ok := errs[0].(ErrorCoder); ok { + sc = err.ErrorCode().Descriptor().HTTPStatusCode + } + case ErrorCoder: + sc = errs.ErrorCode().Descriptor().HTTPStatusCode + err = Errors{err} // create an envelope. + default: + // We just have an unhandled error type, so just place in an envelope + // and move along. + err = Errors{err} + } + + if sc == 0 { + sc = http.StatusInternalServerError + } + + w.WriteHeader(sc) + + if err := json.NewEncoder(w).Encode(err); err != nil { + return err + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/register.go b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/register.go new file mode 100644 index 0000000000..42f911b31f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/register.go @@ -0,0 +1,86 @@ +package errcode + +import ( + "fmt" + "net/http" + "sort" + "sync" +) + +var ( + errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} + idToDescriptors = map[string]ErrorDescriptor{} + groupToDescriptors = map[string][]ErrorDescriptor{} +) + +// ErrorCodeUnknown is a generic error that can be used as a last +// resort if there is no situation-specific error message that can be used +var ErrorCodeUnknown = Register("errcode", ErrorDescriptor{ + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + HTTPStatusCode: http.StatusInternalServerError, +}) + +var nextCode = 1000 +var registerLock sync.Mutex + +// Register will make the passed-in error known to the environment and +// return a new ErrorCode +func Register(group string, descriptor ErrorDescriptor) ErrorCode { + registerLock.Lock() + defer registerLock.Unlock() + + descriptor.Code = ErrorCode(nextCode) + + if _, ok := idToDescriptors[descriptor.Value]; ok { + panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value)) + } + if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { + panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code)) + } + + groupToDescriptors[group] = append(groupToDescriptors[group], descriptor) + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + + nextCode++ + return descriptor.Code +} + +type byValue []ErrorDescriptor + +func (a byValue) Len() int { return len(a) } +func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value } + +// GetGroupNames returns the list of Error group names that are registered +func GetGroupNames() []string { + keys := []string{} + + for k := range groupToDescriptors { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// GetErrorCodeGroup returns the named group of error descriptors +func GetErrorCodeGroup(name string) []ErrorDescriptor { + desc := groupToDescriptors[name] + sort.Sort(byValue(desc)) + return desc +} + +// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are +// registered, irrespective of what group they're in +func GetErrorAllDescriptors() []ErrorDescriptor { + result := []ErrorDescriptor{} + + for _, group := range GetGroupNames() { + result = append(result, GetErrorCodeGroup(group)...) + } + sort.Sort(byValue(result)) + return result +} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access.go.orig b/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access.go.orig deleted file mode 100644 index 8d9f8a06a9..0000000000 --- a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access.go.orig +++ /dev/null @@ -1,98 +0,0 @@ -// Package basic provides a simple authentication scheme that checks for the -// user credential hash in an htpasswd formatted file in a configuration-determined -// location. -// -// The use of SHA hashes (htpasswd -s) is enforced since MD5 is insecure and simple -// system crypt() may be as well. -// -// This authentication method MUST be used under TLS, as simple token-replay attack is possible. -package basic - -import ( - "errors" - "fmt" - "net/http" - - ctxu "github.com/docker/distribution/context" - "github.com/docker/distribution/registry/auth" - "golang.org/x/net/context" -) - -type accessController struct { - realm string - htpasswd *HTPasswd -} - -type challenge struct { - realm string - err error -} - -var _ auth.AccessController = &accessController{} -var ( - // ErrPasswordRequired - returned when no auth token is given. -<<<<<<< HEAD - ErrPasswordRequired = errors.New("authorization credential required") - // ErrInvalidCredential - returned when the auth token does not authenticate correctly. -======= - ErrPasswordRequired = errors.New("authorization credential required") - // ErrInvalidCredential - returned when the auth token does not authenticate correctly. ->>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings - ErrInvalidCredential = errors.New("invalid authorization credential") -) - -func newAccessController(options map[string]interface{}) (auth.AccessController, error) { - realm, present := options["realm"] - if _, ok := realm.(string); !present || !ok { - return nil, fmt.Errorf(`"realm" must be set for basic access controller`) - } - - path, present := options["path"] - if _, ok := path.(string); !present || !ok { - return nil, fmt.Errorf(`"path" must be set for basic access controller`) - } - - return &accessController{realm: realm.(string), htpasswd: NewHTPasswd(path.(string))}, nil -} - -func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) { - req, err := ctxu.GetRequest(ctx) - if err != nil { - return nil, err - } - - authHeader := req.Header.Get("Authorization") - if authHeader == "" { - challenge := challenge{ - realm: ac.realm, - } - return nil, &challenge - } - - user, pass, ok := req.BasicAuth() - if !ok { - return nil, errors.New("Invalid Authorization header") - } - - credential := strings.Split(string(text), ":") - if len(credential) != 2 { - challenge.err = ErrInvalidCredential - return nil, &challenge - } - - return auth.WithUser(ctx, auth.UserInfo{Name: user}), nil -} - -func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) { - header := fmt.Sprintf("Basic realm=%q", ch.realm) - w.Header().Set("WWW-Authenticate", header) - w.WriteHeader(http.StatusUnauthorized) -} - -func (ch *challenge) Error() string { - return fmt.Sprintf("basic authentication challenge: %#v", ch) -} - -func init() { - auth.Register("basic", auth.InitFunc(newAccessController)) -} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access_test.go.orig b/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access_test.go.orig deleted file mode 100644 index e732d01013..0000000000 --- a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/access_test.go.orig +++ /dev/null @@ -1,140 +0,0 @@ -package basic - -import ( - "encoding/base64" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/docker/distribution/registry/auth" - "golang.org/x/net/context" -) - -func TestBasicAccessController(t *testing.T) { - - testRealm := "The-Shire" -<<<<<<< HEAD - testUsers := []string{"bilbo", "frodo", "MiShil", "DeokMan"} - testPasswords := []string{"baggins", "baggins", "새주", "공주님"} - testHtpasswdContent := `bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs= - frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W - MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2 - DeokMan:공주님` -======= - testUser := "bilbo" - testHtpasswdContent := "bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=" ->>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files - - tempFile, err := ioutil.TempFile("", "htpasswd-test") - if err != nil { - t.Fatal("could not create temporary htpasswd file") - } - if _, err = tempFile.WriteString(testHtpasswdContent); err != nil { - t.Fatal("could not write temporary htpasswd file") - } - - options := map[string]interface{}{ - "realm": testRealm, - "path": tempFile.Name(), - } - - accessController, err := newAccessController(options) - if err != nil { - t.Fatal("error creating access controller") - } - - tempFile.Close() - -<<<<<<< HEAD - var userNumber = 0 - -======= ->>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(nil, "http.request", r) - authCtx, err := accessController.Authorized(ctx) - if err != nil { - switch err := err.(type) { - case auth.Challenge: - err.ServeHTTP(w, r) - return - default: - t.Fatalf("unexpected error authorizing request: %v", err) - } - } - - userInfo, ok := authCtx.Value("auth.user").(auth.UserInfo) - if !ok { - t.Fatal("basic accessController did not set auth.user context") - } - -<<<<<<< HEAD - if userInfo.Name != testUsers[userNumber] { - t.Fatalf("expected user name %q, got %q", testUsers[userNumber], userInfo.Name) -======= - if userInfo.Name != testUser { - t.Fatalf("expected user name %q, got %q", testUser, userInfo.Name) ->>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files - } - - w.WriteHeader(http.StatusNoContent) - })) - - client := &http.Client{ - CheckRedirect: nil, - } - - req, _ := http.NewRequest("GET", server.URL, nil) - resp, err := client.Do(req) - - if err != nil { - t.Fatalf("unexpected error during GET: %v", err) - } - defer resp.Body.Close() - - // Request should not be authorized - if resp.StatusCode != http.StatusUnauthorized { - t.Fatalf("unexpected non-fail response status: %v != %v", resp.StatusCode, http.StatusUnauthorized) - } - -<<<<<<< HEAD - for i := 0; i < len(testUsers); i++ { - userNumber = i - req, _ = http.NewRequest("GET", server.URL, nil) - sekrit := testUsers[i] + ":" + testPasswords[i] - credential := "Basic " + base64.StdEncoding.EncodeToString([]byte(sekrit)) - - req.Header.Set("Authorization", credential) - resp, err = client.Do(req) - - if err != nil { - t.Fatalf("unexpected error during GET: %v", err) - } - defer resp.Body.Close() - - // Request should be authorized - if resp.StatusCode != http.StatusNoContent { - t.Fatalf("unexpected non-success response status: %v != %v for %s %s %s", resp.StatusCode, http.StatusNoContent, testUsers[i], testPasswords[i], credential) - } -======= - req, _ = http.NewRequest("GET", server.URL, nil) - - sekrit := "bilbo:baggins" - credential := "Basic " + base64.StdEncoding.EncodeToString([]byte(sekrit)) - - req.Header.Set("Authorization", credential) - resp, err = client.Do(req) - - if err != nil { - t.Fatalf("unexpected error during GET: %v", err) - } - defer resp.Body.Close() - - // Request should be authorized - if resp.StatusCode != http.StatusNoContent { - t.Fatalf("unexpected non-success response status: %v != %v", resp.StatusCode, http.StatusNoContent) ->>>>>>> Implementation of a basic authentication scheme using standard .htpasswd files - } - -} diff --git a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/htpasswd.go.orig b/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/htpasswd.go.orig deleted file mode 100644 index 152a0548be..0000000000 --- a/Godeps/_workspace/src/github.com/docker/distribution/registry/auth/basic/htpasswd.go.orig +++ /dev/null @@ -1,155 +0,0 @@ -package basic - -import ( - "crypto/sha1" - "encoding/base64" - "encoding/csv" - "errors" - "os" - "regexp" - "strings" - - "golang.org/x/crypto/bcrypt" -) - -<<<<<<< HEAD -// AuthenticationFailureErr - a generic error message for authentication failure to be presented to agent. -var ErrAuthenticationFailure = errors.New("Bad username or password") -======= -// ErrSHARequired - returned in error field of challenge when the htpasswd was not made using SHA1 algorithm. -// (SHA1 is considered obsolete but the alternative for htpasswd is MD5, or system crypt...) -var ErrSHARequired = errors.New("htpasswd file must use SHA (htpasswd -s)") ->>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings - -// HTPasswd - holds a path to a system .htpasswd file and the machinery to parse it. -type HTPasswd struct { - path string - reader *csv.Reader -} - -<<<<<<< HEAD -// AuthType represents a particular hash function used in the htpasswd file. -type AuthType int - -const ( - // PlainText - Plain-text password storage (htpasswd -p) - PlainText AuthType = iota - // SHA1 - sha hashed password storage (htpasswd -s) - SHA1 - // ApacheMD5 - apr iterated md5 hashing (htpasswd -m) - ApacheMD5 - // BCrypt - BCrypt adapative password hashing (htpasswd -B) - BCrypt - // Crypt - System crypt() hashes. (htpasswd -d) - Crypt -) - -// String returns a text representation of the AuthType -func (at AuthType) String() string { - switch at { - case PlainText: - return "plaintext" - case SHA1: - return "sha1" - case ApacheMD5: - return "md5" - case BCrypt: - return "bcrypt" - case Crypt: - return "system crypt" - } - return "unknown" -} - -======= ->>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings -// NewHTPasswd - Create a new HTPasswd with the given path to .htpasswd file. -func NewHTPasswd(htpath string) *HTPasswd { - return &HTPasswd{path: htpath} -} - -<<<<<<< HEAD -var bcryptPrefixRegexp = regexp.MustCompile(`^\$2[ab]?y\$`) -======= -// AuthenticateUser - Check a given user:password credential against the receiving HTPasswd's file. -func (htpasswd *HTPasswd) AuthenticateUser(user string, pwd string) (bool, error) { ->>>>>>> Fixed WWW-Authenticate: header, added example config and import into main, fixed golint warnings - -// GetAuthCredentialType - Inspect an htpasswd file credential and guess the encryption algorithm used. -func GetAuthCredentialType(cred string) AuthType { - if strings.HasPrefix(cred, "{SHA}") { - return SHA1 - } - if strings.HasPrefix(cred, "$apr1$") { - return ApacheMD5 - } - if bcryptPrefixRegexp.MatchString(cred) { - return BCrypt - } - // There's just not a great way to distinguish between these next two... - if len(cred) == 13 { - return Crypt - } - return PlainText -} - -// AuthenticateUser - Check a given user:password credential against the receiving HTPasswd's file. -func (htpasswd *HTPasswd) AuthenticateUser(user string, pwd string) (bool, error) { - - // Open the file. - in, err := os.Open(htpasswd.path) - if err != nil { - return false, err - } - - // Parse the contents of the standard .htpasswd until we hit the end or find a match. - reader := csv.NewReader(in) - reader.Comma = ':' - reader.Comment = '#' - reader.TrimLeadingSpace = true - for entry, readerr := reader.Read(); entry != nil || readerr != nil; entry, readerr = reader.Read() { - if readerr != nil { - return false, readerr - } - if len(entry) == 0 { - continue - } - if entry[0] == user { - credential := entry[1] - credType := GetAuthCredentialType(credential) - switch credType { - case SHA1: - { - sha := sha1.New() - sha.Write([]byte(pwd)) - hash := base64.StdEncoding.EncodeToString(sha.Sum(nil)) - return entry[1][5:] == hash, nil - } - case ApacheMD5: - { - return false, errors.New(ApacheMD5.String() + " htpasswd hash function not yet supported") - } - case BCrypt: - { - err := bcrypt.CompareHashAndPassword([]byte(credential), []byte(pwd)) - if err != nil { - return false, err - } - return true, nil - } - case Crypt: - { - return false, errors.New(Crypt.String() + " htpasswd hash function not yet supported") - } - case PlainText: - { - if pwd == credential { - return true, nil - } - return false, ErrAuthenticationFailure - } - } - } - } - return false, ErrAuthenticationFailure -} diff --git a/client/client_test.go b/client/client_test.go index 2a1ad8bfdf..8f597fa01c 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -470,7 +470,7 @@ func testPublish(t *testing.T, rootType data.KeyAlgorithm) { r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) - r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) + //r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) ts := httptest.NewServer(r) diff --git a/errors/errors.go b/errors/errors.go index 7a64343eb6..1902e369fe 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -1,22 +1,84 @@ package errors import ( - "fmt" + "net/http" + + "github.com/docker/distribution/registry/api/errcode" ) -// HTTPError represents an application error which will map to -// an HTTP status code and returned error object. -type HTTPError struct { - HTTPStatus int - Code int - Err error -} +const errGroup = "notary.v1" -// Error implements the error interface -func (he *HTTPError) Error() string { - msg := "" - if he.Err != nil { - msg = he.Err.Error() - } - return fmt.Sprintf("%d: %s", he.Code, msg) -} +var ( + // ErrNoStorage lint comment + ErrNoStorage = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NO_STORAGE", + Message: "The server is misconfigured and has no storage.", + Description: "No storage backend has been configured for the server.", + HTTPStatusCode: http.StatusInternalServerError, + }) + // ErrNoFilename lint comment + ErrNoFilename = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NO_FILENAME", + Message: "No file/role name provided.", + Description: "No file/role name is provided to associate an update with.", + HTTPStatusCode: http.StatusBadRequest, + }) + // ErrInvalidRole lint comment + ErrInvalidRole = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "INVALID_ROLE", + Message: "The role you are attempting to operate on is invalid.", + Description: "The user attempted to operate on a role that is not deemed valid.", + HTTPStatusCode: http.StatusBadRequest, + }) + // ErrMalformedJSON lint comment + ErrMalformedJSON = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MALFORMED_JSON", + Message: "JSON sent by the client could not be parsed by the server", + Description: "The client sent malformed JSON.", + HTTPStatusCode: http.StatusBadRequest, + }) + // ErrUpdating lint comment + ErrUpdating = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "UPDATING", + Message: "An error has occurred while updating the TUF repository.", + Description: "An error occurred when attempting to apply an update at the storage layer.", + HTTPStatusCode: http.StatusInternalServerError, + }) + // ErrMetadataNotFound lint comment + ErrMetadataNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "METADATA_NOT_FOUND", + Message: "You have requested metadata that does not exist.", + Description: "The user requested metadata that is not known to the server.", + HTTPStatusCode: http.StatusNotFound, + }) + // ErrMalformedUpload lint comment + ErrMalformedUpload = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "MALFORMED_UPLOAD", + Message: "The body of your request is malformed.", + Description: "The user uploaded new TUF data and the server was unable to parse it as multipart/form-data.", + HTTPStatusCode: http.StatusBadRequest, + }) + // ErrGenericNotFound lint comment + ErrGenericNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "GENERIC_NOT_FOUND", + Message: "You have requested a resource that does not exist.", + Description: "The user requested a non-specific resource that is not known to the server.", + HTTPStatusCode: http.StatusNotFound, + }) + // ErrNoCryptoService lint comment + ErrNoCryptoService = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "NO_CRYPTOSERVICE", + Message: "The server does not have a signing service configured.", + Description: "No signing service has been configured for the server and it has been asked to perform an operation that requires either signing, or key generation.", + HTTPStatusCode: http.StatusInternalServerError, + }) + // ErrUnauthorized lint comment + ErrUnauthorized = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "UNAUTHORIZED", + Message: "You are not authorized for this request.", + Description: "The user was not authorized for the request.", + HTTPStatusCode: http.StatusUnauthorized, + }) + // ErrUnknown is the generic internal server error + ErrUnknown = errcode.ErrorCodeUnknown +) diff --git a/errors/errors_test.go b/errors/errors_test.go deleted file mode 100644 index d5d2d68c6c..0000000000 --- a/errors/errors_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package errors - -import ( - "fmt" - "testing" -) - -func TestHTTPError(t *testing.T) { - err := HTTPError{ - 400, - 1234, - fmt.Errorf("Test error"), - } - errStr := err.Error() - - if errStr != "1234: Test error" { - t.Fatalf("Error did not create expected string") - } -} diff --git a/server/handlers/default.go b/server/handlers/default.go index b7a8b38083..75e48d6744 100644 --- a/server/handlers/default.go +++ b/server/handlers/default.go @@ -3,9 +3,7 @@ package handlers import ( "bytes" "encoding/json" - "fmt" "io" - "io/ioutil" "net/http" "strings" @@ -21,56 +19,32 @@ import ( ) // MainHandler is the default handler for the server -func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { if r.Method == "GET" { _, err := w.Write([]byte("{}")) if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } + return errors.ErrUnknown.WithDetail(err) } } else { - //w.WriteHeader(http.StatusNotFound) - return &errors.HTTPError{ - HTTPStatus: http.StatusNotFound, - Code: 9999, - Err: nil, - } + return errors.ErrGenericNotFound.WithDetail(nil) } return nil } // AtomicUpdateHandler will accept multiple TUF files and ensure that the storage // backend is atomically updated with all the new records. -func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { defer r.Body.Close() s := ctx.Value("metaStore") - if s == nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store is nil"), - } - } store, ok := s.(storage.MetaStore) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } + return errors.ErrNoStorage.WithDetail(nil) } vars := mux.Vars(r) gun := vars["imageName"] reader, err := r.MultipartReader() if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: err, - } + return errors.ErrMalformedUpload.WithDetail(nil) } var updates []storage.MetaUpdate for { @@ -80,17 +54,9 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req } role := strings.TrimSuffix(part.FileName(), ".json") if role == "" { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: fmt.Errorf("Empty filename provided. No updates performed"), - } + return errors.ErrNoFilename.WithDetail(nil) } else if !data.ValidRole(role) { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: fmt.Errorf("Invalid role: %s. No updates performed", role), - } + return errors.ErrInvalidRole.WithDetail(role) } meta := &data.SignedTargets{} var input []byte @@ -98,11 +64,7 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req dec := json.NewDecoder(io.TeeReader(part, inBuf)) err = dec.Decode(meta) if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: err, - } + return errors.ErrMalformedJSON.WithDetail(nil) } version := meta.Signed.Version updates = append(updates, storage.MetaUpdate{ @@ -113,80 +75,17 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req } err = store.UpdateMany(gun, updates) if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } - } - return nil -} - -// UpdateHandler adds the provided json data for the role and GUN specified in the URL -func UpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { - defer r.Body.Close() - s := ctx.Value("metaStore") - if s == nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store is nil"), - } - } - store, ok := s.(storage.MetaStore) - if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } - } - vars := mux.Vars(r) - gun := vars["imageName"] - tufRole := vars["tufRole"] - input, err := ioutil.ReadAll(r.Body) - if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: err, - } - } - meta := &data.SignedTargets{} - err = json.Unmarshal(input, meta) - if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusBadRequest, - Code: 9999, - Err: err, - } - } - update := storage.MetaUpdate{ - Role: tufRole, - Version: meta.Signed.Version, - Data: input, - } - err = store.UpdateCurrent(gun, update) - if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } + return errors.ErrUpdating.WithDetail(err) } return nil } // GetHandler returns the json for a specified role and GUN. -func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } + return errors.ErrNoStorage.WithDetail(nil) } vars := mux.Vars(r) gun := vars["imageName"] @@ -194,26 +93,14 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er out, err := store.GetCurrent(gun, tufRole) if err != nil { if _, ok := err.(*storage.ErrNotFound); ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusNotFound, - Code: 9999, - Err: err, - } + return errors.ErrMetadataNotFound.WithDetail(nil) } logrus.Errorf("[Notary Server] 500 GET repository: %s, role: %s", gun, tufRole) - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } + return errors.ErrUnknown.WithDetail(err) } if out == nil { logrus.Errorf("[Notary Server] 404 GET repository: %s, role: %s", gun, tufRole) - return &errors.HTTPError{ - HTTPStatus: http.StatusNotFound, - Code: 9999, - Err: err, - } + return errors.ErrMetadataNotFound.WithDetail(nil) } logrus.Debug("Writing data") w.Write(out) @@ -221,49 +108,33 @@ func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *er } // DeleteHandler deletes all data for a GUN. A 200 responses indicates success. -func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } + return errors.ErrNoStorage.WithDetail(nil) } vars := mux.Vars(r) gun := vars["imageName"] err := store.Delete(gun) if err != nil { logrus.Errorf("[Notary Server] 500 DELETE repository: %s", gun) - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } + return errors.ErrUnknown.WithDetail(err) } return nil } // GetTimestampHandler returns a timestamp.json given a GUN -func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } + return errors.ErrNoStorage.WithDetail(nil) } cryptoServiceVal := ctx.Value("cryptoService") cryptoService, ok := cryptoServiceVal.(signed.CryptoService) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("CryptoService not configured"), - } + return errors.ErrNoCryptoService.WithDetail(nil) } vars := mux.Vars(r) @@ -272,17 +143,9 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req out, err := timestamp.GetOrCreateTimestamp(gun, store, cryptoService) if err != nil { if _, ok := err.(*storage.ErrNoKey); ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusNotFound, - Code: 9999, - Err: err, - } - } - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, + return errors.ErrMetadataNotFound.WithDetail(nil) } + return errors.ErrUnknown.WithDetail(err) } logrus.Debug("Writing data") @@ -292,24 +155,16 @@ func GetTimestampHandler(ctx context.Context, w http.ResponseWriter, r *http.Req // GetTimestampKeyHandler returns a timestamp public key, creating a new key-pair // it if it doesn't yet exist -func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { s := ctx.Value("metaStore") store, ok := s.(storage.MetaStore) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Version store not configured"), - } + return errors.ErrNoStorage.WithDetail(nil) } c := ctx.Value("cryptoService") crypto, ok := c.(signed.CryptoService) if !ok { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("CryptoService not configured"), - } + return errors.ErrNoCryptoService.WithDetail(nil) } vars := mux.Vars(r) @@ -317,20 +172,12 @@ func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http. key, err := timestamp.GetOrCreateTimestampKey(gun, store, crypto, data.ED25519Key) if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: err, - } + return errors.ErrUnknown.WithDetail(err) } out, err := json.Marshal(key) if err != nil { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("Error serializing key."), - } + return errors.ErrUnknown.WithDetail(err) } w.Write(out) return nil diff --git a/server/server.go b/server/server.go index 9f1889bc88..f6de8ed03d 100644 --- a/server/server.go +++ b/server/server.go @@ -81,7 +81,7 @@ func Run(ctx context.Context, addr, tlsCertFile, tlsKeyFile string, trust signed r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|snapshot)}.json").Handler(hand(handlers.GetHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.json").Handler(hand(handlers.GetTimestampHandler, "pull")) r.Methods("GET").Path("/v2/{imageName:.*}/_trust/tuf/timestamp.key").Handler(hand(handlers.GetTimestampKeyHandler, "push", "pull")) - r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) + // r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/{tufRole:(root|targets|timestamp|snapshot)}.json").Handler(hand(handlers.UpdateHandler, "push", "pull")) r.Methods("DELETE").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.DeleteHandler, "push", "pull")) svr := http.Server{ diff --git a/utils/http.go b/utils/http.go index 3832a62d33..e5b86d1755 100644 --- a/utils/http.go +++ b/utils/http.go @@ -4,17 +4,16 @@ import ( "net/http" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/auth" "github.com/endophage/gotuf/signed" "github.com/gorilla/mux" "golang.org/x/net/context" - - "github.com/docker/notary/errors" ) // contextHandler defines an alterate HTTP handler interface which takes in // a context for authorization and returns an HTTP application error. -type contextHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError +type contextHandler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error // rootHandler is an implementation of an HTTP request handler which handles // authorization and calling out to the defined alternate http handler. @@ -67,7 +66,10 @@ func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if err := root.handler(ctx, w, r); err != nil { logrus.Error("[Notary Server] ", err.Error()) - http.Error(w, err.Error(), err.HTTPStatus) + e := errcode.ServeJSON(w, err) + if e != nil { + logrus.Error(e) + } return } return diff --git a/utils/http_test.go b/utils/http_test.go index c2692565fd..f72ef31678 100644 --- a/utils/http_test.go +++ b/utils/http_test.go @@ -1,30 +1,24 @@ package utils import ( - "fmt" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" - "golang.org/x/net/context" - "github.com/endophage/gotuf/signed" + "golang.org/x/net/context" "github.com/docker/notary/errors" ) -func MockContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { +func MockContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { return nil } -func MockBetterErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) *errors.HTTPError { - return &errors.HTTPError{ - HTTPStatus: http.StatusInternalServerError, - Code: 9999, - Err: fmt.Errorf("TestError"), - } +func MockBetterErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + return errors.ErrUnknown.WithDetail("Test Error") } func TestRootHandlerFactory(t *testing.T) { @@ -78,7 +72,7 @@ func TestRootHandlerError(t *testing.T) { t.Fatal(err) } contentStr := strings.Trim(string(content), "\r\n\t ") - if contentStr != "9999: TestError" { + if strings.TrimSpace(contentStr) != `{"errors":[{"code":"UNKNOWN","message":"unknown error","detail":"Test Error"}]}` { t.Fatalf("Error Body Incorrect: `%s`", content) } }