1 package verify
2
3 import (
4 "net/http"
5
6 "edge-infra.dev/pkg/edge/iam/log"
7 "edge-infra.dev/pkg/edge/iam/verify/templates"
8
9 "github.com/MicahParks/keyfunc"
10 "github.com/coreos/go-oidc/v3/oidc"
11 "github.com/gin-gonic/gin"
12 "github.com/golang-jwt/jwt/v4"
13 "golang.org/x/oauth2"
14 )
15
16 type IDClaims struct {
17 Organization string `json:"org"`
18 }
19
20 func (v *Verifier) callback(ctx *gin.Context) {
21 log := log.Get(ctx.Request.Context())
22 result := &Result{
23 Name: "verify callback result",
24 Pass: true,
25 }
26
27
28 provider, err := oidc.NewProvider(oidc.InsecureIssuerURLContext(ctx, Issuer()), IssuerURL())
29 if err != nil {
30 err := ctx.AbortWithError(http.StatusInternalServerError, err)
31 if err != nil {
32 log.Error(err, "failed to abort with error")
33 }
34 return
35 }
36
37
38 oauth2Config := oauth2.Config{
39 ClientID: v.ClientID,
40 ClientSecret: v.ClientSecret,
41 RedirectURL: v.ClientURL + verifyCallbackPath,
42 Endpoint: provider.Endpoint(),
43 Scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile"},
44 }
45
46
47 jwksURL := IssuerURL() + wellKnownJWKSPath
48 jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{})
49 if err != nil {
50
51 step := Step{Name: "load JWKS", Pass: false}
52 writeResult(ctx, templates.Callback, result, step)
53 return
54 }
55
56
57 code, _ := ctx.GetQuery("code")
58
59
60 tokenSet, err := oauth2Config.Exchange(ctx, code, oauth2.AccessTypeOffline)
61 if err != nil {
62 step := Step{Name: "exchange code for tokens", Pass: false}
63 writeResult(ctx, templates.Callback, result, step)
64 return
65 }
66
67
68 if !tokenSet.Valid() {
69 step := Step{Name: "valid token", Pass: false}
70 writeResult(ctx, templates.Callback, result, step)
71 return
72 }
73
74
75 accessToken, err := jwt.Parse(tokenSet.AccessToken, jwks.Keyfunc)
76 if err != nil {
77 step := Step{Name: "parse, validate and verify the token", Pass: false}
78 writeResult(ctx, templates.Callback, result, step)
79 return
80 }
81
82
83 rawIDToken, ok := tokenSet.Extra("id_token").(string)
84 if !ok {
85 step := Step{Name: "token includes and id_token", Pass: false}
86 writeResult(ctx, templates.Callback, result, step)
87 return
88 }
89
90
91 verifier := provider.Verifier(&oidc.Config{
92 ClientID: v.ClientID,
93 })
94
95
96 idToken, err := verifier.Verify(ctx, rawIDToken)
97 if err != nil {
98 step := Step{Name: "id_token is valid", Pass: false}
99 writeResult(ctx, templates.Callback, result, step)
100 return
101 }
102
103 idClaims := IDClaims{}
104 if err := idToken.Claims(&idClaims); err != nil {
105 step := Step{Name: "id_token has valid claims", Pass: false}
106 writeResult(ctx, templates.Callback, result, step)
107 return
108 }
109
110 claims, _ := accessToken.Claims.(jwt.MapClaims)
111 steps := make([]Step, 0)
112 steps = append(steps,
113 Step{Name: "valid access token", Pass: accessToken.Valid},
114 Step{Name: "organization is correct", IsPublic: true, Pass: claims["org"] == BslExpectedOrganization(), Expected: BslExpectedOrganization(), Got: claims["org"].(string)},
115 Step{Name: "subject is correct", IsPublic: true, Pass: claims["sub"] == BslExpectedSubject(), Expected: BslExpectedSubject(), Got: claims["sub"].(string)},
116 Step{Name: "we have scopes", Pass: claims["scp"] != ""},
117 Step{Name: "we have roles", Pass: claims["rls"] != ""},
118
119
120 Step{Name: "id token has correct audience", IsPublic: true, Pass: idToken.Audience[0] == v.ClientID, Expected: v.ClientSecret, Got: idToken.Audience[0]},
121 Step{Name: "id token has correct subject", IsPublic: true, Pass: idToken.Subject == BslExpectedSubject(), Expected: BslExpectedSubject(), Got: idToken.Subject},
122 )
123
124 writeResult(ctx, templates.Callback, result, steps...)
125 }
126
View as plain text