case 4712 - notification subscriptions are the caller's own; object events only to subscribers who may read the record type

Step 6 of 4712 (security review F17). A subscription kept the UserId sent by
the client, and object events (created, modified, deleted, age) were matched by
record type only, so a user could get record names of types it may not read,
delivered to any address. Now a subscription is created for the caller and an
update cannot move it to another user, unless the caller administers users; and
object events skip subscribers without read rights on the record type.

Checked: Service's subscription asked for SuperUser is saved as its own; with
"customer created" subscriptions, Inventory (no read on customers) got 0 events,
Service 1. Full suite: 109 passed, 5 skipped, the same 2 known failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 14:39:40 -07:00
parent 351eb2d851
commit 87fdc12dce
4 changed files with 23 additions and 2 deletions

View File

@@ -72,6 +72,13 @@ namespace AyaNova.Biz
// PROCESS STANDARD CREATE NOTIFICATION
//
//
//case 4712: object events (created, modified, deleted, age) go only to subscribers with read rights on that type
private static async Task<bool> SubscriberMayReadAsync(AyContext ct, long userId, AyaType aType)
{
var roles = await ct.User.AsNoTracking().Where(z => z.Id == userId).Select(z => z.Roles).FirstOrDefaultAsync();
return AyaNova.Api.ControllerHelpers.Authorized.HasReadFullRole(roles, aType);
}
public static async Task ProcessStandardObjectCreatedEvents(ICoreBizObjectModel newObject, AyContext ct)
{
//CREATED SUBSCRIPTIONS
@@ -81,6 +88,8 @@ namespace AyaNova.Biz
{
//not for inactive users
if (!await UserBiz.UserIsActive(sub.UserId)) continue;
//case 4712: not for users who may not read this type of record
if (!await SubscriberMayReadAsync(ct, sub.UserId, newObject.AyaType)) continue;
if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags))
{
NotifyEvent n = new NotifyEvent()
@@ -107,6 +116,8 @@ namespace AyaNova.Biz
{
//not for inactive users
if (!await UserBiz.UserIsActive(sub.UserId)) continue;
//case 4712: not for users who may not read this type of record
if (!await SubscriberMayReadAsync(ct, sub.UserId, newObject.AyaType)) continue;
if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags))
{
//Note: age is set by advance notice which is consulted by CoreJobNotify in it's run so the deliver date is not required here only the reference EventDate to check for deliver
@@ -145,6 +156,8 @@ namespace AyaNova.Biz
{
//not for inactive users
if (!await UserBiz.UserIsActive(sub.UserId)) continue;
//case 4712: not for users who may not read this type of record
if (!await SubscriberMayReadAsync(ct, sub.UserId, newObject.AyaType)) continue;
if (ObjectHasAllSubscriptionTags(newObject.Tags, sub.Tags))
{
NotifyEvent n = new NotifyEvent()
@@ -185,6 +198,8 @@ namespace AyaNova.Biz
{
//not for inactive users
if (!await UserBiz.UserIsActive(sub.UserId)) continue;
//case 4712: not for users who may not read this type of record
if (!await SubscriberMayReadAsync(ct, sub.UserId, bizObject.AyaType)) continue;
if (ObjectHasAllSubscriptionTags(bizObject.Tags, sub.Tags))
{
//TODO: On deliver should point to history event log record or take from there and insert into delivery message?