-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathauthentication_middleware.go
168 lines (138 loc) · 4.92 KB
/
authentication_middleware.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
package gincloudflareaccess
import (
"errors"
"fmt"
"log"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
)
const (
cloudflareAccessContextKeyPrincipal = "CFAccessPrincipal"
cloudflareAccessContextKeyMarker = "CFAccessMarker"
defaultHeaderName = "Cf-Access-Jwt-Assertion"
defaultCookieName = "CF_Authorization"
)
func (instance *cloudflareAccessMiddlewareImpl) AuthenticationMiddleware() gin.HandlerFunc {
return buildAuthenticatorMiddleware(instance)
}
func (instance *cloudflareAccessMiddlewareImpl) extractToken(c *gin.Context) (string, error) {
if instance.config.TokenExtractFunc != nil {
return instance.config.TokenExtractFunc(c)
}
// look first in the headers
headers := c.Request.Header
accessJWT := headers.Get(defaultHeaderName)
// look for a value in the cookies
if accessJWT == "" {
cookieValue, cookieErr := c.Request.Cookie(defaultCookieName)
if cookieErr == nil && cookieValue != nil && cookieValue.Value != "" {
accessJWT = cookieValue.Value
}
} else {
accessJWT = strings.TrimPrefix(accessJWT, "Bearer ")
}
return accessJWT, nil
}
func buildAuthenticatorMiddleware(instance *cloudflareAccessMiddlewareImpl) gin.HandlerFunc {
var tokenCache *cache.Cache
if !instance.config.DisableCache {
effectiveDuration := instance.config.CacheTTL
if effectiveDuration == 0 {
effectiveDuration = 5 * time.Minute
}
tokenCache = cache.New(effectiveDuration, 5*time.Minute)
}
return func(c *gin.Context) {
// Mark the request as processed from the engine
c.Set(cloudflareAccessContextKeyMarker, 1)
// Make sure that the incoming request has our token header.
accessJWT, err := instance.extractToken(c)
if err != nil {
instance.handleUnauthorized(c, fmt.Errorf("error extracting token: %v", err))
return
}
if accessJWT == "" {
// There's no authorization token source.
// We go on and don't error right now as this may be a call to a public resource.
c.Next()
return
}
if !instance.config.DisableCache {
// Check if the token is cached
if cachedPrincipalRaw, found := tokenCache.Get(accessJWT); found {
cachedPrincipal := cachedPrincipalRaw.(*CloudflareAccessPrincipal)
// Set the principal in the call context and proceed
c.Set(cloudflareAccessContextKeyPrincipal, cachedPrincipal)
c.Next()
return
}
}
var token *oidc.IDToken
var principal *CloudflareAccessPrincipal
if instance.config.AuthenticationFunc != nil {
principal, err = instance.config.AuthenticationFunc(c.Request.Context(), accessJWT)
} else {
// Verify the access token
token, err = instance.cloudflareAccessClient.VerifyToken(c.Request.Context(), accessJWT)
if err != nil {
// Token verification failed. We block the call right now.
instance.handleUnauthorized(c, err)
return
}
// Build the principal from token
principal, err = instance.cloudflareAccessClient.BuildPrincipal(c.Request.Context(), accessJWT, token)
}
if err != nil {
// Principal identification failed. We block the call and return the error right now.
instance.handleUnauthorized(c, fmt.Errorf("error building principal from token: %v", err))
return
}
// If a custom details fetcher is provided, invoke it
if principal != nil && instance.config.DetailsFetcher != nil {
fetchedDetails, err := instance.config.DetailsFetcher(c, principal)
if err != nil {
instance.handleUnauthorized(c, fmt.Errorf("error loading user details: %v", err))
return
}
principal.Details = fetchedDetails
}
// Set the principal in the call context and proceed
c.Set(cloudflareAccessContextKeyPrincipal, principal)
if !instance.config.DisableCache {
// Put the principal in cache
tokenCache.Set(accessJWT, principal, cache.DefaultExpiration)
}
// May now proceed
c.Next()
}
}
// GetPrincipal extracts the current principal from the request context.
//
// Note that the principal can be nil if no authentication was provided.
func GetPrincipal(c *gin.Context) *CloudflareAccessPrincipal {
raw, exists := c.Get(cloudflareAccessContextKeyPrincipal)
if !exists {
return nil
}
converted, ok := raw.(*CloudflareAccessPrincipal)
if !ok {
panic(fmt.Errorf("unexpected type for principal in context: %T", raw))
}
return converted
}
func assertRequestProcessedByAuthenticator(c *gin.Context) {
_, present := c.Get(cloudflareAccessContextKeyMarker)
if !present {
msg := "an authentication/authorization check was requested but " +
"the current request has not been processed by the Authentication middleware. " +
"Please ensure that you plugged the authentication middleware in the router, usually done " +
"by calling r.Use(yourInstance.AuthenticationMiddleware()), " +
"before plugging other middlewares such as RequireAuthenticated or RequireGroup " +
"or before calling helpers such as GetPrincipal, PrincipalInGroups"
log.Default().Printf("[ERROR] " + msg)
panic(errors.New(msg))
}
}