In order to view and manage data for a vendor, an admin user must have the vendor within their vendor restrictions and privileges within those restrictions.
Alternatively, a user can be "unrestricted" (no restrictions) and thereby have access to both non-vendor data and data from any vendor so long as they have the correct permissions to perform the API requests.
This is described in detail within the admin user service documentation.
Ultimately, this information is populated within the user’s OAuth2 access token by the authentication service.
During request processing, the access token becomes the source of truth for resource services to validate what vendor(s) and operations a user can interact with.
Vendor admin users are typically given limited access and privileges within the Broadleaf admin.
They’re generally limited to interacting with entities that support the concept of vendor discrimination.
This is important, because a vendor user must not be able to view or manipulate entities outside of their assigned vendor restrictions.
If there’s no way to determine what vendor an entity is associated with, then no filtration can be applied to prevent cross-vendor visibility of that entity.
For such entities, the easiest solution is simply to not grant the vendor user the roles/permissions for API operations involving that entity.
There are generally two main aspects to consider when implementing vendor discrimination to control what a vendor user can see and do:
Validate the admin user has the appropriate privileges (roles/permissions) within their vendor restriction(s) to perform the requested API operation
Apply "narrowing" filtration to queries on relevant entities by adding criteria that only includes results within the admin user’s vendor restrictions
Out-of-box, Broadleaf provides robust support for implementing both of these to make a Trackable entity vendor-discriminated.
Non-Trackable entities will require some more customized implementation, so this documentation will focus on the Trackable solution.
While the individual components are described in more detail in Key Components and the Javadocs, this diagram shows the bigger picture of how the security and narrowing aspects are applied when processing API requests.
Security Policy Validation of Vendor Restrictions and Privileges
As shown in the overall flow diagram, an API caller with vendor restrictions can only pass security policy validation if they have the operation’s requisite permissions in at least one of their vendor restrictions.
This is because there is no information about which vendor(s) the API caller is trying to operate in.
Thus, the validation will simply see if there is at least one vendor in which the caller could possibly access the operation, and pass if so.
The expectation is that the narrowing logic that adds vendor-filtration criteria to queries will prevent the caller from seeing/modifying data in a vendor unless the caller has access to it within the current permission requirements.
Example 1. Policy validation scenario
Consider a multi-vendor admin user which has the READ_PRODUCT authority in vendorA, and has the [READ_PRODUCT, UPDATE_PRODUCT] authorities in vendorB.
If this user were to try to read a page of products, the policy validation would pass since the user has the required READ_PRODUCT authority in at least one of their vendor restrictions.
Then, the narrowing logic would see that the user has the READ_PRODUCT authority in both vendorA and vendorB, and would build a filter allowing products from both vendors to appear in the results.
Now let’s say the user tries to make a request to update some productA that happens to be in vendorA.
The policy validation will again pass, since the user has UPDATE_PRODUCT in at least one of their vendor restrictions (vendorB).
The narrowing logic will see that the user has UPDATE_PRODUCT only in vendorB, and will build a filter only allowing products from vendorB to appear in the results.
This will mean that when the query for productA runs, it will simply not be found and the update will fail.
Policy validation also ensures that if a Catalog is provided in a ContextRequest, its associated vendor must be accessible by the caller within the current permission requirements.
This validation has more of a "first-class" status, since by providing a Catalog, the API caller is implictly requesting the operation to occur within a specific vendor (or not in a vendor).
With this knowledge, the validation can quickly reject an invalid request rather than relying on the vendor narrowing logic.
Entity Mutability Validation
There is additional "mutability" policy validation that actually examines an entity and determines if it can be inserted or updated by the current authentication.
The vendor-aware components have been hooked into this validation to ensure vendor restrictions are considered as well.
This is useful for a few reasons:
Preventing a vendor-restricted user from persisting an entity into a different vendor
For example, it should not be possible for a vendor user to create a PriceList with a vendorRef value that is not within their vendor restrictions.
Similarly, it should not be possible for a vendor user to modify the vendor association of an existing entity to something that is not within their vendor restrictions.
Determining ContextState mutability
In the Broadleaf admin application, whether or not an entity is seen in a "read-only" state is determined by the value of ContextState.mutable.
This value is determined after performing several checks, with one of them being the entity mutability validation.
Including vendor considerations here allows for something like a vendor-shared PriceList to be visible to vendor users without its form fields in an editable state.
The "narrowing" aspect of vendor discrimination is relatively straightforward.
The goal is to ensure queries include criteria preventing results outside of the current API caller’s vendor restrictions from being returned.
Most entities define some sort of vendorRef field/column as a soft reference to their owning vendor, and criteria is built against this field.
Depending on the requirements for each entity, there may be differences in what the criteria allows.
For instance, a vendor user should only see SkuInventory within their vendor restrictions with no exceptions.
The criteria for this is a strict "vendor-ref-in-this-set-of-values" condition.
However, with InventoryLocation, Broadleaf supports both vendor-owned locations and "shared" locations that can be seen by all vendors.
In this case, the criteria looks more like "vendor-ref-in-this-set-of-values or inventory-location-is-shared".
This flexibility is made possible by the VendorVisibilityHandler abstraction, which allows behavior to be defined differently for each entity.
Mechanism for Contributing Vendor Criteria to Queries
While implementations should typically never have to deal with these aspects directly, it may be valuable to understand how the Broadleaf framework determines and adds vendor criteria to the queries that end up running.
As an API request’s ContextInfo gets resolved and built, the VendorNarrowingContextInfoCustomizer will register a dynamic QueryInfluencer on it (the actual logic of the QueryInfluencer does not apply at this time)
Later, while preparing the query, the TrackableRepository implementation will examine and invoke each QueryInfluencer on the ContextInfo, and this will include the one registered by the VendorNarrowingContextInfoCustomizer
As per the contract of QueryInfluencer, the repository will provide it the current policy requirements, the persisted-domain entity type being queried, and the original ContextInfo
The vendor narrowing QueryInfluencer then performs a few checks
Does the entity being queried have a supporting VendorVisibilityHandler implementation?
If not, then it’s not vendor discriminated and thus no filter needs to be built.
Are there any policy requirements for the current operation?
If not, then vendor restrictions lose their meaning and no filter can be built.
Is there an authentication present, and does it have vendor restrictions?
The lack of an authentication generally indicates a system-initiated operation or an operation that never required security to enter.
This should have effectively unrestricted access, so no filter will be built.
A present-but-unrestricted authentication should not be restricted to any vendors by design, so no filter will be built.
If the arguments pass the QueryInfluencer 's checks, then it filters to the set of vendor restrictions in which the API caller has the current operation’s requisite permissions.
The filtered set of vendors is passed to the VendorVisibilityManager (which itself
delegates to the appropriate VendorVisibilityHandler) with the expectation that it will build a filter for the entity that restricts results to those vendors.
The TrackableRepository will parse the returned filter and add its criteria to the query.
Which entities are vendor-discriminated by default?
This is not necessarily an exhaustive list, but out-of-box, the following entities support vendor discrimination:
Any catalog-discriminated entity (such as Product)
InventoryLocation and SkuInventory
PriceList and PriceData
This is the catalog entity itself, which is managed within the tenant service.
This is the vendor entity itself, which is managed within the vendor service.
How can I make my Trackable entity vendor-discriminated?
This should be a fairly simple and easy process.
The steps are outlined below.
Introduce a field such as vendorRef on your entity through which you can associate it to a vendor
It is strongly advised to also introduce explicit first-class validation (ex: validation that will always apply such as in EntityValidator) on vendor-discriminated entities to disallow mutation of their vendor associations altogether.
An entity’s vendor association should be set at the time of its creation and never changed thereafter (by anyone, including non-vendor-restricted users).
Introduce a VendorVisibilityHandler implementation for the entity
If the entity in question will need to support caching, ensure to incorporate vendor restrictions as part of cache-key generation.
See VendorAwareCacheKeyEnhancer and its uses in DataTrackingKeyGen for more information.
If the entity in question can be imported using the import microservice, then the import batch handler for it should contain some logic to ignore vendorRef values provided in the import file and instead hard-set the value to whatever is specified in the pre-validated import BatchContext.
This is important, since import batch processing happens in message listeners, which run without an authentication in the security context.
Thus, there can’t be any security validation applied at this time to see which vendors are/aren’t allowed.
For this reason, the import service pre-validates a particular vendorRef that the import should be running within, and provides this in the BatchContext.