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 }