Skip to content

Commit

Permalink
Added Comments (#30)
Browse files Browse the repository at this point in the history
* Added Comments

* Fixed typo
  • Loading branch information
kristinapathak authored and kcajmagic committed Jul 8, 2019
1 parent 9e68c4b commit 5022272
Show file tree
Hide file tree
Showing 18 changed files with 118 additions and 25 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ an http request. The JWT acquirer gets a JWT from a configurable endpoint,
caches it, and will get a new JWT at a configurable time before the current JWT
expires.

TODO: Add a static Bearer token, similar to the Basic one.

## Validating Authorization

Validation of Tokens happens once an authorization value has been parsed into
Expand Down
9 changes: 8 additions & 1 deletion bascule/acquire/auth.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
// package acquire is used for getting Auths to pass in http requests.
package acquire

import (
"net/http"

"github.com/goph/emperror"
"github.com/pkg/errors"
"net/http"
)

// Acquirer gets an Authorization value that can be added to an http request.
// The format of the string returned should be the key, a space, and then the
// auth string.
type Acquirer interface {
Acquire() (string, error)
}

// DefaultAcquirer returns nothing. This would not be a valid Authorization.
type DefaultAcquirer struct{}

func (d *DefaultAcquirer) Acquire() (string, error) {
return "", nil
}

// AddAuth adds an auth value to the Authorization header of an http request.
func AddAuth(r *http.Request, acquirer Acquirer) error {
if r == nil {
return errors.New("can't add authorization to nil request")
Expand Down
2 changes: 2 additions & 0 deletions bascule/acquire/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ var (
errMissingCredentials = errors.New("no credentials found")
)

// BasicAcquirer saves a basic auth upon creation and returns it whenever
// Acquire is called.
type BasicAcquirer struct {
encodedCredentials string
}
Expand Down
6 changes: 5 additions & 1 deletion bascule/acquire/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/goph/emperror"
"io/ioutil"
"net/http"
"time"

"github.com/goph/emperror"
)

// TODO: add a different basic JWT Acquirer that simply stores the JWT and
// rename this stuff.

type ParseToken func([]byte) (string, error)

func DefaultTokenParser(data []byte) (string, error) {
Expand Down
14 changes: 13 additions & 1 deletion bascule/basculehttp/constructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
)

const (
// DefaultHeaderName is the http header to get the authorization
// information from.
DefaultHeaderName = "Authorization"
)

Expand Down Expand Up @@ -88,8 +90,12 @@ func (c *constructor) error(logger bascule.Logger, e ErrorResponseReason, auth s
c.onErrorResponse(e, err)
}

// COption is any function that modifies the constructor - used to configure
// the constructor.
type COption func(*constructor)

// WithHeaderName sets the headername and verifies it's valid. The headername
// is the name of the header to get the authorization information from.
func WithHeaderName(headerName string) COption {
return func(c *constructor) {
if len(headerName) > 0 {
Expand All @@ -100,25 +106,31 @@ func WithHeaderName(headerName string) COption {
}
}

// WithTokenFactory sets the TokenFactory for the constructor to use.
func WithTokenFactory(key bascule.Authorization, tf TokenFactory) COption {
return func(c *constructor) {
c.authorizations[key] = tf
}
}

// WithCLogger sets the function to use to get the logger from the context.
// If no logger is set, nothing is logged.
func WithCLogger(getLogger func(context.Context) bascule.Logger) COption {
return func(c *constructor) {
c.getLogger = getLogger
}
}

// WithCErrorResponseFunc sets the function that is called when an error occurs.
func WithCErrorResponseFunc(f OnErrorResponse) COption {
return func(c *constructor) {
c.onErrorResponse = f
}
}

// New returns an Alice-style constructor which decorates HTTP handlers with security code
// NewConstructor creates an Alice-style decorator function that acts as
// middleware: parsing the http request to get a Token, which is added to the
// context.
func NewConstructor(options ...COption) func(http.Handler) http.Handler {
c := &constructor{
headerName: DefaultHeaderName,
Expand Down
20 changes: 16 additions & 4 deletions bascule/basculehttp/enforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ import (

//go:generate stringer -type=NotFoundBehavior

// NotFoundBehavior is an enum that specifies what to do when the
// Authorization used isn't found in the map of rules.
type NotFoundBehavior int

// Behavior on not found
const (
Forbid NotFoundBehavior = iota
Allow
)

type enforcer struct {
notFoundBehavior NotFoundBehavior
rules map[bascule.Authorization]bascule.Validators
rules map[bascule.Authorization]bascule.Validator
getLogger func(context.Context) bascule.Logger
onErrorResponse OnErrorResponse
}
Expand Down Expand Up @@ -75,35 +76,46 @@ func (e *enforcer) decorate(next http.Handler) http.Handler {
})
}

// EOption is any function that modifies the enforcer - used to configure
// the enforcer.
type EOption func(*enforcer)

// WithNotFoundBehavior sets the behavior upon not finding the Authorization
// value in the rules map.
func WithNotFoundBehavior(behavior NotFoundBehavior) EOption {
return func(e *enforcer) {
e.notFoundBehavior = behavior
}
}

func WithRules(key bascule.Authorization, v bascule.Validators) EOption {
// WithRules sets the validator to be used for a given Authorization value.
func WithRules(key bascule.Authorization, v bascule.Validator) EOption {
return func(e *enforcer) {
e.rules[key] = v
}
}

// WithELogger sets the function to use to get the logger from the context.
// If no logger is set, nothing is logged.
func WithELogger(getLogger func(context.Context) bascule.Logger) EOption {
return func(e *enforcer) {
e.getLogger = getLogger
}
}

// WithEErrorResponseFunc sets the function that is called when an error occurs.
func WithEErrorResponseFunc(f OnErrorResponse) EOption {
return func(e *enforcer) {
e.onErrorResponse = f
}
}

// NewListenerDecorator creates an Alice-style decorator function that acts as
// middleware, allowing for Listeners to be called after a token has been
// authenticated.
func NewEnforcer(options ...EOption) func(http.Handler) http.Handler {
e := &enforcer{
rules: make(map[bascule.Authorization]bascule.Validators),
rules: make(map[bascule.Authorization]bascule.Validator),
getLogger: bascule.GetDefaultLoggerFunc,
onErrorResponse: DefaultOnErrorResponse,
}
Expand Down
7 changes: 6 additions & 1 deletion bascule/basculehttp/errorResponseReason.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package basculehttp

//go:generate stringer -type=ErrorResponseReason

// ErrorResponseReason is an enum that specifies the reason parsing/validating
// a token failed. Its primary use is for metrics and logging.
type ErrorResponseReason int

// Behavior on not found
const (
MissingHeader ErrorResponseReason = iota
InvalidHeader
Expand All @@ -14,6 +16,9 @@ const (
ChecksFailed
)

// OnErrorResponse is a function that takes the error response reason and the
// error and can do something with it. This is useful for adding additional
// metrics or logs.
type OnErrorResponse func(ErrorResponseReason, error)

// default function does nothing
Expand Down
9 changes: 8 additions & 1 deletion bascule/basculehttp/http.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// package basculehttp contains some basic http middleware (in the form of
// Alice-style decorators) that can be used to extract and parse a Token from
// an http header, validate the Token, and allow for the consumer to add
// additional logs or metrics upon an error or a valid Token.
package basculehttp

import "net/http"
Expand All @@ -8,11 +12,14 @@ type statusCoder interface {
StatusCode() int
}

// headerer allows errors and other types to supply headers, mainly for writing HTTP responses.
// headerer allows errors and other types to supply headers, mainly for writing
// HTTP responses.
type headerer interface {
Headers() http.Header
}

// ErrorHeaderer implements headerer, allowing an error to supply http headers
// in an error response.
type ErrorHeaderer struct {
err error
headers http.Header
Expand Down
5 changes: 5 additions & 0 deletions bascule/basculehttp/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/Comcast/comcast-bascule/bascule"
)

// Listener is anything that takes the Authentication information of an
// authenticated Token.
type Listener interface {
OnAuthenticated(bascule.Authentication)
}
Expand All @@ -30,6 +32,9 @@ func (l *listenerDecorator) decorate(next http.Handler) http.Handler {
})
}

// NewListenerDecorator creates an Alice-style decorator function that acts as
// middleware, allowing for Listeners to be called after a token has been
// authenticated.
func NewListenerDecorator(listeners ...Listener) func(http.Handler) http.Handler {
l := &listenerDecorator{}

Expand Down
22 changes: 16 additions & 6 deletions bascule/basculehttp/tokenFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,27 @@ var (
ErrorUnexpectedClaims = errors.New("claims wasn't MapClaims as expected")
)

// TokenFactory is a strategy interface responsible for creating and validating a secure token
// TokenFactory is a strategy interface responsible for creating and validating
// a secure Token.
type TokenFactory interface {
ParseAndValidate(context.Context, *http.Request, bascule.Authorization, string) (bascule.Token, error)
}

// TokenFactoryFunc makes it so any function that has the same signature as
// TokenFactory's ParseAndValidate function implements TokenFactory.
type TokenFactoryFunc func(context.Context, *http.Request, bascule.Authorization, string) (bascule.Token, error)

func (tff TokenFactoryFunc) ParseAndValidate(ctx context.Context, r *http.Request, a bascule.Authorization, v string) (bascule.Token, error) {
return tff(ctx, r, a, v)
}

// An example TokenFactory that this package should supply in some form.
// This type allows client code to simply use an in-memory map of users and passwords
// to authenticate against. Other implementations might look things up in a database, etc.
// BasicTokenFactory parses a basic auth and verifies it is in a map of valid
// basic auths.
type BasicTokenFactory map[string]string

// ParseAndValidate expects the given value to be a base64 encoded string with
// the username followed by a colon and then the password. The function checks
// that the username password pair is in the map and returns a Token if it is.
func (btf BasicTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Request, _ bascule.Authorization, value string) (bascule.Token, error) {
decoded, err := base64.StdEncoding.DecodeString(value)
if err != nil {
Expand All @@ -64,18 +69,23 @@ func (btf BasicTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Reque
// failed authentication
return nil, ErrorInvalidPassword
}
// "basic" is a placeholder here ... token types won't always map to the Authorization header.
// For example, a JWT should have a type of "jwt" or some such, not "bearer"
// "basic" is a placeholder here ... token types won't always map to the
// Authorization header. For example, a JWT should have a type of "jwt" or some such, not "bearer"
return bascule.NewToken("basic", principal, bascule.Attributes{}), nil
}

// BearerTokenFactory parses and does basic validation for a JWT token.
type BearerTokenFactory struct {
DefaultKeyId string
Resolver key.Resolver
Parser bascule.JWTParser
Leeway bascule.Leeway
}

// ParseAndValidate expects the given value to be a JWT with a kid header. The
// kid should be resolvable by the Resolver and the JWT should be Parseable and
// pass any basic validation checks done by the Parser. If everything goes
// well, a Token of type "jwt" is returned.
func (btf BearerTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Request, _ bascule.Authorization, value string) (bascule.Token, error) {
if len(value) == 0 {
return nil, errors.New("empty value")
Expand Down
14 changes: 13 additions & 1 deletion bascule/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ const (
capabilitiesKey = "capabilities"
)

// CreateAllowAllCheck returns a Validator that never returns an error.
func CreateAllowAllCheck() ValidatorFunc {
return func(_ context.Context, _ Token) error {
return nil
}
}

// CreateValidTypeCheck returns a Validator that checks that the token's type
// is one of the given valid types.
func CreateValidTypeCheck(validTypes []string) ValidatorFunc {
return func(_ context.Context, token Token) error {
tt := token.Type()
Expand All @@ -32,6 +35,8 @@ func CreateValidTypeCheck(validTypes []string) ValidatorFunc {
}
}

// CreateNonEmptyTypeCheck returns a Validator that checks that the token's
// type isn't an empty string.
func CreateNonEmptyTypeCheck() ValidatorFunc {
return func(_ context.Context, token Token) error {
if token.Type() == "" {
Expand All @@ -41,6 +46,8 @@ func CreateNonEmptyTypeCheck() ValidatorFunc {
}
}

// CreateNonEmptyPrincipalCheck returns a Validator that checks that the
// token's Principal isn't an empty string.
func CreateNonEmptyPrincipalCheck() ValidatorFunc {
return func(_ context.Context, token Token) error {
if token.Principal() == "" {
Expand All @@ -50,6 +57,9 @@ func CreateNonEmptyPrincipalCheck() ValidatorFunc {
}
}

// CreateListAttributeCheck returns a Validator that runs checks against the
// content found in the key given. It runs every check and returns all errors
// it finds.
func CreateListAttributeCheck(key string, checks ...func(context.Context, []interface{}) error) ValidatorFunc {
return func(ctx context.Context, token Token) error {
val, ok := token.Attributes()[key]
Expand All @@ -74,7 +84,9 @@ func CreateListAttributeCheck(key string, checks ...func(context.Context, []inte
}
}

func NonEmptyStringListCheck(ctx context.Context, vals []interface{}) error {
// NonEmptyStringListCheck checks that the list of values given are a list of
// one or more nonempty strings.
func NonEmptyStringListCheck(_ context.Context, vals []interface{}) error {
if len(vals) == 0 {
return errors.New("expected at least one value")
}
Expand Down
5 changes: 5 additions & 0 deletions bascule/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@ type Authentication struct {
Request Request
}

// Request holds request information that may be useful for validating the
// token.
type Request struct {
URL string
Method string
}

type authenticationKey struct{}

// WithAuthentication adds the auth given to the context given, provided a way
// for other users of the context to get the authentication.
func WithAuthentication(ctx context.Context, auth Authentication) context.Context {
return context.WithValue(ctx, authenticationKey{}, auth)
}

// FromContext gets the Authentication from the context provided.
func FromContext(ctx context.Context) (Authentication, bool) {
auth, ok := ctx.Value(authenticationKey{}).(Authentication)
return auth, ok
Expand Down
Loading

0 comments on commit 5022272

Please sign in to comment.