case 4712 - customer contacts reach only their own customer's work orders
Step 2 of 4712 (security review F13). The customer work-order rights checked
only tag-based feature switches, never which customer a work order belongs to.
New UserBiz.CustomerContactOwnsCustomerAsync (own customer, or for a head
office contact a customer of its head office), used by:
- workorder/customer/{id}: another customer's work order is not found (404);
- customer work order report (-100): exactly one work order, which must be the
contact's (the renderer used every id it was given);
- work order attachment list and download for contacts; other attachment types
are refused to contacts (the customer screens show them only on work orders).
Checked: other customer's work order 404 (was 200); report on another's or on
two work orders not found; own customer report still accepted (202).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -157,11 +157,16 @@ namespace AyaNova.Api.Controllers
|
||||
|
||||
//Is this a customer user attempting to view a wo attachments??
|
||||
var userType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
if (ayaType == AyaType.WorkOrder && (userType == UserType.Customer || userType == UserType.HeadOffice))
|
||||
//case 4712: customer contacts see attachments only on their own customer's work orders
|
||||
if (userType == UserType.Customer || userType == UserType.HeadOffice)
|
||||
{
|
||||
if (ayaType != AyaType.WorkOrder)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var wo = await ct.WorkOrder.AsNoTracking().Where(x => x.Id == ayaId).Select(x => new { x.Tags, x.CustomerId }).FirstOrDefaultAsync();
|
||||
if (wo == null || !await UserBiz.CustomerContactOwnsCustomerAsync(ct, UserIdFromContext.Id(HttpContext.Items), wo.CustomerId))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
//check if allowed
|
||||
var woTags = await ct.WorkOrder.AsNoTracking().Where(x => x.Id == ayaId).Select(x => x.Tags).FirstOrDefaultAsync();
|
||||
var custUserRights = await UserBiz.CustomerUserEffectiveRightsAsync(UserIdFromContext.Id(HttpContext.Items), woTags);
|
||||
var custUserRights = await UserBiz.CustomerUserEffectiveRightsAsync(UserIdFromContext.Id(HttpContext.Items), wo.Tags);
|
||||
if (!custUserRights.ThisWOCanAttachments)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
@@ -553,11 +558,16 @@ namespace AyaNova.Api.Controllers
|
||||
//is this allowed?
|
||||
//Is this a customer user attempting to view a wo attachments??
|
||||
|
||||
if (dbObject.AttachToAType == AyaType.WorkOrder && (DownloadUser.UserType == UserType.Customer || DownloadUser.UserType == UserType.HeadOffice))
|
||||
//case 4712: customer contacts download attachments only from their own customer's work orders
|
||||
if (DownloadUser.UserType == UserType.Customer || DownloadUser.UserType == UserType.HeadOffice)
|
||||
{
|
||||
if (dbObject.AttachToAType != AyaType.WorkOrder)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var wo = await ct.WorkOrder.AsNoTracking().Where(x => x.Id == dbObject.AttachToObjectId).Select(x => new { x.Tags, x.CustomerId }).FirstOrDefaultAsync();
|
||||
if (wo == null || !await UserBiz.CustomerContactOwnsCustomerAsync(ct, DownloadUser.Id, wo.CustomerId))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
//check if allowed
|
||||
var woTags = await ct.WorkOrder.AsNoTracking().Where(x => x.Id == dbObject.AttachToObjectId).Select(x => x.Tags).FirstOrDefaultAsync();
|
||||
var custUserRights = await UserBiz.CustomerUserEffectiveRightsAsync(DownloadUser.Id, woTags);
|
||||
var custUserRights = await UserBiz.CustomerUserEffectiveRightsAsync(DownloadUser.Id, wo.Tags);
|
||||
if (!custUserRights.ThisWOCanAttachments)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
@@ -581,13 +581,24 @@ What could be the issue here?
|
||||
{
|
||||
log.LogDebug("customer user report requested");
|
||||
//get the user and workorder data and set the actual report id or return null if not found
|
||||
var woTags = await ct
|
||||
//case 4712: exactly one work order, and it must belong to the contact's customer (the render
|
||||
//below uses every id in SelectedRowIds, so extra ids are not passed on)
|
||||
if (reportRequest.SelectedRowIds == null || reportRequest.SelectedRowIds.Length != 1)
|
||||
{
|
||||
AddError(ApiErrorCode.NOT_FOUND, "id");
|
||||
return null;
|
||||
}
|
||||
var wo = await ct
|
||||
.WorkOrder.AsNoTracking()
|
||||
.Where(x => x.Id == reportRequest.SelectedRowIds[0])
|
||||
.Select(x => x.Tags)
|
||||
.Select(x => new { x.Tags, x.CustomerId })
|
||||
.FirstOrDefaultAsync();
|
||||
if (woTags == null)
|
||||
if (wo == null || !await UserBiz.CustomerContactOwnsCustomerAsync(ct, UserId, wo.CustomerId))
|
||||
{
|
||||
AddError(ApiErrorCode.NOT_FOUND, "id");
|
||||
return null;
|
||||
}
|
||||
var woTags = wo.Tags;
|
||||
var cr = await UserBiz.CustomerUserEffectiveRightsAsync(UserId, woTags);
|
||||
if (cr.ThisWOEffectiveWOReportId == null)
|
||||
return null;
|
||||
|
||||
@@ -185,6 +185,20 @@ namespace AyaNova.Biz
|
||||
}
|
||||
}
|
||||
|
||||
//case 4712: true if the customer is a customer contact's own customer or, for a head office contact,
|
||||
//one of its head office's customers; false for any other user type
|
||||
internal static async Task<bool> CustomerContactOwnsCustomerAsync(AyContext ct, long userId, long customerId)
|
||||
{
|
||||
var u = await ct.User.AsNoTracking().Where(x => x.Id == userId).Select(x => new { x.UserType, x.CustomerId, x.HeadOfficeId }).FirstOrDefaultAsync();
|
||||
if (u == null)
|
||||
return false;
|
||||
if (u.UserType == UserType.Customer)
|
||||
return u.CustomerId != null && u.CustomerId == customerId;
|
||||
if (u.UserType == UserType.HeadOffice)
|
||||
return u.HeadOfficeId != null && await ct.Customer.AsNoTracking().AnyAsync(x => x.Id == customerId && x.HeadOfficeId == u.HeadOfficeId);
|
||||
return false;
|
||||
}
|
||||
|
||||
//For auth and access in client, also when opening wo and also when reporting wo
|
||||
internal static async Task<CustomerRightsRecord> CustomerUserEffectiveRightsAsync(long userId, List<string> thisWorkOrderTags = null)
|
||||
{
|
||||
|
||||
@@ -314,6 +314,8 @@ namespace AyaNova.Biz
|
||||
{
|
||||
var sourceWo = await ct.WorkOrder.AsNoTracking().FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (sourceWo == null) return null;
|
||||
//case 4712: another customer's work order is answered as not found
|
||||
if (!await UserBiz.CustomerContactOwnsCustomerAsync(ct, UserId, sourceWo.CustomerId)) return null;
|
||||
var customerEffectiveRights = await UserBiz.CustomerUserEffectiveRightsAsync(UserId, sourceWo.Tags);
|
||||
var ret = new CustomerWorkOrder();
|
||||
ret.Id = sourceWo.Id;
|
||||
|
||||
Reference in New Issue
Block a user