-
Notifications
You must be signed in to change notification settings - Fork 6
/
keycloak_roles.go
147 lines (130 loc) · 4.37 KB
/
keycloak_roles.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
package keycloak
import (
"net/http"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/thoas/go-funk"
)
type (
// KeycloakRolesConfig defines the config for the KeycloakRoles roles middleware.
KeycloakRolesConfig struct {
// Skipper defines a function to skip middleware.
Skipper middleware.Skipper
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc middleware.BeforeFunc
// SuccessHandler defines a function which is executed for a valid token.
SuccessHandler KeycloakSuccessHandler
// ErrorHandler defines a function which is executed for an invalid token.
// It may be used to define a custom KeycloakRoles error.
ErrorHandler KeycloakErrorHandler
// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
ErrorHandlerWithContext KeycloakErrorHandlerWithContext
// KeycloakRoles defines the KeycloakRoles roles having access.
KeycloakRoles []string
// TokenContextKey is the context key which stores the keycloak jwt token
// Optional. Default value "user".
TokenContextKey string
// RolesContextKey is the context key which stores the roles as []string
// Optional. Default value "roles".
RolesContextKey string
}
)
// Errors
var (
ErrClaimsMissing = echo.NewHTTPError(http.StatusInternalServerError, "no claims in context found")
ErrRealmAccessMissing = echo.NewHTTPError(http.StatusInternalServerError, "no realm_access in claims found")
ErrRolesMissing = echo.NewHTTPError(http.StatusInternalServerError, "no roles in realm_access claim found")
ErrRolesInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid roles")
)
var (
// DefaultKeycloakRolesConfig is the default KeycloakRoles roles middleware config.
DefaultKeycloakRolesConfig = KeycloakRolesConfig{
Skipper: middleware.DefaultSkipper,
TokenContextKey: "user",
RolesContextKey: "roles",
}
)
// KeycloakRoles returns a KeycloakRoles auth middleware.
//
// For valid token, it sets the user in context and calls next handler.
// For invalid roles, it returns "403 - Forbidden" error.
// For missing token in context, it returns "500 - Internal Server Error" error.
func KeycloakRoles(roles []string) echo.MiddlewareFunc {
c := DefaultKeycloakRolesConfig
c.KeycloakRoles = roles
return KeycloakRolesWithConfig(c)
}
// KeycloakRolesWithConfig returns a KeycloakRoles auth middleware with config.
// See: `KeycloakRoles()`.
func KeycloakRolesWithConfig(config KeycloakRolesConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultKeycloakRolesConfig.Skipper
}
if len(config.KeycloakRoles) == 0 {
panic("echo: keycloak roles middleware requires keycloak roles")
}
if config.TokenContextKey == "" {
config.TokenContextKey = DefaultKeycloakRolesConfig.TokenContextKey
}
if config.RolesContextKey == "" {
config.RolesContextKey = DefaultKeycloakRolesConfig.RolesContextKey
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
if config.BeforeFunc != nil {
config.BeforeFunc(c)
}
var err error
var roles []string
token := c.Get(DefaultKeycloakRolesConfig.TokenContextKey).(*jwt.Token)
claims, ok := token.Claims.(*jwt.MapClaims)
if !ok || claims == nil {
err = ErrClaimsMissing
} else {
realmAcces, ok := (*claims)["realm_access"].(map[string]interface{})
if !ok {
err = ErrRealmAccessMissing
} else {
rolesRaw, ok := realmAcces["roles"].([]interface{})
if !ok {
err = ErrRolesMissing
} else {
for _, r := range rolesRaw {
roles = append(roles, r.(string))
}
err = ErrRolesInvalid
for _, r := range config.KeycloakRoles {
if funk.ContainsString(roles, r) {
err = nil
break
}
}
}
}
}
if err == nil && token.Valid {
c.Set(config.RolesContextKey, roles)
if config.SuccessHandler != nil {
config.SuccessHandler(c)
}
return next(c)
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
if config.ErrorHandlerWithContext != nil {
return config.ErrorHandlerWithContext(err, c)
}
return &echo.HTTPError{
Code: http.StatusForbidden,
Message: ErrRolesInvalid.Error(),
Internal: err,
}
}
}
}