Organizations, Groups, and Virtual Folders

The identity model introduces first-class organizations, groups, users to groups, and folder-level access control lists. This chapter is for operators who run a multi-tenant deployment - for example, a publishing team sharing files with multiple customer organizations.

The model

Four concepts:

Quick start (CLI)

Create an org, a group, a folder, and grant the group access to the folder. Then put a user in the group.

admin org-add force5 --display-name "Force 5" --operator
admin group-add force5 publishers --can-sign --can-checksum
admin folder-add /releases/ --real-path /var/sss/releases
admin folder-perm-add --folder 1 --group force5/publishers --permission READ_WRITE
admin user-join-group alice force5/publishers

After these commands, user alice can read and write into /releases/ via the Web Portal (the path resolves to /var/sss/releases on disk). Her personal home folder still works exactly as before.

Capability matrix

A user's effective capabilities are the union across all their groups, applying these rules:

FieldRuleWhy
read_onlyAND across groupsMost permissive wins. Joining any non-read-only group lets the user write.
can_checksum, can_signORAny grant of the capability is enough.
mfa_requiredOROne group requiring MFA forces it.
quota_bytesMIN ignoring NULLMost restrictive wins; NULL inherits from org.

Folder ACLs

A folder is a (logical path, real path) pair stored in sftp_folders. The same logical path can map to different real paths across folder rows - this enables per-group root mounts where two distinct groups see / as different physical roots.

Grants are rows in sftp_folder_permissions with folder_id, group_id, and permission. A user reaches a folder through any group they belong to.

Permission rollup for the same folder: READWRITE = READ_WRITE. The effective permission is the union across all the user's groups that grant the folder.

Mount conflict detection

A mount conflict exists when a single user is granted two different folders (different folder_id) with the same folder_path but different folder_real_path values. The Web Portal cannot show two different physical locations at the same logical path, so this configuration is rejected.

The check runs at five mutation sites:

  1. Admin adds a user to a group (Web, Swing, CLI).
  2. Admin attaches a folder grant to a group.
  3. Admin edits a folder's folder_path.
  4. LDAP refresh on login: the conflict-introducing groups are skipped, login proceeds with the safe pre-existing memberships, and an AUTH_MOUNT_CONFLICT_SUPPRESSED audit event is emitted.
  5. Login backstop: rejected with AUTH_MOUNT_CONFLICT if a conflict somehow exists at session bootstrap.

Diagnose a user's conflicts with admin mount-conflicts <username>.

SFTP mount routing

By default, an SFTP user's session is anchored at their personal home directory: the SFTP root / IS their home. Web Portal and WebDAV clients see any ACL'd folders they have access to; SFTP clients do not — folder ACLs affect the Portal and WebDAV without changing what SFTP sees.

Enable mount routing to make ACL'd folders visible over SFTP as well. Each user's mount table is computed at session bootstrap (v_user_effective_folder_permissions) and folders appear as top-level directories at the SFTP root, alongside the user's home files.

Turn it on in conf/sftp-server.xml:

<sftp-mount-routing enabled="true"/>

Default is false — existing installs upgrade with SFTP semantics bit-identical to the pre-feature behavior. Turning it on is a one-way opt-in per install; users see the change on their next SFTP connection.

Semantics

The mount table is a snapshot taken at session bootstrap — admin edits mid-session don't affect running SFTP sessions. The user's next connection picks up the fresh state.

Caveats

LDAP membership refresh

When LDAP authenticates a user, the server reads the entry's memberOf, extracts each group's CN (e.g. CN=Force5-Publishers,OU=Groups,...Force5-Publishers), and matches those CN strings case-insensitively against enabled sftp_groups.name rows in the provider's org. Every matching group becomes an active membership for that user in that org.

This is the entire LDAP-to-SSS-group mapping mechanism. No XML mapping table. Operators name SSS groups to match their AD group names; mapping is automatic.

What the refresh replaces (and what it doesn't)

The refresh is an atomic replace of the user's memberships within the provider's org, not just an add:

Folder Health (reconciler)

The folder reconciler iterates every row in sftp_folders on a periodic tick (default 30 min) and checks whether folder_real_path exists as a directory on disk. It writes the verdict to sftp_folder_status.

When a folder is marked MISSING (volume not mounted, directory deleted out-of-band), file operations on it return "folder is currently unavailable" instead of leaking ENOENT or showing a confusing empty listing.

List all currently-MISSING folders with admin folder-health, or the REST endpoint GET /admin/api/folder-health.

Authentication providers (probe-all-parallel)

Each authentication source (LDAP, JDBC, flat file, public key) is now a row in sftp_authentication_providers scoped to an organization. Login probes every enabled provider in parallel on virtual threads and reduces:

Per-provider timeouts (default 3s) prevent a stuck provider from extending the global wait (default 10s). Wall-clock cost of login is max(latencies), not sum.

Free vs paid

FeatureFreePaid
One default org + one default groupYesYes
Add additional orgs / groupsNoYes
Folder ACLsSingle default group onlyAny number of groups
Per-user read_only flagYesYes
Group capability flags (can_checksum, can_sign, mfa_required)NoYes
LDAP / probe-all-parallelNoProfessional+
Folder reconcilerYesYes

CLI reference

# Organizations
admin org-list
admin org-add <name> [--display-name S] [--contact-email E] [--quota BYTES] [--operator]
admin org-show <name>
admin org-edit <name> [--display-name S] [--contact-email E] [--quota BYTES] [--enable | --disable]

# Groups
admin group-list <org-name>
admin group-add <org-name> <group-name> [--display-name S] [--readonly] [--can-checksum] [--can-sign] [--mfa-required]
admin group-show <org-name>/<group-name>

# User ↔ group membership
admin user-join-group <username> <org-name>/<group-name>
admin user-leave-group <username> <org-name>/<group-name>
admin user-memberships <username>
admin user-folders <username>

# Folders
admin folder-list
admin folder-add <folder-path> --real-path <p>
admin folder-edit <folder-id> [--folder-path <p>] [--real-path <p>]
admin folder-delete <folder-id>

# Folder grants
admin folder-perm-list [--folder <id>] [--group <org/name>]
admin folder-perm-add --folder <id> --group <org/name> --permission <READ|WRITE|READ_WRITE>
admin folder-perm-remove <permission-id>
admin folder-perm-set <permission-id> <READ|WRITE|READ_WRITE>

# Mount routing (SFTP / FTP / Portal channels)
admin get-mount-routing
admin set-mount-routing [--sftp true|false] [--ftp true|false] [--portal true|false]

# Diagnostics
admin folder-health
admin mount-conflicts <username>

# Auth providers
admin provider-list
admin provider-prop-list <provider-id>
admin provider-prop-set <provider-id> <key> <value> [--secret]

Folder path normalization

Folder paths are auto-normalized on save: a missing leading slash is prepended (shared becomes /shared), and only single-segment paths are accepted (the root / alone, or one segment like /shared/). Nested paths such as /a/b/c/ are rejected with a clear error — the model doesn't support nested mounts. This is enforced in the DAO, so all three admin surfaces (Web, Swing, CLI) behave identically, and manual DB edits still surface the error at the next admin write. Grouping conventions like /team-shared/ handle "nested" naming without needing nested paths.

Editing a folder

Renaming or repointing a folder runs the same mount-conflict check as adding a user to a group or attaching a new grant. If the edit would introduce a conflict for any user who currently reaches the folder, the write is rolled back and the admin surface returns the affected paths and groups. There is no partial-save state — either the edit is clean or the folder keeps its prior values.

Mount routing per channel

Mount routing is a per-channel toggle. Enabling it makes ACL'd folders visible to that channel's clients; leaving it off preserves the pre-identity- model behavior where clients see only the user's home.

ChannelConfig elementFile
SFTP<sftp-mount-routing enabled="true"/>sftp-server.xml
FTP / FTPS<ftp-mount-routing enabled="true"/>sftp-server.xml
Web File Portal<portal-mount-routing enabled="true"/>admin-console.xml

The Portal toggle is auto-flipped the first time a folder is granted to any group. Operators upgrading from a pre-identity-model install see Portal mount routing turn on as soon as they create the first grant; SFTP and FTP stay off unless explicitly enabled. Toggling a channel while the server is running takes effect on new sessions immediately — no restart required.

The $shared$ reserved name

$shared$ is reserved as a folder path segment and cannot be used as either a logical or physical name. The admin surfaces reject it at input time and the runtime throws if it appears through any other route.