basculehttp

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2024 License: Apache-2.0 Imports: 9 Imported by: 20

Documentation

Overview

Package basculehttp provides a token-based security workflow for HTTP handlers using bascule.

Index

Examples

Constants

View Source
const (
	// WWWAuthenticateHeader is the HTTP header used for StatusUnauthorized challenges
	// when encountered by the Middleware.
	//
	// This value is used by default when no header is supplied to Challenges.WriteHeader.
	WWWAuthenticateHeader = "WWW-Authenticate"

	// RealmParameter is the name of the reserved parameter for realm.
	RealmParameter = "realm"

	// CharsetParameter is the name of the reserved parameter for charset.
	CharsetParameter = "charset"

	// Token68Parameter is the name of the reserved attribute for token68 encoding.
	// This package does not support token68 encoding.
	Token68Parameter = "token68"
)
View Source
const (
	// DefaultAuthorizationHeader is the default HTTP header used for authorization
	// tokens in an HTTP request.
	DefaultAuthorizationHeader = "Authorization"
)

Variables

View Source
var (
	// ErrInvalidChallengeScheme indicates that a scheme was improperly formatted.  Usually,
	// this methods the scheme was either blank or contained whitespace.
	ErrInvalidChallengeScheme = errors.New("Invalid challenge auth scheme")

	// ErrInvalidChallengeParameter indicates that an attempt was made to an a challenge
	// auth parameter that wasn't validly formatted.  Usually, this means that the
	// name contained whitespace.
	ErrInvalidChallengeParameter = errors.New("Invalid challenge auth parameter")

	// ErrReservedChallengeParameter indicates that an attempt was made to add a
	// challenge auth parameter that was reserved by the RFC.
	//
	// Since this package explicitly does not support token68, trying to set a token68
	// parameter results in this error.
	ErrReservedChallengeParameter = errors.New("Reserved challenge auth parameter")
)
View Source
var (
	// ErrInvalidAuthorization indicates an authorization header value did not
	// correspond to the standard.
	ErrInvalidAuthorization = errors.New("invalid authorization")
)
View Source
var (
	// ErrNoAuthenticator is returned by NewMiddleware to indicate that an Authorizer
	// was configured without an Authenticator.
	ErrNoAuthenticator = errors.New("An Authenticator is required if an Authorizer is configured")
)

Functions

func AsValidator added in v1.1.0

AsValidator an HTTP-specific version of bascule.AsValidator. This function eases the syntactic pain of using golang's generics.

func BasicAuth added in v1.0.0

func BasicAuth(userName, password string) string

BasicAuth produces the basic authorization string described by RFC 2617.

func DefaultErrorMarshaler added in v1.0.0

func DefaultErrorMarshaler(_ *http.Request, err error) (contentType string, content []byte, marshalErr error)

DefaultErrorMarshaler returns a plaintext representation of the error.

func DefaultErrorStatusCoder added in v1.0.0

func DefaultErrorStatusCoder(_ *http.Request, err error) int

DefaultErrorStatusCoder is the strategy used when no ErrorStatusCoder is supplied. The following tests are done in order:

(1) First, if err is nil, this method returns 0.

(2) If any error in the chain provides a 'StatusCode() int' method, the result from that method is returned.

(3) If err has bascule.ErrMissingCredentials in its chain, this function returns http.StatusUnauthorized.

(3) If err has bascule.ErrBadCredentials in its chain, this function returns http.StatusUnauthorized.

(4) If err has bascule.ErrUnauthorized in its chain, this function returns http.StatusForbidden.

(5) If err has bascule.ErrInvalidCredentials in its chain, this function returns http.StatusBadRequest.

(6) Otherwise, this method returns 0 to indicate that it doesn't know how to produce a status code from the error.

func NewAuthenticator added in v1.0.0

func NewAuthenticator(opts ...bascule.AuthenticatorOption[*http.Request]) (*bascule.Authenticator[*http.Request], error)

NewAuthenticator is a convenient wrapper around bascule.NewAuthenticator. This function eases the syntactical pain of generics when creating Middleware.

func NewAuthorizer added in v1.0.0

func NewAuthorizer(opts ...bascule.AuthorizerOption[*http.Request]) (*bascule.Authorizer[*http.Request], error)

NewAuthorizer is a convenient wrapper around bascule.NewAuthorizer. This function eases the syntactical pain of generics when creating Middleware.

func UseStatusCode added in v1.0.0

func UseStatusCode(statusCode int, err error) error

UseStatusCode associates an HTTP status code with the given error. This function will override any existing status code associated with err.

Types

type AuthorizationParser added in v1.0.0

type AuthorizationParser struct {
	// contains filtered or unexported fields
}

AuthorizationParsers is a bascule.TokenParser that handles the Authorization header.

By default, this parser will use the standard Authorization header, which can be changed via with WithAuthorizationHeader option.

func NewAuthorizationParser added in v1.0.0

func NewAuthorizationParser(opts ...AuthorizationParserOption) (*AuthorizationParser, error)

NewAuthorizationParser constructs an Authorization parser from a set of configuration options.

func (*AuthorizationParser) Parse added in v1.0.0

func (ap *AuthorizationParser) Parse(ctx context.Context, source *http.Request) (bascule.Token, error)

Parse extracts the appropriate header, Authorization by default, and parses the scheme and value. Schemes are case-insensitive, e.g. BASIC and Basic are the same scheme.

If no authorization header is found in the request, this method returns ErrMissingCredentials.

If a token parser is registered for the given scheme, that token parser is invoked. Otherwise, UnsupportedSchemeError is returned, indicating the scheme in question.

type AuthorizationParserOption added in v1.0.0

type AuthorizationParserOption interface {
	// contains filtered or unexported methods
}

AuthorizationParserOption is a configurable option for an AuthorizationParser.

func WithAuthorizationHeader added in v1.0.0

func WithAuthorizationHeader(header string) AuthorizationParserOption

WithAuthorizationHeader changes the name of the header holding the token. By default, the header used is DefaultAuthorizationHeader.

func WithBasic added in v1.0.0

func WithBasic() AuthorizationParserOption

WithBasic is a shorthand for WithScheme that registers basic token parsing using the default scheme.

func WithScheme added in v1.0.0

func WithScheme(scheme Scheme, parser bascule.TokenParser[string]) AuthorizationParserOption

WithScheme registers a string-based token parser that handles a specific authorization scheme. Invocations to this option are cumulative and will overwrite any existing registration.

type BasicToken added in v1.0.0

type BasicToken interface {
	UserName() string
	Password() string
}

BasicToken is the interface that Basic Auth tokens implement.

type BasicTokenParser added in v1.0.0

type BasicTokenParser struct{}

BasicTokenParser is a string-based bascule.TokenParser that produces BasicToken instances from strings.

func (BasicTokenParser) Parse added in v1.0.0

Parse assumes that value is of the format required by https://datatracker.ietf.org/doc/html/rfc7617. The returned Token will return the basic auth username from its Principal() method. The returned Token will also implement BasicToken.

type Challenge added in v1.0.0

type Challenge struct {
	// Scheme is the name of scheme supplied in the challenge.  This field is required.
	Scheme Scheme

	// Parameters are the optional auth parameters.
	Parameters ChallengeParameters
}

Challenge represets an HTTP authentication challenge, as defined by RFC 7235.

func NewBasicChallenge added in v1.0.0

func NewBasicChallenge(realm string, UTF8 bool) (c Challenge)

NewBasicChallenge is a convenience for creating a Challenge for basic auth.

Although realm is optional, it is HIGHLY recommended to set it to something recognizable for a client.

func (Challenge) Write added in v1.0.0

func (c Challenge) Write(o *strings.Builder) (err error)

Write formats this challenge to the given builder. Any error halts formatting and that error is returned.

type ChallengeParameters added in v1.0.0

type ChallengeParameters struct {
	// contains filtered or unexported fields
}

ChallengeParameters holds the set of parameters. The zero value of this type is ready to use. This type handles writing parameters as well as provides commonly used parameter names for convenience.

It is not required by spec, but any realm parameter is always placed first. Additionally, the output of parameters is consistently ordered and will always follow the order in which the parameters were set, realm being the exception.

Token68 is not supported. Any attempt to set that parameter will result in an error.

func NewChallengeParameters added in v1.0.0

func NewChallengeParameters(s ...string) (cp ChallengeParameters, err error)

NewChallengeParameters creates a ChallengeParameters from a sequence of name/value pairs. The strings are expected to be in name1, value1, name2, value2, ..., nameN, valueN sequence. If the number of strings is odd, this method returns an error. If any duplicate names occur, only the last name/value pair is used.

If any error occurs while setting parameters, execution is halted and that error is returned.

func (*ChallengeParameters) Len added in v1.0.0

func (cp *ChallengeParameters) Len() (c int)

Len returns the number of name/value pairs contained in these parameters.

func (*ChallengeParameters) Set added in v1.0.0

func (cp *ChallengeParameters) Set(name, value string) (err error)

Set sets the value of a parameter. If a parameter was already set, it is ovewritten. The realm may be set via this method, but token68 will be rejected as invalid.

This method returns ErrInvalidChallengeParameter if passed a name or a value that is blank or contains whitespace.

func (*ChallengeParameters) SetCharset added in v1.1.0

func (cp *ChallengeParameters) SetCharset(value string) (err error)

SetCharset sets a charset auth parameter. Basic auth is the main scheme that uses this. The value cannot be blank or contain any whitespace.

func (*ChallengeParameters) SetRealm added in v1.1.0

func (cp *ChallengeParameters) SetRealm(value string) (err error)

SetRealm sets a realm auth parameter. The value cannot be blank or contain any whitespace.

func (*ChallengeParameters) String added in v1.0.0

func (cp *ChallengeParameters) String() string

String returns the RFC 7235 format of these parameters.

func (*ChallengeParameters) Write added in v1.0.0

func (cp *ChallengeParameters) Write(dst *strings.Builder)

Write formats this challenge to the given builder.

type Challenges added in v1.0.0

type Challenges []Challenge

Challenges represents a sequence of challenges to associated with a StatusUnauthorized response.

func (Challenges) Append added in v1.0.0

func (chs Challenges) Append(ch ...Challenge) Challenges

Append appends challenges to this set. The semantics of this method are the same as the built-in append.

func (Challenges) WriteHeader added in v1.0.0

func (chs Challenges) WriteHeader(dst http.Header) error

WriteHeader write one WWWAuthenticateHeader for each challenge in this set.

If any challenge returns an error during formatting, execution is halted and that error is returned.

func (Challenges) WriteHeaderCustom added in v1.1.0

func (chs Challenges) WriteHeaderCustom(dst http.Header, name string) error

WriteHeaderCustom inserts one HTTP authenticate header per challenge in this set. If this set is empty, the given http.Header is not modified.

The name is used as the header name for each header this method writes. Typically, this will be WWW-Authenticate or Proxy-Authenticate. The name parameter is required.

If any challenge returns an error during formatting, execution is halted and that error is returned.

type ErrorMarshaler added in v1.0.0

type ErrorMarshaler func(request *http.Request, err error) (contentType string, content []byte, marshalErr error)

ErrorMarshaler is a strategy for marshaling an error's contents, particularly to be used in an HTTP response body.

type ErrorStatusCoder added in v1.0.0

type ErrorStatusCoder func(request *http.Request, err error) int

ErrorStatusCoder is a strategy for determining the HTTP response code for an error.

If this closure returns a value less than 100, which is the smallest valid HTTP response code, the caller should supply a useful default.

type Middleware added in v1.0.0

type Middleware struct {
	// contains filtered or unexported fields
}

Middleware is an immutable HTTP workflow that can decorate multiple handlers.

A Middleware can have either or both of an Authenticator, which creates tokens from HTTP requests, and an Authorizer, which approves access to the resource identified by the request. The behavior of a Middleware depends mostly on these two components.

If both an authenticator and an authorizer are supplied, the full bascule workflow, including events, is implemented.

If an authenticator is supplied without an authorizer, only token creation is implemented. Without an authorizer, it is assumed that all tokens have access to all requests.

If no authenticator is supplied, but an authorizer IS supplied, then NewMiddleware returns an error. An authenticator is required in order to create tokens.

Finally, if neither an authenticator or an authorizer is supplied, then this Middleware is a noop. Any attempt to decorate handlers will result in those handlers being returned as is. This allows a Middleware to be turned off via configuration.

Example (Authentication)

ExampleMiddleware_authentication shows how to authenticate a token.

tp, _ := NewAuthorizationParser(
	WithBasic(),
)

m, _ := NewMiddleware(
	UseAuthenticator(
		NewAuthenticator(
			bascule.WithTokenParsers(tp),
			bascule.WithValidators(
				AsValidator(
					// the signature of validator closures is very flexible
					// see bascule.AsValidator
					func(token bascule.Token) error {
						if basic, ok := token.(BasicToken); ok && basic.Password() == "correct_password" {
							return nil
						}

						return bascule.ErrBadCredentials
					},
				),
			),
		),
	),
)

h := m.ThenFunc(
	func(response http.ResponseWriter, request *http.Request) {
		t, _ := bascule.GetFrom(request)
		fmt.Println("principal:", t.Principal())
	},
)

requestForJoe := httptest.NewRequest("GET", "/", nil)
requestForJoe.SetBasicAuth("joe", "correct_password")
response := httptest.NewRecorder()
h.ServeHTTP(response, requestForJoe)
fmt.Println("we let joe in with the code:", response.Code)

requestForCurly := httptest.NewRequest("GET", "/", nil)
requestForCurly.SetBasicAuth("joe", "bad_password")
response = httptest.NewRecorder()
h.ServeHTTP(response, requestForCurly)
fmt.Println("this isn't joe:", response.Code)
Output:

principal: joe
we let joe in with the code: 200
this isn't joe: 401
Example (Authorization)

ExampleMiddleware_authorization shows how to set up custom authorization for tokens.

tp, _ := NewAuthorizationParser(
	WithBasic(),
)

m, _ := NewMiddleware(
	UseAuthenticator(
		NewAuthenticator(
			bascule.WithTokenParsers(tp),
		),
	),
	UseAuthorizer(
		NewAuthorizer(
			bascule.WithApproverFuncs(
				// this can also be a type that implements the bascule.Approver interface,
				// when used with bascule.WithApprovers
				func(_ context.Context, resource *http.Request, token bascule.Token) error {
					if token.Principal() != "joe" {
						// only joe can access this resource
						return bascule.ErrUnauthorized
					}

					return nil // approved
				},
			),
		),
	),
)

h := m.ThenFunc(
	func(response http.ResponseWriter, request *http.Request) {
		t, _ := bascule.GetFrom(request)
		fmt.Println("principal:", t.Principal())
	},
)

requestForJoe := httptest.NewRequest("GET", "/", nil)
requestForJoe.SetBasicAuth("joe", "password")
response := httptest.NewRecorder()
h.ServeHTTP(response, requestForJoe)
fmt.Println("we let joe in with the code:", response.Code)

requestForCurly := httptest.NewRequest("GET", "/", nil)
requestForCurly.SetBasicAuth("curly", "another_password")
response = httptest.NewRecorder()
h.ServeHTTP(response, requestForCurly)
fmt.Println("we didn't authorize curly:", response.Code)
Output:

principal: joe
we let joe in with the code: 200
we didn't authorize curly: 403
Example (Basicauth)

ExampleMiddleware_basicauth illustrates how to use a basculehttp Middleware with just basic auth.

tp, _ := NewAuthorizationParser(
	WithBasic(),
)

m, _ := NewMiddleware(
	UseAuthenticator(
		NewAuthenticator(
			bascule.WithTokenParsers(tp),
		),
	),
)

// decorate a handler that needs authorization
h := m.ThenFunc(
	func(response http.ResponseWriter, request *http.Request) {
		if t, ok := bascule.GetFrom(request); ok {
			fmt.Println("principal:", t.Principal())
		} else {
			fmt.Println("no token found")
		}
	},
)

// what happens when no authorization is set?
noAuth := httptest.NewRequest("GET", "/", nil)
response := httptest.NewRecorder()
h.ServeHTTP(response, noAuth)
fmt.Println("no authorization response code:", response.Code)

// what happens when a valid Basic token is set?
withBasic := httptest.NewRequest("GET", "/", nil)
withBasic.SetBasicAuth("joe", "password")
response = httptest.NewRecorder()
h.ServeHTTP(response, withBasic)
fmt.Println("with basic auth response code:", response.Code)
Output:

no authorization response code: 401
principal: joe
with basic auth response code: 200

func NewMiddleware added in v1.0.0

func NewMiddleware(opts ...MiddlewareOption) (m *Middleware, err error)

NewMiddleware creates an immutable Middleware instance from a supplied set of options. No options will result in a Middleware with default behavior.

If no authenticator is configured, but an authorizer is, this function returns ErrNoAuthenticator.

Note that if no workflow components are configured, i.e. neither an authenticator nor an authorizer are supplied, then the returned Middleware is a noop.

func (*Middleware) Then added in v1.0.0

func (m *Middleware) Then(protected http.Handler) http.Handler

Then produces an http.Handler that uses this Middleware's workflow to protected a given handler.

func (*Middleware) ThenFunc added in v1.0.0

func (m *Middleware) ThenFunc(protected http.HandlerFunc) http.Handler

ThenFunc is like Then, but protects a handler function.

type MiddlewareOption added in v1.0.0

type MiddlewareOption interface {
	// contains filtered or unexported methods
}

MiddlewareOption is a functional option for tailoring a Middleware.

func UseAuthenticator added in v1.0.0

func UseAuthenticator(authenticator *bascule.Authenticator[*http.Request], err error) MiddlewareOption

UseAuthenticator is a variant of WithAuthenticator that allows a caller to nest function calls a little easier. The output of NewAuthenticator can be passed directly to this option.

Note: If no authenticator is supplied, NewMiddeware returns an error.

func UseAuthorizer added in v1.0.0

func UseAuthorizer(authorizer *bascule.Authorizer[*http.Request], err error) MiddlewareOption

UseAuthorizer is a variant of WithAuthorizer that allows a caller to nest function calls a little easier. The output of NewAuthorizer can be passed directly to this option.

func WithAuthenticator added in v1.0.0

func WithAuthenticator(authenticator *bascule.Authenticator[*http.Request]) MiddlewareOption

WithAuthenticator supplies the Authenticator workflow for the middleware.

func WithAuthorizer added in v1.0.0

func WithAuthorizer(authorizer *bascule.Authorizer[*http.Request]) MiddlewareOption

WithAuthorizer supplies the Authorizer workflow for the middleware.

The Authorizer is optional. If no authorizer is supplied, then no authorization takes place and no authorization events are fired.

func WithChallenges added in v1.0.0

func WithChallenges(ch ...Challenge) MiddlewareOption

WithChallenges adds WWW-Authenticate challenges to be used when a StatusUnauthorized is detected. Multiple invocations of this option are cumulative. Each challenge results in a separate WWW-Authenticate header, in the order specified by this option.

func WithErrorMarshaler added in v1.0.0

func WithErrorMarshaler(em ErrorMarshaler) MiddlewareOption

WithErrorMarshaler sets the strategy used to marshal errors to HTTP response bodies. If this option is omitted or if esc is nil, DefaultErrorMarshaler is used.

func WithErrorStatusCoder added in v1.0.0

func WithErrorStatusCoder(esc ErrorStatusCoder) MiddlewareOption

WithErrorStatusCoder sets the strategy used to write errors to HTTP responses. If this option is omitted or if esc is nil, DefaultErrorStatusCoder is used.

type Scheme added in v1.0.0

type Scheme string

Scheme is the authorization header scheme, e.g. Basic, Bearer, etc.

const (
	// SchemeBasic is the Basic HTTP authorization scheme.
	SchemeBasic Scheme = "Basic"

	// SchemeBearer is the Bearer HTTP authorization scheme.
	SchemeBearer Scheme = "Bearer"
)

func ParseAuthorization added in v1.0.0

func ParseAuthorization(raw string) (s Scheme, v string, err error)

ParseAuthorization parses an authorization value typically passed in the Authorization HTTP header.

The required format is <scheme><single space><credential value>. This function is strict: it requires no leading or trailing space and exactly (1) space as a separator. If the raw value does not adhere to this format, ErrInvalidAuthorization is returned.

type UnsupportedSchemeError added in v1.0.0

type UnsupportedSchemeError struct {
	Scheme Scheme
}

UnsupportedSchemeError is used to indicate that a particular HTTP Authorization scheme is not supported by the server.

func (*UnsupportedSchemeError) Error added in v1.0.0

func (use *UnsupportedSchemeError) Error() string

func (*UnsupportedSchemeError) StatusCode added in v1.0.0

func (use *UnsupportedSchemeError) StatusCode() int

StatusCode marks this error as using the http.StatusUnauthorized code. This is appropriate for almost all cases, as this error occurs because the server does not accept or understand the scheme that the HTTP client supplied.

Directories

Path Synopsis
Package basculecaps provide a standard format for token capabilities in the context of HTTP-based workflow.
Package basculecaps provide a standard format for token capabilities in the context of HTTP-based workflow.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL