package services import ( "context" "database/sql" "encoding/json" "fmt" "net/http" "strings" "time" "github.com/lib/pq" "github.com/rs/zerolog/log" "k8s.io/utils/ptr" "edge-infra.dev/pkg/edge/api/apierror" sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql" "edge-infra.dev/pkg/edge/api/bsl/types" "edge-infra.dev/pkg/edge/api/graph/mapper" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/middleware" sqlquery "edge-infra.dev/pkg/edge/api/sql" apiTypes "edge-infra.dev/pkg/edge/api/types" "edge-infra.dev/pkg/edge/api/utils" bannerv1alpha1 "edge-infra.dev/pkg/edge/apis/banner/v1alpha1" "edge-infra.dev/pkg/edge/bsl" chariotClientApi "edge-infra.dev/pkg/edge/chariot/client" "edge-infra.dev/pkg/edge/constants/api/banner" "edge-infra.dev/pkg/edge/controllers/util/edgedb" "edge-infra.dev/pkg/sds/clustersecrets/audit" ) const ( BspEnterpriseUnitPath = "/provisioning/enterprise-units" getEnterpriseUnit = "/provisioning/enterprise-units/%s" updateOrganization = "/provisioning/organizations/%s" bspOrgPath = "/provisioning/organizations" bspExternalUserPath = "/provisioning/users/external" bspEnterpriseUnitOrganizationsPath = BspEnterpriseUnitPath + "?typeNamePattern=organization" bspDeleteOrgPath = "/provisioning/organizations/%s/purge" bspSubOrganizationsPath = "/provisioning/organizations/find-by-parent?parentOrganizationName=%s" + bspPaging bspUserAssignedSubOrgsPath = "/provisioning/users/external/%s/accounts?pageNumber=%d&pageSize=%d" bspPaging = "&pageNumber=%d&pageSize=%d" bspEnterpriseUnitGrantBasePath = "/provisioning/enterprise-unit-grants/unit-grants/%s/%s" bspGetAllAssignedOrgURL = "/provisioning/enterprise-unit-grants/unit-grants?username=%s" bspGetOrgUsersListPath = "/provisioning/enterprise-unit-grants/unit-grants?enterpriseUnitId=%s&pageNumber=%d&pageSize=%d" bspUserDetailsPath = "/provisioning/users/user-details" pageSize = 200 commerceOrg = "commerce" ProvisionedStatus = "PROVISIONED" ProvisioningStatusMessage = "Edge Provisioning" ) //go:generate mockgen -destination=../mocks/mock_organization_service.go -package=mocks edge-infra.dev/pkg/edge/api/services BannerService type BannerService interface { GetBannerTenantInfo(ctx context.Context, orgName string) (*model.Tenant, error) GetBanners(ctx context.Context) ([]*model.Banner, error) GetBannerCRByID(ctx context.Context, id string) (*bannerv1alpha1.Banner, error) GetBanner(ctx context.Context, bannerEdgeID string) (*model.Banner, error) CheckBannerInBSL(ctx context.Context, bannerModel *model.Banner) (*model.Banner, error) CreateEUBanner(ctx context.Context, name string, description *string) (*model.Banner, error) CreateOrganizationBanner(ctx context.Context, name string, description *string) (*model.Banner, error) UndoCreateBanner(ctx context.Context, name, id, uuid, bannerObject string, isOrgBanner *bool, bucketPath string) error DeleteEUBanner(ctx context.Context, name, id string) (bool, error) DeleteOrgBanner(ctx context.Context, id string) (bool, error) AssignUserToEUBanners(ctx context.Context, username string, banners []string) error AssignUserToOrgBanners(ctx context.Context, username string, banners []*model.Banner) error GetUsersForEuBanner(ctx context.Context, organizationID string) ([]*model.User, error) GetBSLOrganization(ctx context.Context, organization string) (*bsl.BSLOrganization, error) GetUserAssignedBanners(ctx context.Context, username string) ([]*model.BannerInfo, error) CreateBannerCr(ctx context.Context, uuid, projectID, name, enterpriseUnitID string, enablements []string) (string, error) DeleteBannerSQLEntry(ctx context.Context, bannerUUID string) error CreateBannerSQLEntry(ctx context.Context, projectID, bannerName, bannerType, enterpriseUnitID, bannerUUID, description string) error GetBannerProjectID(ctx context.Context, bannerEdgeID string) (string, error) GetBannerTypeFromID(ctx context.Context, id string) (banner.Type, error) GetBannerTypeFromUUID(ctx context.Context, uuid string) (banner.Type, error) GetBannerByEdgeID(ctx context.Context, bannerUUID string) (*model.Banner, error) GetBannerByBSLID(ctx context.Context, bannerBSLID string) (*model.Banner, error) GetEdgeBannerInfo(ctx context.Context, banners []*model.Banner) []*model.Banner GetClusterInfraInfo(ctx context.Context, bannerEdgeID string) (*model.Cluster, error) GetBannerByNameAndTenant(ctx context.Context, name, orgName string) (*model.Banner, error) DeleteClusterSQLEntry(ctx context.Context, clusterEdgeID string) error GetBannerInfraInfo(ctx context.Context) (*model.Cluster, error) GetBannerInfraBucketPath(ctx context.Context) (string, error) UpdateBannerSQL(ctx context.Context, update *model.Banner) error UpdateEUBanner(ctx context.Context, update *model.Banner) error UpdateOrgBanner(ctx context.Context, update *model.Banner) error GetBSLEU(ctx context.Context, eu string) (*types.EnterpriseUnitData, error) UpdateBannerRemoteAccessIP(ctx context.Context, remoteAccessIP string, bannerEdgeID string) error GetBannerRemoteAccessIP(ctx context.Context, bannerEdgeID string) (string, error) GetBannerInfraStatus(ctx context.Context, bannerEdgeID string) (*model.BannerStatus, error) UpdateBannerEdgeSecurityCompliance(ctx context.Context, optInEdgeSecurityCompliance model.EdgeSecurityComplianceOptions, bannerEdgeID string) error } type bannerService struct { GkeService GkeClient ChariotService ChariotService BSLClient *bsl.Client TopLevelProjectID string SQLDB *sql.DB config *apiTypes.Config } func (o *bannerService) UpdateBannerSQL(ctx context.Context, update *model.Banner) error { _, err := o.SQLDB.ExecContext(ctx, sqlquery.UpdateBanner, update.Name, update.Description, update.BannerEdgeID) if err != nil { return sqlerr.Wrap(err) } return nil } func (o *bannerService) UpdateEUBanner(ctx context.Context, update *model.Banner) error { desc := "" if update.Description != nil { desc = *update.Description } payload := mapper.ToEnterpriseUnitData(update.BannerBSLId, update.Name, desc, types.IsOrganization, true) err := o.BSLClient.WithUserTokenCredentials(ctx).SetPayload(payload).Put(BspEnterpriseUnitPath) if err != nil { log.Err(err).Msg("error from bsp while updating an eu") } return err } func (o *bannerService) UpdateOrgBanner(ctx context.Context, update *model.Banner) error { desc := "" if update.Description != nil { desc = *update.Description } orgData := &types.UpdateOrganizationData{ Description: desc, DisplayName: update.Name, } err := o.BSLClient.WithUserTokenCredentials(ctx).SetPayload(orgData).Put(fmt.Sprintf(updateOrganization, update.BannerBSLId)) if err != nil { log.Err(err).Msg("error from bsp while updating an org") } return err } func (o *bannerService) GetBanner(ctx context.Context, bannerEdgeID string) (*model.Banner, error) { var ( optedInEdgeSecurityCompliance *bool ) row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByBannerID, bannerEdgeID) bannerModel := &model.Banner{} bannerModel.BslEntityTypes = []string{} err := row.Scan(&bannerModel.BannerEdgeID, &bannerModel.BannerBSLId, &bannerModel.Name, &bannerModel.BannerType, &bannerModel.Description, &bannerModel.ProjectID, &bannerModel.TenantEdgeID, &bannerModel.BslDataSynced, pq.Array(&bannerModel.BslEntityTypes), &optedInEdgeSecurityCompliance) if err != nil { return bannerModel, sqlerr.Wrap(err) } bannerModel.OptInEdgeSecurityCompliance = o.config.EdgeOptInSecurityCompliance if optedInEdgeSecurityCompliance != nil { bannerModel.OptInEdgeSecurityCompliance = *optedInEdgeSecurityCompliance } return bannerModel, nil } func (o *bannerService) GetBannerInfraStatus(ctx context.Context, bannerEdgeID string) (*model.BannerStatus, error) { status := &model.BannerStatus{} row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerInfraStatusByBannerEdgeID, bannerEdgeID) if err := row.Scan(&status.Reason, &status.Message, &status.LastUpdatedAt); err != nil { return nil, sqlerr.Wrap(err) } switch status.Reason { case string(edgedb.InfraStatusReady): status.Reason = ProvisionedStatus status.Ready = true default: status.Reason = string(edgedb.InfraStatusProvisioning) // Check if the banner's status message and lastUpdatedAt timestamp are empty // since they may not be available yet (e.g., it has not been reconciled, // there're issues writing to the db, etc.). If empty, defaults are used. if status.Message == "" { status.Message = ProvisioningStatusMessage } if status.LastUpdatedAt == "" { status.LastUpdatedAt = time.Now().Format(time.RFC3339) } } return status, nil } func (o *bannerService) CheckBannerInBSL(ctx context.Context, bannerModel *model.Banner) (*model.Banner, error) { matchErr := apierror.New(apierror.BannerCheckMessage) var bslBanner *model.Banner if banner.Type(bannerModel.BannerType) == banner.Org { org, err := o.GetBSLOrganization(ctx, bannerModel.BannerBSLId) if err != nil { return bannerModel, matchErr.AddGenericErrorExtension(apierror.BannerCheckKey, []string{fmt.Sprintf("banner %s not found in bsl: %s", bannerModel.Name, err.Error())}) } bslBanner = mapper.BSLOrgToBanner(org) } else { eu, err := o.GetBSLEU(ctx, bannerModel.BannerBSLId) if err != nil { return bannerModel, matchErr.AddGenericErrorExtension(apierror.BannerCheckKey, []string{fmt.Sprintf("banner %s not found in bsl: %s", bannerModel.Name, err.Error())}) } bslBanner = mapper.EnterpriseUnitToBanner(eu) } bannerModel, matchErr = bannerCheck(bslBanner, bannerModel, matchErr) if len(matchErr.Ext[apierror.BannerCheckKey].([]string)) == 0 { return bannerModel, nil } return bannerModel, matchErr } func (o *bannerService) GetBannerByNameAndTenant(ctx context.Context, name, orgName string) (*model.Banner, error) { var ( optedInEdgeSecurityCompliance *bool ) var tenant model.Tenant row := o.SQLDB.QueryRowContext(ctx, sqlquery.TenantGetByName, orgName) if err := row.Scan(&tenant.TenantEdgeID, &tenant.TenantBSLId, &tenant.OrgName); err != nil { return nil, sqlerr.Wrap(err) } row = o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByBannerByNameAndTenant, name, tenant.TenantEdgeID) bannerModel := model.Banner{} err := row.Scan(&bannerModel.BannerEdgeID, &bannerModel.BannerBSLId, &bannerModel.Name, &bannerModel.BannerType, &bannerModel.Description, &bannerModel.ProjectID, &bannerModel.TenantEdgeID, &optedInEdgeSecurityCompliance) if err != nil { return nil, sqlerr.Wrap(err) } bannerModel.OptInEdgeSecurityCompliance = o.config.EdgeOptInSecurityCompliance if optedInEdgeSecurityCompliance != nil { bannerModel.OptInEdgeSecurityCompliance = *optedInEdgeSecurityCompliance } return &bannerModel, nil } func (o *bannerService) GetBannerTenantInfo(ctx context.Context, orgName string) (*model.Tenant, error) { var orgUUID, id, name string row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerTenantByOrgName, orgName) err := row.Scan(&orgUUID, &id, &name) tenant := &model.Tenant{ TenantEdgeID: orgUUID, TenantBSLId: id, OrgName: name, } if err != nil { return tenant, sqlerr.Wrap(err) } return tenant, nil } func (o *bannerService) GetBannerProjectID(ctx context.Context, bannerEdgeID string) (string, error) { var projectID string row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetProjectIDByBanner, bannerEdgeID) err := row.Scan(&projectID) if err != nil { return projectID, sqlerr.Wrap(err) } return projectID, nil } // GetBanners gets all the enterprise unit and sub orgs from BSL, maps them to a Banner and returns the banner. func (o *bannerService) GetBanners(ctx context.Context) ([]*model.Banner, error) { user := middleware.ForContext(ctx) var ( banners []*model.Banner eus []*types.EnterpriseUnitData orgs []*types.OrganizationViewData err error ) if utils.Contains(user.Roles, string(model.RoleEdgeOrgAdmin)) { eus, err = getAllEUs(o.BSLClient.WithUserTokenCredentials(ctx)) if err != nil { return nil, err } banners = append(banners, mapper.EnterpriseUnitsToBanners(eus)...) orgName := bsl.GetOrgShortName(user.Organization) orgs, err = getAllSubOrgs(o.BSLClient.WithUserTokenCredentials(ctx), orgName) if err != nil { return nil, err } banners = append(banners, mapper.SubOrgsToBanners(orgs)...) } else { banners, err = o.getUserAssignedBanners(ctx, bsl.CreateFullAccountName(user)) if err != nil { return nil, err } } if len(banners) == 0 { return banners, nil } return o.GetEdgeBannerInfo(ctx, banners), nil } func (o *bannerService) GetEdgeBannerInfo(ctx context.Context, banners []*model.Banner) []*model.Banner { edgeBanners := []*model.Banner{} for _, banner := range banners { matchErr := apierror.New(apierror.BannerCheckMessage) row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByBSLID, banner.BannerBSLId) sqlInfo := &model.Banner{} sqlInfo.BslEntityTypes = []string{} var optedInEdgeSecurityCompliance *bool err := row.Scan(&sqlInfo.BannerEdgeID, &sqlInfo.BannerBSLId, &sqlInfo.Name, &sqlInfo.BannerType, &sqlInfo.ProjectID, &sqlInfo.TenantEdgeID, &sqlInfo.Description, &sqlInfo.BslDataSynced, pq.Array(&sqlInfo.BslEntityTypes), &optedInEdgeSecurityCompliance) if err != nil { _, matchErr = bannerCheck(banner, nil, matchErr) if len(matchErr.Ext[apierror.BannerCheckKey].([]string)) > 0 { banner.MismatchInfo = matchErr.Ext[apierror.BannerCheckKey].([]string) } } else { banner, matchErr = bannerCheck(banner, sqlInfo, matchErr) if len(matchErr.Ext[apierror.BannerCheckKey].([]string)) > 0 { banner.MismatchInfo = matchErr.Ext[apierror.BannerCheckKey].([]string) } banner.OptInEdgeSecurityCompliance = o.config.EdgeOptInSecurityCompliance if optedInEdgeSecurityCompliance != nil { banner.OptInEdgeSecurityCompliance = *optedInEdgeSecurityCompliance } edgeBanners = append(edgeBanners, banner) } } return edgeBanners } func (o *bannerService) UpdateBannerRemoteAccessIP(ctx context.Context, remoteAccessIP string, bannerEdgeID string) error { if _, err := o.SQLDB.ExecContext(ctx, sqlquery.UpdateBannerRemoteAccessIP, remoteAccessIP, bannerEdgeID); err != nil { return sqlerr.Wrap(err) } return nil } func (o *bannerService) UpdateBannerEdgeSecurityCompliance(ctx context.Context, optInEdgeSecurityCompliance model.EdgeSecurityComplianceOptions, bannerEdgeID string) error { user := middleware.ForContext(ctx) auditLog := audit.New("api") var optInSetting *bool switch optInEdgeSecurityCompliance { case model.EdgeSecurityComplianceOptionsOptOut: optInSetting = ptr.To(false) case model.EdgeSecurityComplianceOptionsOptIn: optInSetting = ptr.To(true) case model.EdgeSecurityComplianceOptionsDefault: optInSetting = nil } if _, err := o.SQLDB.ExecContext(ctx, sqlquery.UpdateBannerEdgeSecurityCompliance, optInSetting, bannerEdgeID); err != nil { return sqlerr.Wrap(err) } var complianceType string if optInSetting == nil { complianceType = audit.ComplianceDefault } else if *optInSetting { complianceType = audit.ComplianceOptIn } else if !*optInSetting { complianceType = audit.ComplianceOptOut } auditLog.LogComplianceChange(complianceType, "action_by", user.Username, "bannerEdgeID", bannerEdgeID) return nil } func bannerCheck(bslBanner, sqlBanner *model.Banner, matchErr *apierror.Error) (*model.Banner, *apierror.Error) { message := []string{} if val, ok := matchErr.Ext[apierror.BannerCheckKey]; ok { message = val.([]string) } if sqlBanner == nil { message = append(message, fmt.Sprintf("matching row not found for bsl banner: %s", bslBanner.Name)) return bslBanner, matchErr.AddGenericErrorExtension(apierror.BannerCheckKey, message) } // check banner name if bslBanner.Name != sqlBanner.Name { message = append(message, fmt.Sprintf("name not match for bsl banner %s (bsl name: %s, sql name: %s)", bslBanner.Name, bslBanner.Name, sqlBanner.Name)) sqlBanner.Name = bslBanner.Name } //check banner description bslDescription := "" sqlDescription := "" if bslBanner.Description != nil { bslDescription = *bslBanner.Description } if sqlBanner.Description != nil { sqlDescription = *sqlBanner.Description } if bslDescription != sqlDescription { message = append(message, fmt.Sprintf("description not match for bsl banner %s (bsl description: %s, sql description: %s)", bslBanner.Name, bslDescription, sqlDescription)) sqlBanner.Description = bslBanner.Description } return sqlBanner, matchErr.AddGenericErrorExtension(apierror.BannerCheckKey, message) } // GetBannerNameByBannerID get the banner name by the eu/Org id func (o *bannerService) GetBannerNameByBannerID(ctx context.Context, id string) (string, error) { row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerNameByBannerID, id) var name string if err := row.Scan(&name); err != nil { return "", sqlerr.Wrap(err) } return name, nil } // GetBannerEdgeIDByBannerID get the banner edge id by the eu/Org id // Deadcode func (o *bannerService) GetBannerEdgeIDByBannerID(ctx context.Context, id string) (string, error) { row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerEdgeIDByBannerID, id) var name string if err := row.Scan(&name); err != nil { return "", sqlerr.Wrap(err) } return name, nil } // GetClusterInfraInfo get the active status of the banner by banner edge id and 'cluster-infra' label func (o *bannerService) GetClusterInfraInfo(ctx context.Context, bannerEdgeID string) (*model.Cluster, error) { row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetClusterInfraInfo, bannerEdgeID) var clusterEdgeID, clusterName, projectID string var active bool if err := row.Scan(&clusterEdgeID, &clusterName, &projectID, &active); err != nil { return nil, sqlerr.Wrap(err) } cluster := &model.Cluster{ ClusterEdgeID: clusterEdgeID, Name: clusterName, ProjectID: projectID, Active: &active, BannerEdgeID: bannerEdgeID, } return cluster, nil } func (o *bannerService) GetBannerInfraInfo(ctx context.Context) (*model.Cluster, error) { row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerInfraInfo) var clusterEdgeID, clusterName, projectID string var active bool if err := row.Scan(&clusterEdgeID, &clusterName, &projectID, &active); err != nil { return nil, sqlerr.Wrap(err) } cluster := &model.Cluster{ ClusterEdgeID: clusterEdgeID, Name: clusterName, ProjectID: projectID, Active: &active, BannerEdgeID: "", } return cluster, nil } func (o *bannerService) GetBannerInfraBucketPath(ctx context.Context) (string, error) { bi, err := o.GetBannerInfraInfo(ctx) if err != nil { return "", err } return bi.ClusterEdgeID, nil } func (o *bannerService) GetBannerByEdgeID(ctx context.Context, bannerUUID string) (*model.Banner, error) { var ( optedInEdgeSecurityCompliance *bool ) row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByID, bannerUUID) var uuid, bannerID, bannerName, bannerType, projectID, tenant, description string if err := row.Scan(&uuid, &bannerID, &bannerName, &bannerType, &projectID, &tenant, &description, &optedInEdgeSecurityCompliance); err != nil { return nil, sqlerr.Wrap(err) } bannerModel := &model.Banner{ BannerBSLId: bannerID, Name: bannerName, Description: &description, BannerType: bannerType, TenantEdgeID: tenant, ProjectID: projectID, BannerEdgeID: bannerUUID, } bannerModel.OptInEdgeSecurityCompliance = o.config.EdgeOptInSecurityCompliance if optedInEdgeSecurityCompliance != nil { bannerModel.OptInEdgeSecurityCompliance = *optedInEdgeSecurityCompliance } return bannerModel, nil } func (o *bannerService) GetBannerByBSLID(ctx context.Context, bannerBSLID string) (*model.Banner, error) { banner := &model.Banner{} banner.BslEntityTypes = []string{} var optedInEdgeSecurityCompliance *bool row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByBSLID, bannerBSLID) err := row.Scan(&banner.BannerEdgeID, &banner.BannerBSLId, &banner.Name, &banner.BannerType, &banner.ProjectID, &banner.TenantEdgeID, &banner.Description, &banner.BslDataSynced, pq.Array(&banner.BslEntityTypes), &optedInEdgeSecurityCompliance) if err != nil { log.Ctx(ctx).Err(err).Msg("matching row not found for bsl banner: " + bannerBSLID) return nil, sqlerr.Wrap(err) } banner.OptInEdgeSecurityCompliance = o.config.EdgeOptInSecurityCompliance if optedInEdgeSecurityCompliance != nil { banner.OptInEdgeSecurityCompliance = *optedInEdgeSecurityCompliance } return banner, nil } // probably need to better differentiate when to return api model vs cr func (o *bannerService) GetBannerCRByID(ctx context.Context, bannerUUID string) (*bannerv1alpha1.Banner, error) { row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerByID, bannerUUID) var uuid, bannerID, bannerName, bannerType, projectID, tenant, description string var edgeOptInSecurityCompliance *bool if err := row.Scan(&uuid, &bannerID, &bannerName, &bannerType, &projectID, &tenant, &description, &edgeOptInSecurityCompliance); err != nil { return nil, sqlerr.Wrap(err) } // todo - mostly copy paste from capability service, need to refactor sql calls to prevent cross service dependencies rows, err := o.SQLDB.QueryContext(ctx, sqlquery.ListCapabilitiesByBannerQuery, bannerUUID) if err != nil { return nil, sqlerr.Wrap(err) } var capabilities []string defer rows.Close() for rows.Next() { var capability model.Capability if err = rows.Scan(&capability.UUID, &capability.Name, &capability.Description); err != nil { return nil, sqlerr.Wrap(err) } capabilities = append(capabilities, capability.Name) } if err := rows.Err(); err != nil { return nil, sqlerr.Wrap(err) } user := middleware.ForContext(ctx) bslName := bsl.GetOrgShortName(user.Organization) return utils.NewBanner(uuid, bannerName, bslName, bannerID, projectID, capabilities), nil } // GetBSLOrganization gets the bsl organization with the provided reference. func (o *bannerService) GetBSLOrganization(ctx context.Context, organization string) (*bsl.BSLOrganization, error) { data := &bsl.BSLOrganization{} err := o.BSLClient.WithUserTokenCredentials(ctx).JSON(http.MethodGet, fmt.Sprintf("%s/%s", BslOrgPath, organization), data) if err != nil { return nil, err } return data, nil } func (o *bannerService) GetBSLEU(ctx context.Context, eu string) (*types.EnterpriseUnitData, error) { data := &types.EnterpriseUnitData{} err := o.BSLClient.WithUserTokenCredentials(ctx).JSON(http.MethodGet, fmt.Sprintf(getEnterpriseUnit, eu), data) if err != nil { return nil, err } return data, nil } func (o *bannerService) GetBannerTypeFromID(ctx context.Context, id string) (banner.Type, error) { var bannerType string err := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerTypeFromIDQuery, id).Scan(&bannerType) if err != nil { return "", sqlerr.Wrap(err) } return banner.Type(bannerType), nil } func (o *bannerService) GetBannerTypeFromUUID(ctx context.Context, uuid string) (banner.Type, error) { var bannerType string err := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerTypeFromUUIDQuery, uuid).Scan(&bannerType) if err != nil { return "", sqlerr.Wrap(err) } return banner.Type(bannerType), nil } func (o *bannerService) GetBannerTypeFromNameAndTenant(ctx context.Context, name, tenant string) (banner.Type, error) { var bannerType string err := o.SQLDB.QueryRowContext(ctx, sqlquery.GetBannerTypeFromNameAndTenantQuery, name, tenant).Scan(&bannerType) if err != nil { return "", sqlerr.Wrap(err) } return banner.Type(bannerType), nil } func (o *bannerService) GetUserAssignedBanners(ctx context.Context, username string) ([]*model.BannerInfo, error) { banners, err := o.getUserAssignedBanners(ctx, username) if err != nil { return make([]*model.BannerInfo, 0), err } for _, banner := range banners { sqlBanner, err := o.GetBannerByBSLID(ctx, banner.BannerBSLId) if err != nil { return nil, err } banner.BannerEdgeID = sqlBanner.BannerEdgeID banner.BannerType = sqlBanner.BannerType } return toBannersList(banners), nil } func (o *bannerService) GetBannerRemoteAccessIP(ctx context.Context, bannerEdgeID string) (string, error) { var remoteAccessIP string if err := o.SQLDB.QueryRowContext(ctx, sqlquery.GetRemoteAccessIPByBannerEdgeID, bannerEdgeID).Scan(&remoteAccessIP); err != nil { return "", sqlerr.Wrap(err) } return remoteAccessIP, nil } func toBannersList(data []*model.Banner) []*model.BannerInfo { banners := make([]*model.BannerInfo, 0) for _, banner := range data { banners = append(banners, &model.BannerInfo{BannerBSLId: banner.BannerBSLId, Name: banner.Name, BannerType: banner.BannerType, BannerEdgeID: banner.BannerEdgeID}) } return banners } func (o *bannerService) getUserAssignedBanners(ctx context.Context, username string) ([]*model.Banner, error) { banners := make([]*model.Banner, 0) lowercaseUsername := strings.ToLower(username) orgs, err := o.getUserAssignedOrgs(ctx, lowercaseUsername) if err != nil { return nil, err } banners = append(banners, mapper.SubExternalUsersToBanners(orgs)...) eus, err := o.getUserAssignedEUs(ctx, lowercaseUsername) if err != nil { return nil, err } banners = append(banners, mapper.EnterpriseUnitsToBanners(eus)...) return banners, nil } func (o *bannerService) getUserAssignedOrgs(ctx context.Context, username string) ([]*types.ExternalUserData, error) { orgs := []*types.ExternalUserData{} data := &types.GetExternalUserResponse{} user := middleware.ForContext(ctx) pageNumber := 0 client := o.BSLClient.WithUserTokenCredentials(ctx) for !data.LastPage { err := client.JSON(http.MethodGet, fmt.Sprintf(bspUserAssignedSubOrgsPath, username, pageNumber, pageSize), data) if err != nil { if apierror.IsNotFoundError(err) { //fix this for Error return []*types.ExternalUserData{}, nil } return nil, err } //remove tenant and commerce orgs, remove orgs that are not a child of the tenant org for _, org := range data.PageContent { if org.OrganizationName == user.Organization && strings.Contains(username, commerceOrg) { continue } if strings.Contains(org.OrganizationName, commerceOrg) { continue } if !strings.Contains(org.OrganizationName, user.Organization) { continue } orgs = append(orgs, org) } pageNumber++ } return orgs, nil } func (o *bannerService) getUserAssignedEUs(ctx context.Context, username string) ([]*types.EnterpriseUnitData, error) { var orgs []*types.EnterpriseUnitData data := &types.FindEnterpriseUnitsResponse{} client := o.BSLClient.WithUserTokenCredentials(ctx) pageNumber := 0 for !data.LastPage { err := client.JSON(http.MethodGet, fmt.Sprintf(bspGetAllAssignedOrgURL+bspPaging, username, pageNumber, pageSize), data) if err != nil { return nil, err } for _, eu := range data.PageContent { if eu.EnterpriseUnitID == "" { continue } userOrganization, err := getEnterpriseUnitData(client, eu.EnterpriseUnitID) if err != nil { return nil, err } if userOrganization.Active { orgs = append(orgs, &types.EnterpriseUnitData{ Name: userOrganization.Name, Description: userOrganization.Description, EnterpriseUnitID: userOrganization.EnterpriseUnitID, }) } } pageNumber++ } return orgs, nil } func getEnterpriseUnitData(client *bsl.Request, enterpriseUnitID string) (*types.EnterpriseUnitData, error) { data := &types.EnterpriseUnitData{} return data, client.JSON(http.MethodGet, BspEnterpriseUnitPath+"/"+enterpriseUnitID, data) } // getAllEUs gets all the enterprise units from BSL. func getAllEUs(client *bsl.Request) ([]*types.EnterpriseUnitData, error) { var orgs []*types.EnterpriseUnitData data := &types.FindEnterpriseUnitsResponse{} pageNumber := 0 for pageNumber <= data.TotalPages { err := client. JSON(http.MethodGet, fmt.Sprintf(bspEnterpriseUnitOrganizationsPath+bspPaging, pageNumber, pageSize), data) if err != nil { return nil, err } orgs = append(orgs, data.PageContent...) pageNumber++ } return orgs, nil } // getAllSubOrgs gets all the sub orgs for a parent organization in BSL. func getAllSubOrgs(client *bsl.Request, parentOrg string) ([]*types.OrganizationViewData, error) { var orgs []*types.OrganizationViewData data := &types.GetOrganizationSubOrgsResponse{} pageNumber := 0 for pageNumber <= data.TotalPages { if data.LastPage { break } err := client.JSON(http.MethodGet, fmt.Sprintf(bspSubOrganizationsPath, parentOrg, pageNumber, pageSize), data) if err != nil { return nil, err } orgs = append(orgs, data.PageContent...) pageNumber++ } return orgs, nil } func (o *bannerService) CreateBannerCr(ctx context.Context, uuid, projectID, name, enterpriseUnitID string, enablements []string) (string, error) { user := middleware.ForContext(ctx) bannerRequest := utils.NewBanner(uuid, name, bsl.GetOrgShortName(user.Organization), enterpriseUnitID, projectID, enablements) if err := bannerRequest.IsValid(); err != nil { return "", err } bannerRequestByte, err := json.Marshal(bannerRequest) if err != nil { return "", err } bannerRequestBase64 := utils.ToBase64(bannerRequestByte) return bannerRequestBase64, nil } func (o *bannerService) CreateBannerSQLEntry(ctx context.Context, projectID, bannerName, bannerType, enterpriseUnitID, bannerUUID, description string) error { var tenantID string user := middleware.ForContext(ctx) row := o.SQLDB.QueryRowContext(ctx, sqlquery.GetTenantByOrgNameQuery, bsl.GetOrgShortName(user.Organization)) if err := row.Scan(&tenantID); err != nil { return sqlerr.Wrap(err) } _, err := o.SQLDB.ExecContext(ctx, sqlquery.BannerInsertQuery, enterpriseUnitID, bannerName, bannerType, projectID, tenantID, bannerUUID, description) if err != nil { return sqlerr.Wrap(err) } return nil } func (o *bannerService) DeleteBannerSQLEntry(ctx context.Context, bannerUUID string) error { _, err := o.SQLDB.ExecContext(ctx, sqlquery.DeleteBannerQuery, bannerUUID) if err != nil { return sqlerr.Wrap(err) } return nil } func (o *bannerService) CreateEUBanner(ctx context.Context, name string, description *string) (*model.Banner, error) { desc := "" if !utils.IsNullOrEmpty(description) { desc = *description } eud := &types.EnterpriseUnitData{} err := o.BSLClient.WithUserTokenCredentials(ctx). SetPayload(mapper.ToEnterpriseUnitData("", name, desc, types.IsOrganization, true)). JSON(http.MethodPost, BspEnterpriseUnitPath, eud) if err != nil { log.Err(err).Msg("error from bsp while creating an org") return nil, err } return mapper.EnterpriseUnitToBanner(eud), nil } func (o *bannerService) CreateOrganizationBanner(ctx context.Context, name string, description *string) (*model.Banner, error) { user := middleware.ForContext(ctx) desc := "" if !utils.IsNullOrEmpty(description) { desc = *description } createOrg := &types.CreateOrganizationRequest{ Name: name, Description: desc, DisplayName: name + " Banner", ParentOrganizationName: user.Organization, } orgResponse := &types.CreateOrganizationResponse{} err := o.BSLClient.WithUserTokenCredentials(ctx).SetPayload(createOrg).JSON(http.MethodPost, bspOrgPath, orgResponse) if err != nil { log.Err(err).Msg("error from bsp while creating an org") return nil, err } return mapper.ToOrgBanner(orgResponse), nil } func (o *bannerService) UndoCreateBanner(ctx context.Context, name, id, uuid, bannerObject string, isOrgBanner *bool, bucketPath string) error { var err error if isOrgBanner != nil && *isOrgBanner { _, err = o.DeleteOrgBanner(ctx, id) } else { _, err = o.DeleteEUBanner(ctx, name, id) } deleteErr := o.DeleteBannerSQLEntry(ctx, uuid) if deleteErr != nil { if err != nil { err = fmt.Errorf("%w, error deleting banner sql entry: %s", err, deleteErr) } else { err = fmt.Errorf("error deleting banner sql entry: %w", deleteErr) } } deleteStoreClusterMessage := chariotClientApi. NewChariotMessage(). SetOperation(chariotClientApi.Delete). SetOwner(ComponentOwner). SetBanner(o.TopLevelProjectID). SetCluster(bucketPath). AddObject(bannerObject) if deleteChariotMsgErr := o.ChariotService.InvokeChariotPubsub(ctx, deleteStoreClusterMessage, nil); deleteChariotMsgErr != nil { err = fmt.Errorf("%w, error calling chariot v2 and deleting a banner cr: %s", err, deleteChariotMsgErr) } else { err = fmt.Errorf("error calling chariot v2 and deleting a banner cr: %w", deleteChariotMsgErr) } return err } func (o *bannerService) DeleteEUBanner(ctx context.Context, name, id string) (bool, error) { dname := name + "-deleted-" + time.Now().UTC().String() payload := mapper.ToEnterpriseUnitData(id, dname, "organization deleted", "", false) err := o.BSLClient.WithUserTokenCredentials(ctx).SetPayload(payload).Put(BspEnterpriseUnitPath) if err != nil { log.Err(err).Msg("error from bsp while deleting an org") return false, err } return true, nil } func (o *bannerService) DeleteOrgBanner(ctx context.Context, id string) (bool, error) { client := o.BSLClient.WithUserTokenCredentials(ctx) err := client.Delete(fmt.Sprintf(bspDeleteOrgPath, id)) if err != nil { log.Err(err).Msg("error from bsp while deleting an org") return false, err } return true, nil } func (o *bannerService) AssignUserToEUBanners(ctx context.Context, username string, banners []string) error { if err := o.clearAssignedEUBanners(ctx, username); err != nil { return err } client := o.BSLClient.WithUserTokenCredentials(ctx) for _, banner := range banners { err := client.Put(fmt.Sprintf(bspEnterpriseUnitGrantBasePath, username, banner)) if err != nil { return err } } return nil } // AssignUserToOrgBanners creates external user func (o *bannerService) AssignUserToOrgBanners(ctx context.Context, username string, banners []*model.Banner) error { if err := o.clearAssignOrgBanners(ctx, username); err != nil { return err } externalUser := &types.CreateExternalUserRequest{ Status: ActiveStatus, Username: username, } client := o.BSLClient.WithUserTokenCredentials(ctx) for _, banner := range banners { err := client.SetOrgID(banner.BannerBSLId).SetPayload(externalUser).Post(bspExternalUserPath) if err != nil { return err } } return nil } func (o *bannerService) GetUsersForEuBanner(ctx context.Context, bannerBSLID string) ([]*model.User, error) { data := &types.FindUnitGrantsResponse{} client := o.BSLClient.WithUserTokenCredentials(ctx) var userNames []types.UserName pageNumber := 0 for !data.LastPage { err := client.JSON(http.MethodGet, fmt.Sprintf(bspGetOrgUsersListPath, bannerBSLID, pageNumber, pageSize), data) if err != nil { return nil, err } if data.TotalPages == 0 { // weird bsl bug where LastPage is false and TotalPages is zero break } for _, grantViewData := range data.PageContent { userNames = append(userNames, types.UserName{Username: grantViewData.Username}) } pageNumber++ } if len(userNames) == 0 { return make([]*model.User, 0), nil } payload := types.GetUserDetailsRequest{UserIDs: userNames} res := &types.GetUserDetailsResponse{} err := client.SetPayload(payload).JSON(http.MethodPost, bspUserDetailsPath, res) if err != nil { return nil, err } return res.Users, nil } func (o *bannerService) clearAssignedEUBanners(ctx context.Context, username string) error { data, err := o.getUserAssignedEUs(ctx, username) if err != nil { return err } client := o.BSLClient.WithUserTokenCredentials(ctx) for _, eud := range data { if eud.EnterpriseUnitID == "" { continue } if err := revokeUser(client, username, eud.EnterpriseUnitID); err != nil { return err } } return nil } func revokeUser(client *bsl.Request, user string, organizationID string) error { return client.Delete(fmt.Sprintf(bspEnterpriseUnitGrantBasePath, user, organizationID)) } func (o *bannerService) clearAssignOrgBanners(ctx context.Context, username string) error { orgs, err := o.getUserAssignedOrgs(ctx, username) if err != nil { return err } client := o.BSLClient.WithUserTokenCredentials(ctx) for _, org := range orgs { if org.Primary { continue } if err := o.deleteExternalUser(client, username, org.OrganizationName); err != nil { return err } } return nil } func (o *bannerService) deleteExternalUser(client *bsl.Request, username, org string) error { return client.SetOrg(org).Delete(fmt.Sprintf("%s/%s", bspUsersPath, username)) } func (o *bannerService) DeleteClusterSQLEntry(ctx context.Context, clusterEdgeID string) error { _, err := o.SQLDB.ExecContext(ctx, sqlquery.DeleteClusterQuery, clusterEdgeID) if err != nil { return sqlerr.Wrap(err) } return nil } func NewBannerService(gkeService GkeClient, chariotService ChariotService, bslClient *bsl.Client, project string, sqlDB *sql.DB, config *apiTypes.Config) BannerService { return &bannerService{ GkeService: gkeService, ChariotService: chariotService, BSLClient: bslClient, SQLDB: sqlDB, TopLevelProjectID: project, config: config, } }