Security
Verified against source (17)
security/ObjectAccess.Fields.cssecurity/PersonAccess.Fields.cssecurity/SecurityAccess.csConfig/SecurityAccess.configConfig/Person.configProviders/QboMembershipUser.cssecurity/app.configapplication/Enumerations.csConfiguration/SqlPattern.csConfiguration/ObjectConfiguration.csConfiguration/DbStatement.csConfig/Abstract.configConfig/Organization.configsecurity/Global.asax.csqbo4.Security/SecurityEndpoints.csHandlers/QboApiAuthenticationHandler.csqbo4.Security.Bridge/BridgeOptions.cs
Data model
Section titled “Data model”| Table | Purpose |
|---|---|
Person |
User account: Person (user name), Active, Lockout, LastLogin, ForcePasswordChange, LicenseAccepted. |
SystemRole |
Role; UniversalAccess bypasses extranet filters. |
SystemMember |
Person to SystemRole membership. |
SystemFunction |
A named permission, such as ContactSearch. |
SystemPermission |
Grants a SystemFunction to a role. |
PersonAccess |
Puts a user in a group: GroupObject / GroupObjectID (for example Organization / 42). |
ObjectAccess |
Grants a group access to a record: GroupObject / GroupObjectID, HasSelect, HasInsert, HasUpdate, HasDelete, plus the source of the grant (SourceTable, SourceColumn, SourceObjectID). |
SystemAddress |
IP allow and exclude rules, optionally scoped to a role or user. |
SecurityAccess |
API keys and one-time access keys bound to a Person. |
SecurityLog |
Sign-in successes and failures (with FailureReason), lockouts, password changes, impersonation. |
ObjectAccess rows are maintained when records are written, so reads only need an EXISTS check.
Functional permissions
Section titled “Functional permissions”IUser.HasPermission(systemFunction) checks the user’s role flags against the cached security map. Members of Administrators always pass. When a function isn’t defined yet:
SystemFunctionAutoCreate(defaulttrue) creates it, so new statements and services show up for granting.SystemFunctionMissing(defaulttrue) is returned. Set it tofalsebefore production so undefined functions are denied.
Statements declare their permission with Permission="...", for example Permission="{Table}BulkUpdate".
Extranet filters in statements
Section titled “Extranet filters in statements”Statements opt into row-level security with an {Extranet.*} token:
<Statement Name="SelectBasic" Query="SELECT {Select.Basic} FROM {From.Basic} WHERE {Table}.{Table}ID IN ({Where.IdList}) {Extranet.Default}"/>At run time the token becomes an AND EXISTS (...) sub-query, or an empty string. The filter applies when extranet security is enabled and either the user lacks universal access or the module sets UniversalAccessEnabled="false". It’s skipped for an inherited user unless the statement sets AllowInheritance="false".
SqlExtranet |
Filter |
|---|---|
None |
No filter. |
Basic |
An ObjectAccess row for this table and ID, joined to the user’s PersonAccess. |
Generic |
An ObjectAccess row for the record’s parent (Object / ObjectID). |
Group |
A PersonAccess row for the record itself. Used for group tables such as Organization. |
Custom |
The module’s filter named Extranet. |
Default |
The module’s ExtranetDefault (Basic if not set). |
<Organization Table="Organization" Type="qbo.Contact.OrganizationObject, qbo.Contact" ExtranetDefault="Group" ...>To customize a module, set ExtranetDefault="Custom" and define an Extranet filter on it. If the filter is missing, QBO throws a configuration error rather than failing open.
Template-level participation
Section titled “Template-level participation”Templates implement ITemplate.ExtranetEnabled:
public enum Extranet { None, Self, Family, Template }ProcessTemplate always returns Family. Access to a process therefore covers the workflows, tasks, documents, and messages under it.
Inherited users
Section titled “Inherited users”Some operations need related records the caller wasn’t granted directly. For example, taking the next item from a worklist reads the worklist, its template, and the parent record. Code can instantiate related objects with InheritedUser. Permission checks pass, and extranet filters are skipped, for statements whose AllowInheritance is true (the default comes from the AllowInheritance application setting). Set AllowInheritance="false" on powerful statements.
Granting access through the API
Section titled “Granting access through the API”Every module has GrantAccess and RevokeAccess statements that upsert or remove ObjectAccess rows for a record and a group:
POST /api/contact/grantaccess ?ObjectID=1001 &GroupObject=Organization &ID=42 &HasSelect=1 &HasUpdate=1ObjectID is the record. GroupObject and ID name the group table and one or more group IDs. Has* flags left out keep their current values.
Authenticating API calls
Section titled “Authenticating API calls”Send an API key in the X-ApiKey header:
GET /api/contact/search?LastName=Smith&Output=JsonX-ApiKey: 3f2b8c1e-7d4a-4e9b-a1c2-5b6d7e8f9a0bThe key is a SecurityAccess.GUID. QBO rejects it (and logs a Login Failure with the reason) if the bound Person is inactive or locked out, the key isn’t enabled yet or has expired, its RemainingLogins is 0, or the caller’s IP fails SystemAddress or IPMask checks. On success QBO logs Login Success, decrements RemainingLogins if set, and issues a forms-authentication cookie, so later calls can reuse the session.
ApiKeyEnabled(defaulttrue) turns API key authentication on or off.- Automation clients without a key (identified by
BasicAuthenticationHeaders,BasicAuthenticationUserAgents, or a missingUser-Agent) get a401with an HTTP Basic challenge instead of a redirect to the login page. - ASP.NET Core services authenticate the same
X-ApiKeyheader throughQboApiAuthenticationHandler.
Single sign-on endpoints
Section titled “Single sign-on endpoints”The ASP.NET Core security service maps sign-in endpoints under its area (for example /oidc/security/... when hosted in an oidc child application):
| Endpoint | Purpose |
|---|---|
/security/login/schemes |
Lists configured sign-in schemes. |
/security/login/{scheme} |
Starts a remote sign-in (for example google, microsoft, or a custom OIDC provider). |
/security/logout |
Signs out. |
/security/userinfo |
Returns the current user’s claims. |
After the provider authenticates the user, the bridge sets the user name from the email claim and runs the scheme’s PreValidateStatement, if one is defined. If that statement returns a Person column, it replaces the user name. The bridge then runs the standard login. With AutoPreValidate, a Person/Login{Scheme} statement is used automatically. Provisioning users, roles (SystemMember), and group access (PersonAccess) from claims belongs in that statement.