File: id.go

package info (click to toggle)
golang-github-spiffe-go-spiffe 2.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,116 kB
  • sloc: makefile: 157
file content (258 lines) | stat: -rw-r--r-- 7,540 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package spiffeid

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
)

const (
	schemePrefix    = "spiffe://"
	schemePrefixLen = len(schemePrefix)
)

// FromPath returns a new SPIFFE ID in the given trust domain and with the
// given path. The supplied path must be a valid absolute path according to the
// SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func FromPath(td TrustDomain, path string) (ID, error) {
	if err := ValidatePath(path); err != nil {
		return ID{}, err
	}
	return makeID(td, path)
}

// FromPathf returns a new SPIFFE ID from the formatted path in the given trust
// domain. The formatted path must be a valid absolute path according to the
// SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func FromPathf(td TrustDomain, format string, args ...interface{}) (ID, error) {
	path, err := FormatPath(format, args...)
	if err != nil {
		return ID{}, err
	}
	return makeID(td, path)
}

// FromSegments returns a new SPIFFE ID in the given trust domain with joined
// path segments. The path segments must be valid according to the SPIFFE
// specification and must not contain path separators.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func FromSegments(td TrustDomain, segments ...string) (ID, error) {
	path, err := JoinPathSegments(segments...)
	if err != nil {
		return ID{}, err
	}
	return makeID(td, path)
}

// FromString parses a SPIFFE ID from a string.
func FromString(id string) (ID, error) {
	switch {
	case id == "":
		return ID{}, errEmpty
	case !strings.HasPrefix(id, schemePrefix):
		return ID{}, errWrongScheme
	}

	pathidx := schemePrefixLen
	for ; pathidx < len(id); pathidx++ {
		c := id[pathidx]
		if c == '/' {
			break
		}
		if !isValidTrustDomainChar(c) {
			return ID{}, errBadTrustDomainChar
		}
	}

	if pathidx == schemePrefixLen {
		return ID{}, errMissingTrustDomain
	}

	if err := ValidatePath(id[pathidx:]); err != nil {
		return ID{}, err
	}

	return ID{
		id:      id,
		pathidx: pathidx,
	}, nil
}

// FromStringf parses a SPIFFE ID from a formatted string.
func FromStringf(format string, args ...interface{}) (ID, error) {
	return FromString(fmt.Sprintf(format, args...))
}

// FromURI parses a SPIFFE ID from a URI.
func FromURI(uri *url.URL) (ID, error) {
	return FromString(uri.String())
}

// ID is a SPIFFE ID
type ID struct {
	id string

	// pathidx tracks the index to the beginning of the path inside of id. This
	// is used when extracting the trust domain or path portions of the id.
	pathidx int
}

// TrustDomain returns the trust domain of the SPIFFE ID.
func (id ID) TrustDomain() TrustDomain {
	if id.IsZero() {
		return TrustDomain{}
	}
	return TrustDomain{name: id.id[schemePrefixLen:id.pathidx]}
}

// MemberOf returns true if the SPIFFE ID is a member of the given trust domain.
func (id ID) MemberOf(td TrustDomain) bool {
	return id.TrustDomain() == td
}

// Path returns the path of the SPIFFE ID inside the trust domain.
func (id ID) Path() string {
	return id.id[id.pathidx:]
}

// String returns the string representation of the SPIFFE ID, e.g.,
// "spiffe://example.org/foo/bar".
func (id ID) String() string {
	return id.id
}

// URL returns a URL for SPIFFE ID.
func (id ID) URL() *url.URL {
	if id.IsZero() {
		return &url.URL{}
	}

	return &url.URL{
		Scheme: "spiffe",
		Host:   id.TrustDomain().String(),
		Path:   id.Path(),
	}
}

// IsZero returns true if the SPIFFE ID is the zero value.
func (id ID) IsZero() bool {
	return id.id == ""
}

// AppendPath returns an ID with the appended path. It will fail if called on a
// zero value. The path to append must be a valid absolute path according to
// the SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) AppendPath(path string) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot append path on a zero ID value")
	}
	if err := ValidatePath(path); err != nil {
		return ID{}, err
	}
	id.id += path
	return id, nil
}

// AppendPathf returns an ID with the appended formatted path. It will fail if
// called on a zero value. The formatted path must be a valid absolute path
// according to the SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) AppendPathf(format string, args ...interface{}) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot append path on a zero ID value")
	}
	path, err := FormatPath(format, args...)
	if err != nil {
		return ID{}, err
	}
	id.id += path
	return id, nil
}

// AppendSegments returns an ID with the appended joined path segments.  It
// will fail if called on a zero value. The path segments must be valid
// according to the SPIFFE specification and must not contain path separators.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) AppendSegments(segments ...string) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot append path segments on a zero ID value")
	}
	path, err := JoinPathSegments(segments...)
	if err != nil {
		return ID{}, err
	}
	id.id += path
	return id, nil
}

// Replace path returns an ID with the given path in the same trust domain. It
// will fail if called on a zero value. The given path must be a valid absolute
// path according to the SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) ReplacePath(path string) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot replace path on a zero ID value")
	}
	return FromPath(id.TrustDomain(), path)
}

// ReplacePathf returns an ID with the formatted path in the same trust domain.
// It will fail if called on a zero value. The formatted path must be a valid
// absolute path according to the SPIFFE specification.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) ReplacePathf(format string, args ...interface{}) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot replace path on a zero ID value")
	}
	return FromPathf(id.TrustDomain(), format, args...)
}

// ReplaceSegments returns an ID with the joined path segments in the same
// trust domain. It will fail if called on a zero value. The path segments must
// be valid according to the SPIFFE specification and must not contain path
// separators.
// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path
func (id ID) ReplaceSegments(segments ...string) (ID, error) {
	if id.IsZero() {
		return ID{}, errors.New("cannot replace path segments on a zero ID value")
	}
	return FromSegments(id.TrustDomain(), segments...)
}

// MarshalText returns a text representation of the ID. If the ID is the zero
// value, nil is returned.
func (id ID) MarshalText() ([]byte, error) {
	if id.IsZero() {
		return nil, nil
	}
	return []byte(id.String()), nil
}

// UnmarshalText decodes a text representation of the ID. If the text is empty,
// the ID is set to the zero value.
func (id *ID) UnmarshalText(text []byte) error {
	if len(text) == 0 {
		*id = ID{}
		return nil
	}
	unmarshaled, err := FromString(string(text))
	if err != nil {
		return err
	}
	*id = unmarshaled
	return nil
}

func makeID(td TrustDomain, path string) (ID, error) {
	if td.IsZero() {
		return ID{}, errors.New("trust domain is empty")
	}
	return ID{
		id:      schemePrefix + td.name + path,
		pathidx: schemePrefixLen + len(td.name),
	}, nil
}