diff --git a/app.go b/app.go index 7d1ee9bb..5f1fe52d 100644 --- a/app.go +++ b/app.go @@ -172,36 +172,56 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont } var accessRecords []auth.Access - resource := auth.Resource{ - Type: "repository", - Name: context.Name, - } - switch r.Method { - case "GET", "HEAD": - accessRecords = append(accessRecords, - auth.Access{ - Resource: resource, - Action: "pull", - }) - case "POST", "PUT", "PATCH": - accessRecords = append(accessRecords, - auth.Access{ - Resource: resource, - Action: "pull", - }, - auth.Access{ - Resource: resource, - Action: "push", - }) - case "DELETE": - // DELETE access requires full admin rights, which is represented - // as "*". This may not be ideal. - accessRecords = append(accessRecords, - auth.Access{ - Resource: resource, - Action: "*", - }) + if context.Name != "" { + resource := auth.Resource{ + Type: "repository", + Name: context.Name, + } + + switch r.Method { + case "GET", "HEAD": + accessRecords = append(accessRecords, + auth.Access{ + Resource: resource, + Action: "pull", + }) + case "POST", "PUT", "PATCH": + accessRecords = append(accessRecords, + auth.Access{ + Resource: resource, + Action: "pull", + }, + auth.Access{ + Resource: resource, + Action: "push", + }) + case "DELETE": + // DELETE access requires full admin rights, which is represented + // as "*". This may not be ideal. + accessRecords = append(accessRecords, + auth.Access{ + Resource: resource, + Action: "*", + }) + } + } else { + // Only allow the name not to be set on the base route. + route := mux.CurrentRoute(r) + + if route == nil || route.GetName() != v2.RouteNameBase { + // For this to be properly secured, context.Name must always be set + // for a resource that may make a modification. The only condition + // under which name is not set and we still allow access is when the + // base route is accessed. This section prevents us from making that + // mistake elsewhere in the code, allowing any operation to proceed. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusForbidden) + + var errs v2.Errors + errs.Push(v2.ErrorCodeUnauthorized) + serveJSON(w, errs) + } } if err := app.accessController.Authorized(r, accessRecords...); err != nil { diff --git a/app_test.go b/app_test.go index 3e9d191d..b8edbd64 100644 --- a/app_test.go +++ b/app_test.go @@ -177,6 +177,11 @@ func TestNewApp(t *testing.T) { t.Fatalf("unexpected content-type: %v != %v", req.Header.Get("Content-Type"), "application/json") } + expectedAuthHeader := "Bearer realm=\"realm-test\",service=\"service-test\"" + if req.Header.Get("Authorization") != expectedAuthHeader { + t.Fatalf("unexpected authorization header: %q != %q", req.Header.Get("Authorization"), expectedAuthHeader) + } + var errs v2.Errors dec := json.NewDecoder(req.Body) if err := dec.Decode(&errs); err != nil { diff --git a/auth/silly/access.go b/auth/silly/access.go index a747fb6d..4995d0dc 100644 --- a/auth/silly/access.go +++ b/auth/silly/access.go @@ -51,7 +51,7 @@ func (ac *accessController) Authorized(req *http.Request, accessRecords ...auth. if len(accessRecords) > 0 { var scopes []string for _, access := range accessRecords { - scopes = append(scopes, fmt.Sprintf("%s:%s:%s", access.Type, access.Resource, access.Action)) + scopes = append(scopes, fmt.Sprintf("%s:%s:%s", access.Type, access.Resource.Name, access.Action)) } challenge.scope = strings.Join(scopes, " ") }