Security API - quandis/qbo3-Documentation GitHub Wiki

The Security module provides a necessary and flexible security layer to protect your QBO resources and instances.

Classes

Class Description
Person Tracks users of the system. Derives from the Contact class.
PersonAccess Maps Person rows to security groups, such as Organization.
PersonDefault Overrides defaults for a Person or Role.
SecurityAccess Enables one-time login to non-users of the system.
SystemAddress Maps the IP addresses or address ranges allow on a Person or System Role basis.
SystemDefault Data driven defaults for the system, such as the site name.
SystemFunction A functional permission of the system, such as MessageInsert or AttachmentDelete.
SystemMember Maps users to roles.
SystemPermission Maps roles to functional permissions.
SystemRole Roles are a group of users, to which permissions are granted.

Password Control

Password strength, reuse, and lockout after failed login attempts can be controlled via application and security settings. See Application Settings below.

Password Strength

Password strength is controlled via the qbo.Security.PasswordRE application setting. QBO enforces password strength via regular expressions so that each system can be configured independently. The default regular expression requires a minimum of 6 characters, without any required special characters or uppercase/lowercase mix.

Password Reuse

Company password policies often dictate the frequency with which a user may reuse a password. For example, "the user may not reuse a given password within a 12 month timespan". Password reuse is controlled via the qbo.Security.PasswordReuseMonths. The default is NULL, which does not prevent password reuse.

Lockout after Failed Login Attempts

If you wish to lock an account out after X number of failed login attempts, this can be configured from:

  • Design > Configuration > Application Configuration
  • Create a key called qbo.Security.LockAttempts, and set the number of failed attempts that should cause a user to be locked out.

The default is 0, which never locks a user out for failed login attempts.

One-Time Access

There are situations where business requirements dictate granting access to a specific record to an entity that may not be a member of the Person table. Examples include:

  • Emailing a document to a requesting party
  • Enabling the public to view Foreclosure Notices of Default
  • Requesting investor approval for a loan modification

In such cases, the SecurityAccess table can be configured to provide seamless but highly restricted access to record(s) without the need to set up user credentials.

When a link is provided with a one-time access key, the user will be logged into the system, as if they had logged in as SecurityAccess.PersonID. This means the user can do anything SecurityAccess.PersonID can do. It is critical to ensure that when granting such access, the SecurityAccess.PersonID be configured to an appropriate degree of security. For example, assume that the goal is to allow a client (Acme Investments) access to documents:

  • Upload attachments to the Acme Investments Organization
  • Create a Person called '[email protected]'
    • grant them access to the Acme Investments Organization
    • grant them permission to AttachmentSelect
  • Create the SecurityAccess row, including the BofA IP address base as the SecurityAccess.IPMask.

Key properties of the SecurityAccess class include:

Property Description
Access Reason Any text description of why you are creating the row.
Person The user will impersonate this user's account when accessing using one-time access.
Enabled and Expires Controls the date range around which the access is valid.
LoginDuration Once accessed, this controls how long the user can be logged in.
LoginURL The one-time access will only work when attempting to navigate to this URL; SQL pattern matching is allowed.
IPMask If not null, the one-time access will only work when the user's IP address matches this mask (SQL pattern matching is allowed).
GUID This is automatically generated, and will be passed on the URL being access in a one-time manner
e.g. /Attachment/AttachmentView.aspx?AttachmentID=27&LoginGUID=c1712830-1cef-4e2c-95b5-9ff972224b3b

System Address: IP-based restrictions

Access for the entire system, or for specific roles or users, can be restricted using the SystemAddress class.

Use case: granting administrators access from any IP address

  • Navigate to Security/SystemAddress.ashx
  • Under Allowed IPs panel go to options -> Add IP Address
    • for System Address put in % to allow all IPs
    • for Applies to Role select Adminstrators from the drop down control
    • Ensure that the Exclude checkbox is unchecked

Use case: prohibit access from outside the United States

This might get out of hand since there are several hundred possible class C IP address ranges in the US. But the sample below can get you started.

  • Navigate to Security/SystemAddress.ashx
  • Under Allowed IPs panel go to options -> Add IP Address
    • for System Address put in 3% to allow all IPs in the 3.* address ranges
    • for Applies to Role leave it as All Roles
    • Ensure that the Exclude checkbox is unchecked
  • After clicking save you would need to go back and add other IP blocks to allow access such as:
    • 4% to allow 4...* address ranges and 5.1.94% to allow 5.1.94.* address ranges

Use case: managers may access from IP addresses registered in India

Similar caveat to the one above. It is best to find the exact IPs that your team is using and restrict access just to those.

  • Navigate to Security/SystemAddress.ashx
  • Under Allowed IPs panel go to options -> Add IP Address
    • for System Address put in 1.6.% to allow all IPs in the 1.6.* address ranges
    • for Applies to Role select the Manager Role you want to grant access
    • Ensure that the Exclude checkbox is unchecked
  • After clicking save you would need to go back and add other IP ranges to allow access such as:
    • 1.7.% to allow 1.7.. ranges and 1.22.% to allow 1.22.. ranges

Use case: [email protected] may access from IP address 193.169.169.1

  • Navigate to Security/SystemAddress.ashx
  • Under Allowed IPs panel go to options -> Add IP Address
    • for System Address put in 193.169.169.1
    • for Person select the Person you want to provide access via the drop down
    • Ensure that the Exclude checkbox is unchecked

API Management of Users, Roles, Membership and Permissions

As with every QBO module, users, roles and permissions can be managed via API calls. Relevant endpoints include:

Endpoint Description
GET api/person/search Retrieve the first page of users.
PUT api/person Create a new user.
POST api/person/{id} Update user matching {id}.
POST api/person/{id}/setpassword Change the password for the user matching {id}.
GET api/person/{id} Get account details for user matching {id}.
PUT api/systemrole Create a new role.
POST api/systemrole/{id} Update role matching {id.}.
GET api/systemrole/{id} Get role details for role matching {id}.
PUT api/systemmember Add a user to a role.
DELETE api/systemmember/{id} Delete a user's membership in a role.
PUT api/systempermission Grant permission to a role.
DELETE api/systempermission/{id} Remove a permission from a role.

Each of these calls requires QBO authentication and filtering.

Extranet Security

Granting access to specific rows of data is referred to as "Extranet Security". Extranet security enabling clients, vendors, and other business partners to log into the same web site, but see different rows of data. For example, the organization assigned to manage a Process should see their processes only, and should not be able to see processes assigned to other organizations.

Note that to see any user-data record records, a user must either:

  • Be granted explicit access to a group (e.g. Organization) associated with the record, or
  • Be a member of a "Universal Access" System Role

This means that users that should be limited to seeing just their Organization's records must not be a member of a Universal Access System Role.

Extranet Security Implementation

QBO databases implement the extranet security model in the data tier. The extranet security model is intended to allow QBO systems to expose a single website to multiple organizations, and restrict those organizations to viewing only rows that they are associated with.

Each QBO table's foreign keys can contribute to the extranet security model. For example, a Process table has keys including AssignedOrganizationID and AssignedPersonID. When a Process row is added, all uses associated with the AssignedOrganization and of course the AssignedPerson should have access to the Process.

Things get trickier, however, when child table rows are added. For example, a Foreclosure table has a parent foreign key Foreclosure.LoanID. Anyone with access to the parent Loan should also be able to see the child Foreclosure. Likewise, anyone with access to the Foreclosure must be able to see it's parent Loan.

For performance reasons, QBO implements the bulk of the burden of maintaining this security model during inserts and updates, ensuring that selects have the fastest performance while still maintaining row-level security.

Data Tier

The key data tier objects that implement this model are:

  • ObjectAccess: table containing rows that grant row-level access to a group
  • PersonAccess: table mapping users to groups
  • {Object} table triggers: populate ObjectAccess rows automatically

Extranet Variants

The SqlPattern class supports the following Extranet clauses:

Setting Description
Basic There must be an ObjectAccess row matching the object.
Generic There must be an ObjectAccess row matching the object's Object/ID.
Group There must be a PersonAccess row matching the object.
Template Reserved for future use.
Custom A filter named 'Extranet' is used in lieu of the statements above.
Default Uses Configuration.ExtranetDefault if defined, which is Basic by default.

Normally, Extranet.Default should be used by custom statements. Each class' configuration file can specify an ExtranetDefault attribute:

<!-- Loan does not specify ExtranetDefault, so Basic will be assumed -->
<Loan Type="qbo.Mortgage.LoanObject, qbo.Mortgage" BaseTransform="Templates/Mortgage/">

<!-- Contact will use Extranet.Generic for statements that use Extranet.Default -->
<Contact Table="Contact" Type="qbo.Contact.ContactObject, qbo.Contact" ExtranetDefault="Generic" BaseTransform="Templates/Contact/">

<!-- Organization will use Extranet.Group for statements that use Extranet.Default -->
<Organization Table="Organization" Type="qbo.Contact.OrganizationObject, qbo.Contact" ExtranetDefault="Group" ...>

How A Statement's Extranet Setting Is Applied

If a statement does not have an {Extranet.*} expression in it, there is no "extra" security applied to the statement.

If a statement has an {Extranet.*} expression in it, there might be "extra" security applied. Specifically:

  • If the user is not a member of a role that has Universal Access, "extra" security is applied
  • If the class has UniversalAccessEnabled=false, "extra" security is applied
  • Otherwise, no extra security is applied

"Extra" security applied means that the {Extranet.*} expression will be replaced by a sub-query. Which sub-query is used to replace the expression depends on the '*' portion of {Extranet.*}. Examples are below.

Taking the Abstract.config Select statement as an example:

<Statement Name="SelectBasic" Query="SELECT {Select.Basic} FROM {From.Basic} WHERE {Table}.{Table}ID IN ({Where.IdList}) {Extranet.Default}"/>

For the Loan class, ExtranetDefault = Basic, producing:

AND EXISTS 
(
 SELECT 1
 FROM ObjectAccess WITH (NOLOCK)
 INNER JOIN PersonAccess WITH (NOLOCK)
 ON PersonAccess.GroupObjectID = ObjectAccess.GroupObjectID
 AND PersonAccess.GroupObject = ObjectAccess.GroupObject
 AND PersonAccess.PersonID = @UserID
 WHERE ObjectAccess.ObjectID = [Loan].[LoanID]
 AND ObjectAccess.Object = 'Loan'
 AND ObjectAccess.HasSelect = 1
)

For the Contact class, ExtranetDefault = Generic, producing:

AND EXISTS 
(
 SELECT 1
 FROM ObjectAccess WITH (NOLOCK)
 INNER JOIN PersonAccess WITH (NOLOCK)
 ON PersonAccess.GroupObjectID = ObjectAccess.GroupObjectID
 AND PersonAccess.GroupObject = ObjectAccess.GroupObject
 AND PersonAccess.PersonID = @UserID
 WHERE ObjectAccess.ObjectID = [Contact].ObjectID
 AND ObjectAccess.Object = [Contact].Object
 AND ObjectAccess.HasSelect = 1
)

For the Organization class, ExtranetDefault = Group, producing:

AND EXISTS 
(
 SELECT 1
 FROM PersonAccess WITH (NOLOCK)
 WHERE PersonAccess.GroupObjectID = [Organization].[OrganizationID]
 AND PersonAccess.GroupObject = 'Organization'
 AND PersonAccess.PersonID = @UserID
)

Customizing Extranet Behavior

Two things must be accomplished to customize extranet behavior:

  • The ExtrantDefault setting for the module must be changes, and if the behavior
  • An Extranet filter must be created (or modified) for the module

Use case: restrict Matrix access to most users, but not administrators.

Changing the ExtranetDefault setting can be accomplished by creating a ConfigurationEntry row along the following lines:

// Change the Matrix to use ExtranetDefault="Custom"
INSERT INTO ConfigurationEntry (
 ConfigurationEntry, 
 Catelog, 
 Source, 
 ConfigurationType, 
 ConfigurationKey, 
 ConfigurationXml, 
 CreatedPersonID, 
 CreatedDate, 
 UpdatedPersonID, 
 UpdatedDate
)
SELECT 
 'Matrix',
 '{myclient.quandis.net}',
 'Matrix.config',
 'qbo.Application.Configuration.ObjectConfiguration',
 'Matrix',
 '<Matrix ExtranetDefault="Custom" />',
 1,
 GETDATE(),
 1,
 GETDATE()

Modifying the Extranet filter from Design > Configuration > Modules > Matrix > Filters, along these lines:

AND EXISTS 
(
 SELECT  1
 FROM  Matrix
 WHERE Matrix.CreatedPersonID = @UserID

 UNION

 SELECT  1
 FROM  SystemRole
 INNER JOIN SystemMember
 ON SystemMember.SystemRoleID = SystemRole.SystemRoleID
 AND SystemMember.PersonID = @UserID
 WHERE SystemRole.SystemRole = 'Administrators'

 UNION

 SELECT 1
 FROM ObjectAccess WITH (NOLOCK)
 INNER JOIN PersonAccess WITH (NOLOCK)
 ON PersonAccess.GroupObjectID = ObjectAccess.GroupObjectID
 AND PersonAccess.GroupObject = ObjectAccess.GroupObject
 AND PersonAccess.PersonID = @UserID
 WHERE ObjectAccess.ObjectID = [Matrix].[MatrixID]
 AND ObjectAccess.Object = 'Matrix'
 AND ObjectAccess.HasSelect = 1
)

Application Settings

The following application settings (Design > Configuration > Modules > Person) apply to the security module.

Password Related Application Settings

Setting Default Description
LockoutMessage ... Text to display when a user has been locked out.
LoginFailureMessage Login failed. Text to display when a user login attempt fails.
LoginLockAttempts 3 Consecutive failed login attempts that trigger a lockout.
NotificationTemplate Login - {0} Message Template to use when sending password reset notifications.
PasswordError ... Message to display to end users when they attempt to define a password that violated constraints.
PasswordExclusionWords Quandis,Bank,God,Password Comma-delimited list of words that may not be used in a password.
PasswordHistoryMonths 12 Number of months before password history is purged from logging tables.
PasswordHistoryReuse 10 How many distinct passwords must be used before an old password may be reused.
PasswordRE ... A regular expression used to enforce password policy.
PasswordResetBcc [email protected] Email address to blind carbon copy when issuing password reset emails.
PasswordResetBodyHTML ... Body of message to email to end users upon a password reset.
PasswordResetLinkDuration 30 Minutes before a password reset hyperlink becomes invalid.
PasswordResetSubject Password Reset Email Subject line of password reset emails.
RegistrationSuccessMessage ... Message displayed to an end user upon successful registration.

API Automation Related Application Settings

Setting Default Description
ApiKeyEnabled true When true, use of X-APIKey for authentication is enabled.
BasicAuthenticationHeaders SOAPAction,qboAutomation HttpHeaders that trigger basic authentication.
BasicAuthenticationUserAgents Gecko/ /21.0;Amazon Simple Notification Service Agent;LogicMonitor;LLC User agents that trigger basic authentication.
CORSDomains quandis.com,quandis.net Domains allowed to may cross-origin requests.

Miscellaneous Application Settings

Setting Default Description
HttpsRequired true When true, requests to http will be redirected to https.
ImpersonationEnabled true When true, administrators may impersonate other users.
LicenseLexisNexis true When true, Lexis Nexis required verbiage is included in the standard license agreement.
LicenseRequired false When true, users must accept license terms upon first login.
SystemFunctionAutoCreate true When true, system functions will be dynamically created.
SystemFunctionRecordingCacheKey SystemFunction.Record.{0} Cache key to track system permissions during recording.
SystemFunctionRecordingEnabled true When true, users with permissions to add permissions may record permissions.

Recording Permissions

In an ideal world, QBO clients would think through their security models prior to implementing projects. In an agile development world, they will often decide on their security requirements after they've configured the QBO components to fit their business model. The combination of auto-creating SystemFunctions with the ability to record permissions enables QBO client to achieve good security setup with a minimum of effort.

Under a standard install, a power user can configure the custom templates, statements, services, and listeners needed. During the course of unit testing, SystemFunctions matching custom components will be automatically created. Once unit testing is done, a power user or security administrator should:

  • Create one or more System Roles as dictated by their business organization,
  • For each role, navigate to the role's Summary page, and under the Permissions panel, choose Options > Record

This will launch a new window, where they can Start, Save and Stop recording permissions

  • Click on the 'Start' button
  • In their main window, conduct unit test(s) covering all the functionality the role should have
  • In their recording window, click on the 'Save' button

Note that if there are BDD specs covering the required functionality, one can merely:

  • Start recording
  • Run spec(s)
  • Save the recording

ASP.NET MembershipProviders

QBO uses the standard ASP.NET Membership Provider classes, including overrides:

  • QboMembershipProvider: authenticates credentials against the Person table
  • WindowsMembershipProvider: assumes Windows authentication, but the user must exist in the Person table
  • FacebookProvider: uses Facebook authentication, but the user must exist in the Person table

The membership providers works with the MembershipUser class (or derived classes). QBO includes a QboMembershipUser which combines both the MembershipUser class (via inheritance) and the qbo.Application.Interfaces.IUser.

IUser Interface

All QBO classes handle security via classes that implement the IUser interface:

  • UserID: represents the PersonID in the Person table
  • HasUniversalAccess: if true, bypasses the extranet security model
  • HasPermission: checks to see if a user has permission to a System Function
  • IsInRole: checks to see if a user is a member of a System Role

Note if a SystemFunction is not defined, QBO returns true if SystemFunctionMissing is true, otherwise returns false. The qbo.Secuirty application setting SystemFunctionMissing allows you to set the system up to be "permissive" during development, and then lock it down prior to rolling out to a production environment. The default value is true.

Core Panel - Group Access

In older versions of QBO, power users that worked with Matrix objects had the option to manage access/permissions to the said Matrix by using a 'Group Access' screen that was accessible under the Edit Button drop down menu. This screen allowed a power user to

  • View currently assigned permissions.
  • Search for an organization, role or person.
  • Assign specific permissions for the Matrix to the desired entity that was searched and selected.

This feature has been refactored to be apart of the set of generic panels (located at the bottom of any Summary page, or by clicking on the Miscellaneous section on the section listing on the left) labeled as "Group Access". Because this was added to the generic panels, this can now be used to manage permissions on other QBO objects as well.

The Group Access panel works in the same way that the original Matrix Group Access screen does.

Scroll down on a Matrix Summary page or click Miscellaneous, and click Group Access.

You will be shown the list of People/Organizations/Roles that have some sort of permission on the object. You may optionally use the filter check-boxes and the Filter button to refresh with the selection as necessary.

Group Access Panel View Permissions

Add permissions by clicking on the '+' button on the top right, and then select whether you would like to add permissions for a Person, Organization or Role.

Group Access Panel Add New Permission

You will be presented with eligible entities based on what was chosen. You may filter the list by entering the name of an entity on the top and hitting the Filter button.

Group Access Panel Find Entity To Add

Select the check-boxes for the records for the new permissions, and click the '+' button on the top right again. Follow the prompts to add a new permission record for the selected entities.

You may also remove permission rows when viewing them on the panel by selecting the check-box, and hitting 'Delete' from the ellipses '...' drop down menu.

Group Access Panel Delete Permission

Permissions Inheritance

There are use cases when it is useful to assume a user has permissions to an object simply because she has access to another object. For example, if a user is assigned to a SmartWorklistMember, we can reasonably assume she needs to be able to read the associated SmartWorklist and SmartWorklistTemplate. To facilitate this, AbstractObject includes a protected property called InheritedUser.

Assume a user has permission to call SmartWorklistMember/Next. In order to properly assign a user to the SmartWorklistMember, several other records must be read, and possibly written to, including SmartWorklist, SmartWorklistTemplate, and often the parent object to which the SmartWorklistMember is bound (e.g. a Loan). Without the concept of InheritedUser, the security administrators would need to ensure the user has:

  • functional permission to SmartWorklistSelect, SmartWorklistTemplateSelect, and LoanUpdate
  • extranet access to the SmartWorklist, SmartWorklistTemplate, and Loan

With InheritedUser, the user need only have access to the SmartWorklistMember. As the application tier code instantiates related objects (SmartWorklist, SmartWorklistTemplate, Parent), it does so with InheritedUser. The functional permission check (AbstractObject.VerifyPermission) will check to see if the user is inherited, and if so, bypass the need to have functional permissions. Similarly, the SqlPattern class will bypass adding extranet filters to underlying SQL statements, so the user need not have explicit extranet access to these objects.

This is a great convenience feature, but use cases can arise where using InheritedUser may not be desired. The Statement class includes an AllowInheritance attribute which defaults to true, but can be overridden. Thus, if there are statements which are 'powerful' in their execution and pose a potential security risk if executed as an inherited user, simply set the AllowInheritance attribute to false.