1package identity 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/vault/helper/namespace" 13) 14 15var ( 16 ErrUnbalancedTemplatingCharacter = errors.New("unbalanced templating characters") 17 ErrNoEntityAttachedToToken = errors.New("string contains entity template directives but no entity was provided") 18 ErrNoGroupsAttachedToToken = errors.New("string contains groups template directives but no groups were provided") 19 ErrTemplateValueNotFound = errors.New("no value could be found for one of the template directives") 20) 21 22const ( 23 ACLTemplating = iota // must be the first value for backwards compatibility 24 JSONTemplating 25) 26 27type PopulateStringInput struct { 28 String string 29 ValidityCheckOnly bool 30 Entity *Entity 31 Groups []*Group 32 Namespace *namespace.Namespace 33 Mode int // processing mode, ACLTemplate or JSONTemplating 34 Now time.Time // optional, defaults to current time 35 36 templateHandler templateHandlerFunc 37 groupIDs []string 38 groupNames []string 39} 40 41// templateHandlerFunc allows generating string outputs based on data type, and 42// different handlers can be used based on mode. For example in ACL mode, strings 43// are emitted verbatim, but they're wrapped in double quotes for JSON mode. And 44// some structures, like slices, might be rendered in one mode but prohibited in 45// another. 46type templateHandlerFunc func(interface{}, ...string) (string, error) 47 48// aclTemplateHandler processes known parameter data types when operating 49// in ACL mode. 50func aclTemplateHandler(v interface{}, keys ...string) (string, error) { 51 switch t := v.(type) { 52 case string: 53 if t == "" { 54 return "", ErrTemplateValueNotFound 55 } 56 return t, nil 57 case []string: 58 return "", ErrTemplateValueNotFound 59 case map[string]string: 60 if len(keys) > 0 { 61 val, ok := t[keys[0]] 62 if ok { 63 return val, nil 64 } 65 } 66 return "", ErrTemplateValueNotFound 67 } 68 69 return "", fmt.Errorf("unknown type: %T", v) 70} 71 72// jsonTemplateHandler processes known parameter data types when operating 73// in JSON mode. 74func jsonTemplateHandler(v interface{}, keys ...string) (string, error) { 75 jsonMarshaller := func(v interface{}) (string, error) { 76 enc, err := json.Marshal(v) 77 if err != nil { 78 return "", err 79 } 80 return string(enc), nil 81 } 82 83 switch t := v.(type) { 84 case string: 85 return strconv.Quote(t), nil 86 case []string: 87 return jsonMarshaller(t) 88 case map[string]string: 89 if len(keys) > 0 { 90 return strconv.Quote(t[keys[0]]), nil 91 } 92 if t == nil { 93 return "{}", nil 94 } 95 return jsonMarshaller(t) 96 } 97 98 return "", fmt.Errorf("unknown type: %T", v) 99} 100 101func PopulateString(p PopulateStringInput) (bool, string, error) { 102 if p.String == "" { 103 return false, "", nil 104 } 105 106 // preprocess groups 107 for _, g := range p.Groups { 108 p.groupNames = append(p.groupNames, g.Name) 109 p.groupIDs = append(p.groupIDs, g.ID) 110 } 111 112 // set up mode-specific handler 113 switch p.Mode { 114 case ACLTemplating: 115 p.templateHandler = aclTemplateHandler 116 case JSONTemplating: 117 p.templateHandler = jsonTemplateHandler 118 default: 119 return false, "", fmt.Errorf("unknown mode %q", p.Mode) 120 } 121 122 var subst bool 123 splitStr := strings.Split(p.String, "{{") 124 125 if len(splitStr) >= 1 { 126 if strings.Contains(splitStr[0], "}}") { 127 return false, "", ErrUnbalancedTemplatingCharacter 128 } 129 if len(splitStr) == 1 { 130 return false, p.String, nil 131 } 132 } 133 134 var b strings.Builder 135 if !p.ValidityCheckOnly { 136 b.Grow(2 * len(p.String)) 137 } 138 139 for i, str := range splitStr { 140 if i == 0 { 141 if !p.ValidityCheckOnly { 142 b.WriteString(str) 143 } 144 continue 145 } 146 splitPiece := strings.Split(str, "}}") 147 switch len(splitPiece) { 148 case 2: 149 subst = true 150 if !p.ValidityCheckOnly { 151 tmplStr, err := performTemplating(strings.TrimSpace(splitPiece[0]), &p) 152 if err != nil { 153 return false, "", err 154 } 155 b.WriteString(tmplStr) 156 b.WriteString(splitPiece[1]) 157 } 158 default: 159 return false, "", ErrUnbalancedTemplatingCharacter 160 } 161 } 162 163 return subst, b.String(), nil 164} 165 166func performTemplating(input string, p *PopulateStringInput) (string, error) { 167 168 performAliasTemplating := func(trimmed string, alias *Alias) (string, error) { 169 switch { 170 case trimmed == "id": 171 return p.templateHandler(alias.ID) 172 173 case trimmed == "name": 174 return p.templateHandler(alias.Name) 175 176 case trimmed == "metadata": 177 return p.templateHandler(alias.Metadata) 178 179 case strings.HasPrefix(trimmed, "metadata."): 180 split := strings.SplitN(trimmed, ".", 2) 181 return p.templateHandler(alias.Metadata, split[1]) 182 } 183 184 return "", ErrTemplateValueNotFound 185 } 186 187 performEntityTemplating := func(trimmed string) (string, error) { 188 switch { 189 case trimmed == "id": 190 return p.templateHandler(p.Entity.ID) 191 192 case trimmed == "name": 193 return p.templateHandler(p.Entity.Name) 194 195 case trimmed == "metadata": 196 return p.templateHandler(p.Entity.Metadata) 197 198 case strings.HasPrefix(trimmed, "metadata."): 199 split := strings.SplitN(trimmed, ".", 2) 200 return p.templateHandler(p.Entity.Metadata, split[1]) 201 202 case trimmed == "groups.names": 203 return p.templateHandler(p.groupNames) 204 205 case trimmed == "groups.ids": 206 return p.templateHandler(p.groupIDs) 207 208 case strings.HasPrefix(trimmed, "aliases."): 209 split := strings.SplitN(strings.TrimPrefix(trimmed, "aliases."), ".", 2) 210 if len(split) != 2 { 211 return "", errors.New("invalid alias selector") 212 } 213 var alias *Alias 214 for _, a := range p.Entity.Aliases { 215 if split[0] == a.MountAccessor { 216 alias = a 217 break 218 } 219 } 220 if alias == nil { 221 if p.Mode == ACLTemplating { 222 return "", errors.New("alias not found") 223 } 224 225 // An empty alias is sufficient for generating defaults 226 alias = &Alias{Metadata: make(map[string]string)} 227 } 228 return performAliasTemplating(split[1], alias) 229 } 230 231 return "", ErrTemplateValueNotFound 232 } 233 234 performGroupsTemplating := func(trimmed string) (string, error) { 235 var ids bool 236 237 selectorSplit := strings.SplitN(trimmed, ".", 2) 238 239 switch { 240 case len(selectorSplit) != 2: 241 return "", errors.New("invalid groups selector") 242 243 case selectorSplit[0] == "ids": 244 ids = true 245 246 case selectorSplit[0] == "names": 247 248 default: 249 return "", errors.New("invalid groups selector") 250 } 251 trimmed = selectorSplit[1] 252 253 accessorSplit := strings.SplitN(trimmed, ".", 2) 254 if len(accessorSplit) != 2 { 255 return "", errors.New("invalid groups accessor") 256 } 257 var found *Group 258 for _, group := range p.Groups { 259 var compare string 260 if ids { 261 compare = group.ID 262 } else { 263 if p.Namespace != nil && group.NamespaceID == p.Namespace.ID { 264 compare = group.Name 265 } else { 266 continue 267 } 268 } 269 270 if compare == accessorSplit[0] { 271 found = group 272 break 273 } 274 } 275 276 if found == nil { 277 return "", fmt.Errorf("entity is not a member of group %q", accessorSplit[0]) 278 } 279 280 trimmed = accessorSplit[1] 281 282 switch { 283 case trimmed == "id": 284 return found.ID, nil 285 286 case trimmed == "name": 287 if found.Name == "" { 288 return "", ErrTemplateValueNotFound 289 } 290 return found.Name, nil 291 292 case strings.HasPrefix(trimmed, "metadata."): 293 val, ok := found.Metadata[strings.TrimPrefix(trimmed, "metadata.")] 294 if !ok { 295 return "", ErrTemplateValueNotFound 296 } 297 return val, nil 298 } 299 300 return "", ErrTemplateValueNotFound 301 } 302 303 performTimeTemplating := func(trimmed string) (string, error) { 304 now := p.Now 305 if now.IsZero() { 306 now = time.Now() 307 } 308 309 opsSplit := strings.SplitN(trimmed, ".", 3) 310 311 if opsSplit[0] != "now" { 312 return "", fmt.Errorf("invalid time selector %q", opsSplit[0]) 313 } 314 315 result := now 316 switch len(opsSplit) { 317 case 1: 318 // return current time 319 case 2: 320 return "", errors.New("missing time operand") 321 322 case 3: 323 duration, err := time.ParseDuration(opsSplit[2]) 324 if err != nil { 325 return "", errwrap.Wrapf("invalid duration: {{err}}", err) 326 } 327 328 switch opsSplit[1] { 329 case "plus": 330 result = result.Add(duration) 331 case "minus": 332 result = result.Add(-duration) 333 default: 334 return "", fmt.Errorf("invalid time operator %q", opsSplit[1]) 335 } 336 } 337 338 return strconv.FormatInt(result.Unix(), 10), nil 339 } 340 341 switch { 342 case strings.HasPrefix(input, "identity.entity."): 343 if p.Entity == nil { 344 return "", ErrNoEntityAttachedToToken 345 } 346 return performEntityTemplating(strings.TrimPrefix(input, "identity.entity.")) 347 348 case strings.HasPrefix(input, "identity.groups."): 349 if len(p.Groups) == 0 { 350 return "", ErrNoGroupsAttachedToToken 351 } 352 return performGroupsTemplating(strings.TrimPrefix(input, "identity.groups.")) 353 354 case strings.HasPrefix(input, "time."): 355 return performTimeTemplating(strings.TrimPrefix(input, "time.")) 356 } 357 358 return "", ErrTemplateValueNotFound 359} 360