254 lines
7.7 KiB
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,
|
|
}
|
|
}
|