garm/vendor/github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery/discharge.go
Gabriel Adrian Samfira c61b7fd268
Update go modules
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2023-03-12 16:22:37 +02:00

367 lines
12 KiB
Go

package httpbakery
import (
"context"
"encoding/base64"
"net/http"
"path"
"unicode/utf8"
"github.com/julienschmidt/httprouter"
"gopkg.in/errgo.v1"
"gopkg.in/httprequest.v1"
"gopkg.in/macaroon.v2"
"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers"
)
// ThirdPartyCaveatChecker is used to check third party caveats.
// This interface is deprecated and included only for backward
// compatibility; ThirdPartyCaveatCheckerP should be used instead.
type ThirdPartyCaveatChecker interface {
// CheckThirdPartyCaveat is like ThirdPartyCaveatCheckerP.CheckThirdPartyCaveat
// except that it uses separate arguments instead of a struct arg.
CheckThirdPartyCaveat(ctx context.Context, info *bakery.ThirdPartyCaveatInfo, req *http.Request, token *DischargeToken) ([]checkers.Caveat, error)
}
// ThirdPartyCaveatCheckerP is used to check third party caveats.
// The "P" stands for "Params" - this was added after ThirdPartyCaveatChecker
// which can't be removed without breaking backwards compatibility.
type ThirdPartyCaveatCheckerP interface {
// CheckThirdPartyCaveat is used to check whether a client
// making the given request should be allowed a discharge for
// the p.Info.Condition. On success, the caveat will be discharged,
// with any returned caveats also added to the discharge
// macaroon.
//
// The p.Token field, if non-nil, is a token obtained from
// Interactor.Interact as the result of a discharge interaction
// after an interaction required error.
//
// Note than when used in the context of a discharge handler
// created by Discharger, any returned errors will be marshaled
// as documented in DischargeHandler.ErrorMapper.
CheckThirdPartyCaveat(ctx context.Context, p ThirdPartyCaveatCheckerParams) ([]checkers.Caveat, error)
}
// ThirdPartyCaveatCheckerParams holds the parameters passed to
// CheckThirdPartyCaveatP.
type ThirdPartyCaveatCheckerParams struct {
// Caveat holds information about the caveat being discharged.
Caveat *bakery.ThirdPartyCaveatInfo
// Token holds the discharge token provided by the client, if any.
Token *DischargeToken
// Req holds the HTTP discharge request.
Request *http.Request
// Response holds the HTTP response writer. Implementations
// must not call its WriteHeader or Write methods.
Response http.ResponseWriter
}
// ThirdPartyCaveatCheckerFunc implements ThirdPartyCaveatChecker
// by calling a function.
type ThirdPartyCaveatCheckerFunc func(ctx context.Context, req *http.Request, info *bakery.ThirdPartyCaveatInfo, token *DischargeToken) ([]checkers.Caveat, error)
func (f ThirdPartyCaveatCheckerFunc) CheckThirdPartyCaveat(ctx context.Context, info *bakery.ThirdPartyCaveatInfo, req *http.Request, token *DischargeToken) ([]checkers.Caveat, error) {
return f(ctx, req, info, token)
}
// ThirdPartyCaveatCheckerPFunc implements ThirdPartyCaveatCheckerP
// by calling a function.
type ThirdPartyCaveatCheckerPFunc func(ctx context.Context, p ThirdPartyCaveatCheckerParams) ([]checkers.Caveat, error)
func (f ThirdPartyCaveatCheckerPFunc) CheckThirdPartyCaveat(ctx context.Context, p ThirdPartyCaveatCheckerParams) ([]checkers.Caveat, error) {
return f(ctx, p)
}
// newDischargeClient returns a discharge client that addresses the
// third party discharger at the given location URL and uses
// the given client to make HTTP requests.
//
// If client is nil, http.DefaultClient is used.
func newDischargeClient(location string, client httprequest.Doer) *dischargeClient {
if client == nil {
client = http.DefaultClient
}
return &dischargeClient{
Client: httprequest.Client{
BaseURL: location,
Doer: client,
UnmarshalError: unmarshalError,
},
}
}
// Discharger holds parameters for creating a new Discharger.
type DischargerParams struct {
// CheckerP is used to actually check the caveats.
// This will be used in preference to Checker.
CheckerP ThirdPartyCaveatCheckerP
// Checker is used to actually check the caveats.
// This should be considered deprecated and will be ignored if CheckerP is set.
Checker ThirdPartyCaveatChecker
// Key holds the key pair of the discharger.
Key *bakery.KeyPair
// Locator is used to find public keys when adding
// third-party caveats on discharge macaroons.
// If this is nil, no third party caveats may be added.
Locator bakery.ThirdPartyLocator
// ErrorToResponse is used to convert errors returned by the third
// party caveat checker to the form that will be JSON-marshaled
// on the wire. If zero, this defaults to ErrorToResponse.
// If set, it should handle errors that it does not understand
// by falling back to calling ErrorToResponse to ensure
// that the standard bakery errors are marshaled in the expected way.
ErrorToResponse func(ctx context.Context, err error) (int, interface{})
}
// Discharger represents a third-party caveat discharger.
// can discharge caveats in an HTTP server.
//
// The name space served by dischargers is as follows.
// All parameters can be provided either as URL attributes
// or form attributes. The result is always formatted as a JSON
// object.
//
// On failure, all endpoints return an error described by
// the Error type.
//
// POST /discharge
// params:
// id: all-UTF-8 third party caveat id
// id64: non-padded URL-base64 encoded caveat id
// macaroon-id: (optional) id to give to discharge macaroon (defaults to id)
// token: (optional) value of discharge token
// token64: (optional) base64-encoded value of discharge token.
// token-kind: (mandatory if token or token64 provided) discharge token kind.
// result on success (http.StatusOK):
// {
// Macaroon *macaroon.Macaroon
// }
//
// GET /publickey
// result:
// public key of service
// expiry time of key
type Discharger struct {
p DischargerParams
}
// NewDischarger returns a new third-party caveat discharger
// using the given parameters.
func NewDischarger(p DischargerParams) *Discharger {
if p.ErrorToResponse == nil {
p.ErrorToResponse = ErrorToResponse
}
if p.Locator == nil {
p.Locator = emptyLocator{}
}
if p.CheckerP == nil {
p.CheckerP = ThirdPartyCaveatCheckerPFunc(func(ctx context.Context, cp ThirdPartyCaveatCheckerParams) ([]checkers.Caveat, error) {
return p.Checker.CheckThirdPartyCaveat(ctx, cp.Caveat, cp.Request, cp.Token)
})
}
return &Discharger{
p: p,
}
}
type emptyLocator struct{}
func (emptyLocator) ThirdPartyInfo(ctx context.Context, loc string) (bakery.ThirdPartyInfo, error) {
return bakery.ThirdPartyInfo{}, bakery.ErrNotFound
}
// AddMuxHandlers adds handlers to the given ServeMux to provide
// a third-party caveat discharge service.
func (d *Discharger) AddMuxHandlers(mux *http.ServeMux, rootPath string) {
for _, h := range d.Handlers() {
// Note: this only works because we don't have any wildcard
// patterns in the discharger paths.
mux.Handle(path.Join(rootPath, h.Path), mkHTTPHandler(h.Handle))
}
}
// Handlers returns a slice of handlers that can handle a third-party
// caveat discharge service when added to an httprouter.Router.
// TODO provide some way of customizing the context so that
// ErrorToResponse can see a request-specific context.
func (d *Discharger) Handlers() []httprequest.Handler {
f := func(p httprequest.Params) (dischargeHandler, context.Context, error) {
return dischargeHandler{
discharger: d,
}, p.Context, nil
}
srv := httprequest.Server{
ErrorMapper: d.p.ErrorToResponse,
}
return srv.Handlers(f)
}
//go:generate httprequest-generate-client github.com/go-macaroon-bakery/macaroon-bakery/v3-unstable/httpbakery dischargeHandler dischargeClient
// dischargeHandler is the type used to define the httprequest handler
// methods for a discharger.
type dischargeHandler struct {
discharger *Discharger
}
// dischargeRequest is a request to create a macaroon that discharges the
// supplied third-party caveat. Discharging caveats will normally be
// handled by the bakery it would be unusual to use this type directly in
// client software.
type dischargeRequest struct {
httprequest.Route `httprequest:"POST /discharge"`
Id string `httprequest:"id,form,omitempty"`
Id64 string `httprequest:"id64,form,omitempty"`
Caveat string `httprequest:"caveat64,form,omitempty"`
Token string `httprequest:"token,form,omitempty"`
Token64 string `httprequest:"token64,form,omitempty"`
TokenKind string `httprequest:"token-kind,form,omitempty"`
}
// dischargeResponse contains the response from a /discharge POST request.
type dischargeResponse struct {
Macaroon *bakery.Macaroon `json:",omitempty"`
}
// Discharge discharges a third party caveat.
func (h dischargeHandler) Discharge(p httprequest.Params, r *dischargeRequest) (*dischargeResponse, error) {
id, err := maybeBase64Decode(r.Id, r.Id64)
if err != nil {
return nil, errgo.Notef(err, "bad caveat id")
}
var caveat []byte
if r.Caveat != "" {
// Note that it's important that when r.Caveat is empty,
// we leave DischargeParams.Caveat as nil (Base64Decode
// always returns a non-nil byte slice).
caveat1, err := macaroon.Base64Decode([]byte(r.Caveat))
if err != nil {
return nil, errgo.Notef(err, "bad base64-encoded caveat: %v", err)
}
caveat = caveat1
}
tokenVal, err := maybeBase64Decode(r.Token, r.Token64)
if err != nil {
return nil, errgo.Notef(err, "bad discharge token")
}
var token *DischargeToken
if len(tokenVal) != 0 {
if r.TokenKind == "" {
return nil, errgo.Notef(err, "discharge token provided without token kind")
}
token = &DischargeToken{
Kind: r.TokenKind,
Value: tokenVal,
}
}
m, err := bakery.Discharge(p.Context, bakery.DischargeParams{
Id: id,
Caveat: caveat,
Key: h.discharger.p.Key,
Checker: bakery.ThirdPartyCaveatCheckerFunc(
func(ctx context.Context, cav *bakery.ThirdPartyCaveatInfo) ([]checkers.Caveat, error) {
return h.discharger.p.CheckerP.CheckThirdPartyCaveat(ctx, ThirdPartyCaveatCheckerParams{
Caveat: cav,
Request: p.Request,
Response: p.Response,
Token: token,
})
},
),
Locator: h.discharger.p.Locator,
})
if err != nil {
return nil, errgo.NoteMask(err, "cannot discharge", errgo.Any)
}
return &dischargeResponse{m}, nil
}
// publicKeyRequest specifies the /publickey endpoint.
type publicKeyRequest struct {
httprequest.Route `httprequest:"GET /publickey"`
}
// publicKeyResponse is the response to a /publickey GET request.
type publicKeyResponse struct {
PublicKey *bakery.PublicKey
}
// dischargeInfoRequest specifies the /discharge/info endpoint.
type dischargeInfoRequest struct {
httprequest.Route `httprequest:"GET /discharge/info"`
}
// dischargeInfoResponse is the response to a /discharge/info GET
// request.
type dischargeInfoResponse struct {
PublicKey *bakery.PublicKey
Version bakery.Version
}
// PublicKey returns the public key of the discharge service.
func (h dischargeHandler) PublicKey(*publicKeyRequest) (publicKeyResponse, error) {
return publicKeyResponse{
PublicKey: &h.discharger.p.Key.Public,
}, nil
}
// DischargeInfo returns information on the discharger.
func (h dischargeHandler) DischargeInfo(*dischargeInfoRequest) (dischargeInfoResponse, error) {
return dischargeInfoResponse{
PublicKey: &h.discharger.p.Key.Public,
Version: bakery.LatestVersion,
}, nil
}
// mkHTTPHandler converts an httprouter handler to an http.Handler,
// assuming that the httprouter handler has no wildcard path
// parameters.
func mkHTTPHandler(h httprouter.Handle) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
h(w, req, nil)
})
}
// maybeBase64Encode encodes b as is if it's
// OK to be passed as a URL form parameter,
// or encoded as base64 otherwise.
func maybeBase64Encode(b []byte) (s, s64 string) {
if utf8.Valid(b) {
valid := true
for _, c := range b {
if c < 32 || c == 127 {
valid = false
break
}
}
if valid {
return string(b), ""
}
}
return "", base64.RawURLEncoding.EncodeToString(b)
}
// maybeBase64Decode implements the inverse of maybeBase64Encode.
func maybeBase64Decode(s, s64 string) ([]byte, error) {
if s64 != "" {
data, err := macaroon.Base64Decode([]byte(s64))
if err != nil {
return nil, errgo.Mask(err)
}
if len(data) == 0 {
return nil, nil
}
return data, nil
}
return []byte(s), nil
}