github.com/hashicorp/vault/sdk@v0.11.0/helper/authmetadata/auth_metadata.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package authmetadata
     5  
     6  /*
     7  	authmetadata is a package offering convenience and
     8  	standardization when supporting an `auth_metadata`
     9  	field in a plugin's configuration. This then controls
    10  	what metadata is added to an Auth during login.
    11  
    12  	To see an example of how to add and use it, check out
    13  	how these structs and fields are used in the AWS auth
    14  	method.
    15  
    16  	Or, check out its acceptance test in this package to
    17  	see its integration points.
    18  */
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/hashicorp/go-secure-stdlib/strutil"
    27  	"github.com/hashicorp/vault/sdk/framework"
    28  	"github.com/hashicorp/vault/sdk/logical"
    29  )
    30  
    31  // Fields is for configuring a back-end's available
    32  // default and additional fields. These are used for
    33  // providing a verbose field description, and for parsing
    34  // user input.
    35  type Fields struct {
    36  	// The field name as it'll be reflected in the user-facing
    37  	// schema.
    38  	FieldName string
    39  
    40  	// Default is a list of the default fields that should
    41  	// be included if a user sends "default" in their list
    42  	// of desired fields. These fields should all have a
    43  	// low rate of change because each change can incur a
    44  	// write to storage.
    45  	Default []string
    46  
    47  	// AvailableToAdd is a list of fields not included by
    48  	// default, that the user may include.
    49  	AvailableToAdd []string
    50  }
    51  
    52  func (f *Fields) all() []string {
    53  	return append(f.Default, f.AvailableToAdd...)
    54  }
    55  
    56  // FieldSchema takes the default and additionally available
    57  // fields, and uses them to generate a verbose description
    58  // regarding how to use the "auth_metadata" field.
    59  func FieldSchema(fields *Fields) *framework.FieldSchema {
    60  	return &framework.FieldSchema{
    61  		Type:        framework.TypeCommaStringSlice,
    62  		Description: description(fields),
    63  		DisplayAttrs: &framework.DisplayAttributes{
    64  			Name:  fields.FieldName,
    65  			Value: "field1,field2",
    66  		},
    67  		Default: []string{"default"},
    68  	}
    69  }
    70  
    71  func NewHandler(fields *Fields) *Handler {
    72  	return &Handler{
    73  		fields: fields,
    74  	}
    75  }
    76  
    77  type Handler struct {
    78  	// authMetadata is an explicit list of all the user's configured
    79  	// fields that are being added to auth metadata. If it is set to
    80  	// default or unconfigured, it will be nil. Otherwise, it will
    81  	// hold the explicit fields set by the user.
    82  	authMetadata []string
    83  
    84  	// fields is a list of the configured default and available
    85  	// fields.
    86  	fields *Fields
    87  }
    88  
    89  // AuthMetadata is intended to be used on config reads.
    90  // It gets an explicit list of all the user's configured
    91  // fields that are being added to auth metadata.
    92  func (h *Handler) AuthMetadata() []string {
    93  	if h.authMetadata == nil {
    94  		return h.fields.Default
    95  	}
    96  	return h.authMetadata
    97  }
    98  
    99  // ParseAuthMetadata is intended to be used on config create/update.
   100  // It takes a user's selected fields (or lack thereof),
   101  // converts it to a list of explicit fields, and adds it to the Handler
   102  // for later storage.
   103  func (h *Handler) ParseAuthMetadata(data *framework.FieldData) error {
   104  	userProvidedRaw, ok := data.GetOk(h.fields.FieldName)
   105  	if !ok {
   106  		// Nothing further to do here.
   107  		return nil
   108  	}
   109  	userProvided, ok := userProvidedRaw.([]string)
   110  	if !ok {
   111  		return fmt.Errorf("%s is an unexpected type of %T", userProvidedRaw, userProvidedRaw)
   112  	}
   113  	userProvided = strutil.RemoveDuplicates(userProvided, true)
   114  
   115  	// If the only field the user has chosen was the default field,
   116  	// we don't store anything so we won't have to do a storage
   117  	// migration if the default changes.
   118  	if len(userProvided) == 1 && userProvided[0] == "default" {
   119  		h.authMetadata = nil
   120  		return nil
   121  	}
   122  
   123  	// Validate and store the input.
   124  	if strutil.StrListContains(userProvided, "default") {
   125  		return fmt.Errorf("%q contains default - default can't be used in combination with other fields",
   126  			userProvided)
   127  	}
   128  	if !strutil.StrListSubset(h.fields.all(), userProvided) {
   129  		return fmt.Errorf("%q contains an unavailable field, please select from %q",
   130  			strings.Join(userProvided, ", "), strings.Join(h.fields.all(), ", "))
   131  	}
   132  	h.authMetadata = userProvided
   133  	return nil
   134  }
   135  
   136  // PopulateDesiredMetadata is intended to be used during login
   137  // just before returning an auth.
   138  // It takes the available auth metadata and,
   139  // if the auth should have it, adds it to the auth's metadata.
   140  func (h *Handler) PopulateDesiredMetadata(auth *logical.Auth, available map[string]string) error {
   141  	if auth == nil {
   142  		return errors.New("auth is nil")
   143  	}
   144  	if auth.Metadata == nil {
   145  		auth.Metadata = make(map[string]string)
   146  	}
   147  	if auth.Alias == nil {
   148  		auth.Alias = &logical.Alias{}
   149  	}
   150  	if auth.Alias.Metadata == nil {
   151  		auth.Alias.Metadata = make(map[string]string)
   152  	}
   153  	fieldsToInclude := h.fields.Default
   154  	if h.authMetadata != nil {
   155  		fieldsToInclude = h.authMetadata
   156  	}
   157  	for availableField, itsValue := range available {
   158  		if itsValue == "" {
   159  			// Don't bother setting fields for which there is no value.
   160  			continue
   161  		}
   162  		if strutil.StrListContains(fieldsToInclude, availableField) {
   163  			auth.Metadata[availableField] = itsValue
   164  			auth.Alias.Metadata[availableField] = itsValue
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (h *Handler) MarshalJSON() ([]byte, error) {
   171  	return json.Marshal(&struct {
   172  		AuthMetadata []string `json:"auth_metadata"`
   173  	}{
   174  		AuthMetadata: h.authMetadata,
   175  	})
   176  }
   177  
   178  func (h *Handler) UnmarshalJSON(data []byte) error {
   179  	jsonable := &struct {
   180  		AuthMetadata []string `json:"auth_metadata"`
   181  	}{
   182  		AuthMetadata: h.authMetadata,
   183  	}
   184  	if err := json.Unmarshal(data, jsonable); err != nil {
   185  		return err
   186  	}
   187  	h.authMetadata = jsonable.AuthMetadata
   188  	return nil
   189  }
   190  
   191  func description(fields *Fields) string {
   192  	desc := "The metadata to include on the aliases and audit logs generated by this plugin."
   193  	if len(fields.Default) > 0 {
   194  		desc += fmt.Sprintf(" When set to 'default', includes: %s.", strings.Join(fields.Default, ", "))
   195  	}
   196  	if len(fields.AvailableToAdd) > 0 {
   197  		desc += fmt.Sprintf(" These fields are available to add: %s.", strings.Join(fields.AvailableToAdd, ", "))
   198  	}
   199  	desc += " Not editing this field means the 'default' fields are included." +
   200  		" Explicitly setting this field to empty overrides the 'default' and means no metadata will be included." +
   201  		" If not using 'default', explicit fields must be sent like: 'field1,field2'."
   202  	return desc
   203  }