boulder/probs/probs.go

254 lines
7.7 KiB
Go

package probs
import (
"fmt"
"net/http"
)
// Error types that can be used in ACME payloads
const (
ConnectionProblem = ProblemType("connection")
MalformedProblem = ProblemType("malformed")
ServerInternalProblem = ProblemType("serverInternal")
TLSProblem = ProblemType("tls")
UnauthorizedProblem = ProblemType("unauthorized")
UnknownHostProblem = ProblemType("unknownHost")
RateLimitedProblem = ProblemType("rateLimited")
BadNonceProblem = ProblemType("badNonce")
InvalidEmailProblem = ProblemType("invalidEmail")
RejectedIdentifierProblem = ProblemType("rejectedIdentifier")
AccountDoesNotExistProblem = ProblemType("accountDoesNotExist")
CAAProblem = ProblemType("caa")
DNSProblem = ProblemType("dns")
V1ErrorNS = "urn:acme:error:"
V2ErrorNS = "urn:ietf:params:acme:error:"
)
// ProblemType defines the error types in the ACME protocol
type ProblemType string
// ProblemDetails objects represent problem documents
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
type ProblemDetails struct {
Type ProblemType `json:"type,omitempty"`
Detail string `json:"detail,omitempty"`
// HTTPStatus is the HTTP status code the ProblemDetails should probably be sent
// as.
HTTPStatus int `json:"status,omitempty"`
}
func (pd *ProblemDetails) Error() string {
return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
}
// statusTooManyRequests is the HTTP status code meant for rate limiting
// errors. It's not currently in the net/http library so we add it here.
const statusTooManyRequests = 429
// ProblemDetailsToStatusCode inspects the given ProblemDetails to figure out
// what HTTP status code it should represent. It should only be used by the WFE
// but is included in this package because of its reliance on ProblemTypes.
func ProblemDetailsToStatusCode(prob *ProblemDetails) int {
if prob.HTTPStatus != 0 {
return prob.HTTPStatus
}
switch prob.Type {
case
ConnectionProblem,
MalformedProblem,
TLSProblem,
UnknownHostProblem,
BadNonceProblem,
InvalidEmailProblem,
RejectedIdentifierProblem,
AccountDoesNotExistProblem:
return http.StatusBadRequest
case ServerInternalProblem:
return http.StatusInternalServerError
case
UnauthorizedProblem,
CAAProblem:
return http.StatusForbidden
case RateLimitedProblem:
return statusTooManyRequests
default:
return http.StatusInternalServerError
}
}
// BadNonce returns a ProblemDetails with a BadNonceProblem and a 400 Bad
// Request status code.
func BadNonce(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: BadNonceProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// RejectedIdentifier returns a ProblemDetails with a RejectedIdentifierProblem and a 400 Bad
// Request status code.
func RejectedIdentifier(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: RejectedIdentifierProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// Conflict returns a ProblemDetails with a MalformedProblem and a 409 Conflict
// status code.
func Conflict(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusConflict,
}
}
// Malformed returns a ProblemDetails with a MalformedProblem and a 400 Bad
// Request status code.
func Malformed(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// NotFound returns a ProblemDetails with a MalformedProblem and a 404 Not Found
// status code.
func NotFound(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusNotFound,
}
}
// ServerInternal returns a ProblemDetails with a ServerInternalProblem and a
// 500 Internal Server Failure status code.
func ServerInternal(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: ServerInternalProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusInternalServerError,
}
}
// Unauthorized returns a ProblemDetails with an UnauthorizedProblem and a 403
// Forbidden status code.
func Unauthorized(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: UnauthorizedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusForbidden,
}
}
// MethodNotAllowed returns a ProblemDetails representing a disallowed HTTP
// method error.
func MethodNotAllowed() *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: "Method not allowed",
HTTPStatus: http.StatusMethodNotAllowed,
}
}
// ContentLengthRequired returns a ProblemDetails representing a missing
// Content-Length header error
func ContentLengthRequired() *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: "missing Content-Length header",
HTTPStatus: http.StatusLengthRequired,
}
}
// InvalidContentType returns a ProblemDetails suitable for a missing
// ContentType header, or an incorrect ContentType header
func InvalidContentType(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: MalformedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusUnsupportedMediaType,
}
}
// InvalidEmail returns a ProblemDetails representing an invalid email address
// error
func InvalidEmail(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: InvalidEmailProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// ConnectionFailure returns a ProblemDetails representing a ConnectionProblem
// error
func ConnectionFailure(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: ConnectionProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// UnknownHost returns a ProblemDetails representing an UnknownHostProblem error
func UnknownHost(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: UnknownHostProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// RateLimited returns a ProblemDetails representing a RateLimitedProblem error
func RateLimited(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: RateLimitedProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: statusTooManyRequests,
}
}
// TLSError returns a ProblemDetails representing a TLSProblem error
func TLSError(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: TLSProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// AccountDoesNotExist returns a ProblemDetails representing an
// AccountDoesNotExistProblem error
func AccountDoesNotExist(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: AccountDoesNotExistProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}
// CAA returns a ProblemDetails representing a CAAProblem
func CAA(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: CAAProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusForbidden,
}
}
// DNS returns a ProblemDetails representing a DNSProblem
func DNS(detail string, a ...interface{}) *ProblemDetails {
return &ProblemDetails{
Type: DNSProblem,
Detail: fmt.Sprintf(detail, a...),
HTTPStatus: http.StatusBadRequest,
}
}