/
middleware.go
251 lines (207 loc) · 6.92 KB
/
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
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
* Copyright (C) 2023 Nethesis S.r.l.
* http://www.nethesis.it - info@nethesis.it
*
* SPDX-License-Identifier: GPL-2.0-only
*
* author: Edoardo Spadoni <edoardo.spadoni@nethesis.it>
*/
package middleware
import (
"bytes"
"io"
"io/ioutil"
"regexp"
"strings"
"time"
"github.com/fatih/structs"
"github.com/gin-gonic/gin"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/NethServer/nethsecurity-api/configuration"
"github.com/NethServer/nethsecurity-api/logs"
"github.com/NethServer/nethsecurity-api/methods"
"github.com/NethServer/nethsecurity-api/models"
"github.com/NethServer/nethsecurity-api/response"
)
type login struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
var jwtMiddleware *jwt.GinJWTMiddleware
var identityKey = "id"
func InstanceJWT() *jwt.GinJWTMiddleware {
if jwtMiddleware == nil {
jwtMiddleware := InitJWT()
return jwtMiddleware
}
return jwtMiddleware
}
func InitJWT() *jwt.GinJWTMiddleware {
// define jwt middleware
authMiddleware, errDefine := jwt.New(&jwt.GinJWTMiddleware{
Realm: "nethserver",
Key: []byte(configuration.Config.SecretJWT),
Timeout: time.Hour * 24, // 1 day
MaxRefresh: time.Hour * 24, // 1 day
IdentityKey: identityKey,
Authenticator: func(c *gin.Context) (interface{}, error) {
// check login credentials exists
var loginVals login
if err := c.ShouldBind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
// set login credentials
username := loginVals.Username
password := loginVals.Password
// check login
err := methods.CheckAuthentication(username, password)
if err != nil {
// login fail action
logs.Logs.Println("[INFO][AUTH] authentication failed for user " + username + ": " + err.Error())
// return JWT error
return nil, jwt.ErrFailedAuthentication
}
// login ok action
logs.Logs.Println("[INFO][AUTH] authentication success for user " + username)
// return user auth model
return &models.UserAuthorizations{
Username: username,
}, nil
},
PayloadFunc: func(data interface{}) jwt.MapClaims {
// read current user
if user, ok := data.(*models.UserAuthorizations); ok {
// check if user require 2fa
status, _ := methods.GetUserStatus(user.Username)
// create claims map
return jwt.MapClaims{
identityKey: user.Username,
"role": "",
"actions": []string{},
"2fa": status == "1",
}
}
// return claims map
return jwt.MapClaims{}
},
IdentityHandler: func(c *gin.Context) interface{} {
// handle identity and extract claims
claims := jwt.ExtractClaims(c)
// create user object
user := &models.UserAuthorizations{
Username: claims[identityKey].(string),
Role: "admin",
Actions: nil,
}
// return user
return user
},
Authorizator: func(data interface{}, c *gin.Context) bool {
// check token validation
claims, _ := InstanceJWT().GetClaimsFromJWT(c)
token, _ := InstanceJWT().ParseToken(c)
// log request and body
reqMethod := c.Request.Method
reqURI := c.Request.RequestURI
// check if token exists
if !methods.CheckTokenValidation(claims["id"].(string), token.Raw) {
// write logs
logs.Logs.Println("[INFO][AUTH] authorization failed for user " + claims["id"].(string) + ". " + reqMethod + " " + reqURI)
// not authorized
return false
}
// extract body
reqBody := ""
// if reqURI contains /files path, just replace the body with a static string to avoid logging the file content
if strings.Contains(reqURI, "/files") {
reqBody = "<file>"
}
if reqBody != "<file>" && (reqMethod == "POST" || reqMethod == "PUT") {
// extract body
var buf bytes.Buffer
tee := io.TeeReader(c.Request.Body, &buf)
body, _ := ioutil.ReadAll(tee)
c.Request.Body = ioutil.NopCloser(&buf)
// get JSON string body
jsonB := string(body)
// remove all spaces
jsonB = strings.ReplaceAll(jsonB, " ", "")
// create regex for sensitive words
for _, s := range configuration.Config.SensitiveList {
// create regex
r1 := regexp.MustCompile(`"` + s + `":"(.*?)"`) // match "token|password|secret":"<sensitive>"
r2 := regexp.MustCompile(`"` + s + `","(.*?)"`) // match "token|password|secret","<sensitive>"
// apply regex
jsonB = r1.ReplaceAllString(jsonB, `"`+s+`":"XXX"`)
jsonB = r2.ReplaceAllString(jsonB, `"`+s+`":"XXX"`)
}
// compose req body
reqBody = jsonB
}
logs.Logs.Println("[INFO][AUTH] authorization success for user " + claims["id"].(string) + ". " + reqMethod + " " + reqURI + " " + reqBody)
// authorized
return true
},
LoginResponse: func(c *gin.Context, code int, token string, t time.Time) {
//get claims
tokenObj, _ := InstanceJWT().ParseTokenString(token)
claims := jwt.ExtractClaimsFromToken(tokenObj)
// set token to valid, if not 2FA
if !claims["2fa"].(bool) {
methods.SetTokenValidation(claims["id"].(string), token)
}
// write logs
logs.Logs.Println("[INFO][AUTH] login response success for user " + claims["id"].(string))
// return 200 OK
c.JSON(200, gin.H{"code": 200, "expire": t, "token": token})
},
RefreshResponse: func(c *gin.Context, code int, token string, t time.Time) {
//get claims
tokenObj, _ := InstanceJWT().ParseTokenString(token)
claims := jwt.ExtractClaimsFromToken(tokenObj)
// set token to valid
methods.SetTokenValidation(claims["id"].(string), token)
// write logs
logs.Logs.Println("[INFO][AUTH] refresh response success for user " + claims["id"].(string))
// return 200 OK
c.JSON(200, gin.H{"code": 200, "expire": t, "token": token})
},
LogoutResponse: func(c *gin.Context, code int) {
//get claims
tokenObj, _ := InstanceJWT().ParseToken(c)
claims := jwt.ExtractClaimsFromToken(tokenObj)
// set token to invalid
methods.DelTokenValidation(claims["id"].(string), tokenObj.Raw)
// write logs
logs.Logs.Println("[INFO][AUTH] logout response success for user " + claims["id"].(string))
// reutrn 200 OK
c.JSON(200, gin.H{"code": 200})
},
Unauthorized: func(c *gin.Context, code int, message string) {
// write logs
logs.Logs.Println("[INFO][AUTH] unauthorized request: " + message)
// response not authorized
c.JSON(code, structs.Map(response.StatusUnauthorized{
Code: code,
Message: message,
Data: nil,
}))
return
},
TokenLookup: "header: Authorization, token: jwt",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
})
// check middleware errors
if errDefine != nil {
logs.Logs.Println("[ERR][AUTH] middleware definition error: " + errDefine.Error())
}
// init middleware
errInit := authMiddleware.MiddlewareInit()
// check error on initialization
if errInit != nil {
logs.Logs.Println("[ERR][AUTH] middleware initialization error: " + errInit.Error())
}
// return object
return authMiddleware
}