-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathconfig.go
274 lines (229 loc) · 8.37 KB
/
config.go
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package saml
import (
"fmt"
"net/url"
"time"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/cap/saml/models/core"
)
// ValidUntilFunc represents a function that sets a time until a service provider metadata
// document is valid.
type ValidUntilFunc func() time.Time
// GenerateAuthRequestIDFunc represents a function that generates the
// SAML authentication request ID.
type GenerateAuthRequestIDFunc func() (string, error)
// Config contains configuraiton parameters that are required for a service provider
// to successfully federate with an identity provider and execute a SAML authentication flow.
type Config struct {
// AssertionConsumerServiceURL defines the endpoint at the service provider where
// the identity provider will redirect to with its authentication response. Must be
// a valid URL. Required.
AssertionConsumerServiceURL string
// EntityID is a globally unique identifier of the service provider. Must be a
// valid URL. Required.
EntityID string
// MetadataURL is the endpoint an identity provider serves its metadata XML document.
// Must be a valid URL. Takes precedence over MetadataXML and MetadataParameters.
// Required if MetadataXML or MetadataParameters not set.
MetadataURL string
// MetadataXML is the XML-formatted metadata an identity provider provides to
// configure a service provider. Takes precedence over MetadataParameters. Optional.
MetadataXML string
// MetadataParameters are the individual parameters an identity provider provides
// to configure a service provider. Optional.
MetadataParameters *MetadataParameters
// ValidUntil is a function that defines the time after which the service provider
// metadata document is considered invalid. Optional.
ValidUntil ValidUntilFunc
// GenerateAuthRequestID generates an XSD:ID conforming ID.
GenerateAuthRequestID GenerateAuthRequestIDFunc
}
// MetadataParameters are parameters that are required for SAML federation.
// This can be used when the IDP doesn't support a Metadata URL.
type MetadataParameters struct {
// Issuer is a globally unique identifier of the identity provider.
// Must be a valid URL. Required.
Issuer string
// SingleSignOnURL is the single sign-on service URL of the identity provider.
// Must be a valid URL. Required.
SingleSignOnURL string
// IDPCertificate is the PEM-encoded public key certificate provided by the identity
// provider. Used to verify response and assertion signatures. Required.
IDPCertificate string
// Binding defines the binding that will be used for authentication requests. Defaults
// to HTTP-POST binding. Optional.
Binding core.ServiceBinding
}
// Validate validates the provided metadata parameters.
func (c *MetadataParameters) Validate() error {
if c.Issuer == "" {
return fmt.Errorf("issuer not set")
}
if _, err := url.Parse(c.Issuer); err != nil {
return fmt.Errorf("provided Issuer is not a valid URL: %w", err)
}
if c.SingleSignOnURL == "" {
return fmt.Errorf("SSO URL not set")
}
if _, err := url.Parse(c.SingleSignOnURL); err != nil {
return fmt.Errorf("provided SSO URL is not a valid URL: %w", err)
}
if _, err := parsePEMCertificate([]byte(c.IDPCertificate)); err != nil {
return fmt.Errorf("failed to parse IDP certificate: %w", err)
}
return nil
}
// WithMetadataXML provides optional identity provider metadata in the form of an XML
// document that can be used to configure the service provider.
func WithMetadataXML(metadata string) Option {
return func(o interface{}) {
if o, ok := o.(*configOptions); ok {
o.withMetadataXML = metadata
}
}
}
// WithMetadataParameters provides optional static metadata from an identity provider
// that can be used to configure the service provider.
func WithMetadataParameters(metadata MetadataParameters) Option {
return func(o interface{}) {
if o, ok := o.(*configOptions); ok {
if metadata.Binding == "" {
metadata.Binding = core.ServiceBindingHTTPPost
}
o.withMetadataParameters = &metadata
}
}
}
// WithValidUntil provides the time after which the service provider metadata
// document is considered invalid
func WithValidUntil(validUntil ValidUntilFunc) Option {
return func(o interface{}) {
if o, ok := o.(*configOptions); ok {
o.withValidUntil = validUntil
}
}
}
// WithGenerateAuthRequestID provides an XSD:ID conforming ID for authentication requests
func WithGenerateAuthRequestID(generateAuthRequestID GenerateAuthRequestIDFunc) Option {
return func(o interface{}) {
if o, ok := o.(*configOptions); ok {
o.withGenerateAuthRequestID = generateAuthRequestID
}
}
}
// NewConfig creates a new configuration for a service provider. Identity provider
// metadata can be provided via the metadataURL parameter or the WithMetadataXML
// and WithMetadataParameters options. The metadataURL will always take precedence
// if options are provided.
//
// Options:
// - WithValidUntil
// - WithMetadataXML
// - WithMetadataParameters
// - WithGenerateAuthRequestID
func NewConfig(entityID, acs, metadataURL string, opt ...Option) (*Config, error) {
const op = "saml.NewConfig"
opts := getConfigOptions(opt...)
cfg := &Config{
EntityID: entityID,
AssertionConsumerServiceURL: acs,
MetadataURL: metadataURL,
MetadataXML: opts.withMetadataXML,
MetadataParameters: opts.withMetadataParameters,
ValidUntil: opts.withValidUntil,
GenerateAuthRequestID: opts.withGenerateAuthRequestID,
}
err := cfg.Validate()
if err != nil {
return nil, fmt.Errorf("%s: invalid provider config: %w", op, err)
}
return cfg, nil
}
// Validate validates the Config fields.
func (c *Config) Validate() error {
const op = "saml.Config.Validate"
if c.AssertionConsumerServiceURL == "" {
return fmt.Errorf("%s: ACS URL not set: %w", op, ErrInvalidParameter)
}
if _, err := url.Parse(c.AssertionConsumerServiceURL); err != nil {
return fmt.Errorf("%s: provided ACS URL is not a valid URL: %w", op, ErrInvalidParameter)
}
if c.EntityID == "" {
return fmt.Errorf("%s: EntityID not set: %w", op, ErrInvalidParameter)
}
if _, err := url.Parse(c.EntityID); err != nil {
return fmt.Errorf("%s: provided Entity ID is not a valid URL: %w", op, ErrInvalidParameter)
}
if c.MetadataURL == "" && c.MetadataXML == "" && c.MetadataParameters == nil {
return fmt.Errorf("%s: One of MetadataURL, MetadataXML, or MetadataParameters "+
"must be set: %w", op, ErrInvalidParameter)
}
if c.MetadataURL != "" {
if _, err := url.Parse(c.MetadataURL); err != nil {
return fmt.Errorf(
"%s: provided Metadata URL is not a valid URL: %w",
op,
ErrInvalidParameter,
)
}
}
if c.MetadataXML != "" {
if _, err := parseIDPMetadata([]byte(c.MetadataXML)); err != nil {
return fmt.Errorf("%s: %s: %w", op, err.Error(), ErrInvalidParameter)
}
}
if c.MetadataParameters != nil {
if err := c.MetadataParameters.Validate(); err != nil {
return fmt.Errorf("%s: %s: %w", op, err.Error(), ErrInvalidParameter)
}
}
if c.GenerateAuthRequestID == nil {
return fmt.Errorf(
"%s: GenerateAuthRequestID func not provided: %w",
op,
ErrInvalidParameter,
)
}
return nil
}
type configOptions struct {
withMetadataXML string
withMetadataParameters *MetadataParameters
withValidUntil ValidUntilFunc
withGenerateAuthRequestID GenerateAuthRequestIDFunc
}
func configOptionsDefault() configOptions {
return configOptions{
withValidUntil: defaultValidUntil,
}
}
func getConfigOptions(opt ...Option) configOptions {
opts := configOptionsDefault()
ApplyOpts(&opts, opt...)
// Apply defaults to options
if opts.withGenerateAuthRequestID == nil {
opts.withGenerateAuthRequestID = DefaultGenerateAuthRequestID
}
if opts.withValidUntil == nil {
opts.withValidUntil = defaultValidUntil
}
return opts
}
// DefaultGenerateAuthRequestID generates an auth XSD:ID conform ID.
// A UUID prefixed with an underscore.
func DefaultGenerateAuthRequestID() (string, error) {
newID, err := uuid.GenerateUUID()
if err != nil {
return "", err
}
// Request IDs have to be xsd:ID, which means they need to start with an underscore or letter,
// which is not always given for UUIDs.
return fmt.Sprintf("_%s", newID), nil
}
// defaultValidUntil returns a timestamp with one year
// added to the time when this function is called.
func defaultValidUntil() time.Time {
return time.Now().Add(time.Hour * 24 * 365)
}