Project access and roles
Decide who can see each project, and who can send results or act on its runs, from the web app. Projects' owners manage their members in Project → Access; you set the defaults and the groups. Users see the same page in the user guide.
Nothing changes when you upgrade
The defaults keep how QA-Board worked before roles:
- Projects are public: anyone who can open QA-Board sees them (
projects.default_visibility). - Everyone signed in is a developer of public and internal projects: they can send results, redo, stop or
delete runs (
projects.signed_in_role). - Projects in
QABOARD_LOGIN_RESTRICTED_YAMLstay restricted: they're private, and the users its rules match are their developers.
Give projects owners, then tighten
- Make owners. Open a project's Access tab, add its owners (you're an owner of every project as an admin).
Owners of
groupown every project under it, e.g.group/repoandgroup/repo/subproject. - Owners add their teams: users or groups, as viewers, developers or owners, optionally until a date.
- Set
projects.signed_in_roletoviewerin Admin → Settings: signed-in users still see public and internal projects, and only members act on them. The admin overview says how many projects have owners. - Make sensitive projects private: only their members see them, their runs, and their files.
| Role | Can |
|---|---|
| Viewer | see the project, its runs, metrics and files |
| Developer | also send results (qa run, qa batch), redo, stop, rename or delete runs, start tuning, export, save milestones |
| Owner | also choose the visibility, add and remove members, and decide access requests |
Admins are owners of every project.
How QA-Board decides
A user's role on a/b/c is the highest of:
- their grants, and their groups' grants, on
a/b/c,a/b,a, or all projects (Admin → Groups → Roles on all projects). Expired grants don't count; - what the visibility gives: viewer for anyone on public projects,
projects.signed_in_rolefor signed-in users on public and internal ones, nothing on private ones; - the YAML file's rules, for its projects (developer);
- owner, for admins.
Visibility is inherited: a project's effective visibility is the most restrictive of its own and its prefixes'. A
project can be more private than its group, never more public. Without any, projects.default_visibility applies.
Tokens never have more than their user, their scopes (read, ingest, act) and their projects allow.
Groups
Create groups in Admin → Groups, then grant them roles on projects. Add members by hand, or with rules that add users from their groups in your directory or identity provider:
| Claim | Signs in with | Example values |
|---|---|---|
groups | single sign-on (OpenID Connect), if your IdP sends it | camera-team |
realm_access.roles | Keycloak's realm roles (nested claims are written with dots) | camera |
memberOf | LDAP (or the attributes in QABOARD_LDAP_GROUP_ATTRIBUTES), SAML attributes | CN=camera-algo,OU=Groups,DC=example,DC=com, or just camera-algo |
email_domain | LDAP, SAML, single sign-on if your IdP says the email is verified (email_verified); not local accounts | example.com |
Values match case-insensitively; an LDAP group matches by its full DN or its name. Members you add by hand stay.
To write rules, look at Admin → Audit: auth.login events list the groups each sign-in put users in, and for
single sign-on the groups the identity provider sent.
When rules apply: when users sign in, when you add or delete a rule (for everyone, in a minute), and every day,
with the Users' groups schedule the jobs worker creates (Admin → Storage → Schedules, QABOARD_GROUPS_SYNC_AUTO=0
to manage it yourself). Update now in Admin → Groups runs it at once.
A rule for one sign-in method reads what it last said, a rule for any reads them all: users who sign in with LDAP
one day and single sign-on the next keep the groups both give.
Groups from LDAP
Every day, QA-Board looks up each LDAP account with the service account (QABOARD_LDAP_BIND_DN), one query per user
on one connection, and reads memberOf (or QABOARD_LDAP_GROUP_ATTRIBUTES). Users who leave a group in the directory
leave the QA-Board group the next day, without signing in again. Accounts the directory doesn't know anymore lose
their directory groups. When it doesn't find over 20% of the accounts (a wrong base, an outage), nothing changes and
the job reports an error.
Single sign-on accounts matched to LDAP accounts (QABOARD_OIDC_MATCH_USERS=username) are LDAP accounts: their
directory groups are kept up to date too.
memberOf only lists the groups users are directly in. With Active Directory, set QABOARD_LDAP_NESTED_GROUPS=true
to add the groups of their groups: it costs one more query per user.
Groups from single sign-on
Identity providers send the groups when users sign in. To keep them up to date between sign-ins, enable Refresh
groups daily on the provider (QABOARD_OIDC_REFRESH_GROUPS=true for the environment's): QA-Board asks for
offline_access, keeps each user's refresh token encrypted (it needs QABOARD_ENCRYPTION_KEY), and reads their claims
again every day. When the provider refuses a refresh token, e.g. the user was disabled, the groups it gave end, and
the user's QA-Board sessions from that provider too: signing in again gives the groups back.
- Keycloak: add a Group Membership mapper to the client (
groups), or use realm roles (realm_access.roles). Offline tokens need theoffline_accessrole, which users have by default. - Microsoft Entra ID: in the app registration's Token configuration, add a groups claim, emitted as
sAMAccountNamefor groups synced from Active Directory (else they're ids). Users in over 200 groups get none (Entra's "overage"): choose Groups assigned to the application. QA-Board warns about it in the audit log (groups_overage). - Okta: add a
groupsclaim to the ID token, with a filter.
Access requests
Users without the role they need get a clear refusal: in the web app with the project's owners and a Request
access button, in qa with whom to ask. Owners get an email (when QA-Board sends emails),
and approve or deny in the project's Access tab; the requester gets an email.
What else follows roles
- Results sent by
qa: a signed-in sender (or a token) needs the developer role. Withingest.authset totoken, others are refused; withofforwarn, their results are saved as unverified, like anonymous ones. Anonymous results are still accepted withoffandwarn, also on private projects: setingest.authtotokento stop them (results sent by qa). - Files under
/s/: the outputs and artifacts folders of a project follow its roles, when nginx checks who can read files (restricting who can read files). Sub-projects share their repository's folders: reading them needs access to every non-public project stored there. - Milestones: saving or editing shared ones needs the developer role; favorites only need to see the project.
The YAML file
QABOARD_LOGIN_RESTRICTED_YAML keeps working. Its projects: become private projects whose matching users are
developers, on top of their grants. Its login: rules still decide who can sign in, and paths: which folders
users can read. Move its projects to grants when owners are in place, then remove them from the file.
Settings and API
| Setting | Environment variable | Default | What it does |
|---|---|---|---|
projects.default_visibility | QABOARD_DEFAULT_VISIBILITY | public | The visibility of projects without one: public, internal, private |
projects.signed_in_role | QABOARD_SIGNED_IN_ROLE | developer | The role of signed-in users on public and internal projects: viewer or developer |
The web app uses /api/v1/project/access (visibility, grants, requests), /api/v1/directory (the member picker)
and /api/v1/admin/groups, /api/v1/admin/grants. Scripts can call them with a token of an owner (the act
scope), or an admin (admin for the admin API).