template-monorepo/internal/model/permission/usecase/user_role_usecase.go

148 lines
4.2 KiB
Go
Raw Normal View History

package usecase
import (
"context"
"gateway/internal/model/permission/domain"
"gateway/internal/model/permission/domain/entity"
"gateway/internal/model/permission/domain/enum"
domrepo "gateway/internal/model/permission/domain/repository"
dom "gateway/internal/model/permission/domain/usecase"
"github.com/zeromicro/go-zero/core/logx"
)
// UserRoleUseCaseParam injects role + user-role repositories.
type UserRoleUseCaseParam struct {
Roles domrepo.RoleRepository
UserRoles domrepo.UserRoleRepository
Reloader PolicyReloader
}
type userRoleUseCase struct {
roles domrepo.RoleRepository
userRoles domrepo.UserRoleRepository
reload PolicyReloader
}
// NewUserRoleUseCase returns the assignment manager used by tenant
// admins and SyncFromX flows.
func NewUserRoleUseCase(param UserRoleUseCaseParam) dom.UserRoleUseCase {
return &userRoleUseCase{
roles: param.Roles,
userRoles: param.UserRoles,
reload: param.Reloader,
}
}
func (uc *userRoleUseCase) Assign(ctx context.Context, param *dom.AssignParam) (*entity.UserRole, error) {
if param == nil || param.TenantID == "" || param.UID == "" || param.RoleID == "" {
return nil, errb.InputMissingRequired("tenant_id|uid|role_id")
}
role, err := uc.roles.GetByID(ctx, param.TenantID, param.RoleID)
if err != nil {
return nil, wrapRepoErr(err)
}
source := param.Source
if source == "" {
source = enum.RoleSourceManual
}
if !source.IsValid() {
return nil, errb.InputInvalidFormat("invalid source")
}
ur := &entity.UserRole{
TenantID: param.TenantID,
UID: param.UID,
RoleID: role.ID.Hex(),
Source: source,
}
if err := uc.userRoles.Insert(ctx, ur); err != nil {
return nil, wrapRepoErr(err, "assign role")
}
uc.broadcast(ctx, param.TenantID)
return ur, nil
}
func (uc *userRoleUseCase) Revoke(ctx context.Context, tenantID, uid, roleID string) error {
if err := uc.userRoles.Delete(ctx, tenantID, uid, roleID); err != nil {
return wrapRepoErr(err, "revoke role")
}
uc.broadcast(ctx, tenantID)
return nil
}
func (uc *userRoleUseCase) List(ctx context.Context, tenantID, uid string) ([]*dom.UserRoleSummary, error) {
rows, err := uc.userRoles.ListByUser(ctx, tenantID, uid)
if err != nil {
return nil, wrapRepoErr(err)
}
if len(rows) == 0 {
return nil, nil
}
ids := make([]string, 0, len(rows))
for _, ur := range rows {
ids = append(ids, ur.RoleID)
}
roles, err := uc.roles.ListByTenantAndIDs(ctx, tenantID, ids)
if err != nil {
return nil, wrapRepoErr(err)
}
roleByID := make(map[string]*entity.Role, len(roles))
for _, role := range roles {
roleByID[role.ID.Hex()] = role
}
out := make([]*dom.UserRoleSummary, 0, len(rows))
for _, ur := range rows {
summary := &dom.UserRoleSummary{UserRole: ur}
if role, ok := roleByID[ur.RoleID]; ok {
summary.RoleKey = role.Key
summary.RoleDisplayName = role.DisplayName
}
out = append(out, summary)
}
return out, nil
}
func (uc *userRoleUseCase) ReplaceForSource(
ctx context.Context,
tenantID, uid string,
source enum.RoleSource,
roleKeys []string,
) error {
if !source.IsValid() {
return errb.InputInvalidFormat("invalid source")
}
if source == enum.RoleSourceManual {
// Manual assignments are managed via Assign/Revoke; protect from
// accidental wipe by SyncFromX flows (defence in depth).
return errb.ResInvalidState("manual source cannot be batch-replaced")
}
roleIDs := make([]string, 0, len(roleKeys))
for _, key := range roleKeys {
role, err := uc.roles.GetByKey(ctx, tenantID, key)
if err != nil {
// Skip unknown keys silently — keeps SyncFromX resilient when
// the IdP exposes groups the tenant has not mapped yet.
continue
}
roleIDs = append(roleIDs, role.ID.Hex())
}
if err := uc.userRoles.ReplaceForSource(ctx, tenantID, uid, source, roleIDs); err != nil {
return wrapRepoErr(err, "replace user roles")
}
uc.broadcast(ctx, tenantID)
return nil
}
func (uc *userRoleUseCase) broadcast(ctx context.Context, tenantID string) {
if uc.reload == nil {
return
}
if err := uc.reload(ctx, tenantID); err != nil {
logx.WithContext(ctx).Errorf("permission user-role: broadcast reload tenant=%s: %v", tenantID, err)
}
}
var _ dom.UserRoleUseCase = (*userRoleUseCase)(nil)
var _ = domain.ReservedRoleKeyPrefixes // ensure domain package referenced for go build