DDD suggest a relation between entities - domain-driven-design

I have a following structure.
Resources can have multiple endpoints.
Example:
Resource tickets can be accessed on following endpoints:
/api/tickets
/api/agent/tickets
/api/group/5/tickets
/api/tickets/closed etc.
At first, this looks like aggregate, where Resource is AR, and endpoints are child entities.
I also have UserTypes.
What I need is to build a relation between Usertypes and Endpoints, so each UserType can have a diferrent access for endpoints.
Example for, UserType admin could access all endpoints for tickets resource, while user type agent could have access to only portion of endpoints for the same resource.
What would be a suggested way to connect EndPoints and UserTypes in terms of DDD?

Do you need anything else other than a collection of mapping a between Resources and Endpoints on a UserType? This would give you all usertypes their unique resource endpoint access rights
Also seems to be the same question as Solve apparent need for outside reference to entity inside aggregate (DDD)
I would probably create something like the following:
class ResourceEndpoint {
Guid resourceId;
Guid endpointId;
}
class UserType {
List<ResourceEndpoint> ThingsICanAccess;
}

Related

NestJS - How to implement RBAC with organization-scoped roles

I am designing a REST backend in Nest.js that needs to allow Users to be a part of multiple Organizations. I want to use role-based access control, such that a user can have one or more named roles. Crucially, these roles need to be able to be either "global" (not dependent on any organization, ex. SUPERUSER), or "scoped" (specific to an organization, ex. MANAGER).
I have decided on this basic database design, which links Users to Organizations using the Roles table in a many-one-many relationship:
As you can see, the organizationId field on a Role is optional, and if it is present, then the user is linked to that organization through the role. If it is not present, I assume this to be a "global" role. I find this to be an elegant database design, but I am having trouble implementing the guard logic for my endpoints.
The guard logic would go something like this:
Look up all the Roles from the database that match the current userId.
For global routes, check that at least one of the returned roles is in the list of required roles for the route.
For scoped routes, do the same, but also check that the organizationId of the role matches the organization ID associated with the operation (I'll elaborate below).
Consider these two endpoints for Jobs. The first will retrieve all the jobs associated with a specified organization. The second will find a single job by its id:
Example route 1:
GET /jobs?organizationId=XXXXX
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
getMyJobs(#Query() query: {organizationId: string}) {
return this.jobsService.getJobs({
organizationId: query.organizationId,
})
}
Example route 2:
GET /jobs/:jobId
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get(':jobId')
getJob(#Param('jobId') jobId: string) {
return this.jobsService.getJob(jobId)
}
In the first example, I know the organizationId without doing any work because it is required as a query parameter. This id can be matched against the id specified in the Role. This is trivial to validate, and ensures that only users who belong to that organization can access the endpoint.
In the second example, the organizationId is not provided. I can easily query it from the database by looking up the Job, but that is work that should be done in the service/business logic. Additionally, guard logic executes before getJob. This is where I am stuck.
The only solution I can come up with is to pass the organizationId in every request, perhaps as a url parameter or HTTP header. Seems like there should be a better option than that. I'm sure this pattern is very common, but I don't know what it is called to do any research. Any help regarding this implementation would be greatly appreciated!
It is just another option for you.
You can modify a user object inside RolesGuard by adding a field that stores available organizations for him/her. So you need to calculate organizations for user, who makes a request inside a guard and then put a result array with ids of organizations to a user field (user.availableOrganizationIds = []). And then use it for filtering results
#Roles(Role.MANAGER, Role.EMPLOYEE)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
getMyJobs(#User() user) { // get a user from request
return this.jobsService.getJobs({
organizationIds: user.availableOrganizationIds, // <<- filter by organizations
})
}

How to protect Azure Function Endpoints with custom roles and permission?

I need a starting point to solve the following problem:
Assume there is a model with different entities (e.g. school classes) and different roles that are connected to entities.
Now I want to check in my Azure Function if Bob has a role on this entity which entitles him to rate a student from the school class.
I think of a claim of the form:
TEACHER : [
"class 2b"
]
before.
Which Azure Resources do I need to map such a thing?
I already use Azure AZ for the ID token and my API is implemented in an Azure Function.
I would like to call Azure AD to get an access token which contains those roles and resources of my domain.
I'm afraid that this form is not supported by Azure AD.
The supported form should be "{claim name}": "{claim value}".
If you accept this form, you can refer to my previous answer.
What you need to modify is:
When you create the extensionProperty, you should name the extensionProperty as "TEACHER".
Post https://graph.microsoft.com/v1.0/applications/{object id of the Azure AD application}/extensionProperties
{"name":"TEACHER","dataType":"string","targetObjects":["User"]}
And update the extension property for your account:
Patch https://graph.microsoft.com/v1.0/me
{"extension_6d8190fbf1fe4bc38a5a145520221989_TEACHER":"class 2b"}
Then you can get the custom claim as "TEACHER": "class 2b".

What is the proper way to restrict API access based on user type?

Suppose I have two users, A and B.
A is allowed to access all the resources available, but B can access them only partially.
What is the proper way to prevent B from accessing resources to which B does not have permission?
Should I create some sort of whitelist that specifies only URLs B can access?
The following snippet is what I currently have.
It is a middleware which checks whether each request is allowed to access specific URLs.
const ALLOWED_URLS = ['api/resource1', 'api/resource2', 'api/resource3'];
const sessionCheck = (req, res, next) => {
const url = req.originalUrl;
// check whether accessing URL is allowed
}
Is there any better approach than this?
What you're asking about is called IAM (Identity & Access Management).
The roles, ownership, and permissions on specific resources is generally persisted in your database as business domain objects of their own. This is language agnostic and not specific to node or express. You should NOT build a white list of URLs. The resources you want to protect are in your database. You should map them to permission objects that in turn map to users. Your not protecting URLs, your protecting resources. Everyone is allowed to access any URL but the resources behind them are what you're protecting and those rules/permissions go in your database.
If your looking for industry standards then here are some common names/terms for those IAM objects that would be persisted in your DB:
Group
Role
User
Policy
User A's access would generally be determined by what role they have or what group they belong to. Whether you give user A a role, put them in an authorized group, or give them a direct permission doesn't really matter, these groupings exist to reduce duplication so you can pass around or take away multiple permissions at once. But the general idea is the same; your resources exist in a DB and you specify what is the required or allowed roles, groups, and users that can access those resources and you map users to groups, roles, etc as simple table entries. This means the real authorization logic is not in Node or Express or even in your webapp, its built into the resources themselves and tied to how the data is retrieved.
Resource Retrieval Code
When anyone makes a request for a given resource the query, regardless of your database type, should fail if the user is not authorized. This means the way you are retrieving data must be directly tied to how its authorized and not two separate steps; meaning you should not get the resource, then check if the user is authorized and you should not check if the user is authorized before getting the resource. The best practice is fuse/join the two so that you can not get the resource unless your authorized because we look for the resource using your role and if you don't have the right role we can't find the resource.
For example:
function getAccount(userId,accountId) {
makeSQLCall(userId,accountId)
}
SELECT *
FROM accounts a
WHERE a.accountId = accountId AND u.userId = userId
JOIN users u ON a.allowedRole = u.role
The SQL doesn't matter as the same can be done with other technologies but the last line is the most important (account.allowedRole = user.role). You literally pull the resource from the database using the users role so that if they are not authorized this fails and no data is returned. This is also your base/parent data retrieval function so other functions that don't know about authorization can use this function and authorization will be dealt with under the hood.
Express Pseudo Code
router.get('/api/resource1',function(req,res){
var user = utility.getUserFromRequest(req)
var resource = accountService.getAccount(user,req.body.accountId)
sendResponse(resource)
})
Looking at the code above the authorization is built into your domain model not your web application. If the user making the request above is not authorized they will get no data back. You have to figure out in your own business use case is it enough to return an empty result or do you need to return a 401 HTTP error code. If you need to inform your non-malicious users they are not authorized you can simply perform isAuthoriized(user,accountId) before running accountService.getAccount as a UI convenience. The strength of this approach is that if you or some other developer forgets to check isAuthorized 1st the call will still return no data since isAuthorized() is just for the users benefit and not security. The security is at the domain/db layer.
Typically you would build user resource routes and validate their ownership when you authenticate and their permissions on authorization.
E.g.
server.get('api/v0/products/:user', authMiddleware, (req, res, next) => {
// from the auth middleware comes a parsed token with payload
if (req.payload.user !== req.params.user) {
return res.status(401).send('not allowed')
}
// do action
})
An alternative would be to only access database objects that matches the payload of your authentication
The example would suggest using JWT middleware with a custom property user

Is this domain or application service

I am building authentication micro-service/domain by using DDD and I am still having trouble with identifying where does each service belong. At this point I am not sure does Authentication service belongs to the domain services or application services.
Should I wrap this behavior in domain serrvice, and expose response object via application service, or this should stay as it is - as application service.
public class AuthenticationService : IAuthenticationService
{
IAuthUnitOfWork _uow;
IUserRepository _userRepository;
IUserTokenFactory _userTokenFactory;
public AuthenticationService(IUserTokenFactory userTokenFactory, IUserRepository userRepository,
IAuthUnitOfWork uow)
{
_userTokenFactory = userTokenFactory;
_userRepository = userRepository;
_uow = uow;
}
public async Task<UserTokenResponse> AuthenticateAsync(string email, string password)
{
var user = await _userRepository.GetByEmailAndPasswordAsync(email, password);
//TODO: Add null check for user
var userToken = await _userTokenFactory.CreateWithAsync(user);
await _uow.SaveChangesAsync();
return new UserTokenResponse
{
ExpiressOn = userToken.ExpiressOn,
Token = userToken.Token
};
}
}
Application Services coordinate application flow and infrastructure, but do not execute business logic rules or invariants. It is common to see calls to repositories, units of work, and to accept and return service contract objects or request/response objects. They generally do not accept or return domain entities or valueobjects.
Domain services are unaware of infrastructure or overall application flow - they exclusively encapsulate business logic rules. They accept domain entities or value objects, carry out conditional operations on those entities or objects, or perform business rule calculations, and then return primitives or domain entities or value objects.
Based on these concepts, your sample service is definitely an application service, as it is interacting with your repository and unit of work, and returning a "UserResponse" type (a 'response' type does not sound like a domain entity).
Your application service AuthenticationService is delegating to a service called UserTokenFactory. UserTokenFactory accepts a domain entity (user) and returns a domain valueobject (usertoken). Presumably it encapsulates in an infrastructure-agnostic way the business rules associated with creating the user token. As such, this looks like more like a domain service. A factory which is responsible for the creation of domain concepts such as entities and value objects is just a special type of domain service (in my opinion) although you will most commonly see 'domain services' referring to services that perform some business logic that requires coordinating between multiple types of entities.
So - I think your structure here is appropriate - you have an application service coordinating infrastructure and flow, which delegates to a special service to execute the business logic.

Creating Organization in Liferay using OrganizationLocalServiceUtil.addOrganization?

I am creating an Organization in Liferay using:
OrganizationLocalServiceUtil.addOrganization (
userId, parentOrganizationId, name,
type, recursable, regionId, countryId,
statusId, comments, false, serviceContext);
Following are my confusions:
Why we need parentOrganizationId?
What does organization status refer to?
Why ServiceContext?
The short answer: There's javadoc
Somewhat longer:
Organizations have an implicit hierarchy - thus, if you create an organization, you might as well create it at the intended position in the hierarchy, thus parentOrganizationId.
You might want to try ListTypeConstants.ORGANIZATION_STATUS_DEFAULT as the status you give
Typically, Liferay stores owners or other data with created entities (e.g. for later permission checks). This is data that can be retrieved from serviceContext.
The answer lies in this documentation.
From the documentation as it is:
userId - the primary key of the creator/owner of the organization
parentOrganizationId - the primary key of the organization's parent organization
name - the organization's name
type - the organization's type
recursable - whether the permissions of the organization are to be inherited by its sub-organizations
regionId - the primary key of the organization's region
countryId - the primary key of the organization's country
statusId - the organization's workflow status
comments - the comments about the organization
site - whether the organization is to be associated with a main site
serviceContext - the organization's service context (optionally null). Can set asset category IDs, asset tag names, and expando bridge attributes for the organization.
To add to the above documentation some specifics in response to your question:
Why we need parentOrganizationId?
Liferay has a concept of heirarchical Organization structure, so you can have levels of Organizations.
So if you want to create top-level Organization then use com.liferay.portal.model.OrganizationConstants.DEFAULT_PARENT_ORGANIZATION_ID to pass for
parentOrganizationId
What does organization status refer to?
Liferay has workflow (like Kaleo-workflow) for various assets. If you don't want to use this than pass [com.liferay.portal.kernel.workflow.WorkflowConstants.STATUS_APPROVED][4]
Why ServiceContext?
This you can pass as null as stated.
Basically you can think of this class as a collection of different general methods and attributes like Expando, asset-tags, asset-categories etc which can be passed as a single argument by being enclosed in the ServiceContext object rather than as individual arguments and making the method call tedious.
Here is the documentation.
Here are some more details for you to understand this better: Development Guide & Wiki.
A working snippet for Liferay 6.2 for top level organizations is:
ServiceContext serviceContext = ServiceContextFactory.getInstance(request); //or null
Organization organization = OrganizationServiceUtil.addOrganization(
OrganizationConstants.DEFAULT_PARENT_ORGANIZATION_ID,
organizationName,
OrganizationConstants.TYPE_REGULAR_ORGANIZATION,
RegionConstants.DEFAULT_REGION_ID,
CountryConstants.DEFAULT_COUNTRY_ID,
ListTypeConstants.ORGANIZATION_STATUS_DEFAULT,
"",
false,
serviceContext
);
The comment of Olaf Kock before is correct, and using
WorkflowConstants.STATUS_APPROVED
for the status will yield a
com.liferay.portal.NoSuchListTypeException.
I would upvote him, if this would not be a new account.

Resources