...

Source file src/edge-infra.dev/pkg/sds/remoteaccess/authserver/handlers.go

Documentation: edge-infra.dev/pkg/sds/remoteaccess/authserver

     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  // handleNotAuthenticated handles when request is not authenticated and returns a 401 Unauthorized.
    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  // handleNoExpiration handles when existing session has no expiration time. will probably never happen.
    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  // handleExpiration handles when existing session has expired.
    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  // handleValidSession handles when existing session is valid and not expired.
    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  // handleAuthenticated
   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  // getTenantEdgeID gets the tenant_edge_id of a banner.
   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  // getOrgName gets the org_name of a tenant.
   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  // enforceRoleAccess ensures user's Role is either EDGE_BANNER_ADMIN,
   159  // EDGE_BANNER_OPERATOR or EDGE_BANNER_VIEWER to use VNC and grafana
   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