Skip to content

Commit

Permalink
uber fx Provide() funcs (#104)
Browse files Browse the repository at this point in the history
* initial provide funcs

* add lgtm comment

* added provide func for resolver

* split up tokenfactory file

* split up error responses from reasons, added provide

* tweak ProvideOnErrorHTTPResponse()

* added provide() for parseURL func

* handle nils better

* Added ProvideBearerTokenFactory()

and leeway options for the MetricListener

* experimenting with flatten

* groups cannot be optional

* fixing leeway, resolver names

* added provide funcs for basic auth

* trying out some bearer validator stuff

* wire in capability validator

* fixed group targets

* added capability check provides

* add a unit test for NewCapabilitiesMap()

* added doc.go files

* ignore nil metric options

* updated changelog

* started on comments, moved some code around

* basculechecks comments plus renaming RegexEndpointCheck

* basculehttp comments other than metric listener

* added documentation for metricListener.go
  • Loading branch information
kristinapathak authored May 19, 2021
1 parent 5a74a01 commit c011b12
Show file tree
Hide file tree
Showing 40 changed files with 1,504 additions and 535 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Removed Partner from ParsedValues. [#99](https://github.com/xmidt-org/bascule/pull/99)
- Fixed ProvideMetricValidator() so it works. [#100](https://github.com/xmidt-org/bascule/pull/100)
- Updated error response reason's string representation to be snake case. [#102](https://github.com/xmidt-org/bascule/pull/102)
- Updated objects created with options to ignore nils. [#104](https://github.com/xmidt-org/bascule/pull/104)
- Added Provide() functions in basculehttp and basculechecks for easier setup. [#104](https://github.com/xmidt-org/bascule/pull/104)

## [v0.9.0]
- added helper function for building basic auth map [#59](https://github.com/xmidt-org/bascule/pull/59)
Expand Down
50 changes: 49 additions & 1 deletion basculechecks/capabilitiesmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package basculechecks
import (
"errors"
"fmt"
"regexp"

"github.com/xmidt-org/bascule"
)
Expand All @@ -30,8 +31,18 @@ var (
err: errors.New("endpoint provided is empty"),
reason: EmptyParsedURL,
}
errRegexCompileFail = errors.New("failed to compile regexp")
)

// CapabilitiesMapConfig includes the values needed to set up a map capability
// checker. The checker will verify that one of the capabilities in a provided
// JWT match the string meant for that endpoint exactly. A CapabilitiesMap set
// up with this will use the default KeyPath.
type CapabilitiesMapConfig struct {
Endpoints map[string]string
Default string
}

// CapabilitiesMap runs a capability check based on the value of the parsedURL,
// which is the key to the CapabilitiesMap's map. The parsedURL is expected to
// be some regex values, allowing for bucketing of urls that contain some kind
Expand All @@ -42,7 +53,7 @@ type CapabilitiesMap struct {
KeyPath []string
}

// Check uses the parsed endpoint value to determine which EndpointChecker to
// CheckAuthentication uses the parsed endpoint value to determine which EndpointChecker to
// run against the capabilities in the auth provided. If there is no
// EndpointChecker for the endpoint, the default is used. As long as one
// capability is found to be authorized by the EndpointChecker, no error is
Expand Down Expand Up @@ -92,3 +103,40 @@ func (c CapabilitiesMap) CheckAuthentication(auth bascule.Authentication, vs Par
return fmt.Errorf("%w in [%v] with %v endpoint checker",
ErrNoValidCapabilityFound, capabilities, checker.Name())
}

// NewCapabilitiesMap parses the CapabilitiesMapConfig provided into a
// CapabilitiesMap. The same regular expression provided for the map are also
// needed for labels for a MetricValidator, so an option to be used for that is
// also created.
func NewCapabilitiesMap(config CapabilitiesMapConfig) (CapabilitiesCheckerOut, error) {
// if we don't get a capability value, a nil default checker means always
// returning false.
var defaultChecker EndpointChecker
if config.Default != "" {
defaultChecker = ConstEndpointCheck(config.Default)
}

i := 0
rs := make([]*regexp.Regexp, len(config.Endpoints))
endpointMap := map[string]EndpointChecker{}
for r, checkVal := range config.Endpoints {
regex, err := regexp.Compile(r)
if err != nil {
return CapabilitiesCheckerOut{}, fmt.Errorf("%w [%v]: %v", errRegexCompileFail, r, err)
}
// because rs is the length of config.Endpoints, i never overflows.
rs[i] = regex
i++
endpointMap[r] = ConstEndpointCheck(checkVal)
}

cc := CapabilitiesMap{
Checkers: endpointMap,
DefaultChecker: defaultChecker,
}

return CapabilitiesCheckerOut{
Checker: cc,
Options: []MetricOption{WithEndpoints(rs)},
}, nil
}
74 changes: 72 additions & 2 deletions basculechecks/capabilitiesmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ func TestCapabilitiesMapCheck(t *testing.T) {
"...",
}
goodToken := bascule.NewToken("test", "princ",
bascule.NewAttributes(map[string]interface{}{CapabilityKey: goodCapabilities}))
bascule.NewAttributes(
buildDummyAttributes(CapabilityKeys(), goodCapabilities)))
defaultCapabilities := []string{
"test",
"",
"default checker",
"...",
}
defaultToken := bascule.NewToken("test", "princ",
bascule.NewAttributes(map[string]interface{}{CapabilityKey: defaultCapabilities}))
bascule.NewAttributes(
buildDummyAttributes(CapabilityKeys(), defaultCapabilities)))
badToken := bascule.NewToken("", "", nil)
tests := []struct {
description string
Expand Down Expand Up @@ -172,3 +174,71 @@ func TestCapabilitiesMapCheck(t *testing.T) {
})
}
}

func TestNewCapabilitiesMap(t *testing.T) {
a := ".*"
b := "aaaaa+"
c1 := "yup"
c2 := "nope"
es := map[string]string{a: c1, b: c2}
m := map[string]EndpointChecker{
a: ConstEndpointCheck(c1),
b: ConstEndpointCheck(c2),
}

tests := []struct {
description string
config CapabilitiesMapConfig
expectedChecker CapabilitiesChecker
expectedErr error
}{
{
description: "Success",
config: CapabilitiesMapConfig{
Endpoints: es,
},
expectedChecker: CapabilitiesMap{
Checkers: m,
},
},
{
description: "Success with default",
config: CapabilitiesMapConfig{
Endpoints: es,
Default: "pls",
},
expectedChecker: CapabilitiesMap{
Checkers: m,
DefaultChecker: ConstEndpointCheck("pls"),
},
},
{
description: "Regex fail",
config: CapabilitiesMapConfig{
Endpoints: map[string]string{
`\m\n\b\v`: "test",
},
},
expectedErr: errRegexCompileFail,
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
c, err := NewCapabilitiesMap(tc.config)
if tc.expectedErr != nil {
assert.Empty(c)
require.Error(t, err)
assert.True(errors.Is(err, tc.expectedErr),
fmt.Errorf("error [%v] doesn't contain error [%v] in its err chain",
err, tc.expectedErr),
)
return
}
assert.NoError(err)
assert.NotEmpty(c)
assert.Equal(tc.expectedChecker, c.Checker)
assert.NotNil(c.Options)
})
}
}
62 changes: 49 additions & 13 deletions basculechecks/capabilitiesvalidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"errors"
"fmt"
"regexp"

"github.com/spf13/cast"
"github.com/xmidt-org/bascule"
Expand Down Expand Up @@ -62,25 +63,24 @@ var (
}
)

const (
CapabilityKey = "capabilities"
)

var (
partnerKeys = []string{"allowedResources", "allowedPartners"}
)

func PartnerKeys() []string {
return partnerKeys
}

// EndpointChecker is an object that can determine if a value provides
// authorization to the endpoint.
type EndpointChecker interface {
Authorized(value string, reqURL string, method string) bool
Name() string
}

// CapabilitiesValidatorConfig is input that can be used to build a
// CapabilitiesValidator and some metric options for a MetricValidator. A
// CapabilitiesValidator set up with this will use the default KeyPath and an
// EndpointRegexCheck.
type CapabilitiesValidatorConfig struct {
Type string
Prefix string
AcceptAllMethod string
EndpointBuckets []string
}

// CapabilitiesValidator checks the capabilities provided in a
// bascule.Authentication object to determine if a request is authorized. It
// can also provide a function to be used in authorization middleware that
Expand Down Expand Up @@ -159,7 +159,7 @@ func getCapabilities(attributes bascule.Attributes, keyPath []string) ([]string,
}

if len(keyPath) == 0 {
keyPath = []string{CapabilityKey}
keyPath = CapabilityKeys()
}

val, ok := bascule.GetNestedAttribute(attributes, keyPath...)
Expand All @@ -181,3 +181,39 @@ func getCapabilities(attributes bascule.Attributes, keyPath []string) ([]string,
return vals, nil

}

// NewCapabilitiesValidator uses the provided config to create an
// RegexEndpointCheck and wrap it in a CapabilitiesValidator. Metric Options
// are also created for a Metric Validator by parsing the type to determine if
// the metric validator should only monitor and compiling endpoints into Regexps.
func NewCapabilitiesValidator(config CapabilitiesValidatorConfig) (CapabilitiesCheckerOut, error) {
var out CapabilitiesCheckerOut
if config.Type != "enforce" && config.Type != "monitor" {
// unsupported capability check type. CapabilityCheck disabled.
return out, nil
}
c, err := NewRegexEndpointCheck(config.Prefix, config.AcceptAllMethod)
if err != nil {
return out, fmt.Errorf("error initializing endpointRegexCheck: %w", err)
}

endpoints := make([]*regexp.Regexp, 0, len(config.EndpointBuckets))
for _, e := range config.EndpointBuckets {
r, err := regexp.Compile(e)
if err != nil {
continue
}
endpoints = append(endpoints, r)
}

os := []MetricOption{WithEndpoints(endpoints)}
if config.Type == "monitor" {
os = append(os, MonitorOnly())
}

out = CapabilitiesCheckerOut{
Checker: CapabilitiesValidator{Checker: c},
Options: os,
}
return out, nil
}
11 changes: 8 additions & 3 deletions basculechecks/capabilitiesvalidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ func TestCapabilitiesValidatorCheck(t *testing.T) {
}
if tc.includeToken {
auth.Token = bascule.NewToken("test", "princ",
bascule.NewAttributes(map[string]interface{}{CapabilityKey: capabilities}))
bascule.NewAttributes(
buildDummyAttributes(CapabilityKeys(), capabilities)))
}
if tc.includeAuth {
ctx = bascule.WithAuthentication(ctx, auth)
Expand Down Expand Up @@ -172,7 +173,8 @@ func TestCapabilitiesValidatorCheckAuthentication(t *testing.T) {
}
if tc.includeAttributes {
a.Token = bascule.NewToken("test", "princ",
bascule.NewAttributes(map[string]interface{}{CapabilityKey: capabilities}))
bascule.NewAttributes(
buildDummyAttributes(CapabilityKeys(), capabilities)))
}
if tc.includeURL {
goodURL, err := url.Parse("/test")
Expand Down Expand Up @@ -310,6 +312,9 @@ func TestGetCapabilities(t *testing.T) {
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
if tc.key == nil {
tc.key = CapabilityKeys()
}
m := buildDummyAttributes(tc.key, tc.keyValue)
if tc.missingAttribute {
m = map[string]interface{}{}
Expand Down Expand Up @@ -338,7 +343,7 @@ func TestGetCapabilities(t *testing.T) {
func buildDummyAttributes(keyPath []string, val interface{}) map[string]interface{} {
keyLen := len(keyPath)
if keyLen == 0 {
return map[string]interface{}{CapabilityKey: val}
return nil
}
m := map[string]interface{}{keyPath[keyLen-1]: val}
// we want to move out from the inner most map.
Expand Down
22 changes: 22 additions & 0 deletions basculechecks/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2021 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

/*
Package basculechecks provides bascule validators for JWT capability checking.
*/

package basculechecks
Loading

0 comments on commit c011b12

Please sign in to comment.