Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions ci/migrate-scripts/assert-editable-roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,36 @@
call_command("migrate", verbosity=0)
assert not role.permissions.exists()
assert team.roles.filter(pk=role_id).exists()

users = Group.objects.get(name="Users", internal=True)
assert users.roles.filter(pk=custom_id).exists()
assert users.roles.filter(name="Power user").exists()
assert users.roles.filter(name="Discover users").exists()
assert users.roles.filter(name="View user profiles").exists()
assert (
Group.objects.get(name="Guests", internal=True)
.roles.filter(name="View user profiles")
.exists()
)
assert set(
Role.objects.get(name="Discover users").permissions.values_list(
"codename", flat=True
)
) == {"user.browse", "user.search"}
assert set(
Role.objects.get(name="View user profiles").permissions.values_list(
"codename", flat=True
)
) == {"user.profile"}


for collision_team in Group.objects.filter(name__startswith="Visibility collision "):
role_id = int(collision_team.name.rsplit(" ", 1)[1])
collision = collision_team.roles.get(pk=role_id)
assert collision.name in {
"Discover users (custom 2)",
"View user profiles (custom 2)",
}
assert list(collision.permissions.values_list("codename", flat=True)) == [
"comment.add"
]
12 changes: 12 additions & 0 deletions ci/migrate-scripts/setup-editable-roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@
# Persist original primary keys across the checkout to verify identity preservation.
team.name = f"Role migration assignment {role.pk}:{custom.pk}"
team.save()

# Preserve a local default-team role assignment through visibility upgrades.
users = Group.objects.get(name="Users", internal=True)
users.roles.add(custom)


for name in ("Discover users", "View user profiles"):
collision = Role.objects.create(name=name)
collision.permissions.set(Permission.objects.filter(codename="comment.add"))
Role.objects.create(name=f"{name} (custom)")
collision_team = Group.objects.create(name=f"Visibility collision {collision.pk}")
collision_team.roles.add(collision)
11 changes: 9 additions & 2 deletions docs/admin/access.rst
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@
control <acl>` to utilize it. However you must have superuser privileges in
order to use it.

If you are not interested in details of implementation, and just want to create a

Check failure on line 365 in docs/admin/access.rst

View workflow job for this annotation

GitHub Actions / Linkcheck

https://hosted.weblate.org/: 403 Client Error: Forbidden for url: https://hosted.weblate.org/
simple-enough configuration based on the defaults, or don’t have a site-wide access
to the whole Weblate installation (like on `Hosted Weblate <https://hosted.weblate.org/>`_),
please refer to the :ref:`manage-acl` section.
Expand Down Expand Up @@ -709,6 +709,11 @@
the current defaults. Earlier changes made through the API or database are
replaced. Subsequent upgrades preserve local customizations.

Existing custom roles named `Discover users` or `View user profiles` are
renamed with a ``(custom)`` suffix before the new built-in roles are created.
A numbered suffix is used if that name is already taken. Their permissions
and team assignments are preserved.

.. _privileges:

.. include:: /snippets/permissions.rst
Expand Down Expand Up @@ -748,7 +753,7 @@
Remove roles from this team to limit permissions for
non-authenticated users.

Default roles: `Add suggestion`, `Access repository`
Default roles: `Add suggestion`, `Access repository`, `View user profiles`

`Viewers`
This role ensures the visibility of public projects to all users.
Expand All @@ -765,7 +770,7 @@
By default, :ref:`automatic team assignment <autoteam>` makes all new
accounts members of this team when they join.

Default roles: `Power user`
Default roles: `Power user`, `Discover users`, `View user profiles`

`Reviewers`
Group for reviewers (see :ref:`workflows`).
Expand Down Expand Up @@ -815,3 +820,5 @@
Each account can have an expiry set. After the expiration, the account will be
automatically disabled. This is used for :ref:`project-api`, but can be
utilized for regular users as well.

See :doc:`privacy` to configure user discovery and profile access through roles.
70 changes: 2 additions & 68 deletions docs/admin/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1821,6 +1821,8 @@ PRIVATE_COMMIT_EMAIL_OPT_IN

Configures whether the private commit e-mail is opt-in or opt-out.

See :doc:`/admin/privacy` for privacy configuration examples.

With ``True`` (the default), users use their account e-mail unless they choose a
private commit e-mail. Set to ``False`` to use
:setting:`PRIVATE_COMMIT_EMAIL_TEMPLATE` by default.
Expand Down Expand Up @@ -2984,74 +2986,6 @@ When using a URL prefix, expose Weblate's prefixed ``robots.txt`` at the origin'
include the configured prefix. If other applications share the origin, merge
their crawl rules in the proxy-served document as appropriate.

.. setting:: USER_DIRECTORY_VISIBILITY

USER_DIRECTORY_VISIBILITY
-------------------------

.. versionadded:: 2026.11

Controls discovery of other users. Defaults to ``"all"`` for compatibility.

``"all"``
Keeps user browsing, username autocomplete, and site-wide user search enabled.
``"scoped"``
Requires a resource-specific contributor search, such as
``contributes:my-project`` or ``contributes:my-project/my-component``, on the
user directory page and REST API. Every matching branch of a search must
include a positive resource scope. Generic names, language filters, and
negated scopes do not grant directory access.
``"disabled"``
Disables the user directory page. Resource-scoped REST searches and
autocomplete remain available.

In both restricted modes, searches return contributors only within accessible
resources. Translation editor mentions use the current string's translation as
scope. Generic site-wide searches do not return users. The REST user list still
returns the caller's own details when no search is supplied.

Users with the global ``user.view`` or ``user.edit`` permission retain directory
and administrative access in every mode.

This setting controls discovery independently of
:setting:`USER_PROFILE_VISIBILITY`. Contributor identities can appear in scoped
results even when their profiles are restricted. It does not anonymize
contributors or change attribution in translation history.

In Docker, use :envvar:`WEBLATE_USER_DIRECTORY_VISIBILITY`.

.. setting:: USER_PROFILE_VISIBILITY

USER_PROFILE_VISIBILITY
-----------------------

.. versionadded:: 2026.11

Controls access to user profiles. Defaults to ``"public"`` for compatibility.

``"public"``
Keeps existing public profile access, subject to installation login rules.
``"authenticated"``
Requires signing in to view another user's profile.
``"admins"``
Requires the global ``user.view`` or ``user.edit`` permission to view another
user's profile.

Users can always access their own profile, and users with either global
permission retain administrative access. The policy applies to direct profile
URLs, contribution and suggestion pages, external profile links, contact links,
avatars, and REST user details. Restricted URLs return HTTP 404.
Contributor displays retain names but use a generic avatar and omit profile
links when access is denied. Email and feed displays use public-access rules.

Combine this with :setting:`USER_DIRECTORY_VISIBILITY` to control discovery.
For example, ``"scoped"`` directory visibility with ``"public"`` profile
visibility preserves public contributor profiles while limiting discovery to
accessible resources. Basic contributor identities remain visible in permitted
search results regardless of profile visibility.

In Docker, use :envvar:`WEBLATE_USER_PROFILE_VISIBILITY`.

.. setting:: VCS_ALLOW_HOSTS

VCS_ALLOW_HOSTS
Expand Down
14 changes: 2 additions & 12 deletions docs/admin/install/docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -732,18 +732,6 @@ Generic settings

Configures :setting:`ADMINS_CONTACT`.

.. envvar:: WEBLATE_USER_DIRECTORY_VISIBILITY

Controls user discovery: ``all`` (default), ``scoped``, or ``disabled``.
Resource-scoped API searches and autocomplete remain available in both
restricted modes. See :setting:`USER_DIRECTORY_VISIBILITY`.

.. envvar:: WEBLATE_USER_PROFILE_VISIBILITY

Controls profile access: ``public`` (default), ``authenticated``, or ``admins``.
Self access and administrative access remain available. This is independent
of directory visibility. See :setting:`USER_PROFILE_VISIBILITY`.

.. envvar:: WEBLATE_CONTACT_FORM

Configures contact form behavior, see :setting:`CONTACT_FORM`.
Expand Down Expand Up @@ -1435,6 +1423,8 @@ Generic settings

Configures :setting:`PRIVATE_COMMIT_NAME_OPT_IN`.

See :doc:`/admin/privacy` for privacy configuration examples.

To use private commit e-mails and names by default, set both opt-in variables
to ``False`` in :file:`docker-compose.override.yml`:

Expand Down
108 changes: 108 additions & 0 deletions docs/admin/privacy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
.. _privacy-configuration:

Privacy configuration
=====================

Administrators can limit user discovery and profile access through
:doc:`access`, and make private names and e-mail addresses the default for
version control commits. These controls address different kinds of exposure.

User discovery and profile access
---------------------------------

The following global permissions can be assigned through editable roles:

``user.browse``
Browse and search the user directory without a resource scope.
``user.search``
Search contributors through the user directory within accessible resources.
Every matching Boolean branch must contain a positive scope, such as
``contributes:my-project`` or ``contributes:my-project/my-component``.
A negated scope or a scope on only one side of an OR is insufficient.
``user.profile``
Access other users' profiles, including contribution and suggestion pages,
contact and external profile links, avatars, and REST user details.

By default, the :guilabel:`Discover users` role grants ``user.browse`` and
``user.search``, and the :guilabel:`View user profiles` role grants ``user.profile``.
The :guilabel:`Users` team has both roles. The :guilabel:`Guests` team has
:guilabel:`View user profiles`, preserving public profile access subject to
installation login requirements.

To restrict profiles to authenticated users, remove :guilabel:`View user profiles` from
:guilabel:`Guests`. To restrict profiles to administrators and each user's own profile,
also remove it from :guilabel:`Users` and any other teams granting ``user.profile``.
Restricted profile URLs return HTTP 404. Contributor displays retain names,
but omit profile links and use generic avatars when profile access is denied.
Email and feed displays follow the profile permissions available to guests.

To require scoped HTML directory searches, remove :guilabel:`Discover users` from
:guilabel:`Users` and assign a custom role containing only ``user.search``.
To disable HTML directory access, grant neither ``user.browse`` nor
``user.search``. Generic site-wide search requires ``user.browse``.

Permissions from roles are additive. Check all team assignments when restricting
access: removing one role does not cancel a grant from another. Users with
``user.view`` or ``user.edit`` retain administrative directory and profile
access. Users always retain access to their own profiles.

Editing the :guilabel:`Discover users` role changes its permissions for every
team assigned that role. Removing the role from :guilabel:`Users` only removes
that team's grant; other teams using the role keep their permissions. To customize
discovery for one team, create a separate role and assign it to that team.

Resource-scoped REST discovery and mention autocomplete remain available to
authenticated users with access to the resource, even without ``user.search``.
These results can include basic contributor identities without granting profile
access. See :doc:`/api`.

Private version control identities
----------------------------------

To use private commit identities by default, configure:

.. code-block:: python

PRIVATE_COMMIT_EMAIL_OPT_IN = False
PRIVATE_COMMIT_NAME_OPT_IN = False
PRIVATE_COMMIT_EMAIL_TEMPLATE = "user-{user_id}@users.noreply.{site_domain}"
PRIVATE_COMMIT_NAME_TEMPLATE = "{site_title} user {user_id}"

For Docker installations, use the corresponding environment variables:

.. code-block:: yaml

environment:
WEBLATE_PRIVATE_COMMIT_EMAIL_OPT_IN: "False"
WEBLATE_PRIVATE_COMMIT_NAME_OPT_IN: "False"
WEBLATE_PRIVATE_COMMIT_EMAIL_TEMPLATE: "user-{user_id}@users.noreply.{site_domain}"
WEBLATE_PRIVATE_COMMIT_NAME_TEMPLATE: "{site_title} user {user_id}"

Both opt-in settings default to ``True``. Changing a template alone does not
enable private identities by default. The usual private e-mail template includes
the username; the example above uses an internal user identifier instead.

These defaults apply to new and existing users who have not explicitly chosen
commit identities. Users' explicit choices override the defaults, and users can
still choose their account identities in :ref:`profile-account`. Bots retain
their account identities. A blank template disables the corresponding private
identity.

See :setting:`PRIVATE_COMMIT_EMAIL_OPT_IN`,
:setting:`PRIVATE_COMMIT_NAME_OPT_IN`,
:setting:`PRIVATE_COMMIT_EMAIL_TEMPLATE`, and
:setting:`PRIVATE_COMMIT_NAME_TEMPLATE` for the full settings and template
placeholders.

Limits of these controls
------------------------

Private identities affect future commits. Names and e-mail addresses already
recorded in repository history remain unchanged. Different commit identities
can prevent code hosting services from linking contributions to a user's account.

User discovery and profile permissions do not hide contributor attribution or
translation history. Private VCS identities do not anonymize contributors within
Weblate. Resource-scoped discovery can still identify contributors to accessible
resources. These controls do not provide confidential collaboration or
contributor anonymity.
13 changes: 5 additions & 8 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,10 @@ Users
username, and full name of matching non-bot users. Users with either
permission can list all users and receive detailed information.

:setting:`USER_DIRECTORY_VISIBILITY` can restrict discovery to accessible
resources. In ``scoped`` and ``disabled`` modes, ordinary users must supply
a resource-bounded ``q`` query or an accessible ``unit`` context to discover
other users. Generic filters cannot expand that scope; blocked searches
return no results. Self access and global administrative access remain
available. Basic contributor discovery is independent of
:setting:`USER_PROFILE_VISIBILITY`.
Without ``user.browse``, discovery requires a resource-bounded ``q`` query
or accessible ``unit`` context. Generic filters cannot expand that scope.
Scoped REST discovery does not require ``user.search`` or ``user.profile``.
Self and administrative access remain available. See :doc:`admin/privacy`.

:query string q: User search query, such as ``contributes:project`` or ``contributes:project/component``. Every matching Boolean branch must have a positive resource scope in restricted directory modes.
:query int unit: Rank contributors to this string first. In restricted directory modes, results are limited to contributors to its translation.
Expand Down Expand Up @@ -370,7 +367,7 @@ Users

.. http:get:: /api/users/(str:username)/

Access follows :setting:`USER_PROFILE_VISIBILITY`. Restricted profiles
Access follows the global ``user.profile`` permission. Restricted profiles
return HTTP 404; self access and global administrative access are retained.

Returns information about users.
Expand Down
2 changes: 1 addition & 1 deletion docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Weblate 2026.11
* Added a :guilabel:`Languages` tab to :ref:`workspaces`, with translation and review across projects in a selected language.

* Unified :ref:`bulk-edit` with search and replace, adding regular expressions and clearing translations in manual runs, Automations, and the bulk-edit add-on. Manual bulk edits offer optional previews.
* Added configurable :setting:`USER_DIRECTORY_VISIBILITY` and :setting:`USER_PROFILE_VISIBILITY` to restrict user discovery and profile access while preserving resource-scoped contributor discovery.
* Added role permissions for user discovery and profile access and a :doc:`privacy configuration guide <admin/privacy>`.

* Translation service installations have a :ref:`Logs tab <machinery-error-log>` showing provider errors, including rate limits.

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Learn more about :ref:`contributing`.
admin/backup
admin/auth
admin/access
admin/privacy
admin/workspaces
admin/projects
admin/languages
Expand Down
5 changes: 2 additions & 3 deletions docs/security/threat-model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,8 @@ Security properties Weblate provides
- Conditions
- Violation symptom
- Severity tier
* - User discovery and profile access respect installation visibility
policies. *(documented)* (source: :setting:`USER_DIRECTORY_VISIBILITY`,
:setting:`USER_PROFILE_VISIBILITY`)
* - User discovery and profile access respect global role permissions.
*(documented)* (source: :doc:`/admin/privacy`)
- Operators configure the desired policy. Resource-scoped discovery and
contribution attribution remain available; this does not provide
contributor anonymity. *(documented)*
Expand Down
8 changes: 7 additions & 1 deletion docs/snippets/permissions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,13 @@ List of privileges
| +-------------------------------------------+---------------------------------------+
| | Manage workspace access | :guilabel:`Workspace administration` |
+----------------------+-------------------------------------------+---------------------------------------+
| Site wide privileges | Use management interface | |
| Site wide privileges | Browse users | :guilabel:`Discover users` |
| +-------------------------------------------+---------------------------------------+
| | Search resource contributors | :guilabel:`Discover users` |
| +-------------------------------------------+---------------------------------------+
| | View user profiles | :guilabel:`View user profiles` |
| +-------------------------------------------+---------------------------------------+
| | Use management interface | |
| +-------------------------------------------+---------------------------------------+
| | Manage site configuration | |
| +-------------------------------------------+---------------------------------------+
Expand Down
7 changes: 7 additions & 0 deletions docs/snippets/roles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ List of built-in roles

.. list-table::

* - `Discover users`
- * :guilabel:`Browse users`
* :guilabel:`Search resource contributors`

* - `View user profiles`
- * :guilabel:`View user profiles`

* - `Administration`
- * :guilabel:`Post announcements`
* :guilabel:`Delete announcements`
Expand Down
Loading
Loading