github.com/xmidt-org/webpa-common@v1.11.9/basculechecks/capabilitiesvalidator.go (about)

     1  /**
     2   * Copyright 2020 Comcast Cable Communications Management, LLC
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package basculechecks
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  
    25  	"github.com/goph/emperror"
    26  	"github.com/spf13/cast"
    27  	"github.com/xmidt-org/bascule"
    28  )
    29  
    30  var (
    31  	ErrNoVals                 = errors.New("expected at least one value")
    32  	ErrNoAuth                 = errors.New("couldn't get request info: authorization not found")
    33  	ErrNoToken                = errors.New("no token found in Auth")
    34  	ErrNoValidCapabilityFound = errors.New("no valid capability for endpoint")
    35  	ErrNilAttributes          = errors.New("nil attributes interface")
    36  	ErrNoURL                  = errors.New("invalid URL found in Auth")
    37  )
    38  
    39  const (
    40  	CapabilityKey = "capabilities"
    41  )
    42  
    43  var (
    44  	partnerKeys = []string{"allowedResources", "allowedPartners"}
    45  )
    46  
    47  func PartnerKeys() []string {
    48  	return partnerKeys
    49  }
    50  
    51  // CapabilityChecker is an object that can determine if a capability provides
    52  // authorization to the endpoint.
    53  type CapabilityChecker interface {
    54  	Authorized(string, string, string) bool
    55  }
    56  
    57  // CapabilitiesValidator checks the capabilities provided in a
    58  // bascule.Authentication object to determine if a request is authorized.  It
    59  // can also provide a function to be used in authorization middleware that
    60  // pulls the Authentication object from a context before checking it.
    61  type CapabilitiesValidator struct {
    62  	Checker CapabilityChecker
    63  }
    64  
    65  // CreateValidator creates a function that determines whether or not a
    66  // client is authorized to make a request to an endpoint.  It uses the
    67  // bascule.Authentication from the context to get the information needed by the
    68  // CapabilityChecker to determine authorization.
    69  func (c CapabilitiesValidator) CreateValidator(errorOut bool) bascule.ValidatorFunc {
    70  	return func(ctx context.Context, _ bascule.Token) error {
    71  		auth, ok := bascule.FromContext(ctx)
    72  		if !ok {
    73  			if errorOut {
    74  				return ErrNoAuth
    75  			}
    76  			return nil
    77  		}
    78  
    79  		_, err := c.Check(auth, ParsedValues{})
    80  		if err != nil && errorOut {
    81  			return err
    82  		}
    83  
    84  		return nil
    85  	}
    86  }
    87  
    88  // Check takes the needed values out of the given Authentication object in
    89  // order to determine if a request is authorized.  It determines this through
    90  // iterating through each capability and calling the CapabilityChecker.  If no
    91  // capability authorizes the client for the given endpoint and method, it is
    92  // unauthorized.
    93  func (c CapabilitiesValidator) Check(auth bascule.Authentication, _ ParsedValues) (string, error) {
    94  	if auth.Token == nil {
    95  		return TokenMissingValues, ErrNoToken
    96  	}
    97  	vals, reason, err := getCapabilities(auth.Token.Attributes())
    98  	if err != nil {
    99  		return reason, err
   100  	}
   101  
   102  	if auth.Request.URL == nil {
   103  		return TokenMissingValues, ErrNoURL
   104  	}
   105  	reqURL := auth.Request.URL.EscapedPath()
   106  	method := auth.Request.Method
   107  	err = c.checkCapabilities(vals, reqURL, method)
   108  	if err != nil {
   109  		return NoCapabilitiesMatch, err
   110  	}
   111  	return "", nil
   112  }
   113  
   114  // checkCapabilities uses a CapabilityChecker to check if each capability
   115  // provided is authorized.  If an authorized capability is found, no error is
   116  // returned.
   117  func (c CapabilitiesValidator) checkCapabilities(capabilities []string, reqURL string, method string) error {
   118  	for _, val := range capabilities {
   119  		if c.Checker.Authorized(val, reqURL, method) {
   120  			return nil
   121  		}
   122  	}
   123  	return emperror.With(ErrNoValidCapabilityFound, "capabilitiesFound", capabilities, "urlToMatch", reqURL, "methodToMatch", method)
   124  
   125  }
   126  
   127  // getCapabilities runs some error checks while getting the list of
   128  // capabilities from the attributes.
   129  func getCapabilities(attributes bascule.Attributes) ([]string, string, error) {
   130  	if attributes == nil {
   131  		return []string{}, UndeterminedCapabilities, ErrNilAttributes
   132  	}
   133  
   134  	val, ok := attributes.Get(CapabilityKey)
   135  	if !ok {
   136  		return []string{}, UndeterminedCapabilities, fmt.Errorf("couldn't get capabilities using key %v", CapabilityKey)
   137  	}
   138  
   139  	vals, err := cast.ToStringSliceE(val)
   140  	if err != nil {
   141  		return []string{}, UndeterminedCapabilities, fmt.Errorf("capabilities \"%v\" not the expected string slice: %v", val, err)
   142  	}
   143  
   144  	if len(vals) == 0 {
   145  		return []string{}, EmptyCapabilitiesList, ErrNoVals
   146  	}
   147  
   148  	return vals, "", nil
   149  
   150  }