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

167 lines
5.5 KiB
Go
Raw Permalink Normal View History

package usecase
import (
"context"
"errors"
"fmt"
libmongo "gateway/internal/library/mongo"
redislib "gateway/internal/library/redis"
permcfg "gateway/internal/model/permission/config"
permission "gateway/internal/model/permission/domain"
domrepo "gateway/internal/model/permission/domain/repository"
dom "gateway/internal/model/permission/domain/usecase"
permrepo "gateway/internal/model/permission/repository"
)
// FactoryParam configures the permission module. Repositories may be
// pre-built (used by tests / cmd seed) or auto-constructed from MongoConf.
type FactoryParam struct {
MongoConf *libmongo.Conf
Redis *redislib.Client
Config permcfg.Config
// Optional pre-built repositories. When set, MongoConf is ignored
// for that repository.
Permissions domrepo.PermissionRepository
Roles domrepo.RoleRepository
RolePermissions domrepo.RolePermissionRepository
UserRoles domrepo.UserRoleRepository
RoleMappings domrepo.RoleMappingRepository
// Optional Casbin model text (overrides Config.Casbin.ModelPath).
CasbinModelText string
}
// Module bundles all permission usecase ports.
type Module struct {
Permission dom.PermissionUseCase
Role dom.RoleUseCase
RolePermission dom.RolePermissionUseCase
UserRole dom.UserRoleUseCase
RoleMapping dom.RoleMappingUseCase
AuthorizationQuery dom.AuthorizationQueryUseCase
RBAC dom.RBACUseCase
Permissions domrepo.PermissionRepository
Roles domrepo.RoleRepository
RolePermissions domrepo.RolePermissionRepository
UserRoles domrepo.UserRoleRepository
RoleMappings domrepo.RoleMappingRepository
}
// NewModuleFromParam wires the seven usecases against the configured
// repositories. Mongo is required for catalog/role/user-role/mapping;
// Redis is required for the Casbin enforcer + pub/sub broadcast.
//
// When Redis is missing, RBAC stays nil and Permission/Role mutations
// continue to work but Check() always denies. Mongo missing returns an
// error because the catalog cannot live anywhere else.
func NewModuleFromParam(param FactoryParam) (*Module, error) {
cfg := param.Config.Defaults()
mod := &Module{
Permissions: param.Permissions,
Roles: param.Roles,
RolePermissions: param.RolePermissions,
UserRoles: param.UserRoles,
RoleMappings: param.RoleMappings,
}
if mod.Permissions == nil {
if param.MongoConf == nil || param.MongoConf.Host == "" {
return nil, fmt.Errorf("permission: mongo config required")
}
mod.Permissions = permrepo.NewPermissionRepository(permrepo.PermissionRepositoryParam{Conf: param.MongoConf})
}
if mod.Roles == nil {
mod.Roles = permrepo.NewRoleRepository(permrepo.RoleRepositoryParam{Conf: param.MongoConf})
}
if mod.RolePermissions == nil {
mod.RolePermissions = permrepo.NewRolePermissionRepository(permrepo.RolePermissionRepositoryParam{Conf: param.MongoConf})
}
if mod.UserRoles == nil {
mod.UserRoles = permrepo.NewUserRoleRepository(permrepo.UserRoleRepositoryParam{Conf: param.MongoConf})
}
if mod.RoleMappings == nil {
mod.RoleMappings = permrepo.NewRoleMappingRepository(permrepo.RoleMappingRepositoryParam{Conf: param.MongoConf})
}
mod.Permission = NewPermissionUseCase(PermissionUseCaseParam{Permissions: mod.Permissions})
var reloader PolicyReloader
if cfg.Casbin.Enabled && param.Redis != nil && param.Redis.Zero() != nil {
// Plug the bridge so rbac_usecase can build a Redis adapter
// without importing repository (avoids cycle).
RedisAdapterFactory = func(client *redislib.Client) (domrepo.CasbinPolicyAdapter, error) {
if client == nil || client.Zero() == nil {
return nil, nil
}
return permrepo.NewCasbinRedisAdapter(client)
}
rbacUC, err := NewRBACUseCase(RBACUseCaseParam{
Roles: mod.Roles,
Permissions: mod.Permissions,
RolePermissions: mod.RolePermissions,
UserRoles: mod.UserRoles,
Redis: param.Redis,
ModelPath: cfg.Casbin.ModelPath,
CasbinModelText: param.CasbinModelText,
ReloadChannel: cfg.Reload.Channel,
})
if err != nil && !errors.Is(err, permission.ErrCasbinNotConfigured) {
return nil, err
}
mod.RBAC = rbacUC
if rbacUC != nil {
reloader = rbacUC.BroadcastReload
}
}
mod.Role = NewRoleUseCase(RoleUseCaseParam{
Roles: mod.Roles,
RolePermissions: mod.RolePermissions,
UserRoles: mod.UserRoles,
})
mod.RolePermission = NewRolePermissionUseCase(RolePermissionUseCaseParam{
Roles: mod.Roles,
Permissions: mod.Permissions,
RolePermissions: mod.RolePermissions,
Reloader: reloader,
})
mod.UserRole = NewUserRoleUseCase(UserRoleUseCaseParam{
Roles: mod.Roles,
UserRoles: mod.UserRoles,
Reloader: reloader,
})
mod.RoleMapping = NewRoleMappingUseCase(RoleMappingUseCaseParam{
Roles: mod.Roles,
Mappings: mod.RoleMappings,
})
mod.AuthorizationQuery = NewAuthorizationQueryUseCase(AuthorizationQueryUseCaseParam{
Roles: mod.Roles,
Permissions: mod.Permissions,
RolePermissions: mod.RolePermissions,
UserRoles: mod.UserRoles,
})
return mod, nil
}
// StartBackground starts the policy reload subscriber when configured.
// Safe to call when RBAC is nil (no-op).
func (m *Module) StartBackground(ctx context.Context) error {
if m == nil || m.RBAC == nil {
return nil
}
return m.RBAC.StartReloadSubscriber(ctx)
}
// StopBackground tears down the subscriber. Safe to call when never
// started.
func (m *Module) StopBackground() {
if m == nil || m.RBAC == nil {
return
}
m.RBAC.StopReloadSubscriber()
}