This repository has been archived by the owner on Nov 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
elixir.go
164 lines (132 loc) · 4.29 KB
/
elixir.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
package main
import (
"fmt"
"time"
"github.com/coreos/go-oidc"
"github.com/golang-jwt/jwt/v4"
"github.com/lestrrat/go-jwx/jwk"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
// ElixirIdentity represents an Elixir user instance
type ElixirIdentity struct {
User string
Passport []string
Token string
Profile string
Email string
ExpDate string
}
// Configure an OpenID Connect aware OAuth2 client.
func getOidcClient(conf ElixirConfig) (oauth2.Config, *oidc.Provider) {
contx := context.Background()
provider, err := oidc.NewProvider(contx, conf.Provider)
if err != nil {
log.Fatal(err)
}
oauth2Config := oauth2.Config{
ClientID: conf.ID,
ClientSecret: conf.Secret,
RedirectURL: conf.RedirectURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "ga4gh_passport_v1 profile email"},
}
return oauth2Config, provider
}
// Authenticate with an Oidc client.against Elixir AAI
func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, code, jwkURL string) (ElixirIdentity, error) {
contx := context.Background()
defer contx.Done()
var idStruct ElixirIdentity
oauth2Token, err := oauth2Config.Exchange(contx, code)
if err != nil {
log.Error("Failed to fetch oauth2 code")
return idStruct, err
}
// Extract the Access Token from OAuth2 token.
rawAccessToken := oauth2Token.AccessToken
if rawAccessToken == "" {
log.Error("Failed to extract access token from OAuth2 token")
return idStruct, err
}
// Validate raw token signature and get expiration date
_, rawExpDate, err := validateToken(rawAccessToken, jwkURL)
if err != nil {
return idStruct, fmt.Errorf("could not validate raw jwt against pub key, reason: %v", err)
}
var verifier = provider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})
// Parse and verify Access Token payload.
_, err = verifier.Verify(contx, rawAccessToken)
if err != nil {
log.Error("Failed to verify id token")
return idStruct, err
}
// Fetch user information
userInfo, err := provider.UserInfo(contx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
log.Error("Failed to get userinfo")
return idStruct, err
}
// Extract custom passports, name and email claims
var claims struct {
PassportClaim []string `json:"ga4gh_passport_v1"`
ProfileClaim string `json:"name"`
EmailClaim string `json:"email"`
}
if err := userInfo.Claims(&claims); err != nil {
log.Error("Failed to get custom claims")
return idStruct, err
}
idStruct = ElixirIdentity{
User: userInfo.Subject,
Token: rawAccessToken,
Passport: claims.PassportClaim,
Profile: claims.ProfileClaim,
Email: claims.EmailClaim,
ExpDate: rawExpDate,
}
return idStruct, err
}
// Validate raw (Elixir) jwt against public key from jwk. Return parsed jwt and its expiration date.
func validateToken(rawJwt, jwksURL string) (*jwt.Token, string, error) {
// Fetch public key
set, err := jwk.Fetch(jwksURL)
if err != nil {
return nil, "", fmt.Errorf(err.Error())
}
pubKey, err := set.Keys[0].Materialize()
if err != nil {
return nil, "", fmt.Errorf("failed to materialize public key %s", err.Error())
}
token, err := jwt.Parse(rawJwt, func(token *jwt.Token) (interface{}, error) {
// Validate that the alg is what we expect: RSA or ES
_, okRSA := token.Method.(*jwt.SigningMethodRSA)
_, okES := token.Method.(*jwt.SigningMethodECDSA)
if !(okRSA || okES) {
return nil, fmt.Errorf("unexpected signing method")
}
return pubKey, nil
})
// Validate the error
v, _ := err.(*jwt.ValidationError)
// If error is for signature validation
if err != nil && v.Errors == jwt.ValidationErrorSignatureInvalid {
return nil, "", fmt.Errorf("signature not valid: %s", err.Error())
}
// Verify token dates. Ignores clock skew, but that should be
// irrelevant here since tokens are relatively long-lived
if err := token.Claims.(jwt.MapClaims).Valid(); err != nil {
return nil, "", fmt.Errorf(err.Error())
}
var expireDate time.Time
switch d := token.Claims.(jwt.MapClaims)["exp"].(type) {
case float64:
expireDate = time.Unix(int64(d), 0)
case int64:
expireDate = time.Unix(d, 0)
default:
return nil, "", fmt.Errorf("failed to read expiration date from token")
}
return token, expireDate.Format("2006-01-02 15:04:05"), err
}