2018-08-15 18:42:56 +00:00
|
|
|
package identity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2018-08-23 16:15:02 +00:00
|
|
|
"fmt"
|
2018-08-15 18:42:56 +00:00
|
|
|
"strings"
|
2018-09-18 03:03:00 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
2018-08-15 18:42:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrUnbalancedTemplatingCharacter = errors.New("unbalanced templating characters")
|
|
|
|
ErrNoEntityAttachedToToken = errors.New("string contains entity template directives but no entity was provided")
|
|
|
|
ErrNoGroupsAttachedToToken = errors.New("string contains groups template directives but no groups were provided")
|
|
|
|
ErrTemplateValueNotFound = errors.New("no value could be found for one of the template directives")
|
|
|
|
)
|
|
|
|
|
|
|
|
type PopulateStringInput struct {
|
|
|
|
ValidityCheckOnly bool
|
|
|
|
String string
|
|
|
|
Entity *Entity
|
|
|
|
Groups []*Group
|
2018-09-18 03:03:00 +00:00
|
|
|
Namespace *namespace.Namespace
|
2018-08-15 18:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func PopulateString(p *PopulateStringInput) (bool, string, error) {
|
|
|
|
if p == nil {
|
|
|
|
return false, "", errors.New("nil input")
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.String == "" {
|
|
|
|
return false, "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var subst bool
|
|
|
|
splitStr := strings.Split(p.String, "{{")
|
|
|
|
|
|
|
|
if len(splitStr) >= 1 {
|
|
|
|
if strings.Index(splitStr[0], "}}") != -1 {
|
|
|
|
return false, "", ErrUnbalancedTemplatingCharacter
|
|
|
|
}
|
|
|
|
if len(splitStr) == 1 {
|
|
|
|
return false, p.String, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var b strings.Builder
|
|
|
|
if !p.ValidityCheckOnly {
|
|
|
|
b.Grow(2 * len(p.String))
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, str := range splitStr {
|
|
|
|
if i == 0 {
|
|
|
|
if !p.ValidityCheckOnly {
|
|
|
|
b.WriteString(str)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
splitPiece := strings.Split(str, "}}")
|
|
|
|
switch len(splitPiece) {
|
|
|
|
case 2:
|
|
|
|
subst = true
|
|
|
|
if !p.ValidityCheckOnly {
|
2018-09-18 03:03:00 +00:00
|
|
|
tmplStr, err := performTemplating(p.Namespace, strings.TrimSpace(splitPiece[0]), p.Entity, p.Groups)
|
2018-08-15 18:42:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, "", err
|
|
|
|
}
|
|
|
|
b.WriteString(tmplStr)
|
|
|
|
b.WriteString(splitPiece[1])
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return false, "", ErrUnbalancedTemplatingCharacter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return subst, b.String(), nil
|
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
func performTemplating(ns *namespace.Namespace, input string, entity *Entity, groups []*Group) (string, error) {
|
2018-08-15 18:42:56 +00:00
|
|
|
performAliasTemplating := func(trimmed string, alias *Alias) (string, error) {
|
|
|
|
switch {
|
|
|
|
case trimmed == "id":
|
|
|
|
return alias.ID, nil
|
|
|
|
case trimmed == "name":
|
|
|
|
if alias.Name == "" {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return alias.Name, nil
|
|
|
|
case strings.HasPrefix(trimmed, "metadata."):
|
|
|
|
val, ok := alias.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
|
|
|
|
if !ok {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
performEntityTemplating := func(trimmed string) (string, error) {
|
|
|
|
switch {
|
|
|
|
case trimmed == "id":
|
|
|
|
return entity.ID, nil
|
|
|
|
case trimmed == "name":
|
|
|
|
if entity.Name == "" {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return entity.Name, nil
|
|
|
|
case strings.HasPrefix(trimmed, "metadata."):
|
|
|
|
val, ok := entity.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
|
|
|
|
if !ok {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
case strings.HasPrefix(trimmed, "aliases."):
|
|
|
|
split := strings.SplitN(strings.TrimPrefix(trimmed, "aliases."), ".", 2)
|
|
|
|
if len(split) != 2 {
|
|
|
|
return "", errors.New("invalid alias selector")
|
|
|
|
}
|
|
|
|
var found *Alias
|
|
|
|
for _, alias := range entity.Aliases {
|
|
|
|
if split[0] == alias.MountAccessor {
|
|
|
|
found = alias
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if found == nil {
|
|
|
|
return "", errors.New("alias not found")
|
|
|
|
}
|
|
|
|
return performAliasTemplating(split[1], found)
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
performGroupsTemplating := func(trimmed string) (string, error) {
|
|
|
|
var ids bool
|
|
|
|
|
|
|
|
selectorSplit := strings.SplitN(trimmed, ".", 2)
|
|
|
|
switch {
|
|
|
|
case len(selectorSplit) != 2:
|
|
|
|
return "", errors.New("invalid groups selector")
|
|
|
|
case selectorSplit[0] == "ids":
|
|
|
|
ids = true
|
|
|
|
case selectorSplit[0] == "names":
|
|
|
|
default:
|
|
|
|
return "", errors.New("invalid groups selector")
|
|
|
|
}
|
|
|
|
trimmed = selectorSplit[1]
|
|
|
|
|
|
|
|
accessorSplit := strings.SplitN(trimmed, ".", 2)
|
|
|
|
if len(accessorSplit) != 2 {
|
|
|
|
return "", errors.New("invalid groups accessor")
|
|
|
|
}
|
|
|
|
var found *Group
|
|
|
|
for _, group := range groups {
|
2018-09-18 03:03:00 +00:00
|
|
|
var compare string
|
2018-08-15 18:42:56 +00:00
|
|
|
if ids {
|
|
|
|
compare = group.ID
|
2018-09-18 03:03:00 +00:00
|
|
|
} else {
|
|
|
|
if ns != nil && group.NamespaceID == ns.ID {
|
|
|
|
compare = group.Name
|
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
2018-08-15 18:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if compare == accessorSplit[0] {
|
|
|
|
found = group
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if found == nil {
|
2018-08-23 16:15:02 +00:00
|
|
|
return "", fmt.Errorf("entity is not a member of group %q", accessorSplit[0])
|
2018-08-15 18:42:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
trimmed = accessorSplit[1]
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case trimmed == "id":
|
|
|
|
return found.ID, nil
|
|
|
|
case trimmed == "name":
|
|
|
|
if found.Name == "" {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return found.Name, nil
|
|
|
|
case strings.HasPrefix(trimmed, "metadata."):
|
|
|
|
val, ok := found.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
|
|
|
|
if !ok {
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(input, "identity.entity."):
|
|
|
|
if entity == nil {
|
|
|
|
return "", ErrNoEntityAttachedToToken
|
|
|
|
}
|
|
|
|
return performEntityTemplating(strings.TrimPrefix(input, "identity.entity."))
|
|
|
|
|
|
|
|
case strings.HasPrefix(input, "identity.groups."):
|
|
|
|
if len(groups) == 0 {
|
|
|
|
return "", ErrNoGroupsAttachedToToken
|
|
|
|
}
|
|
|
|
return performGroupsTemplating(strings.TrimPrefix(input, "identity.groups."))
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", ErrTemplateValueNotFound
|
|
|
|
}
|