Pass the subject root namespace to the Code Suggestions seat check
What does this MR do and why?
This MR is a follow-up to merge request !251601 (merged). That MR moved the Code Suggestions availability check into ProjectPolicy and GroupPolicy, as the :access_code_suggestions ability.
The check itself is the condition code_suggestions_enabled_for_user. It lives in a shared module, Ai::DuoAgentPlatform::AccessPolicy, in the file ee/app/policies/ai/duo_agent_platform/access_policy.rb. Both ProjectPolicy and GroupPolicy include this module.
Before this MR, the condition called @user.allowed_to_use?(:code_suggestions) without a root_namespace. Duo Chat already passes root_namespace: @subject.root_ancestor in its own check. This MR makes Code Suggestions do the same, so both features work the same way.
The condition also drops scope: :user. The reason is simple: the result now depends on the subject (the project or the group), not only on the user. So it can no longer be cached per user alone.
The old code comment that pointed to the follow-up issue is removed, since the follow-up is now done.
Behavior change
The seat check does not change. allowed_to_use? still looks at all active Duo seats a user has, in any group. So a user with a seat in group A can still use Code Suggestions in a project that is in group B. This part stays the same.
What changes is which group's "Restrict access based on group membership" rules apply. These are the Ai::NamespaceFeatureAccessRule records for GitLab Duo Non-Agentic. In the code, this is called duo_classic.
Before this MR, only the rules of the user's default GitLab Duo namespace applied.
After this MR, if the user is a member of the top-level group of the project or group being checked, that top-level group's rules apply instead. If the user is not a member of that top-level group, nothing changes. The default Duo namespace rules still apply in that case.
Here is an example. A user has a seat in group A. Group B restricts Duo Non-Agentic to members of a subgroup, B/allowed. The user is a member of group B, but not of B/allowed.
- Before this MR: Code Suggestions in group B projects was allowed.
- After this MR: it is blocked.
- If the user is a member of
B/allowed, access stays allowed. - If the user is not a member of group B at all, access also stays allowed.
This new behavior matches Duo Chat. It also matches the current documentation on the page "Configure access to GitLab Duo": https://docs.gitlab.com/administration/gitlab_duo/configure/access_control/. That page says the rules apply to direct members of a configured group, when they use an AI feature in a project or subgroup under that group's top-level group. So this MR makes the code match the docs. No docs change is needed.
Identity verification is not affected by this change. That check only applies to GitLab Duo Agent Platform features.
References
- Issue: #628218 (closed)
- Previous merge request: !251601 (merged)
- Review comment that asked for this change: !251601 (comment 3804773855)
- Documentation page: https://docs.gitlab.com/administration/gitlab_duo/configure/access_control/
How to set up and validate locally
- Seed a Duo group and project in SaaS mode. Run this from the Rails root:
This creates the group
GITLAB_SIMULATE_SAAS=1 bundle exec 'rake gitlab:duo:setup[duo_pro]'gitlab-duoand gives therootuser a Duo Pro seat in it. - Start GDK with SaaS simulation turned on.
- Open a Rails console. Create a second top-level group with a project, add
rootas a guest, and check the policy. Use this Ruby code:Note:user = User.find_by_username('root') other_group = Group.create!(name: 'other-group', path: 'other-group', organization: user.organization) other_group.namespace_settings.update!(duo_availability: 'default_on') project = Projects::CreateService.new(user, name: 'other-project', path: 'other-project', namespace_id: other_group.id).execute other_group.add_guest(user) # Seat is in gitlab-duo, project is in other-group, no rules: allowed Ability.allowed?(user, :access_code_suggestions, project) # => true # other-group restricts Duo Non-Agentic to members of a subgroup. root is not in it: blocked allowed_subgroup = Group.create!(name: 'allowed', path: 'allowed', parent: other_group, organization: user.organization) Ai::NamespaceFeatureAccessRule.create!(root_namespace: other_group, through_namespace: allowed_subgroup, accessible_entity: 'duo_classic') Ability.allowed?(User.find(user.id), :access_code_suggestions, Project.find(project.id)) # => false # Add root to the allowed subgroup: allowed again allowed_subgroup.add_guest(user) Ability.allowed?(User.find(user.id), :access_code_suggestions, Project.find(project.id)) # => trueallowed_to_use?caches its result inRails.cacheand in the request store. You may need to reload the user and the project, or runRails.cache.clear, between checks.
Tests:
- Two existing specs stubbed
allowed_to_use?with(:code_suggestions). They now expect the extraroot_namespace:argument. Files:ee/spec/policies/project_policy_spec.rbandee/spec/policies/ee/group_policy_spec.rb. - New specs were added in both files, for a user with a seat in group A and a subject (project or group) in group B:
- group B has no access rules: allowed
- group B has rules, but the user is not a member of group B: allowed
- group B has rules, the user is a member of group B but not of the allowed subgroup: not allowed
- the user is a member of the allowed subgroup: allowed
- one spec checks that
allowed_to_use?getsroot_namespace:equal to the subject's root ancestor
- Results: the
access_code_suggestionspolicy specs in both files pass, 27 examples, 0 failures. When the policy change is reverted, exactly 4 of the new examples fail, which confirms they test the right thing. ee/spec/requests/api/code_suggestions_spec.rb: 192 examples, 0 failures, 11 pending.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.