package authserver import ( "errors" "net/http" "slices" "time" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "edge-infra.dev/pkg/edge/api/sql" authproxy "edge-infra.dev/pkg/edge/auth-proxy/types" "edge-infra.dev/pkg/edge/bsl" ) type Permission string const ( Editor Permission = "Editor" Viewer Permission = "Viewer" ) type role struct { value Permission priority int } var ( AllAllowedRoles = map[string]role{ "EDGE_ORG_ADMIN": {value: Editor, priority: 100}, "EDGE_BANNER_OPERATOR": {value: Editor, priority: 10}, "EDGE_BANNER_ADMIN": {value: Editor, priority: 10}, "EDGE_BANNER_VIEWER": {value: Viewer, priority: 1}, } ) // handleNotAuthenticated handles when request is not authenticated and returns a 401 Unauthorized. func (as *AuthServer) handleNotAuthenticated(ctx *gin.Context, startTime time.Time) { as.infoLog(ctx, "Request Unauthorized", ErrInvalidCredentials) ctx.Status(http.StatusUnauthorized) handleMetrics(false, startTime) } // handleNoExpiration handles when existing session has no expiration time. will probably never happen. func (as *AuthServer) handleNoExpiration(ctx *gin.Context, startTime time.Time) { as.infoLog(ctx, "Internal Server Error", ErrSessionHasNoExpiration) ctx.Status(http.StatusInternalServerError) handleMetrics(false, startTime) } // handleExpiration handles when existing session has expired. func (as *AuthServer) handleExpiration(ctx *gin.Context, startTime time.Time) { as.infoLog(ctx, "Request denied", ErrSessionExpired) ctx.Status(http.StatusUnauthorized) handleMetrics(false, startTime) } // handleValidSession handles when existing session is valid and not expired. func (as *AuthServer) handleValidSession(ctx *gin.Context, startTime time.Time, session sessions.Session) { err := as.authFilter(ctx, session) if err != nil { var hError *httpError if errors.As(err, &hError) { ctx.Status(hError.StatusCode()) } else { ctx.Status(http.StatusInternalServerError) } as.Log.Error(err, "error authorizing request") handleMetrics(false, startTime) return } organization := session.Get(authproxy.SessionOrganizationField) if organization == nil { ctx.Status(http.StatusUnauthorized) handleMetrics(false, startTime) return } userOrganization := organization.(string) roles := session.Get(authproxy.SessionRolesField) if roles == nil { ctx.Status(http.StatusUnauthorized) handleMetrics(false, startTime) return } userRoles := roles.([]string) if slices.Contains(userRoles, "EDGE_ORG_ADMIN") { ctx.Status(http.StatusOK) ctx.Header("X-WEBAUTH-ROLE", string(AllAllowedRoles["EDGE_ORG_ADMIN"].value)) handleMetrics(true, startTime) return } banner := ctx.GetHeader(bannerHeaderName) tenantEdgeID, err := as.getTenantEdgeID(ctx, startTime, banner) if err != nil { return } orgName, err := as.getOrgName(ctx, startTime, tenantEdgeID) if err != nil { return } if !as.enforceRoleAccess(ctx, orgName, bsl.GetOrgShortName(userOrganization), userRoles) { ctx.Status(http.StatusUnauthorized) handleMetrics(false, startTime) return } ctx.Status(http.StatusOK) handleMetrics(true, startTime) } // handleAuthenticated func (as *AuthServer) handleAuthenticated(ctx *gin.Context, startTime time.Time, session sessions.Session) { as.Log.Info("new request...") expiration := session.Get(authproxy.SessionExpirationField) switch expiration { case nil: as.handleNoExpiration(ctx, startTime) default: expiresAt := expiration.(time.Time) sessionHasExpired := expiresAt.Before(time.Now()) if sessionHasExpired { as.handleExpiration(ctx, startTime) } else { as.handleValidSession(ctx, startTime, session) } } } // getTenantEdgeID gets the tenant_edge_id of a banner. func (as *AuthServer) getTenantEdgeID(ctx *gin.Context, startTime time.Time, banner string) (string, error) { tenantEdgeID := "" row := as.db.QueryRowContext(ctx, sql.GetTenantEdgeIDFromBanner, banner) if err := row.Scan(&tenantEdgeID); err != nil { as.Log.Error(err, "an error occurred scanning banner query result") ctx.Status(http.StatusInternalServerError) handleMetrics(false, startTime) return "", err } return tenantEdgeID, nil } // getOrgName gets the org_name of a tenant. func (as *AuthServer) getOrgName(ctx *gin.Context, startTime time.Time, tenantEdgeID string) (string, error) { orgName := "" row := as.db.QueryRowContext(ctx, sql.GetOrgNameFromTenant, tenantEdgeID) if err := row.Scan(&orgName); err != nil { as.Log.Error(err, "an error occurred scanning tenant query result") ctx.Status(http.StatusInternalServerError) handleMetrics(false, startTime) return "", err } return orgName, nil } // enforceRoleAccess ensures user's Role is either EDGE_BANNER_ADMIN, // EDGE_BANNER_OPERATOR or EDGE_BANNER_VIEWER to use VNC and grafana func (as *AuthServer) enforceRoleAccess(ctx *gin.Context, orgName, organization string, roles []string) bool { AllowedRoles := AllAllowedRoles allowed := false maxRole := role{value: Viewer, priority: 1} if orgName != organization { return false } for _, key := range roles { if val, exists := AllowedRoles[key]; exists { allowed = true if maxRole.priority < val.priority { maxRole = val } } } ctx.Header(headerKeyWebauthRole, string(maxRole.value)) return allowed }