-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathauthenticator.go
164 lines (138 loc) · 4.45 KB
/
authenticator.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
// Copyright (c) 2021, salesforce.com, inc.
// All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
// For full license text, see the LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
package main
import (
"bytes"
"context"
"fmt"
"net/http"
"net/url"
"strings"
"text/template"
"github.com/pardot/oidc"
"github.com/pardot/oidc/discovery"
"golang.org/x/net/http/httpproxy"
)
type authenticator struct {
// UserTemplate is a template that, when rendered with the JWT claims, should
// match the user being authenticated.
//
// `{{.Subject}}` is used by default if not set.
UserTemplate string
// GroupsClaimKey is the name of the key within the token claims that
// specifies which groups a user is a member of.
//
// `groups` is used by default if not set.
GroupsClaimKey string
// AuthorizedGroups is a list of groups required for authentication to pass.
// A user must be a member of at least one of the groups in the list, if
// specified.
//
// If the list is empty, group membership is not required for authentication
// to pass.
AuthorizedGroups []string
// RequireACRs is a list of required values of the acr claim in the token for
// authentication to pass. At least one of the acrs must be present if specified
//
// If the list is empty, the ACR value is not checked.
RequireACRs []string
verifier *oidc.Verifier
aud string
}
func discoverAuthenticator(ctx context.Context, issuer string, aud string, httpProxy string) (*authenticator, error) {
transport := http.DefaultTransport.(*http.Transport).Clone()
if httpProxy != "" {
// Use no_proxy from environment, if present, but override proxy URL
cfg := httpproxy.FromEnvironment()
cfg.HTTPProxy = httpProxy
cfg.HTTPSProxy = httpProxy
proxyFunc := cfg.ProxyFunc()
transport.Proxy = func(r *http.Request) (*url.URL, error) {
return proxyFunc(r.URL)
}
}
client, err := discovery.NewClient(ctx, issuer, discovery.WithHTTPClient(&http.Client{
Transport: transport,
}))
if err != nil {
return nil, fmt.Errorf("discovering verifier: %v", err)
}
verifier := oidc.NewVerifier(issuer, client)
return &authenticator{
verifier: verifier,
aud: aud,
}, nil
}
// Authenticate authenticates a user with the provided token.
func (a *authenticator) Authenticate(ctx context.Context, user string, token string) error {
claims, err := a.verifier.VerifyRaw(ctx, a.aud, token)
if err != nil {
return fmt.Errorf("verifying token: %v", err)
}
userTemplate := "{{.Subject}}"
if a.UserTemplate != "" {
userTemplate = a.UserTemplate
}
userTmpl, err := template.New("").Funcs(template.FuncMap{
"trimPrefix": func(prefix, s string) string { return strings.TrimPrefix(s, prefix) },
"trimSuffix": func(suffix, s string) string { return strings.TrimSuffix(s, suffix) },
}).Parse(userTemplate)
if err != nil {
return fmt.Errorf("parsing user template: %v", err)
}
buf := new(bytes.Buffer)
if err := userTmpl.Execute(buf, claims); err != nil {
return fmt.Errorf("executing user template: %v", err)
}
wantUser := buf.String()
if wantUser != user {
return fmt.Errorf("expected user %q but is authenticating as %q", wantUser, user)
}
// Validate AuthorizedGroups / GroupClaimsKey
if len(a.AuthorizedGroups) > 0 {
groupsClaimKey := "groups"
if len(a.GroupsClaimKey) > 0 {
groupsClaimKey = a.GroupsClaimKey
}
groupsClaim, ok := claims.Extra[groupsClaimKey].([]interface{})
if !ok {
return fmt.Errorf("user is not member of any groups, but one of %v is required", a.AuthorizedGroups)
}
groups := make([]string, 0, len(groupsClaim))
for _, groupVal := range groupsClaim {
if group, ok := groupVal.(string); ok {
groups = append(groups, group)
}
}
if !isMemberOfAtLeastOneGroup(a.AuthorizedGroups, groups) {
return fmt.Errorf("user is member of %v, but one of %v is required", groups, a.AuthorizedGroups)
}
}
// Validate RequireACRs
if len(a.RequireACRs) > 0 {
if !isACRPresent(a.RequireACRs, claims.ACR) {
return fmt.Errorf("acr is %q, but one of %v is required", claims.ACR, a.RequireACRs)
}
}
return nil
}
func isMemberOfAtLeastOneGroup(authorizedGroups []string, groups []string) bool {
for _, wantGroup := range authorizedGroups {
for _, group := range groups {
if wantGroup == group {
return true
}
}
}
return false
}
func isACRPresent(authorizedACRs []string, acr string) bool {
for _, wantACR := range authorizedACRs {
if wantACR == acr {
return true
}
}
return false
}