[api spec] Update authN and authZ errors

Associate HTTP 401s with Authentication errors rather than Authorization
errors. Changes the meaning of the UNAUTHORIZED error to be authentication
specific.

Defines DENIED error code to be associated with authorization
errors which result in HTTP 403 responses.

Add 'No Such Repository' errors to more endpoints.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2015-09-28 10:41:18 -07:00
parent 794975e9e6
commit 0f670bdc91
4 changed files with 1554 additions and 554 deletions

View file

@ -53,6 +53,7 @@ func main() {
ErrorDescriptors: append(errcode.GetErrorCodeGroup("registry.api.v2"),
// The following are part of the specification but provided by errcode default.
errcode.ErrorCodeUnauthorized.Descriptor(),
errcode.ErrorCodeDenied.Descriptor(),
errcode.ErrorCodeUnsupported.Descriptor()),
}

File diff suppressed because it is too large Load diff

View file

@ -33,16 +33,28 @@ var (
HTTPStatusCode: http.StatusMethodNotAllowed,
})
// ErrorCodeUnauthorized is returned if a request is not authorized.
// ErrorCodeUnauthorized is returned if a request requires
// authentication.
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
Value: "UNAUTHORIZED",
Message: "access to the requested resource is not authorized",
Description: `The access controller denied access for the operation on
a resource. Often this will be accompanied by a 401 Unauthorized
response status.`,
Message: "authentication required",
Description: `The access controller was unable to authenticate
the client. Often this will be accompanied by a
Www-Authenticate HTTP response header indicating how to
authenticate.`,
HTTPStatusCode: http.StatusUnauthorized,
})
// ErrorCodeDenied is returned if a client does not have sufficient
// permission to perform an action.
ErrorCodeDenied = Register("errcode", ErrorDescriptor{
Value: "DENIED",
Message: "requested access to the resource is denied",
Description: `The access controller denied access for the
operation on a resource.`,
HTTPStatusCode: http.StatusForbidden,
})
// ErrorCodeUnavailable provides a common error to report unavialability
// of a service or endpoint.
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{

View file

@ -111,45 +111,67 @@ var (
},
}
unauthorizedResponse = ResponseDescriptor{
Description: "The client does not have access to the repository.",
unauthorizedResponseDescriptor = ResponseDescriptor{
Name: "Authentication Required",
StatusCode: http.StatusUnauthorized,
Description: "The client is not authenticated.",
Headers: []ParameterDescriptor{
authChallengeHeader,
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON error response body.",
Description: "Length of the JSON response body.",
Format: "<length>",
},
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: unauthorizedErrorsBody,
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
}
unauthorizedResponsePush = ResponseDescriptor{
Description: "The client does not have access to push to the repository.",
StatusCode: http.StatusUnauthorized,
repositoryNotFoundResponseDescriptor = ResponseDescriptor{
Name: "No Such Repository Error",
StatusCode: http.StatusNotFound,
Description: "The repository is not known to the registry.",
Headers: []ParameterDescriptor{
authChallengeHeader,
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON error response body.",
Description: "Length of the JSON response body.",
Format: "<length>",
},
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
ErrorCodeNameUnknown,
},
}
deniedResponseDescriptor = ResponseDescriptor{
Name: "Access Denied",
StatusCode: http.StatusForbidden,
Description: "The client does not have required access to the repository.",
Headers: []ParameterDescriptor{
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON response body.",
Format: "<length>",
},
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: unauthorizedErrorsBody,
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeDenied,
},
}
)
@ -345,7 +367,7 @@ var routeDescriptors = []RouteDescriptor{
Name: RouteNameBase,
Path: "/v2/",
Entity: "Base",
Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authorization.`,
Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authentication.`,
Methods: []MethodDescriptor{
{
Method: "GET",
@ -363,24 +385,11 @@ var routeDescriptors = []RouteDescriptor{
},
},
Failures: []ResponseDescriptor{
{
Description: "The client is not authorized to access the registry.",
StatusCode: http.StatusUnauthorized,
Headers: []ParameterDescriptor{
authChallengeHeader,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
},
{
Description: "The registry does not implement the V2 API.",
StatusCode: http.StatusNotFound,
},
unauthorizedResponseDescriptor,
},
},
},
@ -432,28 +441,9 @@ var routeDescriptors = []RouteDescriptor{
},
},
Failures: []ResponseDescriptor{
{
StatusCode: http.StatusNotFound,
Description: "The repository is not known to the registry.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodeNameUnknown,
},
},
{
StatusCode: http.StatusUnauthorized,
Description: "The client does not have access to the repository.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
{
@ -487,28 +477,9 @@ var routeDescriptors = []RouteDescriptor{
},
},
Failures: []ResponseDescriptor{
{
StatusCode: http.StatusNotFound,
Description: "The repository is not known to the registry.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
ErrorCodeNameUnknown,
},
},
{
StatusCode: http.StatusUnauthorized,
Description: "The client does not have access to the repository.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -560,29 +531,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
{
StatusCode: http.StatusUnauthorized,
Description: "The client does not have access to the repository.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
},
{
Description: "The named manifest is not known to the registry.",
StatusCode: http.StatusNotFound,
ErrorCodes: []errcode.ErrorCode{
ErrorCodeNameUnknown,
ErrorCodeManifestUnknown,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -637,17 +588,9 @@ var routeDescriptors = []RouteDescriptor{
ErrorCodeBlobUnknown,
},
},
{
StatusCode: http.StatusUnauthorized,
Description: "The client does not have permission to push to the repository.",
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
{
Name: "Missing Layer(s)",
Description: "One or more layers may be missing during a manifest upload. If so, the missing layers will be enumerated in the error response.",
@ -670,25 +613,6 @@ var routeDescriptors = []RouteDescriptor{
}`,
},
},
{
StatusCode: http.StatusUnauthorized,
Headers: []ParameterDescriptor{
authChallengeHeader,
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON error response body.",
Format: "<length>",
},
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
},
{
Name: "Not allowed",
Description: "Manifest put is not allowed because the registry is configured as a pull-through cache or for some other reason",
@ -733,25 +657,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
{
StatusCode: http.StatusUnauthorized,
Headers: []ParameterDescriptor{
authChallengeHeader,
{
Name: "Content-Length",
Type: "integer",
Description: "Length of the JSON error response body.",
Format: "<length>",
},
},
ErrorCodes: []errcode.ErrorCode{
errcode.ErrorCodeUnauthorized,
},
Body: BodyDescriptor{
ContentType: "application/json; charset=utf-8",
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
{
Name: "Unknown Manifest",
Description: "The specified `name` or `reference` are unknown to the registry and the delete was unable to proceed. Clients can assume the manifest was already deleted if this response is returned.",
@ -845,7 +753,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponse,
{
Description: "The blob, identified by `name` and `digest`, is unknown to the registry.",
StatusCode: http.StatusNotFound,
@ -858,6 +765,9 @@ var routeDescriptors = []RouteDescriptor{
ErrorCodeBlobUnknown,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
{
@ -914,7 +824,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponse,
{
StatusCode: http.StatusNotFound,
ErrorCodes: []errcode.ErrorCode{
@ -930,6 +839,9 @@ var routeDescriptors = []RouteDescriptor{
Description: "The range specification cannot be satisfied for the requested content. This can happen when the range is not formatted correctly or if the range is outside of the valid size of the content.",
StatusCode: http.StatusRequestedRangeNotSatisfiable,
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -993,6 +905,9 @@ var routeDescriptors = []RouteDescriptor{
errcode.ErrorCodeUnsupported,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -1066,7 +981,6 @@ var routeDescriptors = []RouteDescriptor{
ErrorCodeNameInvalid,
},
},
unauthorizedResponsePush,
{
Name: "Not allowed",
Description: "Blob upload is not allowed because the registry is configured as a pull-through cache or for some other reason",
@ -1075,6 +989,9 @@ var routeDescriptors = []RouteDescriptor{
errcode.ErrorCodeUnsupported,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
{
@ -1118,7 +1035,9 @@ var routeDescriptors = []RouteDescriptor{
ErrorCodeNameInvalid,
},
},
unauthorizedResponsePush,
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -1177,7 +1096,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponse,
{
Description: "The upload is unknown to the registry. The upload must be restarted.",
StatusCode: http.StatusNotFound,
@ -1189,6 +1107,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -1249,7 +1170,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponsePush,
{
Description: "The upload is unknown to the registry. The upload must be restarted.",
StatusCode: http.StatusNotFound,
@ -1261,6 +1181,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
{
@ -1328,7 +1251,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponsePush,
{
Description: "The upload is unknown to the registry. The upload must be restarted.",
StatusCode: http.StatusNotFound,
@ -1344,6 +1266,9 @@ var routeDescriptors = []RouteDescriptor{
Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid.",
StatusCode: http.StatusRequestedRangeNotSatisfiable,
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -1420,7 +1345,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponsePush,
{
Description: "The upload is unknown to the registry. The upload must be restarted.",
StatusCode: http.StatusNotFound,
@ -1432,6 +1356,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},
@ -1474,7 +1401,6 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponse,
{
Description: "The upload is unknown to the registry. The client may ignore this error and assume the upload has been deleted.",
StatusCode: http.StatusNotFound,
@ -1486,6 +1412,9 @@ var routeDescriptors = []RouteDescriptor{
Format: errorsBody,
},
},
unauthorizedResponseDescriptor,
repositoryNotFoundResponseDescriptor,
deniedResponseDescriptor,
},
},
},