Skip to content

Security

Verified against source (17)

  • security/ObjectAccess.Fields.cs
  • security/PersonAccess.Fields.cs
  • security/SecurityAccess.cs
  • Config/SecurityAccess.config
  • Config/Person.config
  • Providers/QboMembershipUser.cs
  • security/app.config
  • application/Enumerations.cs
  • Configuration/SqlPattern.cs
  • Configuration/ObjectConfiguration.cs
  • Configuration/DbStatement.cs
  • Config/Abstract.config
  • Config/Organization.config
  • security/Global.asax.cs
  • qbo4.Security/SecurityEndpoints.cs
  • Handlers/QboApiAuthenticationHandler.cs
  • qbo4.Security.Bridge/BridgeOptions.cs
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.

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 (default true) creates it, so new statements and services show up for granting.
  • SystemFunctionMissing (default true) is returned. Set it to false before production so undefined functions are denied.

Statements declare their permission with Permission="...", for example Permission="{Table}BulkUpdate".

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.

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.

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.

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=1

ObjectID is the record. GroupObject and ID name the group table and one or more group IDs. Has* flags left out keep their current values.

Send an API key in the X-ApiKey header:

GET /api/contact/search?LastName=Smith&Output=Json
X-ApiKey: 3f2b8c1e-7d4a-4e9b-a1c2-5b6d7e8f9a0b

The 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 (default true) turns API key authentication on or off.
  • Automation clients without a key (identified by BasicAuthenticationHeaders, BasicAuthenticationUserAgents, or a missing User-Agent) get a 401 with an HTTP Basic challenge instead of a redirect to the login page.
  • ASP.NET Core services authenticate the same X-ApiKey header through QboApiAuthenticationHandler.

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.