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