Skip to main content

User Management

QA-Board offers a user management system. It can be used to block access to anonymous users, or restrict executing code (via Tuning) to logged-in users.

Authentication Providers​

  • LOCAL: an internal users list, created by qaboard admins.
  • LDAP
  • SAML: SSO via SAML

Users sign in from the Login button at the top right of the web app. With LOCAL and LDAP, it opens a form; with SAML, it redirects them to your identity provider.

The Login dialog over the projects list: a user name field with "ines", a password field, and a Log In button.
With local or LDAP accounts, users sign in with their user name and password.

To register a new LOCAL user, either:

  • add an entry to the database under users table.
  • use the signup endpoint (signup() in backend/backend/api/auth.py). It is enabled unless QABOARD_DISABLE_SIGNUP=True:
curl -d "user_name=<user_name>&password=<password>&email=<user_email>&full_name=<user_full_name>" -X POST '<qaboard_url>/api/v1/user/signup/'
tip

This flow can be improved. So far all instances of QA-Board rely on external authentication providers, so it hasn't been a priority. We would welcome merge requests to add a UX flow to register new users.

Environment variables​

tip

To apply changes the backend server needs to be restarted.

note

With docker compose, set them in services.backend.environment in your site overlay (see deployments/example.yml). Only some of them (QABOARD_LOGIN_TYPE, QABOARD_LOGIN_REQUIRED, QABOARD_LOGIN_RESTRICTED, QABOARD_LOGIN_RESTRICTED_YAML) are forwarded from an .env file by docker-compose.yml.

The sign-in policy is set via environment variables such as QABOARD_LOGIN_*, QABOARD_LDAP_*, QABOARD_SAML_*, as described below:

ENV VariableDefaultUsage
QABOARD_LOGIN_TYPELOCALSet to LOCAL/LDAP/SAML
QABOARD_LOGIN_REQUIREDemptyAny non-empty value (e.g. true) blocks anonymous users. Leave it unset/empty to allow them: even false enables it
QABOARD_DISABLE_SIGNUPemptySet to True (exactly) to disable the signup endpoint for LOCAL users
QABOARD_LOGIN_RESTRICTEDemptyAny non-empty value (e.g. true) uses a configuration file to allow only specific users
QABOARD_LOGIN_RESTRICTED_YAMLnoneThe path to the users configuration file
QABOARD_LDAP_HOSTnoneServer hostname (including port)
QABOARD_LDAP_PORT389Server port, usually 389 (or 636 if SSL is used / not supported yet!).
QABOARD_LDAP_USER_BASEnoneSearch base for users.
QABOARD_LDAP_BIND_DNnoneThe Distinguished Name to bind as, this user will be used to lookup information about other users.
QABOARD_LDAP_PASSWORDnoneThe password to bind with for the lookup user.
QABOARD_LDAP_USER_FILTERnoneUser lookup filter, the placeholder {login} will be replaced by the user supplied login. (e.g. (&(objectClass=inetOrgPerson)(|(uid={login})(mail={login}))), or (&(objectClass=user)(|(sAMAccountName={login}))))
QABOARD_LDAP_ATTRIBUTE_EMAILmail
QABOARD_LDAP_ATTRIBUTE_COMMON_NAMEcn
QABOARD_SAML_DIRnoneThe path to the directory with the SAML configuration files, as in the python-saml docs
QABOARD_SAML_ATTRIBUTE_EMAILnone
QABOARD_SAML_ATTRIBUTE_COMMON_NAMEnone
QABOARD_SAML_ATTRIBUTE_USER_NAMEnone
QABOARD_SAML_ATTRIBUTE_IDnone

Blocking anonymous users​

To block anonymous users, who are not logged-in, from accessing any content on QA-Board, set the environment variable QABOARD_LOGIN_REQUIRED=true (any non-empty value) and restart the backend. The webapp reads it from the backend: no frontend rebuild is needed.

Image viewer

Restricting sign-in​

To allow only specific users to sign-in, set the environment variables:

  • QABOARD_LOGIN_RESTRICTED=true
  • QABOARD_LOGIN_RESTRICTED_YAML=path/to/users_restrict.yml

The YAML file specifies multiple cases where users will be accepted. It can be based on the user name, email, or any parameter from the authentication provider (or users table in the database).
Example:

users_restrict.yml
login:
# only users with one of those emails will be able to login
email:
- mr.nobody@example.com
- user3@example.com

# those users will also be allowed to login
user_name:
- john.doe
- jane.doe

# here we can use SAML/LDAP attributes to restrict based on the rank, job position...
data:
http://sso.company.com/2023/11/CompId:
- C123
- C777
http://sso.company.com/2023/11/GrdName:
- Staff
- Team Leader

Managing Projects​

If you need to restrict a project so that only specific users have access, you can configure this in the configuration file.
Example:

users_restrict.yml
projects:
# restricting a namespace
my_namespace:
# only users with one of those emails will be able to access projects that are under the namespace "my_namespace", but not to "my_namespace/proj1"
email:
- mr.nobody@example.com
- user3@example.com

# those users will also be allowed to access projects that are under the namespace "my_namespace", but not to "my_namespace/proj1"
user_name:
- john.doe
- jane.doe

# here we can use SAML/LDAP attributes to restrict based on the rank, job position...
data:
http://sso.company.com/2023/11/CompId:
- C123
- C777
http://sso.company.com/2023/11/GrdName:
- Staff
- Team Leader

# restricting a project
my_namespace/proj1:
# only users with one of those emails will be able to access proj1
email:
- mr.nobody@example.com

# those users will also be allowed to access proj1
user_name:
- john.doe

If both a 'namespace' and a 'project' are set, the project will take priority. For example for the following "namespace/project", you can restrict the specific project to a certain group and restrict the namespace to a larger group.

A full configuration file that has both 'login' and 'project' restrictions will look like this:

users_restrict.yml
login:
# only users with one of those emails will be able to login
email:
- mr.nobody@example.com
- user3@example.com

# those users will also be allowed to login
user_name:
- john.doe
- jane.doe

# here we can use SAML/LDAP attributes to restrict based on the rank, job position...
data:
http://sso.company.com/2023/11/CompId:
- C123
- C777
http://sso.company.com/2023/11/GrdName:
- Staff
- Team Leader

projects:
# restricting a namespace
my_namespace:
# only users with one of those emails will be able to access projects that are under the namespace "my_namespace", but not to "my_namespace/proj1"
email:
- mr.nobody@example.com
- user3@example.com

# those users will also be allowed to access projects that are under the namespace "my_namespace", but not to "my_namespace/proj1"
user_name:
- john.doe
- jane.doe

# here we can use SAML/LDAP attributes to restrict based on the rank, job position...
data:
http://sso.company.com/2023/11/CompId:
- C123
- C777
http://sso.company.com/2023/11/GrdName:
- Staff
- Team Leader

# restricting a project
my_namespace/proj1:
# only users with one of those emails will be able to access proj1
email:
- mr.nobody@example.com

# those users will also be allowed to access proj1
user_name:
- john.doe

Restricting who can read files​

nginx serves the output and artifacts files under /s/, and by default anyone who can reach the server can read them. You can restrict them in two ways, and combine them:

  • Restricted projects: with QABOARD_LOGIN_RESTRICTED, only the users allowed to see a project read its files.
  • Unix permissions: in some folders, like /home, users only read what their own Unix account can read.

In both cases, nginx must ask the backend before sending files: add include snippets/qaboard-files-auth.conf; to all the /s/ locations of your nginx configuration (with the helm chart, set proxy.restrictFiles: true). Files can be reached from several locations through symlinks: the backend checks where links lead, but only for the locations that ask it.

In the file browser, users who are not signed in see Sign in to see these files, others You don't have access. Scripts can authenticate with an API token: curl -H "Authorization: Bearer <token>" https://qaboard/s/...

Restricted projects​

The projects restricted in users_restrict.yml also restrict their files: the outputs and artifacts folders from the projects' storage in qaboard.yaml. If needed, you can also restrict other folders under paths, with the same rules as projects:

users_restrict.yml
paths:
/mnt/qaboard/confidential:
user_name:
- john.doe
# The most specific folder wins. With no rules, anyone can read it
/mnt/qaboard/confidential/public: {}

Unix permissions​

Set QABOARD_FILES_UNIX_PERMISSIONS to the folders where it applies, separated by : (e.g. /home:/mnt/users). There, users read a file only if their Unix account could: it must be allowed to read the file, and to traverse the folders above it. Users who are not signed in only read what everyone can read.

  • The backend finds users' accounts by their QA-Board user name: see Users' accounts. Users without an account get the permissions of "others".
  • ACLs are ignored.

Users' accounts​

The file browser shows who owns each file, and Unix permissions need users' groups. The backend reads the accounts in its own /etc/passwd and /etc/group. If you have complete files, mount them.

With a directory service (LDAP, SSSD, NIS...), you usually don't: the backend can instead run a command that prints the accounts, in the /etc/passwd format, and another one for the groups (/etc/group format). For instance, through ssh to a host that sees your users:

QABOARD_ACCOUNTS_COMMAND=ssh qaboard@some-host getent passwd
QABOARD_GROUPS_COMMAND=ssh qaboard@some-host getent group
  • The backend runs them in the background when their last output is older than QABOARD_ACCOUNTS_REFRESH seconds (default: a day), and keeps it in $QABOARD_DATA_SHARED_DIR/accounts: one run serves all the backend's workers and replicas. To refresh now, delete that folder's passwd.
  • If a command fails, or prints less than half of the accounts it printed last time, the backend keeps the previous accounts, and tries again 10 minutes later. Errors show in its logs ([accounts]).
  • Only lines in the right format count: ssh banners (sent to stderr) and messages printed by shell startup files are ignored.
  • Accounts the commands don't know are looked up in /etc/passwd and /etc/group.
  • With SSSD, getent passwd alone only lists all users if enumeration is enabled (enumerate = true), which is often not the case. Check the number of lines, or use a command that queries the directory itself, e.g. ldapsearch, formatted as name:x:uid:gid:gecos:home:shell.
  • The command runs with the backend's environment, e.g. the ssh key used for the LSF bridge, and its own user's permissions.
Limits
  • nginx asks the backend before sending each file. It answers quickly, without database queries for public files, but if the backend is down, nginx doesn't serve files.
  • Sub-projects share the storage of their git repository: restricting a sub-project restricts the files of the whole repository.
  • With a folder per user in the storage (e.g. outputs: /mnt/qaboard/outputs/{user}), the project's folder is restricted in every user's folder.
  • Results of restricted projects saved outside of their storage (e.g. --share to another folder) are not restricted, unless you list their folders under paths. The backend reads users_restrict.yml when it starts, and the projects' folders every 5 minutes.
  • The image server (IIIF) and the API endpoints that read images don't check permissions. Their paths are hard to guess (they contain commit hashes), but whoever knows one can see its images.
  • WebDAV (services/nginx/webdav.conf) doesn't check them either: it's not enabled by default.