after some time, I am still struggling to model my domain properly. Let me briefly introduce simplified background.
It is product monitoring SaaS. User needs to obtain a membership which defines his abilities and limits, let's call him a Member. Member can subscribe to products in order to keep track on product changes, and therefore being notified about it. Member can also create a group to which he can add subscribed products in order to customize notification behavior - "hey, in case of these products, notify me only if price drops more than 20%". Simply as that.
At first, I created three aggregates.
ProductAggregate
MembershipAggregate
GroupAggregate
Even though my use case is fairly simply, I can't figure what is a proper way of modeling that.
Member can subscribe to products. Does "subscribe" method belongs to Membership or Product aggregate? Membership can exist without subscriptions, so is Product.
Member can create group – I would say it belongs to GroupAggregate, but membership limits (i.e. member can create max. 3 groups) needs to be checked. Group has no idea about that, so we need to load membership aggregate to check if it is possible.
As you see above, both cases require knowledge about membership limits, so it would be natural to place it all in membership aggregate. On the other hand, pretty much every action in the system will depend on user membership limits and thus everything would have to go through that aggregate – which is obviously bad.
The only solution I came up with is to build membership with method like "canCreateGroup()" etc. and retrieve that aggregate in command handler (application layer). So CreateGroupCommandHandler would do:
Load membership aggregate, execute canCreateGroup
Load group aggregate, execute CreateGroup
However, this way everything related to membership would be checked in application layer (command handlers) and I believe it is a domain responsibility, so it would be wrong as well.
It appears that there's a requirement that no member ever breach their limits. In that case, every operation that could possibly breach the limits has to run through the membership aggregate. There's no way around that.
You can model the process of creating a group as its own process (with state, which enables resumption) in the domain, as in the saga pattern. For a given member ID and group ID, the create group saga:
attempts to add the group to the member's set of pending groups (the member validates that the group would not breach limits; note that this command has to be idempotent)
if that succeeds, records that the member has approved creation of the group
creates the group
if that succeeds, records that the group has been created
moves the group from the member's set of pending groups to the member's set of active groups
if that succeeds, record that the group creation process has completed
The reservation process means that the failure mode would be a group which never gets created stays in pending and eventually prevents a member from creating more groups. This situation can be detected by subscribing to the events from members (you seem to be event sourcing, judging from the tagging) and canceling (or perhaps resuming, depending on the interval) hung group creation attempts.
Related
I am modelling user management domain where I have to address domain activities such as registration, login, role management etc.. I have come up with below aggregates.
When user registration is approved, I have to add an entry into users table and then persist his roles
Users <<root>> ----> User Roles (child entity)
New roles can be created by choosing appropriate privileges
Roles <<root>> ----> Role Privileges (child entity)
Privilege master table
Privileges <<root>>
My questions is, in the User Roles (child entity) of Users aggregate, can I have role_id which links to Roles aggregate root?
If I have to persist user roles, the business invariant is to make sure the roles are valid roles and hence I have to validate role_id against role master. So in the same transaction, can read data from another aggregate? Please note that, these aggregates are in the same microservice.
Thanks for the help.
Define "links".
It's perfectly OK for an aggregate to refer/link to another aggregate root by a stable identifier (e.g. a string ID). You can then use that ID to request another aggregate (e.g. from a repository). With that other aggregate you can perform reads, but should never perform updates.
Remember that aggregates define consistency and transactional boundaries. The aggregate you get need not be the latest version of that aggregate: it's a version of that aggregate from some point before you requested it (hopefully it's the latest version as of when you requested it, but that might not always be the case) and it cannot reflect changes since you requested it.
However, if your reference/link is through something more direct, then those consistency/transactional boundaries are violated, so that's generally not allowed (or at least not a good idea, as you're giving up other benefits of DDD).
In the systems, there may be data that is restricted in nature.
Sometimes access to specific entities should be easily restricted or granted based on user or group membership.
What is the best way to implement this in the microservice architecture?
#1
Should access control, managing permissions etc. be the responsibility of the microserive itself? Developers will have to implement access control, store, and update permissions for every service. Seems like not very robust and error-prone approach.
#2
Create dedicated microservice handling permission management? This service will be called by other microserives to check access permissions for each entity and filtering entities before returning results. Centralized permissions storage and management is an advantage but microservice will have to make a call to "Permission Service" for each entity to check access rights what may have a negative influence on performance. And developers still have to integrate access checks into their services what leaves space for an error.
#3
Make access control responsibility of the API Gateway or Service Mesh. It is possible to think of an implementation that will automatically filter responses of all services. But in the case when the microservice returns list of entities permissions should be checked for each entity. Still a potential performance problem.
Example
Consider the following synthetic example.
Healthcare system dealing with test results, X-Ray images etc. Health information is very sensitive and should not be disclosed.
Test results should be available only to:
patient
doctor
laboratory
Attending doctor may send the patient to another specialist. A new doctor should have access to test results too. So access can be granted dynamically.
So each entity (e.g. test results, X-Ray image) has a set of rules what users and groups are allowed to access it.
Imagine there is a microservice called "Test Results Service" dealing with test results. Should it be responsible for access control, manage permissions etc.? Or permissions management should be extracted to separate microservice?
Healthcare system may also handle visits to a doctor. Information about patient's visit to the doctor should be available to:
patient
doctor
clinic receptionist
This is the example of a different entity type that requires entity level access restriction based on user or group membership.
It is easy to imagine even more examples when entity level access control is required.
I came to the following generic solution.
ACL security model is used. Each object in the system has associated set of permissions. Permissions defines who and what actions can perform on the object.
Microservices are responsible for entity-level authorization and filter objects in responses based on permissions of the objects.
Central Access Control Service is responsible for the creation, update, and deletion of permissions for all objects in the system. Access Control Service database is the primary store of objects' permissions.
Permissions stored in microservices databases are synchronized with Access Control Service database using event-carried state transfer. Every time, permissions are changed an event is sent to the message broker. Microservices can subscribe to these events to synchronize permissions.
API Gateway can be used as the additional protection layer. API Gateway can call Access Control Service directly (RPC) to check response objects' permissions or load recently revoked permissions.
Design features:
A way to uniquely identify each object in the system is required (e.g. UUID).
Permissions synchronization in microservices are eventual consistent. In case of partitioning between message broker and microservice permissions will not be synchronized. It may be a problem with revocation of the permissions. The solution to this problem is a separate topic.
Looks like security is a part of business logic here. In both examples.
Then security could be a part of data scheme.
For example,
Patient can see his tests:
select * from test_result where patient_id=*patient_id*
Doctor can see all test from his medical department:
select * from test_result where branch_id=*doctor_branch*
I believe that to have separate MS for access control is a really bad idea and could lead serious performance problems. Just imagine situation that somebody with zero entity access tries to fetch all entities each time :) You will always need to handle larger result sets than actually needed.
Firstly, this is very bad idea to have a separate (per microservice) security model. It should be single always cross-cutting all application, because it can lead to a hell with access management, permissions granting and mapping between entities in different microservices.
In second, I assume that you are wrong with understanding how to organize microservices..? You should dedicate the principle of splitting functionality into microservices: by features, by domain, etc. Look at Single Responsibility, DDD and other approaches which helps you to achieve clear behavior of your MS.
So, in best case, you should have to:
Choose right security model ABAC or RBAC - there are a lot of other options, but looking at your example I guess the ABAC is the best one
Create separate MS for access management - the main responsibility of this MS is a CRUD and assignment of groups/roles/permissions/attributes to the people accounts.
Create separate MS for providing only permitted health information.
In third, how it works?:
With ABAC you can setup hierarchical roles/permissions (based on groups/attributes) - it helps you to resolve a delegation path of who is permitted to the data
Setup authorization (via auth-MS) and store the list of permissions (in session, cookies, etc)
Check access for a given user for a needed data in health-info-MS. Here we have several options how to do this:
If you use memory-grids (hazelcast, coherence), you can easily create filters with predicates based on security attributes.
If you're using SQL (hibernate, plain SQL, etc.) you should generate queries to return only permitted data - add security specific criteria to the where clause
Few more details about SQL queries with security check in where: before the SQL execution (if hibernate & spring is easy to do with spring-method-auth hook) you should resolve all permissions assigned to a user - you can do this with call to auth-MS.
Example
We created CRUD permissions for TestResult entity - VIEW, EDIT, DELETE.
The role DOCTOR can see any TestResults - so, it has VIEW permission
The role PATIENT can see only his/her TestResults
So, you create a business rules which provide the correct where clause for each business role (DOCTOR, PATIENT, LAB, etc.) and at the end the SQL request would be like:
For patient who has assigned VIEW permission:
select * from test_result where id=*patient_id* and 1=1
For patient who hasn't assigned VIEW permission:
select * from test_result where id=*patient_id* and 1!=1
NOTE: In business rules we can add 1=1 or 1!=1 to permit/restrict query result
I have a query that get accounts for a business unit based on a set criteria. When using one business unit, the query works find and brings back the results. Running the same code, changing only the business unit, running with a different business unit, the result come back empty.
If I run this query in HeidiSQL it works for both business units.
Another feature to this problem is in the bad business unit, a Customer Care Rep can not get the results, yet Customer Care Supervisor can. In the good business unit, both can get the results.
This leads me to think there is a problem with security. However I have compared both business units teams and roles and they match.
Is there something else, that I am overlooking that could stop a team from getting data from the database.
From: https://andrewbschultz.com/2011/08/09/business-units-bus-and-security-roles-in-microsoft-dynamics-crm-2011-solution-exports/
Without BUs, the following security configurations would be possible:
A user could have access to his own CRM records
A user could have access to all CRM records
With BUs, the following additional security
configurations are possible:
A user can have access to all records owned by users in his business
unit
A user can have access to all records owned by users in his own
and any child business units
My understanding is that if the records are owned by a user from BU1, then users in BU2 won't be able to access them until ownership is transfered to a user in BU2.
In order to have users in both BUs access the same records, you have to share all of them with at least one (or maybe all) users in BU2.
I'm using Odoo v8 and need a means to prevent certain users (either (preferably) in a sales team or in a user group) from accessing certain contacts (assigned to a different sales team) or the contacts at all.
I tried to achieve this via Record rules but don't seem to get this to work.
Users and user roles are critical points concerning internal security in OpenERP. OpenERP provides several security mechanisms concerning user roles, all implemented in the OpenERP Server. They are implemented in the lowest server level, which is the ORM engine. OpenERP distinguishes three different concepts:
user: a person identified by its login and password. Note that all employees of a company are not necessarily OpenERP users; an user is somebody who accesses the application.
group: a group of users that has some access rights. A group gives its access rights to the users that belong to the group. Ex: Sales Manager, Accountant, etc.
security rule: a rule that defines the access rights a given group grants to its users. Security rules are attached to a given resource, for example the Invoice model.
Security rules are attached to groups. Users are assigned to several groups. This gives users the rights that are attached to their groups. Therefore controlling user roles is done by managing user groups and adding or modifying security rules attached to those groups.
Users
Users represent physical persons using OpenERP. They are identified with a login and a password,they use OpenERP, they can edit their own preferences, … By default, a user has no access right. The more we assign groups to the user, the more he or she gets rights to perform some actions. A user may belong to several groups.
User groups
The groups determine the access rights to the different resources. A user may belong to several groups. If he belongs to several groups, we always use the group with the highest rights for a selected resource. A group can inherit all the rights from another group
Rights
Security rules are attached to groups. You can assign several security rules at the group level, each rule being of one of the following types :
- access rights are global rights on an object,
- record rules are records access filters,
- fields access right,
- workflow transition rules are operations rights.
You can also define rules that are global, i.e. they are applied to all users, indiscriminately of the groups they belong to. For example, the multi-company rules are global; a user can only see invoices of the companies he or she belongs to.
Concerning configuration, it is difficult to have default generic configurations that suit all applications. Therefore, like SAP, OpenERP is by default pre-configured with best-practices.
Access rights
Access rights are rules that define the access a user can have on a particular object . Those global rights are defined per document type or model. Rights follow the CRUD model: create, read (search), update (write), delete. For example, you can define rules on invoice creation. By default, adding a right to an object gives the right to all records of that specific object.
Record rules
When accessing an object, records are filtered based on record rules. Record rules or access filters are therefore filters that limits records of an object a group can access. A record rule is a condition that each record must satisfy to be created, read, updated (written) or deleted. Records that do not meet the constraints are filtered.
For example, you can create a rule to limit a group in such a way that users of that group will see business opportunities in which he or she is flagged as the salesman. The rule can be salesman = connected_user. With that rule, only records respecting the rule will be displayed.
Field access rights
New in version 7.0.
OpenERP now supports real access control at the field level, not just on the view side. Previously it was already possible to set a groups attribute on a <field> element (or in fact most view elements), but with cosmetics effects only: the element was made invisible on the client side, while still perfectly available for read/write access at the RPC level.
As of OpenERP 7.0 the existing behavior is preserved on the view level, but a new groups attribute is available on all model fields, introducing a model-level access control on each field. The syntax is the same as for the view-level attribute:
_columns = {
'secret_key': fields.char('Secret Key', groups="base.group_erp_manager,base.group_system")
}
There is a major difference with the view-level groups attribute: restricting the access at the model level really means that the field will be completely unavailable for users who do not belong to the authorized groups:
Restricted fields will be completely removed from all related views, not just hidden. This is important to keep in mind because it means the field value will not be available at all on the client side, and thus unavailable e.g. for on_change calls.
Restricted fields will not be returned as part of a call to fields_get() or fields_view_get() This is in order to avoid them appearing in the list of fields available for advanced search filters, for example. This does not prevent getting the list of a model’s fields by querying ir.model.fields directly, which is fine.
Any attempt to read or write directly the value of the restricted fields will result in an AccessError exception.
As a consequence of the previous item, restricted fields will not be available for use within search filters (domains) or anything that would require read or write access.
It is quite possible to set groups attributes for the same field both at the model and view level, even with different values. Both will carry their effect, with the model-level restriction taking precedence and removing the field completely in case of restriction.
Note
The tests related to this feature are in openerp/tests/test_acl.py.
Warning
At the time of writing the implementation of this feature is partial
and does not yet restrict read/write RPC access to the field. The
corresponding test is written already but currently disabled.
You can add a Rule for a group of users. I just made it and it's work fine.
We have a Salesforce app where we have some custom objects and want to expose the various custom object rcords to customers.
We need to ensure that customers can see only the records belonging to their Account. Because of the way these records are setup(owned by various system users at different levels of processing), we cannot use owner based sharing...and cannot use criteria based sharing since its not dynamic(I cant use criteria based sharing to say "Share this record with all customer portal users who belong to the same Account as the record" at runtime).
So I know I have to use Apex based sharing. I have read up on the sharing objects and the sharing table. But how would I approach this.
I can write a trigger which upon inserting will create a share object and get all userids who belong to the customer portal group and whose account equals the account of the record and associate them with the share object of the record.
But I feel this is overkill correct? Lets say there are 5 users from one of our customers and lets say there are 500 records created a day...that means 2500 share objects a day just for 1 customer...for 10 customers this can go upto 25000...and scale in this way...
Am I right here?
Another problem would be if a new person joined that customer team..unless another process updates the sharing on older records, he/she cannot see the older records.
So is there a better/elegant way to do this? I thought of adding a share object to the group...but there is just one group "Customer portal group" and how do I associate the group with the account of the users?
I will appreciate any thoughts about this.
You should take a look at high-volume customer portal users. They're much cheaper relative to standard customer portal users and should meet your needs. Unlike regular users they have a totally different sharing concept. In a nutshell if they own an object they can see, if not they can't. You can then extend this based on whether a contact or account lookup on the object matches the logged in user.
Read up on this documentation:
License Types (scan to High Volume Customer Portal)
Granting High-Volume Portal Users Access to Records (login required)
You can use groups for sharing to avoid creating so many sharing records. You would have one group per account and one sharing record per account. Instead of managing thousands of sharing records you would have to manage hundreds of groups.
I haven't tried this approach with this many groups, but I read some time ago that it should work (someone posted using a LOT of groups for sharing). If you do try this, please tell us if it worked OK.