diff --git a/api/appStore/InstalledAppRestHandler.go b/api/appStore/InstalledAppRestHandler.go index 461bb23dcb..9077817a59 100644 --- a/api/appStore/InstalledAppRestHandler.go +++ b/api/appStore/InstalledAppRestHandler.go @@ -28,6 +28,7 @@ import ( util3 "github.com/devtron-labs/devtron/pkg/appStore/util" "github.com/devtron-labs/devtron/pkg/bean" "net/http" + "reflect" "strconv" "strings" "time" @@ -363,6 +364,8 @@ func (handler *InstalledAppRestHandlerImpl) DeployBulk(w http.ResponseWriter, r common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden) return } + charts, authRes := handler.checkForHelmDeployAuth(request, token) + request.ChartGroupInstallChartRequest = charts //RBAC block ends here visited := make(map[string]bool) @@ -391,10 +394,116 @@ func (handler *InstalledAppRestHandlerImpl) DeployBulk(w http.ResponseWriter, r handler.Logger.Errorw("service err, DeployBulk", "err", err, "payload", request) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return + } else { + res = authRes } common.WriteJsonResp(w, err, res, http.StatusOK) } +func (handler *InstalledAppRestHandlerImpl) checkForHelmDeployAuth(request chartGroup.ChartGroupInstallRequest, token string) ([]*chartGroup.ChartGroupInstallChartRequest, *chartGroup.ChartGroupInstallAppRes) { + //the value of this map is array of integer because the GetHelmObjectByProjectIdAndEnvId method may return "//" for error cases + //so different environments may contain same object, to handle that we are using (map[string] []int) + rbacObjectToEnvIdMap1 := make(map[string][]int) + rbacObjectToEnvIdMap2 := make(map[string][]int) + + rbacObjectArray1 := make([]string, 0) + rbacObjectArray2 := make([]string, 0) + + envIdToChartGroupInstallChartRequest := make(map[int][]*chartGroup.ChartGroupInstallChartRequest) + + for _, chartGroupInstall := range request.ChartGroupInstallChartRequest { + envIdToChartGroupInstallChartRequest[chartGroupInstall.EnvironmentId] = append(envIdToChartGroupInstallChartRequest[chartGroupInstall.EnvironmentId], chartGroupInstall) + rbacObject1, rbacObject2 := handler.enforcerUtil.GetHelmObjectByProjectIdAndEnvId(request.ProjectId, chartGroupInstall.EnvironmentId) + _, ok := rbacObjectToEnvIdMap1[rbacObject1] + if !ok { + rbacObjectToEnvIdMap1[rbacObject1] = make([]int, 0) + } + rbacObjectToEnvIdMap1[rbacObject1] = append(rbacObjectToEnvIdMap1[rbacObject1], chartGroupInstall.EnvironmentId) + rbacObjectArray1 = append(rbacObjectArray1, rbacObject1) + _, ok = rbacObjectToEnvIdMap2[rbacObject2] + if !ok { + rbacObjectToEnvIdMap2[rbacObject2] = make([]int, 0) + } + rbacObjectToEnvIdMap2[rbacObject2] = append(rbacObjectToEnvIdMap2[rbacObject2], chartGroupInstall.EnvironmentId) + rbacObjectArray2 = append(rbacObjectArray2, rbacObject2) + } + resultObjectMap1 := handler.enforcer.EnforceInBatch(token, casbin.ResourceHelmApp, casbin.ActionCreate, rbacObjectArray1) + resultObjectMap2 := handler.enforcer.EnforceInBatch(token, casbin.ResourceHelmApp, casbin.ActionCreate, rbacObjectArray2) + + authorizedEnvIdSet := make(map[int]bool) + + //O(n) time loop , at max we will only iterate through all the envs + for obj, ok := range resultObjectMap1 { + if ok { + envIds := rbacObjectToEnvIdMap1[obj] + for _, envId := range envIds { + authorizedEnvIdSet[envId] = true + } + } + } + for obj, ok := range resultObjectMap2 { + if ok { + envIds := rbacObjectToEnvIdMap2[obj] + for _, envId := range envIds { + authorizedEnvIdSet[envId] = true + } + } + } + authorizedChartGroupInstallRequests := make([]*chartGroup.ChartGroupInstallChartRequest, 0) + for envId, _ := range authorizedEnvIdSet { + authorizedChartGroupInstall := envIdToChartGroupInstallChartRequest[envId] + for _, authChartGroup := range authorizedChartGroupInstall { + authorizedChartGroupInstallRequests = append(authorizedChartGroupInstallRequests, authChartGroup) + } + } + unauthorizedChartGroupInstallRequests := make([]*chartGroup.ChartGroupInstallChartRequest, 0) + + for _, req := range request.ChartGroupInstallChartRequest { + isAuthorized := false + for _, authReq := range authorizedChartGroupInstallRequests { + if reflect.DeepEqual(req, authReq) { + isAuthorized = true + break + } + } + if !isAuthorized { + unauthorizedChartGroupInstallRequests = append(unauthorizedChartGroupInstallRequests, req) + } + } + + // Create slices for ChartGroupInstallMetadata + authorizedMetadata := make([]chartGroup.ChartGroupInstallMetadata, 0) + unauthorizedMetadata := make([]chartGroup.ChartGroupInstallMetadata, 0) + + for _, req := range authorizedChartGroupInstallRequests { + metadata := handler.getChartGroupInstallMetadata(req, string(chartGroup.StatusSuccess), string(chartGroup.ReasonTriggered)) + authorizedMetadata = append(authorizedMetadata, metadata) + } + + for _, req := range unauthorizedChartGroupInstallRequests { + metadata := handler.getChartGroupInstallMetadata(req, string(chartGroup.StatusFailed), string(chartGroup.ReasonNotAuthorize)) + unauthorizedMetadata = append(unauthorizedMetadata, metadata) + } + unauthorizeCount := len(unauthorizedChartGroupInstallRequests) + totalCount := len(request.ChartGroupInstallChartRequest) + // Combine all metadata into a single ChartGroupInstallAppRes + chartGroupInstallAppRes := &chartGroup.ChartGroupInstallAppRes{ + ChartGroupInstallMetadata: append(authorizedMetadata, unauthorizedMetadata...), + Summary: fmt.Sprintf(chartGroup.FAILED_TO_TRIGGER, unauthorizeCount, totalCount), + } + return authorizedChartGroupInstallRequests, chartGroupInstallAppRes +} + +func (handler *InstalledAppRestHandlerImpl) getChartGroupInstallMetadata(req *chartGroup.ChartGroupInstallChartRequest, triggerStatus string, reason string) chartGroup.ChartGroupInstallMetadata { + metadata := chartGroup.ChartGroupInstallMetadata{ + AppName: req.AppName, + EnvironmentId: req.EnvironmentId, + TriggerStatus: triggerStatus, + Reason: reason, + } + return metadata +} + func (handler *InstalledAppRestHandlerImpl) CheckAppExists(w http.ResponseWriter, r *http.Request) { userId, err := handler.userAuthService.GetLoggedInUser(r) if userId == 0 || err != nil { diff --git a/pkg/appStore/chartGroup/bean.go b/pkg/appStore/chartGroup/bean.go index 65e20c5e2e..5fef383cb6 100644 --- a/pkg/appStore/chartGroup/bean.go +++ b/pkg/appStore/chartGroup/bean.go @@ -33,6 +33,23 @@ type ChartGroupInstallChartRequest struct { ReferenceValueKind string `json:"referenceValueKind, omitempty" validate:"oneof=DEFAULT TEMPLATE DEPLOYED"` ChartGroupEntryId int `json:"chartGroupEntryId"` //optional } - +type ChartGroupInstallMetadata struct { + AppName string `json:"appName"` + EnvironmentId int `json:"environmentId"` + TriggerStatus string `json:"triggerStatus"` + Reason string `json:"reason"` +} type ChartGroupInstallAppRes struct { + ChartGroupInstallMetadata []ChartGroupInstallMetadata `json:"chartGroupInstallMetadata"` + Summary string `json:"summary"` } +type TriggerStatus string +type Reason string + +const FAILED_TO_TRIGGER = "%d/%d failed to trigger" +const ( + StatusFailed TriggerStatus = "failed" + StatusSuccess TriggerStatus = "success" + ReasonNotAuthorize Reason = "not authorized" + ReasonTriggered Reason = "triggered" +) diff --git a/wire_gen.go b/wire_gen.go index 035f7d9a30..e3fab195a9 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject