-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathprovider.go
233 lines (191 loc) · 5.96 KB
/
provider.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
// +build go1.12
package oidc
import (
"context"
"fmt"
"net/url"
"strings"
"time"
// just to get the cookie..
"github.com/ermites-io/oidc/internal/jwk"
"github.com/ermites-io/oidc/internal/state"
"github.com/ermites-io/oidc/internal/token"
)
type Provider struct {
name string
clientId string // provider specific
clientSecret string // provider specific
clientUrlRedirect string // redirect Url setup at your idp.
clientUrlRedirectPath string // for the cookie
urlAuth string // where to redirect when you click "login with <provider>" to start the openid sequence
urlToken string // where to request the token in the sequence.
urlJwks string // where are the authorities for the JWTs
issuer string // who issued the certificates.
scopes []string // we might need more..scopes
// openid by default
oauthOnly bool
// auth parts
state *state.Verifier // state provider
jwk jwk.Keys // jwt verifier XXX types needs to change name
}
type Token struct {
AccessToken string
RefreshToken string
Expiry time.Time
IdToken string // The IdToken
}
func NewProvider(name, urlOidcConf string) (*Provider, error) {
// parse the Oidc Configuration
authz, token, issuer, jwks, err := parseOpenIDConfiguration(urlOidcConf)
if err != nil {
return nil, err
}
oidc := Provider{
//urlRedirect: urlRedirect,
name: name,
urlAuth: authz,
urlToken: token,
urlJwks: jwks,
issuer: issuer,
scopes: openidScopes,
}
return &oidc, nil
}
func NewProviderOauthOnly(name, urlAuth, urlToken string) (*Provider, error) {
oidc := Provider{
name: name,
urlAuth: urlAuth,
urlToken: urlToken,
scopes: openidScopes,
oauthOnly: true,
}
return &oidc, nil
}
func (p *Provider) SetAuth(clientId, clientSecret, clientUrlRedirect string) error {
// ok setup the basics
p.clientId = clientId
p.clientSecret = clientSecret // TODO to xor in memory
p.clientUrlRedirect = clientUrlRedirect
// want to avoid parsing all the time
u, err := url.ParseRequestURI(p.clientUrlRedirect)
if err != nil {
return err
}
p.clientUrlRedirectPath = u.RequestURI()
// auth contains the jwk stuff
p.state, err = state.NewVerifier(clientId, clientSecret)
if err != nil {
return err
}
p.jwk, err = jwk.MapFromUrl(p.urlJwks)
if err != nil {
return err
}
return nil
}
func (p *Provider) RequestIdentityParams(nonce string) (cookieValue, cookiePath, IdpRedirectUrl string, err error) {
return p.RequestIdentityParamsWithUserdata(nonce, nil)
}
// XXX TODO: probably need to be renamed properly
func (p *Provider) RequestIdentityParamsWithUserdata(nonce string, userdata []byte) (cookieValue, cookiePath, IdpRedirectUrl string, err error) {
cookie, state, err := p.state.NewWithData(p.name, nonce, userdata)
if err != nil {
return
}
// this is harcoded for "now"
responseType := "code"
scope := strings.Join(p.scopes, " ")
//fmt.Printf("SCOPE: %s\n", scope)
cookieValue = cookie
cookiePath = p.clientUrlRedirectPath // when we setAuth we set this value
IdpRedirectUrl, err = p.buildUrlAuth(responseType, scope, nonce, state)
return
}
func (p *Provider) validateIdToken(nonce string, idt *token.Id) error {
// TODO: call idt.Validate(issuer, clientid, nonce)
// signed Idp nonce vs state embedded nonce
if idt.Claims.Nonce != nonce {
return fmt.Errorf("invalid state idt.nonce: %s vs nonce: %s", idt.Claims.Nonce, nonce)
}
// Claims aud vs issuer
if idt.Claims.Aud != p.clientId {
return fmt.Errorf("invalid aud: %s vs clientId: %s", idt.Claims.Aud, p.clientId)
}
// claims iss vs issuer
if idt.Claims.Iss != p.issuer {
return fmt.Errorf("invalid iss: %s vs issuer: %s", idt.Claims.Iss, p.issuer)
}
// TODO: Expiration
expirationTime := time.Unix(int64(idt.Claims.Exp), 0)
nowTime := time.Now()
if nowTime.After(expirationTime) {
return fmt.Errorf("invalid exp: %v vs now: %v", expirationTime, time.Now())
}
return nil
}
// XXX TODO: this is the real authentication
func (p *Provider) ValidateIdentityParams(ctx context.Context, code, cookie, state string) (t *Token, err error) {
t, _, err = p.ValidateIdentityParamsWithUserdata(ctx, code, cookie, state)
return
}
func (p *Provider) ValidateIdentityParamsWithUserdata(ctx context.Context, code, cookie, state string) (t *Token, userdata []byte, err error) {
// YES, we unpack again for fuck sake!
nonce, udata, err := p.state.ValidateWithData(cookie, state, DefaultStateTimeout)
if err != nil {
fmt.Printf("state '%s' is not valid: %v\n", state, err)
return nil, nil, err
}
// authentification is finished since we don't have token ids etc..
if p.oauthOnly {
t, err = p.tokenRequestOauth(ctx, code, state)
if err != nil {
//fmt.Printf("TOKEN REQUEST OAUTH ERR: %v\n", err)
return nil, nil, err
}
fmt.Printf("Tokens: %v\n", t)
return t, udata, nil
}
// yes so..
// TODO: need to give back id token, access token, refresh token (if any)
// needs to see how i will wire the usercontrolled handler.
// return the accesstoken & refresh token too
t, err = p.tokenRequest(ctx, code)
if err != nil {
return nil, nil, err
}
//fmt.Printf("%s\n", t)
fmt.Printf("Tokens: %v\n", t)
idt, err := token.Parse(t.IdToken)
if err != nil {
//panic(err)
return nil, nil, err
}
// create functions..
kid, blob, sig := idt.GetVerifyInfo()
err = p.jwk.Verify(kid, blob, sig)
if err != nil {
//panic(err)
return nil, nil, err
}
// TODO here we verify the issuer, the aud, the nonce, etc.. etc.. etc..
err = p.validateIdToken(nonce, idt)
if err != nil {
//panic(err)
return nil, nil, err
}
// show the token.
//return t.IdToken, t.AccessToken, nil
return t, udata, nil
}
func (p *Provider) GetName() string {
return p.name
}
// return provider from the cookie.
func GetProvider(cookie string) (string, error) {
var nilstr string
e, err := state.ParseEnvelope(cookie)
if err != nil {
return nilstr, err
}
return e.GetProvider(), nil
}