1 package authserver
2
3 import (
4 "errors"
5 "net/http"
6 "slices"
7 "time"
8
9 "github.com/gin-contrib/sessions"
10 "github.com/gin-gonic/gin"
11
12 "edge-infra.dev/pkg/edge/api/sql"
13 authproxy "edge-infra.dev/pkg/edge/auth-proxy/types"
14 "edge-infra.dev/pkg/edge/bsl"
15 )
16
17 type Permission string
18
19 const (
20 Editor Permission = "Editor"
21 Viewer Permission = "Viewer"
22 )
23
24 type role struct {
25 value Permission
26 priority int
27 }
28
29 var (
30 AllAllowedRoles = map[string]role{
31 "EDGE_ORG_ADMIN": {value: Editor, priority: 100},
32 "EDGE_BANNER_OPERATOR": {value: Editor, priority: 10},
33 "EDGE_BANNER_ADMIN": {value: Editor, priority: 10},
34 "EDGE_BANNER_VIEWER": {value: Viewer, priority: 1},
35 }
36 )
37
38
39 func (as *AuthServer) handleNotAuthenticated(ctx *gin.Context, startTime time.Time) {
40 as.infoLog(ctx, "Request Unauthorized", ErrInvalidCredentials)
41 ctx.Status(http.StatusUnauthorized)
42 handleMetrics(false, startTime)
43 }
44
45
46 func (as *AuthServer) handleNoExpiration(ctx *gin.Context, startTime time.Time) {
47 as.infoLog(ctx, "Internal Server Error", ErrSessionHasNoExpiration)
48 ctx.Status(http.StatusInternalServerError)
49 handleMetrics(false, startTime)
50 }
51
52
53 func (as *AuthServer) handleExpiration(ctx *gin.Context, startTime time.Time) {
54 as.infoLog(ctx, "Request denied", ErrSessionExpired)
55 ctx.Status(http.StatusUnauthorized)
56 handleMetrics(false, startTime)
57 }
58
59
60 func (as *AuthServer) handleValidSession(ctx *gin.Context, startTime time.Time, session sessions.Session) {
61 err := as.authFilter(ctx, session)
62 if err != nil {
63 var hError *httpError
64 if errors.As(err, &hError) {
65 ctx.Status(hError.StatusCode())
66 } else {
67 ctx.Status(http.StatusInternalServerError)
68 }
69
70 as.Log.Error(err, "error authorizing request")
71 handleMetrics(false, startTime)
72 return
73 }
74
75 organization := session.Get(authproxy.SessionOrganizationField)
76 if organization == nil {
77 ctx.Status(http.StatusUnauthorized)
78 handleMetrics(false, startTime)
79 return
80 }
81 userOrganization := organization.(string)
82 roles := session.Get(authproxy.SessionRolesField)
83 if roles == nil {
84 ctx.Status(http.StatusUnauthorized)
85 handleMetrics(false, startTime)
86 return
87 }
88 userRoles := roles.([]string)
89 if slices.Contains(userRoles, "EDGE_ORG_ADMIN") {
90 ctx.Status(http.StatusOK)
91 ctx.Header("X-WEBAUTH-ROLE", string(AllAllowedRoles["EDGE_ORG_ADMIN"].value))
92 handleMetrics(true, startTime)
93 return
94 }
95 banner := ctx.GetHeader(bannerHeaderName)
96 tenantEdgeID, err := as.getTenantEdgeID(ctx, startTime, banner)
97 if err != nil {
98 return
99 }
100 orgName, err := as.getOrgName(ctx, startTime, tenantEdgeID)
101 if err != nil {
102 return
103 }
104 if !as.enforceRoleAccess(ctx, orgName, bsl.GetOrgShortName(userOrganization), userRoles) {
105 ctx.Status(http.StatusUnauthorized)
106 handleMetrics(false, startTime)
107 return
108 }
109
110 ctx.Status(http.StatusOK)
111 handleMetrics(true, startTime)
112 }
113
114
115 func (as *AuthServer) handleAuthenticated(ctx *gin.Context, startTime time.Time, session sessions.Session) {
116 as.Log.Info("new request...")
117 expiration := session.Get(authproxy.SessionExpirationField)
118 switch expiration {
119 case nil:
120 as.handleNoExpiration(ctx, startTime)
121 default:
122 expiresAt := expiration.(time.Time)
123 sessionHasExpired := expiresAt.Before(time.Now())
124 if sessionHasExpired {
125 as.handleExpiration(ctx, startTime)
126 } else {
127 as.handleValidSession(ctx, startTime, session)
128 }
129 }
130 }
131
132
133 func (as *AuthServer) getTenantEdgeID(ctx *gin.Context, startTime time.Time, banner string) (string, error) {
134 tenantEdgeID := ""
135 row := as.db.QueryRowContext(ctx, sql.GetTenantEdgeIDFromBanner, banner)
136 if err := row.Scan(&tenantEdgeID); err != nil {
137 as.Log.Error(err, "an error occurred scanning banner query result")
138 ctx.Status(http.StatusInternalServerError)
139 handleMetrics(false, startTime)
140 return "", err
141 }
142 return tenantEdgeID, nil
143 }
144
145
146 func (as *AuthServer) getOrgName(ctx *gin.Context, startTime time.Time, tenantEdgeID string) (string, error) {
147 orgName := ""
148 row := as.db.QueryRowContext(ctx, sql.GetOrgNameFromTenant, tenantEdgeID)
149 if err := row.Scan(&orgName); err != nil {
150 as.Log.Error(err, "an error occurred scanning tenant query result")
151 ctx.Status(http.StatusInternalServerError)
152 handleMetrics(false, startTime)
153 return "", err
154 }
155 return orgName, nil
156 }
157
158
159
160 func (as *AuthServer) enforceRoleAccess(ctx *gin.Context, orgName, organization string, roles []string) bool {
161 AllowedRoles := AllAllowedRoles
162 allowed := false
163 maxRole := role{value: Viewer, priority: 1}
164 if orgName != organization {
165 return false
166 }
167 for _, key := range roles {
168 if val, exists := AllowedRoles[key]; exists {
169 allowed = true
170 if maxRole.priority < val.priority {
171 maxRole = val
172 }
173 }
174 }
175 ctx.Header(headerKeyWebauthRole, string(maxRole.value))
176 return allowed
177 }
178
View as plain text