...

Source file src/edge-infra.dev/pkg/edge/api/graph/resolver/roles.go

Documentation: edge-infra.dev/pkg/edge/api/graph/resolver

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  
     9  	"edge-infra.dev/pkg/edge/api/apierror"
    10  	"edge-infra.dev/pkg/edge/api/graph/model"
    11  	"edge-infra.dev/pkg/edge/api/middleware"
    12  	"edge-infra.dev/pkg/edge/api/utils"
    13  	"edge-infra.dev/pkg/edge/bsl"
    14  
    15  	"github.com/99designs/gqlgen/graphql"
    16  )
    17  
    18  func UserHasMatchingRole(ctx context.Context, apiRoles []model.Role) bool {
    19  	u := middleware.ForContext(ctx)
    20  	if u == nil || len(u.Roles) == 0 {
    21  		return false
    22  	}
    23  	for _, role := range apiRoles {
    24  		if utils.Contains(u.Roles, string(role)) {
    25  			return true
    26  		}
    27  	}
    28  	return false
    29  }
    30  
    31  func (r *Resolver) CanAssignRole() func(ctx context.Context, obj interface{}, next graphql.Resolver, abilityMap map[string]interface{}) (res interface{}, err error) {
    32  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, abilityMap map[string]interface{}) (res interface{}, err error) {
    33  		userRoles, err := middleware.GetEdgeRoles(ctx)
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  		bm, ok := obj.(map[string]interface{})
    38  		if !ok {
    39  			return nil, fmt.Errorf("input object is not a map")
    40  		}
    41  
    42  		requestedRoles, ok := bm["roles"]            // assign roles
    43  		requestedRole, okRole := bm["role"].(string) // assign role
    44  		if !ok {
    45  			if !okRole {
    46  				return nil, fmt.Errorf("there is no role field in the input map")
    47  			}
    48  		}
    49  
    50  		accessibleRoles := []string{}
    51  		for _, userRole := range userRoles {
    52  			if abilityMap[userRole] != nil {
    53  				for _, role := range abilityMap[userRole].([]interface{}) {
    54  					accessibleRoles = append(accessibleRoles, role.(string))
    55  				}
    56  			}
    57  		}
    58  
    59  		// For assign role
    60  		if requestedRole != "" {
    61  			if utils.Contains(accessibleRoles, requestedRole) {
    62  				return next(ctx)
    63  			}
    64  			return nil, fmt.Errorf("access denied: user cannot assign the requested role %s", requestedRole)
    65  		}
    66  
    67  		// Assign roles
    68  		// Get user being assigned roles
    69  		userToAssign, okUser := bm["username"].(string)
    70  		if !okUser {
    71  			return nil, fmt.Errorf("fail to grab user to assign info")
    72  		}
    73  
    74  		userCurrentRoles, err := r.RoleService.GetEdgeGroupsForUserUser(ctx, userToAssign)
    75  		if err != nil {
    76  			return nil, fmt.Errorf("fail to get role for current user being assigned")
    77  		}
    78  
    79  		if rqRole, okRq := requestedRoles.([]interface{}); okRq {
    80  			for _, roleValue := range rqRole {
    81  				role := roleValue.(string)
    82  				if role == "" {
    83  					return nil, fmt.Errorf("empty role input")
    84  				}
    85  				if !utils.Contains(accessibleRoles, role) && !utils.Contains(userCurrentRoles, role) {
    86  					// Check if user being assiged already has role(s) in the requested role list
    87  					return nil, fmt.Errorf("access denied: user cannot assign the requested role %s", role)
    88  				}
    89  			}
    90  		}
    91  		return next(ctx)
    92  	}
    93  }
    94  
    95  func (r *Resolver) HasHelmWorkloadAccess() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
    96  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
    97  		if UserHasMatchingRole(ctx, []model.Role{model.RoleTotpRole}) {
    98  			return next(ctx)
    99  		}
   100  		if UserHasMatchingRole(ctx, []model.Role{model.RoleEdgeBootstrap}) {
   101  			return next(ctx)
   102  		}
   103  		id, ok, o := getUserOrg(ctx, obj, field)
   104  		if !ok || id == "" {
   105  			return next(ctx)
   106  		}
   107  		banners, errs := r.BannerService.GetBanners(ctx)
   108  		if errs != nil && errs.Error() != apierror.BannerCheckMessage {
   109  			return nil, apierror.AddEmptyAPIErrorToResponse(ctx, errs)
   110  		}
   111  		banner, errs := r.HelmService.GetBannerByHelmEdgeID(ctx, id)
   112  		if errs != nil {
   113  			return nil, apierror.AddEmptyAPIErrorToResponse(ctx, errs)
   114  		}
   115  		t, err := r.TenantService.GetTenantByBannerID(ctx, banner.BannerEdgeID)
   116  		if err != nil || o != t.OrgName {
   117  			return nil, fmt.Errorf("access denied: user org %s does not have access to requested organization", o)
   118  		}
   119  		if !isUserAssignedToBanner(banners, banner.BannerEdgeID) {
   120  			return nil, fmt.Errorf("access denied: user does not have access to the the requested workload resource %s", id)
   121  		}
   122  		addIDsToContext(ctx, t.TenantEdgeID, banner.BannerEdgeID, "")
   123  		return next(ctx)
   124  	}
   125  }
   126  
   127  func (r *Resolver) HasClusterAccess() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   128  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   129  		if UserHasMatchingRole(ctx, []model.Role{model.RoleTotpRole}) {
   130  			return next(ctx)
   131  		}
   132  		if UserHasMatchingRole(ctx, []model.Role{model.RoleEdgeBootstrap}) {
   133  			return next(ctx)
   134  		}
   135  		id, ok, o := getUserOrg(ctx, obj, field)
   136  		if !ok || id == "" {
   137  			return next(ctx)
   138  		}
   139  		banners, errs := r.BannerService.GetBanners(ctx)
   140  		if errs != nil && errs.Error() != apierror.BannerCheckMessage {
   141  			return nil, apierror.AddEmptyAPIErrorToResponse(ctx, errs)
   142  		}
   143  		cluster, errs := r.StoreClusterService.GetCluster(ctx, id)
   144  		if errs != nil {
   145  			return nil, apierror.AddEmptyAPIErrorToResponse(ctx, errs)
   146  		}
   147  		t, err := r.TenantService.GetTenantByBannerID(ctx, cluster.BannerEdgeID)
   148  		if err != nil || o != t.OrgName {
   149  			return nil, fmt.Errorf("access denied: user org %s does not have access to requested organization", o)
   150  		}
   151  		if !isUserAssignedToBanner(banners, cluster.BannerEdgeID) {
   152  			return nil, fmt.Errorf("access denied: user does not have access to the banner %s that the requested cluster resource %s exists in", cluster.BannerEdgeID, id)
   153  		}
   154  		addIDsToContext(ctx, t.TenantEdgeID, cluster.BannerEdgeID, cluster.ClusterEdgeID)
   155  		return next(ctx)
   156  	}
   157  }
   158  
   159  func addIDsToContext(ctx context.Context, tenantID string, bannerID string, clusterEdgeID string) {
   160  	user := middleware.ForContext(ctx)
   161  	user.ClusterEdgeID = clusterEdgeID
   162  	user.BannerID = bannerID
   163  	user.TenantID = tenantID
   164  }
   165  
   166  func fullUsername(username, org string) string {
   167  	return fmt.Sprintf("acct:%s@%s", org, username)
   168  }
   169  
   170  func (r *Resolver) HasBannerAccess() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   171  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   172  		if UserHasMatchingRole(ctx, []model.Role{model.RoleTotpRole}) {
   173  			return next(ctx)
   174  		}
   175  		id, ok, o := getUserOrg(ctx, obj, field)
   176  		if !ok || id == "" {
   177  			return next(ctx)
   178  		}
   179  		t, err := r.TenantService.GetTenantByBannerID(ctx, id)
   180  		if err != nil || o != t.OrgName {
   181  			return nil, fmt.Errorf("access denied: user org %s does not have access to requested organization", o)
   182  		}
   183  		banners, errs := r.BannerService.GetBanners(ctx)
   184  		if errs != nil && errs.Error() != apierror.BannerCheckMessage {
   185  			return nil, apierror.AddAPIErrorToResponse(ctx, err)
   186  		}
   187  		if !isUserAssignedToBanner(banners, id) {
   188  			return nil, fmt.Errorf("access denied: user does not have access to requested banner resource %s", id)
   189  		}
   190  		addIDsToContext(ctx, t.TenantEdgeID, id, "")
   191  		return next(ctx)
   192  	}
   193  }
   194  
   195  func (r *Resolver) HasLabelAccess() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) { //nolint
   196  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   197  		//if totp token don't validate
   198  		if UserHasMatchingRole(ctx, []model.Role{model.RoleTotpRole}) {
   199  			return next(ctx)
   200  		}
   201  		bm, o := verifyUserAccess(ctx, obj)
   202  		if bm[field] == nil {
   203  			return next(ctx)
   204  		}
   205  		return fieldInterceptor(ctx, next, bm, o, field, func(ctx context.Context, id, o string) error {
   206  			return r.checkIfUserHasLabelAccessByBanner(ctx, id, o)
   207  		})
   208  	}
   209  }
   210  
   211  func (r *Resolver) HasRole() func(ctx context.Context, obj interface{}, next graphql.Resolver, roles []model.Role) (interface{}, error) {
   212  	return func(ctx context.Context, _ interface{}, next graphql.Resolver, roles []model.Role) (interface{}, error) {
   213  		if UserHasMatchingRole(ctx, roles) {
   214  			return next(ctx)
   215  		}
   216  		return nil, fmt.Errorf("access denied: user not one of %v", roles)
   217  	}
   218  }
   219  
   220  // HasTerminalAccess
   221  func (r *Resolver) HasTerminalAccess() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (interface{}, error) { //nolint
   222  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   223  		if UserHasMatchingRole(ctx, []model.Role{model.RoleTotpRole}) {
   224  			return next(ctx)
   225  		}
   226  		bm, o := verifyUserAccess(ctx, obj)
   227  		if bm[field] == nil {
   228  			return next(ctx)
   229  		}
   230  		return fieldInterceptor(ctx, next, bm, o, field, func(ctx context.Context, id, o string) error {
   231  			return r.checkUserTerminalAccess(ctx, id, o)
   232  		})
   233  	}
   234  }
   235  
   236  // HasValidProviderPinSetting
   237  func (r *Resolver) HasValidProviderPinSetting() func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (interface{}, error) { //nolint
   238  	return func(ctx context.Context, obj interface{}, next graphql.Resolver, field string) (res interface{}, err error) {
   239  		o := obj.(map[string]interface{})
   240  		x := o[field].(int64)
   241  		if x >= 3 && x <= 5 {
   242  			return next(ctx)
   243  		}
   244  		return nil, fmt.Errorf("%s must be between 3-5", field)
   245  	}
   246  }
   247  
   248  // verifyUserAccess
   249  func verifyUserAccess(ctx context.Context, obj interface{}) (map[string]interface{}, string) {
   250  	u := middleware.ForContext(ctx)
   251  	if u == nil || obj == nil {
   252  		return make(map[string]interface{}, 0), ""
   253  	}
   254  	bm := obj.(map[string]interface{})
   255  	o := bsl.GetOrgShortName(u.Organization)
   256  	return bm, o
   257  }
   258  
   259  // fieldInterceptor
   260  func fieldInterceptor(ctx context.Context, next graphql.Resolver, bm map[string]interface{}, o, field string, cb func(ctx context.Context, id string, o string) error) (interface{}, error) {
   261  	_type := reflect.TypeOf(bm[field])
   262  	switch _type.Kind() {
   263  	case reflect.Slice:
   264  		fieldParams := bm[field].([]interface{})
   265  		for i := range fieldParams {
   266  			fieldParam := fieldParams[i]
   267  			id, ok := fieldParam.(string)
   268  			if !ok || id == "" {
   269  				return next(ctx)
   270  			}
   271  			err := cb(ctx, id, o)
   272  			if err != nil {
   273  				return nil, err
   274  			}
   275  		}
   276  	case reflect.String:
   277  		id, ok := bm[field].(string)
   278  		if !ok || id == "" {
   279  			return next(ctx)
   280  		}
   281  		err := cb(ctx, id, o)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  	default:
   286  		return nil, errors.New("unknown entity type")
   287  	}
   288  	return next(ctx)
   289  }
   290  
   291  func getUserOrg(ctx context.Context, obj interface{}, field string) (string, bool, string) {
   292  	bm, o := verifyUserAccess(ctx, obj)
   293  	id, ok := bm[field].(string)
   294  	return id, ok, o
   295  }
   296  
   297  func isUserAssignedToBanner(banners []*model.Banner, bannerID string) bool {
   298  	for _, banner := range banners {
   299  		if banner.BannerEdgeID == bannerID {
   300  			return true
   301  		}
   302  	}
   303  	return false
   304  }
   305  
   306  func (r *Resolver) checkIfUserHasLabelAccessByBanner(ctx context.Context, labelEdgeID, userOrg string) error {
   307  	banners, err := r.BannerService.GetBanners(ctx)
   308  	if err != nil && err.Error() != apierror.BannerCheckMessage {
   309  		return err
   310  	}
   311  	label, err := r.LabelService.GetLabel(ctx, labelEdgeID)
   312  	if err != nil {
   313  		return fmt.Errorf("access denied: user org %s does not have access to requested organization", userOrg)
   314  	}
   315  	if label.BannerEdgeID == nil {
   316  		return nil
   317  	}
   318  	t, errs := r.LabelService.GetLabelTenant(ctx, labelEdgeID)
   319  	if errs != nil || userOrg != t.OrgName {
   320  		return fmt.Errorf("access denied: user org %s does not have access to requested organization", userOrg)
   321  	}
   322  	if !isUserAssignedToBanner(banners, *label.BannerEdgeID) {
   323  		return fmt.Errorf("access denied: user does not have access to the banner %s that the requested label resource %s exists in", *label.BannerEdgeID, labelEdgeID)
   324  	}
   325  	return nil
   326  }
   327  
   328  func (r *Resolver) checkUserTerminalAccess(ctx context.Context, terminalEdgeID, org string) error {
   329  	getLabel := false
   330  	terminal, err := r.TerminalService.GetTerminal(ctx, terminalEdgeID, &getLabel)
   331  	if err != nil {
   332  		return err
   333  	}
   334  	cluster, err := r.StoreClusterService.GetClusterByClusterEdgeID(ctx, terminal.ClusterEdgeID)
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	addIDsToContext(ctx, "", cluster.BannerEdgeID, terminal.ClusterEdgeID)
   340  
   341  	u := middleware.ForContext(ctx)
   342  	if u == nil {
   343  		return fmt.Errorf("access denied: user not authenticated")
   344  	}
   345  	switch {
   346  	case UserHasMatchingRole(ctx, []model.Role{model.RoleEdgeOrgAdmin}):
   347  		return nil
   348  	case UserHasMatchingRole(ctx, []model.Role{model.RoleEdgeBannerAdmin, model.RoleEdgeBannerOperator}):
   349  		userAssignedBanners, err := r.BannerService.GetUserAssignedBanners(ctx, fullUsername(u.Username, org))
   350  		if err != nil {
   351  			return err
   352  		}
   353  		for _, banner := range userAssignedBanners {
   354  			if cluster.BannerEdgeID == banner.BannerEdgeID {
   355  				return nil
   356  			}
   357  		}
   358  		fallthrough
   359  	default:
   360  		return fmt.Errorf("access denied: user does not have access to terminal %s", terminalEdgeID)
   361  	}
   362  }
   363  

View as plain text