Fossil

Administering User Capabilities
Login

Fossil includes a powerful role-based access control system which affects which users have which capabilities within a given served Fossil repository. We call this the capability system, or “caps” for short.

Fossil stores a user’s capabilities as an unordered string of ASCII characters, one capability per, limited to alphanumerics. Caps are case-sensitive: “A” and “a” are different capabilities. We explain how we came to assign each character below.

User Categories

Before we explain individual user capabilities and their proper administration, we want to talk about an oft-overlooked and misunderstood feature of Fossil: user categories.

Fossil defines four user categories. Two of these apply based on the user’s login status: nobody and anonymous. The other two act like Unix or LDAP user groups: reader and developer. Because we use the word “group” for another purpose in Fossil, we will avoid using it that way again in this document. The correct term in Fossil is “category.”

Fossil user categories give you a way to define capability sets for four hard-coded situations within the Fossil C source code. Logically speaking:

(developerreader)anonymousnobody

When a user visits a served Fossil repository via its web UI, they initially get the capabilities of the “nobody” user category. This category would be better named “everybody” because it applies whether you’re logged in or not.

When a user logs in as “anonymous” via /login they get all of the “nobody” category’s caps plus those assigned to the “anonymous” user category. It would be better named “user” because it affects all logged-in users, not just those logged in via Fossil’s anonymous user feature.

When a user with either the “reader” (u) or “developer” (v) capability letter logs in, they get their individual user caps plus those assigned to this special user category. They also get those assigned to the “anonymous” and “nobody” categories.

Because “developer” users do not automatically inherit “reader” caps, it is standard practice to give both letters to your “developer” users: uv.

Fossil shows how these capabilities apply hierarchically in the user editing screen (Admin → Users → name) with the [N] [A] [D] [R] tags next to each capability check box. If a user gets a capability from one of the user categories already assigned to it, there is no value in redundantly assigning that same cap to the user explicitly. For example, with the default dei cap set for the “developer” category, the cap set ve is redundant because v grants dei, which includes e.

We suggest that you lean heavily on these fixed user categories when setting up new users. Ideally, your users will group neatly into one of the predefined categories, but if not, you might be able to shoehorn them into our fixed scheme. For example, the administrator of a repo that’s mainly used as a wiki or forum for non-developers could treat the “developer” user category as if it were called “author”.

There is currently no way to define custom user categories.

Individual User Capabilities

When one or more users need to be different from the basic capabilities defined in user categories, you can assign caps to individual users. For the most part, you want to simply read the reference material below when doing such work.

However, it is useful at this time to expand on the logical expression above, which covered only the four fixed user categories. When we bring the individual user capabilities into it, the complete expression of the way Fossil implements user power becomes:

setupadminmoderator(developerreader)[subscriber]anonymousnobody

The two additions at the top are clear: setup is all-powerful, and admin users are subordinate to the setup user(s). Both are superior to all other users.

The moderator insertion could go anywhere from where it’s shown now down to above the “anonymous” level, depending on what other caps you give to your moderators. Also, there is not just one type of moderator: Fossil has wiki, ticket, and forum moderators, each independent of the others. Usually your moderators are fairly high-status users, with developer capabilities or higher, but Fossil does allow the creation of low-status moderators.

The placement of “subscriber” in that hierarchy is for the sort of subscriber who has registered an account on the repository purely to receive email alerts and announcements. Users with additional caps can also be subscribers, but not all users are in fact subscribers, which is why we show it in square brackets. (See Users vs Subscribers.)

New Repository Defaults

When you create a new repository, Fossil creates only one user account named after your OS user name by default.

Fossil gives the initial repository user the all-powerful Setup capability.

Users who visit a served repository without logging in get the “nobody” user category’s caps which default to gjorz: clone the repo, read the wiki, check-out files via the web UI, view tickets, and pull version archives. The defaults are suited to random passers-by on a typical FOSS project’s public web site and its code repository.

Users who prove they are not a bot by logging in — even if only as “anonymous” — get the “nobody” capability set plus hmnc: see internal hyperlinks, append to existing wiki articles, file new tickets, and comment on existing tickets. We chose these additional capabilities as those we don’t want bots to have, but which a typical small FOSS project would be happy to give anonymous humans visiting the project site.

The “reader” user category is typically assigned to users who want to be identified within the repository but who primarily have a passive role in the project. The default capability set on a Fossil repo adds kptw caps to those granted by “nobody” and “anonymous”. This category is not well-named, because the default caps are all about modifying repository content: edit existing wiki pages, change one’s own password, create new ticket report formats, and modify existing tickets. This category would be better named “participant”.

Those in the “developer” category get all of the above plus the dei caps: delete wiki articles and tickets, view sensitive user material, and check in changes.

Consequences of Taking a Repository Private

When you click Admin → Security-Audit → “Take it private,” one of the things it does is set the user capabilities for the “nobody” and “anonymous” user categories to blank, so that users who haven’t logged in can’t even see your project’s home page, and the option to log in as “anonymous” isn’t even offered. Until you log in with a user name, all you see is the repository’s skin and those few UI elements that work without any user capability checks at all, such as the “Login” link.

Beware: Fossil does not reassign the capabilities these users had to other users or to the “reader” or “developer” user category! All users except those with Setup capability will lose all capabilities they inherited from “nobody” and “anonymous” categories. Setup is the lone exception.

If you will have non-Setup users in your private repo, you should parcel out some subset of the capability set the “nobody” and “anonymous” categories had to other categories or to individual users first.

Default User Name

By default, Fossil assumes your OS user account name is the same as the one you use in any Fossil repository. It is the default for a new repository, though you can override this with the --admin-user option. Fossil has other ways of overriding this in other contexts such as the name@ syntax in clone URLs.

It’s simplest to stick with the default; a mismatch can cause problems. For example, if you clone someone else’s repo anonymously, turn off autosync, and make check-ins to that repository, they will be assigned to your OS user name by default. If you later get a login on the remote repository under a different name and sync your repo with it, your earlier “private” check-ins will get synced to the remote under your OS user name!

This is unavoidable because those check-ins are already written durably to the local Fossil block chain. Changing a check-in’s user name during sync would require rewriting parts of that block chain, which then means it isn’t actually a “sync” protocol. Either the local and remote clones would be different or the check-in IDs would change as the artifacts get rewritten. That in turn means all references to the old IDs in check-in comments, wiki articles, forum posts, tickets, and more would break.

When such problems occur, you can amend the check-in to hide the incorrect name from Fossil reports, but the original values remain in the repository forever.

This does mean that anyone with check-in rights on your repository can impersonate any Fossil user in those check-ins. They check in their work under any name they like locally, then upon sync, those names are transferred as-is to the remote repository. Be careful who you give check-in rights to!

Login Groups

The Admin → Login-Groups UI feature and its corresponding login-group command solve a common problem with Fossil: you’ve created multiple repositories that some set of users all need access to, those users all have the same access level on all of these shared repositories, and you don’t want to redundantly configure the user set for each repository.

This feature ties changes to the “user” table in one repo to that in one or more other repos. With this configured, you get a new choice on the user edit screen, offering to make changes specific to the one repository only or to apply it to all others in the login group as well.

A user can log into one repo in a login group only if that user has an entry in that repo’s user table. That is, setting up a login group doesn’t automatically transfer all user accounts from the joined repo to the joining repo. Only when a user exists by name in both repos will that user be able to share credentials across the repos.

Login groups can have names, allowing one “master” repo to host multiple subsets of its users to other repos.

Trust in login groups is transitive within a single server. If repo C joined repo B and repo B joined A, changes in C’s user table affect both A and B, if you tell Fossil that the change applies to all repos in the login group.

Cloning the User Table

When cloning over HTTP, the initial user table in the local clone is set to its “new state:” only one user with Setup capability, named after either your OS user account or after the user given in the clone URL.

There is one exception: if you clone as a named Setup user, you get a complete copy of the user information. This restriction keeps the user table private except for the only user allowed to make absolutely complete clones of a remote repo, such as for failover or backup purposes. Every other user’s clone is missing this and a few other items, either for information security or PII privacy reasons.

When cloning with file system paths, file:// URLs, or over SSH, you get a complete clone, including the parent repo’s complete user table.

All of the above applies to login groups as well.

Caps Affect Web Interfaces Only

User caps only affect Fossil’s UI pages, remote operations over http[s]:// URLs, and the JSON API.

User caps do not affect operations done on a local repo opened via a file:// URL or a file system path. This should strike you as sensible: only local file permissions matter when operating on a local SQLite DB file. The same is true when working on a clone done over such a path, except that there are then two sets of file system permission checks: once to modify the working check-out’s repo clone DB file, then again on sync with the parent DB file.

What may surprise you is that user caps also do not affect SSH! When you make a change to such a repository, the change first goes to the local clone, where file system permissions are all that matter, but then upon sync, the situation is effectively the same as when the parent repo is on the local file system. If you can log into the remote system over SSH and that user has the necessary file system permissions on that remote repo DB file, your user is effectively the all-powerful Setup user on both sides of the SSH connection.

Fossil reuses the HTTP-based sync protocol in both cases above, tunnelling HTTP through an OS pipe or through SSH (FIXME?), but all of the user cap checks in Fossil are on its web interfaces only.

TODO: Why then can I not /xfer my local repo contents to a remote repo without logging in?

Capability Reference

This section documents each currently-defined user capability character in more detail than the brief summary on the user capability “key” page. Each entry begins with the capability letter used in the Fossil user editor followed by the C code’s name for that cap within the FossilUserPerms object.

The mnemonics given here vary from obviously-correct to post facto rationalizations to the outright fanciful. To some extent, this is unavoidable.

Implementation Details

Capability Letter Choices

We assigned user capability characters using only lowercase ASCII letters at first, so those are the most important within Fossil: they control the functions most core to Fossil’s operation. Once we used up most of the lowercase letters, we started using uppercase, and then during the development of the forum feature we assigned most of the decimal numerals. Eventually, we might have to start using punctuation. We expect to run out of reasons to define new caps before we’re forced to switch to Unicode, though the possibilities for mnemonic assignments with emoji are intriguing. 😉

The existing caps are usually mnemonic, especially among the earliest and therefore most central assignments, made when we still had lots of letters to choose from. There is still hope for good future mnemonic assignments among the uppercase letters, which are mostly still unused.

Why Not Bitfields?

When Fossil is deciding whether a user has a given capability, it simply searches the cap string for a given character. This is slower than checking bits in a bitfield, but it’s fast enough in the context where it runs: at the front end of an HTTP request handler, where the nanosecond differences in such implementation details are completely swamped by the millisecond scale ping time of that repo’s network connection, followed by the requires I/O to satisfy the request. A strchr() call is plenty fast in that context.