Authorize reading CustomEmoji for granular tokens
What does this MR do and why?
CustomEmoji declared no granular token directive, so fine-grained
personal access tokens got an empty customEmoji connection: count
was right but nodes was empty, without any error. Tools that sync
emojis with such a token then saw none and tried to recreate every
emoji, which failed on the existing names.
Add a read_custom_emoji permission on the group boundary, which pairs
with the existing create and delete permissions.
Screenshots
| Before | After |
|---|---|
![]() |
![]() |
References
How to set up and validate locally
Prerequisites: a group (my-group) with at least one custom emoji; a fine-grained PAT scoped to that group with Group: Read and Custom Emoji: Create (plus Custom Emoji: Read once this MR is applied); and a classic PAT with api or read_api for comparison.
Example:
List custom emoji with a classic vs. a fine-grained token.
Run the same query once with each token, $CLASSIC_TOKEN and $FINE_GRAINED_TOKEN:
curl --request POST 'https://gitlab.example.com/api/graphql' \
--header "Authorization: Bearer $CLASSIC_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"query": "{ group(fullPath: \"my-group\") { customEmoji { count nodes { id name } } } }"}'Classic token (output shortened):
{"data":{"group":{"customEmoji":{"count":39,"nodes":[{"id":"gid://gitlab/CustomEmoji/303","name":"awesome"}, ...]}}}}Fine-grained token (count matches, nodes is empty, no error):
{"data":{"group":{"customEmoji":{"count":39,"nodes":[]}}}}With this MR
- Add
Custom Emoji: Readto the fine-grained token (the permission appears next toCustom Emoji: CreateandCustom Emoji: Deleteon the group boundary). - Rerun Example 1 with
$FINE_GRAINED_TOKEN.nodesnow matches the classic token's output. - Without
Custom Emoji: Read,nodesstays empty.
Set up with Caproni
This shows how to try the MR in Caproni without a full image build. You layer the MR's changed files on top of the webservice image the cluster already runs, load the result into the cluster, and point the chart at it. Only the webservice needs the change, because it serves GraphQL.
Run the docker and git commands from a gitlab-org/gitlab checkout of this MR's branch, and the caproni commands from the gitlab-caproni directory. Only four files matter at runtime:
app/graphql/types/custom_emoji_type.rbapp/graphql/types/permission_types/custom_emoji.rbconfig/authz/permissions/custom_emoji/read.ymlconfig/authz/permission_groups/assignable_permissions/groups/custom_emoji/read.yml
This was verified on a cluster running gitlab-webservice-ee:v19.4.1. The files apply unchanged on top of 19.4.1.
-
Find the image the cluster runs:
caproni kubectl -n gitlab get deploy gitlab-webservice-default \ -o jsonpath='{.spec.template.spec.containers[0].image}' # registry.gitlab.com/gitlab-org/build/cng/gitlab-webservice-ee:v19.4.1 -
Build an image with the changed files on top of it (from the
gitlabcheckout):BASE=registry.gitlab.com/gitlab-org/build/cng/gitlab-webservice-ee:v19.4.1 CTX=$(mktemp -d) git ls-files -z -- \ app/graphql/types/custom_emoji_type.rb \ app/graphql/types/permission_types/custom_emoji.rb \ config/authz/permissions/custom_emoji/read.yml \ config/authz/permission_groups/assignable_permissions/groups/custom_emoji/read.yml \ | xargs -0 cp --parents -t "$CTX" printf 'FROM %s\nCOPY --chown=git:git . /srv/gitlab/\n' "$BASE" \ | docker build -t "$BASE-emoji" -f - "$CTX" -
Load it into the cluster:
docker save "$BASE-emoji" | caproni image load -
In
caproni.local.yaml, comment outedit_reloaders(in production mode, plaincaproni up, no reloader may be in edit mode) and add the following. The tag must not belatest: the chart usesimagePullPolicy: IfNotPresent, so a loaded non-latesttag is used without pulling.deployers: gitlab: helm: dynamic_overrides: - type: inline value_map: gitlab.webservice.image.tag: v19.4.1-emoji -
Deploy and check that the new image is running:
caproni up caproni kubectl -n gitlab get deploy gitlab-webservice-default \ -o jsonpath='{.spec.template.spec.containers[0].image}' # registry.gitlab.com/gitlab-org/build/cng/gitlab-webservice-ee:v19.4.1-emoji -
In http://gitlab.caproni.test, create a classic PAT and a fine-grained PAT scoped to the group with
Group: ReadandCustom Emoji: Read. Then run the example above against http://gitlab.caproni.test instead ofhttps://gitlab.example.com. -
To undo, remove the override, restore
edit_reloadersincaproni.local.yaml, and runcaproni up.
Alternatively, edit mode (caproni run) runs the local checkout directly and needs no image. It uses the gitlabhq_development database while the cluster pods use gitlabhq_production, so tokens created in one mode do not exist in the other.
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.

