github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/applicationtenancy/directive.go (about) 1 package applicationtenancy 2 3 import ( 4 "context" 5 6 gqlgen "github.com/99designs/gqlgen/graphql" 7 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 8 "github.com/kyma-incubator/compass/components/director/internal/model" 9 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 10 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 11 "github.com/kyma-incubator/compass/components/director/pkg/log" 12 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 13 "github.com/kyma-incubator/compass/components/director/pkg/resource" 14 tenantpkg "github.com/kyma-incubator/compass/components/director/pkg/tenant" 15 "github.com/pkg/errors" 16 ) 17 18 // BusinessTenantMappingService is responsible for the service-layer tenant operations. 19 // 20 //go:generate mockery --name=BusinessTenantMappingService --output=automock --outpkg=automock --case=underscore --disable-version-string 21 type BusinessTenantMappingService interface { 22 CreateTenantAccessForResource(ctx context.Context, tenantAccess *model.TenantAccess) error 23 ListByParentAndType(ctx context.Context, parentID string, tenantType tenantpkg.Type) ([]*model.BusinessTenantMapping, error) 24 GetCustomerIDParentRecursively(ctx context.Context, tenantID string) (string, error) 25 GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) 26 GetTenantByExternalID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) 27 } 28 29 // ApplicationService is responsible for the service-layer application operations. 30 // 31 //go:generate mockery --name=ApplicationService --output=automock --outpkg=automock --case=underscore --disable-version-string 32 type ApplicationService interface { 33 ListAll(ctx context.Context) ([]*model.Application, error) 34 } 35 36 type directive struct { 37 transact persistence.Transactioner 38 tenantService BusinessTenantMappingService 39 appService ApplicationService 40 } 41 42 // NewDirective creates a directive object 43 func NewDirective(transact persistence.Transactioner, tenantService BusinessTenantMappingService, appService ApplicationService) *directive { 44 return &directive{ 45 transact: transact, 46 tenantService: tenantService, 47 appService: appService, 48 } 49 } 50 51 // SynchronizeApplicationTenancy handles graphql.EventTypeNewApplication, graphql.EventTypeNewSingleTenant, and graphql.EventTypeNewMultipleTenants events. 52 // In EventTypeNewApplication we extract the customer parent of the owner of the new application. We give access to accounts under that customer to access the application. 53 // In EventTypeNewSingleTenant we get the new tenant's customer parent. If the new tenant is account we give him access to all Atom applications under the customer. 54 // In EventTypeNewMultipleTenants we do the same as for EventTypeNewSingleTenant event but for multiple new tenants. 55 func (d *directive) SynchronizeApplicationTenancy(ctx context.Context, _ interface{}, next gqlgen.Resolver, eventType graphql.EventType) (res interface{}, err error) { 56 resp, err := next(ctx) 57 if err != nil { 58 log.C(ctx).WithError(err).Errorf("An error occurred while processing request: %s", err.Error()) 59 return resp, err 60 } 61 62 tx, err := d.transact.Begin() 63 if err != nil { 64 log.C(ctx).WithError(err).Errorf("An error occurred while opening database transaction") 65 return nil, apperrors.NewInternalError("Unable to initialize database transaction: %s", err.Error()) 66 } 67 defer d.transact.RollbackUnlessCommitted(ctx, tx) 68 69 ctx = persistence.SaveToContext(ctx, tx) 70 71 log.C(ctx).Debugf("Preparing tenant access creation for event: %s", eventType) 72 73 if eventType == graphql.EventTypeNewApplication { 74 if err := d.handleNewApplicationCreation(ctx, resp); err != nil { 75 return nil, err 76 } 77 } else if eventType == graphql.EventTypeNewSingleTenant { 78 if err := d.handleNewSingleTenantCreation(ctx, resp); err != nil { 79 return nil, err 80 } 81 } else if eventType == graphql.EventTypeNewMultipleTenants { 82 if err := d.handleNewMultipleTenantCreation(ctx, resp); err != nil { 83 return nil, err 84 } 85 } 86 87 if err := tx.Commit(); err != nil { 88 log.C(ctx).WithError(err).Errorf("An error occurred while closing database transaction: %s", err.Error()) 89 return nil, apperrors.NewInternalError("Unable to finalize request %v", err) 90 } 91 92 return resp, nil 93 } 94 95 func (d *directive) createTenantAccessForOrgApplications(ctx context.Context, newTenantParentID, receivingAccessTnt string) error { 96 orgTenants, err := d.tenantService.ListByParentAndType(ctx, newTenantParentID, tenantpkg.Organization) 97 if err != nil { 98 log.C(ctx).WithError(err).Errorf("An error occurred while listing tenants by parent with ID %s and %s: %v", newTenantParentID, tenantpkg.Organization, err) 99 return apperrors.NewInternalError("An error occurred while listing tenants by parent with ID %s and %s: %v", newTenantParentID, tenantpkg.Organization, err) 100 } 101 102 for _, orgTenant := range orgTenants { 103 ctx = tenant.SaveToContext(ctx, orgTenant.ID, orgTenant.ExternalTenant) 104 tenantApps, err := d.appService.ListAll(ctx) 105 if err != nil { 106 log.C(ctx).WithError(err).Errorf("An error occurred while listing applications for tenant %s: %v", orgTenant.ID, err) 107 return apperrors.NewInternalError("An error occurred while listing applications for tenant %s: %v", orgTenant.ID, err) 108 } 109 110 for _, app := range tenantApps { 111 if err := d.tenantService.CreateTenantAccessForResource(ctx, &model.TenantAccess{InternalTenantID: receivingAccessTnt, ResourceType: resource.Application, ResourceID: app.ID, Owner: false}); err != nil { 112 log.C(ctx).WithError(err).Errorf("An error occurred while creating tenant access: %v", err) 113 return apperrors.NewInternalError("An error occurred while creating tenant access: %v", err) 114 } 115 } 116 } 117 118 return nil 119 } 120 121 func (d *directive) createTenantAccessForNewApplication(ctx context.Context, tntFromContext *model.BusinessTenantMapping, appID string) error { 122 var err error 123 parentTntID := tntFromContext.ID 124 125 if tntFromContext.Type != tenantpkg.Customer { 126 parentExternalID, err := d.tenantService.GetCustomerIDParentRecursively(ctx, tntFromContext.ID) 127 if err != nil { 128 log.C(ctx).WithError(err).Errorf("An error occurred while getting tenant %s customer parent", tntFromContext.ID) 129 return errors.Wrapf(err, "while getting customer parent for tenant: %s", tntFromContext.ID) 130 } 131 132 parent, err := d.tenantService.GetTenantByExternalID(ctx, parentExternalID) 133 if err != nil { 134 log.C(ctx).WithError(err).Errorf("An error occurred while getting parent model by external ID %s", parentExternalID) 135 return errors.Wrapf(err, "while getting parent: %s", tntFromContext.ID) 136 } 137 138 parentTntID = parent.ID 139 } 140 141 log.C(ctx).Debugf("Found parent: %s for tenant with ID %s", parentTntID, tntFromContext.ID) 142 143 tenants, err := d.tenantService.ListByParentAndType(ctx, parentTntID, tenantpkg.Account) 144 if err != nil { 145 log.C(ctx).WithError(err).Errorf("An error occurred while listing tenants by parent ID %s", parentTntID) 146 return errors.Wrapf(err, "while listing tenants by parent %s and type %s", parentTntID, tenantpkg.Account) 147 } 148 149 for _, tenant := range tenants { 150 if err := d.tenantService.CreateTenantAccessForResource(ctx, &model.TenantAccess{InternalTenantID: tenant.ID, ResourceType: resource.Application, ResourceID: appID, Owner: true}); err != nil { 151 log.C(ctx).WithError(err).Errorf("An error occurred while creating tenant access for tenant %s and application %s", tenant.ID, appID) 152 return errors.Wrap(err, "while creating tenant access") 153 } 154 } 155 156 return nil 157 } 158 159 func (d *directive) getTenantAndValidateTenantAccessEligibility(ctx context.Context, tenantID string) (*model.BusinessTenantMapping, error) { 160 tntModel, err := d.tenantService.GetTenantByID(ctx, tenantID) 161 if err != nil { 162 log.C(ctx).WithError(err).Errorf("An error occurred while getting tenant with ID %s", tenantID) 163 return nil, err 164 } 165 166 if tntModel.Type != tenantpkg.Account || tntModel.Parent == "" { 167 log.C(ctx).Debugf("Tenant with ID %s is not Account type or does not have a parent. Skipping tenant access creation.", tntModel.ID) 168 return nil, nil 169 } 170 171 return tntModel, nil 172 } 173 174 func (d *directive) processSingleTenant(ctx context.Context, tenantID string) error { 175 tntModel, err := d.getTenantAndValidateTenantAccessEligibility(ctx, tenantID) 176 if err != nil { 177 return err 178 } 179 180 if tntModel == nil { 181 return nil 182 } 183 184 return d.createTenantAccessForOrgApplications(ctx, tntModel.Parent, tntModel.ID) 185 } 186 187 func (d *directive) handleNewSingleTenantCreation(ctx context.Context, resp interface{}) error { 188 tenantID, ok := resp.(string) 189 if !ok { 190 log.C(ctx).Errorf("An error occurred while casting the graphql response entity to single tenant string") 191 return apperrors.NewInvalidDataError("An error occurred while casting the response entity: %v", resp) 192 } 193 194 return d.processSingleTenant(ctx, tenantID) 195 } 196 197 func (d *directive) handleNewMultipleTenantCreation(ctx context.Context, resp interface{}) error { 198 tenantIDs, ok := resp.([]string) 199 if !ok { 200 log.C(ctx).Errorf("An error occurred while casting the response entity to string array") 201 return apperrors.NewInvalidDataError("An error occurred while casting the response entity: %v", resp) 202 } 203 204 for _, tenantID := range tenantIDs { 205 if err := d.processSingleTenant(ctx, tenantID); err != nil { 206 return err 207 } 208 } 209 210 return nil 211 } 212 213 func (d *directive) handleNewApplicationCreation(ctx context.Context, resp interface{}) error { 214 tntIDFromContext, err := tenant.LoadFromContext(ctx) 215 if err != nil { 216 log.C(ctx).WithError(err).Errorf("An error occurred while loading tenant from context") 217 return err 218 } 219 220 tntModel, err := d.tenantService.GetTenantByID(ctx, tntIDFromContext) 221 if err != nil { 222 log.C(ctx).WithError(err).Errorf("An error occurred while getting tenant with ID %s", tntIDFromContext) 223 return errors.Wrapf(err, "while fetching tenant with id: %s", tntIDFromContext) 224 } 225 226 log.C(ctx).Debugf("Found a matching tenant in the database: %s", tntModel.ID) 227 228 if !isAtomTenant(tntModel.Type) { 229 log.C(ctx).Infof("Tenant type is %s. Will not continue with tanancy synchronization", tntModel.Type) 230 return nil 231 } 232 233 entity, ok := resp.(graphql.Entity) 234 if !ok { 235 log.C(ctx).Errorf("An error occurred while casting the graphql response entity") 236 return apperrors.NewInvalidDataError("An error occurred while casting the response entity: %v", resp) 237 } 238 239 return d.createTenantAccessForNewApplication(ctx, tntModel, entity.GetID()) 240 } 241 242 func isAtomTenant(tenantType tenantpkg.Type) bool { 243 if tenantType == tenantpkg.ResourceGroup || tenantType == tenantpkg.Folder || tenantType == tenantpkg.Organization { 244 return true 245 } 246 247 return false 248 }