承接 mhpdigital/cross-tenant-security-bundle 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

mhpdigital/cross-tenant-security-bundle

Composer 安装命令:

composer require mhpdigital/cross-tenant-security-bundle

包简介

Symfony bundle for role-based, row-level tenant isolation in Doctrine repositories

README 文档

README

Role-based, row-level tenant isolation for Doctrine repositories in Symfony.

Pick one of three traits per repository to declare its access policy. Filtering is applied automatically in createQueryBuilder() (and find(), findBy(), findOneBy(), findAll()), so controllers and services never pass the current user around — the security context is read from the token.

Install & register

composer require mhpdigital/cross-tenant-security-bundle

Wire the factory as Doctrine's repository factory:

# config/packages/doctrine.yaml
doctrine:
    orm:
        repository_factory: 'Mhpdigital\CrossTenantSecurity\Repository\CrossTenantRepositoryFactory'

The factory injects token_storage, role_hierarchy and request_stack into every repository that uses one of the traits.

Access matrix

Trait console / worker authenticated web token-less web
CrossTenantRepository all rows your tenant filter (override) none (1=0)
AdminOnlyAccessRepository all rows ROLE_SUPER_ADMIN → all, else none none
OpenAccessRepository all rows all rows all rows (public)

"console / worker" = any process with no HTTP request in flight (a console command, a Messenger/queue worker, a cron run). These are trusted local processes and get full access automatically — see Console context.

Examples

1. Tenant-scoped — CrossTenantRepository

Each user sees only their own rows; ROLE_SUPER_ADMIN sees all. Use the trait-alias pattern: call the library's secured builder first (you inherit the unauthenticated 1=0 and the console-context bypass), then add your own filter.

use Mhpdigital\CrossTenantSecurity\Repository\CrossTenantRepository;

class PostRepository extends ServiceEntityRepository
{
    use CrossTenantRepository {
        CrossTenantRepository::createQueryBuilder as secureQueryBuilder;
    }

    public function createQueryBuilder($alias, $indexBy = null): QueryBuilder
    {
        $qb = $this->secureQueryBuilder($alias, $indexBy);

        // Authenticated non-super-admins are scoped to their own rows.
        // (No current user ⇒ console context ⇒ full access, so this is skipped.)
        if ($this->getCurrentUser() !== null && $this->getHighestRole() !== 'ROLE_SUPER_ADMIN') {
            $qb->andWhere("$alias.author = :_author")
               ->setParameter('_author', $this->getUserId());
        }

        return $qb;
    }
}

2. Public lookup — OpenAccessRepository

Genuinely public reference data — everyone, including unauthenticated requests, sees all rows. Use for sex, country, currency, status, category, tag. No override needed.

use Mhpdigital\CrossTenantSecurity\Repository\OpenAccessRepository;

class SexRepository extends ServiceEntityRepository
{
    use OpenAccessRepository;
}

If a lookup must require login, do not use this trait — use CrossTenantRepository (authenticated → all rows, token-less web → none).

3. Admin-only — AdminOnlyAccessRepository

Only ROLE_SUPER_ADMIN (and trusted console/worker runs) see rows; everyone else sees none.

use Mhpdigital\CrossTenantSecurity\Repository\AdminOnlyAccessRepository;

class AuditLogRepository extends ServiceEntityRepository
{
    use AdminOnlyAccessRepository;
}

4. Content filter + access gate (custom createQueryBuilder)

When a repository has an always-on content filter (e.g. hide soft-deleted or unpublished rows) as well as an access gate, reimplement createQueryBuilder() and place isConsoleContext() between them — the content filter must apply in every context, but the access gate is bypassed for console/worker:

public function createQueryBuilder($alias, $indexBy = null): QueryBuilder
{
    $qb = $em->createQueryBuilder()->select($alias)->from(/* … */);

    $qb->andWhere("$alias.deleted IS NULL");   // (1) content filter — ALWAYS applies

    if ($this->isConsoleContext()) {           // CLI/worker bypasses ONLY the gate below
        return $qb;
    }

    // (2) access gate — bypassed in console
    if (!\in_array($this->getHighestRole(), self::READER_ROLES, true)) {
        $qb->andWhere('1=0');
    }

    return $qb;
}

A console index build then sees all non-deleted rows without createUnrestrictedQueryBuilder(), while deleted rows stay hidden everywhere.

Console / worker context

A console command, Messenger/queue worker or cron run has no HTTP request and carries no security token. The bundle detects this (isConsoleContext() — request-presence, so functional tests that issue a sub-request keep full web semantics) and grants full access through the secured createQueryBuilder(). CLI/background code no longer needs createUnrestrictedQueryBuilder().

If request_stack is not wired, the bundle assumes a web context and fails closed.

Explicit, context-independent bypass

$qb = $repo->createUnrestrictedQueryBuilder('e'); // raw Doctrine builder, no filtering at all

Use only when you must bypass filtering regardless of context (e.g. an authenticated admin maintenance action). For ordinary CLI/background work the console-context detection already grants full access, so you should rarely need this.

See also

The example/ app is a runnable Symfony project exercising every trait — example/src/Repository/* and example/tests/Integration/BundleIntegrationTest.php are the executable documentation, including the console-context tests.

统计信息

  • 总下载量: 399
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-30

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固