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’s user category set is currently fixed. There is no way to define custom categories.

These categories form a strict hierarchy. Mathematically speaking:

developerreaderanonymousnobody

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 capability letter u signs in, they get their own user’s explicit capabilities plus those assigned to the “reader” category. They also get those assigned to the “anonymous” and “nobody” categories.

That then extends to those in the “developer” category, being those with capability letter v: they get their own explicit caps, plus the “developer” caps, plus the “reader” caps, plus the “anonymous” caps, plus the “nobody” caps. Thus the hierarchy mathematically defined above.

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 “contributor” or “author.”

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 mathematical expression above, which covered only the four user categories. If we bring the individual user capabilities into it, the full hierarchy of user power in Fossil is:

setupadminmoderatordeveloperreadersubscriberanonymousnobody

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 “contributor,” or “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.

The default setup does not explicitly define anything between “developer” and “setup,” but there is the intermediary Admin capability, a.

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 and clones done over http[s]:// URLs. If you use any other URL type, Fossil will not check user caps.

This is sensible when working only on a local repository: only local file permissions matter when operating on a local SQLite DB file. The same sense extends to clones done via a file system path (/path/to/repo.fossil) or through a file:// URL. The only difference is that there are 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.

However, Fossil also ignores caps when working on a repo cloned over 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 the web UI route handlers only.

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

The All-Powerful Setup User

A user with Setup capability, s needs no other user capabliities, because its scope of its power is hard-coded in the Fossil C source. You can take all capabilities away from all of the user categories so that the Setup user inherits no capabilities from them, yet the Setup user will still be able to use every feature of the Fossil web user interface.

Another way to look at it is that the setup user is a superset of all other capabilities, even Admin capability, a. This is literally how it’s implemented in the code: enabling setup capability on a user turns on all of the flags controlled by all of the other capability characters.

When you run fossil ui, you are effectively given setup capability on that repo through that UI instance, regardless of the capability set defined in the repo’s user table. This is why ui always binds to localhost without needing the --localhost flag: in this mode, anyone who can connect to that repo’s web UI has full power over that repo.

See the Admin vs Setup article for a deeper treatment on the differences between these two related capability sets.

Capability Reference

This section documents each currently-defined user capability character in more detail than the brief summary on the user capability “key” page.

Implementation Details

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.

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.