package native import ( "context" "github.com/tech/sendico/pkg/auth/internal/native/nstructures" "github.com/tech/sendico/pkg/db/role" "github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mutil/mzap" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" ) // RoleManager manages roles using Casbin. type RoleManager struct { logger mlogger.Logger enforcer *Enforcer rdb role.DB rolePermissionRef primitive.ObjectID } // NewRoleManager creates a new RoleManager. func NewRoleManager(logger mlogger.Logger, enforcer *Enforcer, rolePermissionRef primitive.ObjectID, rdb role.DB) *RoleManager { return &RoleManager{ logger: logger.Named("role"), enforcer: enforcer, rdb: rdb, rolePermissionRef: rolePermissionRef, } } // validateObjectIDs ensures that all provided ObjectIDs are non-zero. func (rm *RoleManager) validateObjectIDs(ids ...primitive.ObjectID) error { for _, id := range ids { if id.IsZero() { return merrors.InvalidArgument("Object references cannot be zero") } } return nil } // fetchRolesFromPolicies retrieves and converts policies to roles. func (rm *RoleManager) fetchRolesFromPolicies(roles []nstructures.RoleAssignment, organizationRef primitive.ObjectID) []model.RoleDescription { result := make([]model.RoleDescription, len(roles)) for i, role := range roles { result[i] = model.RoleDescription{ Base: storable.Base{ID: *role.GetID()}, OrganizationRef: organizationRef, } } return result } // Create creates a new role in an organization. func (rm *RoleManager) Create(ctx context.Context, organizationRef primitive.ObjectID, description *model.Describable) (*model.RoleDescription, error) { if err := rm.validateObjectIDs(organizationRef); err != nil { return nil, err } role := &model.RoleDescription{ OrganizationRef: organizationRef, Describable: *description, } if err := rm.rdb.Create(ctx, role); err != nil { rm.logger.Warn("Failed to create role", zap.Error(err), mzap.ObjRef("organization_ref", organizationRef)) return nil, err } rm.logger.Info("Role created successfully", mzap.StorableRef(role), mzap.ObjRef("organization_ref", organizationRef)) return role, nil } // Assign assigns a role to a user in the given organization. func (rm *RoleManager) Assign(ctx context.Context, role *model.Role) error { if err := rm.validateObjectIDs(role.DescriptionRef, role.AccountRef, role.OrganizationRef); err != nil { return err } assogment := nstructures.RoleAssignment{Role: *role} err := rm.enforcer.rdb.Create(ctx, &assogment) return rm.logPolicyResult("assign", err == nil, err, role.DescriptionRef, role.AccountRef, role.OrganizationRef) } // Delete removes a role entirely and cleans up associated Casbin policies. func (rm *RoleManager) Delete(ctx context.Context, roleRef primitive.ObjectID) error { if err := rm.validateObjectIDs(roleRef); err != nil { rm.logger.Warn("Failed to delete role", mzap.ObjRef("role_ref", roleRef)) return err } if err := rm.rdb.Delete(ctx, roleRef); err != nil { rm.logger.Warn("Failed to delete role", mzap.ObjRef("role_ref", roleRef)) return err } if err := rm.enforcer.rdb.DeleteRole(ctx, roleRef); err != nil { rm.logger.Warn("Failed to remove role", zap.Error(err), mzap.ObjRef("role_ref", roleRef)) return err } rm.logger.Info("Role deleted successfully along with associated policies", mzap.ObjRef("role_ref", roleRef)) return nil } // Revoke removes a role from a user. func (rm *RoleManager) Revoke(ctx context.Context, roleRef, accountRef, organizationRef primitive.ObjectID) error { if err := rm.validateObjectIDs(roleRef, accountRef, organizationRef); err != nil { return err } err := rm.enforcer.rdb.RemoveRole(ctx, roleRef, organizationRef, accountRef) return rm.logPolicyResult("revoke", err == nil, err, roleRef, accountRef, organizationRef) } // logPolicyResult logs results for Assign and Revoke. func (rm *RoleManager) logPolicyResult(action string, result bool, err error, roleRef, accountRef, organizationRef primitive.ObjectID) error { if err != nil { rm.logger.Warn("Failed to "+action+" role", zap.Error(err), mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef)) return err } msg := "Role " + action + "ed successfully" if !result { msg = "Role already " + action + "ed" } rm.logger.Info(msg, mzap.ObjRef("role_ref", roleRef), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef)) return nil } // List retrieves all roles in an organization or all roles if organizationRef is zero. func (rm *RoleManager) List(ctx context.Context, organizationRef primitive.ObjectID) ([]model.RoleDescription, error) { roles4Venues, err := rm.enforcer.rdb.RolesForVenue(ctx, organizationRef) if err != nil { rm.logger.Warn("Failed to fetch grouping policies", zap.Error(err), mzap.ObjRef("organization_ref", organizationRef)) return nil, err } roles := rm.fetchRolesFromPolicies(roles4Venues, organizationRef) rm.logger.Info("Retrieved roles for organization", mzap.ObjRef("organization_ref", organizationRef), zap.Int("count", len(roles))) return roles, nil }