/
require_auth.go
135 lines (98 loc) · 4.99 KB
/
require_auth.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
package middlewares
import (
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/session"
)
// Require1FA check if user has enough permissions to execute the next handler.
func Require1FA(next RequestHandler) RequestHandler {
return func(ctx *AutheliaCtx) {
if s, err := ctx.GetSession(); err != nil || s.AuthenticationLevel < authentication.OneFactor {
ctx.ReplyForbidden()
return
}
next(ctx)
}
}
// RequireElevated requires various elevation criteria.
func RequireElevated(next RequestHandler) RequestHandler {
return func(ctx *AutheliaCtx) {
var (
userSession session.UserSession
err error
)
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Error("Error occurred attempting to lookup user session during an elevation check.")
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{FirstFactor: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
if userSession.AuthenticationLevel < authentication.OneFactor {
ctx.Logger.Warn("An anonymous user attempted to access an elevated protected endpoint.")
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{FirstFactor: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
if !handleRequireElevatedShouldDoNext(ctx, &userSession) {
return
}
next(ctx)
}
}
func handleRequireElevatedShouldDoNext(ctx *AutheliaCtx, userSession *session.UserSession) (doNext bool) {
var err error
if ctx.Configuration.IdentityValidation.ElevatedSession.SkipSecondFactor && userSession.AuthenticationLevel >= authentication.TwoFactor {
ctx.Logger.WithFields(map[string]any{"user": userSession.Username}).Trace("The user session elevation was not checked as the user has performed second factor authentication and the policy to skip this is enabled.")
return true
}
if ctx.Configuration.IdentityValidation.ElevatedSession.RequireSecondFactor && userSession.AuthenticationLevel < authentication.TwoFactor {
var info model.UserInfo
if info, err = ctx.Providers.StorageProvider.LoadUserInfo(ctx, userSession.Username); err != nil {
ctx.Logger.WithError(err).Error("Error occurred attempting to lookup user information during a elevation check.")
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{SecondFactor: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
if info.HasTOTP || info.HasWebAuthn || info.HasDuo {
ctx.Logger.WithFields(map[string]any{"user": userSession.Username}).Info("The user session elevation was not checked as the user must have also performed second factor authentication.")
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{SecondFactor: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
}
if userSession.Elevations.User == nil {
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{Elevation: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
return handleRequireElevatedShouldDoNextValidate(ctx, userSession)
}
func handleRequireElevatedShouldDoNextValidate(ctx *AutheliaCtx, userSession *session.UserSession) (doNext bool) {
var err error
invalid := false
if ctx.GetClock().Now().After(userSession.Elevations.User.Expires) {
invalid = true
ctx.Logger.WithFields(map[string]any{"user": userSession.Username, "expired": userSession.Elevations.User.Expires.Unix()}).Info("The user session elevation was expired. It will be destroyed and the users access will be forbidden.")
}
if !ctx.RemoteIP().Equal(userSession.Elevations.User.RemoteIP) {
invalid = true
ctx.Logger.WithFields(map[string]any{"user": userSession.Username, "expected_ip": userSession.Elevations.User.RemoteIP.String()}).Warn("The user session elevation did not have a matching IP. It will be destroyed and the users access will be forbidden.")
}
if invalid {
userSession.Elevations.User = nil
if err = ctx.SaveSession(*userSession); err != nil {
ctx.Logger.WithError(err).Error("Error occurred trying to save the user session after a policy constraint violation occurred.")
}
if err = ctx.ReplyJSON(OKResponse{Status: "KO", Data: ElevatedForbiddenResponse{Elevation: true}}, fasthttp.StatusForbidden); err != nil {
ctx.Logger.WithError(err).Error("Error occurred encoding JSON response during an elevation check.")
}
return
}
return true
}