Skip to content

feat: role-based feature visibility — App Profiles + Platform Profiles (backend) - #2669

Open
ppatel9703 wants to merge 25 commits into
devfrom
feat/ui-profiles-rework
Open

feat: role-based feature visibility — App Profiles + Platform Profiles (backend)#2669
ppatel9703 wants to merge 25 commits into
devfrom
feat/ui-profiles-rework

Conversation

@ppatel9703

@ppatel9703 ppatel9703 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a Role-Based Feature Visibility system to SEMOSS with two independent subsystems:

  • App Profiles — flat per-portal feature gating with profiles and delegated manager access
  • Platform Profiles — platform-wide feature gating for controlling shell/nav visibility globally

Both systems are backed by new security DB tables, fully integrated into the SEMOSS OWL/metamodel, and exposed via Pixel reactors.


What Changed

New Tables (Security DB)

App Profile system — 5 tables (group/subgroup tables removed in this branch):

Table Purpose
APP_FEATURE Feature catalogue per app (featureId, featureKey, appId)
APP_PROFILE Named flat profile per app (isDefault flag; IS_GROUP column retained but always false)
APP_PROFILE_FEATURE Feature enabled/disabled state per profile
APP_USER_PROFILE Explicit user → profile assignment
APP_PROFILE_MANAGER Non-admin users delegated manage rights for an app

Platform Profile system — 3 tables:

Table Purpose
PLATFORM_PROFILE Named platform-wide profile
PLATFORM_PROFILE_FEATURE Feature key/enabled state per platform profile
PLATFORM_USER_PROFILE User → platform profile assignment

Tables are created in AbstractSecurityUtils on startup (idempotent CREATE IF NOT EXISTS) and registered in SecurityOwlCreator with full OWL relations.


App Profile Reactors — 21 reactors in src/prerna/reactor/appprofile/

feature/

  • CreateAppFeature, UpdateAppFeature, DeleteAppFeature, GetAppFeatures
  • CheckAppFeature, CheckFeature (alias), GetAppUserFeatures

profile/

  • CreateAppProfile, UpdateAppProfile, DeleteAppProfile, GetAppProfiles
  • SetAppProfileFeature, GetAppProfileFeatures, GetAppProfileUsers

manager/

  • AddAppProfileManager, RemoveAppProfileManager, GetAppProfileManagers

user/

  • AssignAppUserProfile, RemoveAppUserProfile
  • GetUserAppProfiles, GetUserProfile (legacy alias)

Removed in this branch (group/subgroup model):

  • subgroup/ — 9 reactors deleted: CreateAppSubgroup, UpdateAppSubgroup, DeleteAppSubgroup, GetAppSubgroups, SetAppSubgroupFeature, GetAppSubgroupFeatures, GetAppSubgroupUsers, AssignAppUserSubgroup, RemoveAppUserSubgroup

Platform Profile Reactors — 10 reactors in src/prerna/reactor/platformprofile/

  • CreatePlatformProfile, UpdatePlatformProfile, DeletePlatformProfile, GetPlatformProfiles
  • SetPlatformFeature, GetPlatformFeatures
  • AssignUserPlatformProfile, RemoveUserPlatformProfile, GetPlatformProfileUsers
  • GetUserPlatformFeatures

Supporting Changes

File Change
AbstractSecurityUtils.java Creates all new tables on startup
SecurityOwlCreator.java Registers tables + OWL relations to PROJECT, SMSS_USER
ReactorKeysEnum.java Added FEATURE_KEY, ENABLED, IS_DEFAULT, IS_GROUP, PROFILE_ID keys
SystemEngineRegistry.java Added prerna.reactor.appprofile to security DB allowed prefix set

Key Design Decisions

Flat Profile Model (group/subgroup removed)

The original design included group-type profiles with subgroups, which caused four concrete bugs:

  1. setProfileFeature threw for group profiles but the UI rendered toggles anyway
  2. userCount was wrong for group profiles (counted APP_USER_PROFILE not APP_USER_SUBGROUP)
  3. Direct user assignment to a group profile created a silent feature dead-end
  4. Delegated managers could see subgroup write controls that would fail at the backend

All profiles are now flat. Feature states are set directly on the profile. IS_GROUP column is retained in the DB schema for continuity but is always written as false and never read.

Feature Resolution Order (App Profiles)

  1. Explicit profile assignment → use that profile's feature states
  2. Default profile (IS_DEFAULT=true) as fallback
  3. All false if no assignment and no default

Access Control

  • App profile writes: admin OR user in APP_PROFILE_MANAGER for that app
  • Platform profile writes: admin only
  • Evaluation reads (CheckAppFeature, GetAppUserFeatures, GetUserAppProfiles): any authenticated user, returns only their own data — canEvaluateFeatures guard removed

getUserFeatures return type

AppProfileUtils.getUserFeatures now returns Map<String, Boolean> (featureKey → true/false for every feature in the app's catalogue). Previously returned Map<String, Object> with nested feature metadata.

getExplicitUserProfiles — two-query in-memory join

OWL join traversal between APP_USER_PROFILE and APP_PROFILE was unreliable. Replaced with two single-table queries joined in memory.

Duplicate profile name guard

createProfile rejects duplicate names per app at the application layer before INSERT.

Bulk Assignment

AssignAppUserProfile accepts a list of user IDs. Returns { assigned, skipped, errors } — each user processed independently.

Platform Profile — replace semantics

A user can be in at most one platform profile. Assigning to a new profile deletes the prior assignment first. App profiles are additive (a user can be in multiple).


How to Test

# Create feature + profile
CreateAppFeature(app=["<appId>"], featureKey=["dark-mode"]);
CreateAppProfile(app=["<appId>"], name=["Standard"], isDefault=["true"]);

# Set feature state
SetAppProfileFeature(app=["<appId>"], profileId=["<profileId>"], featureId=["<featureId>"], enabled=["true"]);

# Bulk assign users
AssignAppUserProfile(app=["<appId>"], userId=["<user1>", "<user2>"], profileId=["<profileId>"]);
# Returns { assigned: [user1, user2], skipped: [], errors: {} }

# Evaluate features
GetAppUserFeatures(app=["<appId>"]);
# Returns { "dark-mode": true }

CheckAppFeature(app=["<appId>"], featureKey=["dark-mode"]);
# Returns true

# Delegate manager
AddAppProfileManager(app=["<appId>"], userId=["<user1>"]);

Checklist

  • No protected files committed (RDF_Map.prop, social.properties, db/, server.xml)
  • All new tables created idempotently in AbstractSecurityUtils
  • OWL relations registered in SecurityOwlCreator
  • SystemEngineRegistry allowlist updated for new package prefix
  • Write operations gated on admin or manager check
  • Evaluation reads open to any authenticated user (canEvaluateFeatures guard removed)
  • All reads use QueryColumnSelector aliases + QueryExecutionUtility (no raw JDBC for reads)
  • Bulk assign: each user processed independently, non-empty list validated
  • Group/subgroup model fully removed — 9 reactor files deleted, all subgroup methods removed from AppProfileUtils
  • Platform profile one-per-user semantics enforced (DELETE before INSERT on reassign)
  • IS_GROUP column preserved in schema for continuity, always written false, never read

Patel, Parth and others added 3 commits June 24, 2026 16:25
…rm Profiles)

Part A — App Profiles:
- AbstractSecurityUtils: 4 new tables (APP_PROFILE, APP_FEATURE,
  APP_PROFILE_FEATURE, APP_USER_PROFILE) bootstrapped in initialize()
- AppProfileUtils: full CRUD for profiles, features, profile-feature
  assignments, user-profile assignments, and feature evaluation
  (checkFeature, getUserFeatures — enabled-only, fail-closed)
- 16 reactors in prerna.reactor.appprofile covering all Pixel operations
- SecurityProjectUtils: cascade deleteUserProfile calls in removeProjectUser,
  removeExpiredProjectUser, and removeProjectUsers to prevent stale assignments

Part B — Platform Profiles:
- AbstractSecurityUtils: 3 new tables (PLATFORM_PROFILE,
  PLATFORM_PROFILE_FEATURE, PLATFORM_USER_PROFILE)
- PlatformProfileUtils: CRUD for platform profiles, predefined nav-key
  feature toggles, user assignments, and getUserFeatures (fail-open for
  unassigned users)
- 9 reactors in prerna.reactor.platformprofile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getProfiles() and getFeatures() used SelectQueryStruct/WrapperManager
which performs OWL metadata lookups. APP_PROFILE and APP_FEATURE are
not registered in SEMOSS's schema, so getPhysicalPropertyNameFromConceptualName
returns null causing NPE during SQL composition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add getPlatformProfileUsers() to PlatformProfileUtils — queries
  PLATFORM_USER_PROFILE for all users assigned to a given profile
- Add GetPlatformProfileUsersReactor (admin-only) so the admin UI
  Members tab can list users assigned to each platform profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ppatel9703
ppatel9703 requested a review from a team as a code owner June 24, 2026 20:40
@snyk-io

snyk-io Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Patel, Parth and others added 22 commits June 25, 2026 13:57
…code quality

- Multi-profile: users can be assigned to multiple profiles simultaneously;
  GetAppUserFeatures returns union of features across all assignments
- Group-style profiles (isGroup=true): named sub-groups with independent
  feature flags; users assigned to sub-groups via AssignAppUserSubgroup
- Delegated BU admin: APP_PROFILE_MANAGER table grants 'assign' permission
  so non-owners can manage user assignments without full profile management rights
- New DB tables: APP_PROFILE_SUBGROUP, APP_SUBGROUP_FEATURE, APP_USER_SUBGROUP,
  APP_PROFILE_MANAGER (via SecurityOwlCreator + AbstractSecurityUtils migration)
- New Pixel commands: GetUserAppProfiles, GetAppUserFeatures, CheckAppFeature,
  AssignAppUserProfile, RemoveAppUserProfile, CreateAppSubgroup, UpdateAppSubgroup,
  DeleteAppSubgroup, GetAppSubgroups, SetAppSubgroupFeature, GetAppSubgroupFeatures,
  AssignAppUserSubgroup, RemoveAppUserSubgroup, AddAppProfileManager,
  GetAppProfileManagers, RemoveAppProfileManager, GetUserAppProfile
- Renamed for domain clarity: GetProfileFeatures→GetAppProfileFeatures,
  SetProfileFeature→SetAppProfileFeature, SetSubgroupFeature→SetAppSubgroupFeature,
  GetSubgroupFeatures→GetAppSubgroupFeatures, GetSubgroupUsers→GetAppSubgroupUsers,
  GetProfileUsers→GetAppProfileUsers, AddProfileManager→AddAppProfileManager,
  GetProfileManagers→GetAppProfileManagers, RemoveProfileManager→RemoveAppProfileManager
- Backwards-compat aliases kept with @deprecated for old Pixel names
- Code quality: classLogger on all reactors, ReactorKeysEnum constants for all
  key strings (9 new enum entries added), PixelOperationType on all NounMetadata
  returns, CUSTOM_DATA_STRUCTURE for list-of-maps (was VECTOR), N+1 queries
  eliminated in getProfiles() and getSubgroups() via SQL subqueries,
  canEvaluateFeatures admin bypass added
- featureKey param renamed from "key" in CreateAppFeature/UpdateAppFeature

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d read access to profile managers

- AppProfileUtils: `getExplicitUserSubgroups` now returns PROFILE_ID so the
  parent group profile can be looked up for each subgroup membership
- AppProfileUtils: `checkFeature` now unions subgroup features with the parent
  group profile's base features, so group-level toggles apply to all members
- AppProfileUtils: `getUserFeatures` does the same union for the full feature
  list returned to the client
- 8 read-only reactors (GetAppFeatures, GetAppProfileFeatures,
  GetAppProfileManagers, GetAppProfileUsers, GetAppProfiles,
  GetAppSubgroupFeatures, GetAppSubgroups, GetUserProfile) changed from
  `canManageProfiles` to `canAssignProfiles` so profile managers can view
  profiles and assign users without needing full admin
- PlatformProfileUtils: import cleanup and remove deprecated nav.build
  from predefined feature keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…c, convert reads to SQS

- Move AppProfileUtils from prerna.auth.utils to prerna.reactor.appprofile
- Move PlatformProfileUtils from prerna.auth.utils to prerna.reactor.platformprofile
- Add prerna.reactor.appprofile and prerna.reactor.platformprofile to
  SecurityDb access allowlist in SystemEngineRegistry
- Add SecurityProjectUtils import for relocated AppProfileUtils
- Convert AppProfileUtils.getFeatures() from PreparedStatement to
  SelectQueryStruct (simple single-table read); keep correlated subqueries
  and JOINs as PreparedStatement
- Add class-level Javadoc to all 34 appprofile reactors and 10 platform-
  profile reactors
- Add method-level Javadoc to all public static methods in both util classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and convert reads to QS

- Move 30 appprofile reactors from flat package into feature/, profile/,
  subgroup/, manager/, user/ sub-packages for maintainability
- Remove legacy alias reactors (AssignUserProfile, RemoveUserProfile,
  GetUserAppProfile, GetUserFeatures) that duplicated newer equivalents
- Refactor AppProfileUtils read methods to use aliased QueryColumnSelector
  + QueryExecutionUtility.flushRsToMap/flushToInteger, eliminating raw
  JDBC (ResultSet, WrapperManager, PreparedStatement) from all read paths
- Replace correlated subqueries in getProfiles/getSubgroups with
  LEFT JOIN + GROUP BY + COUNT via QS relations
- Convert PlatformProfileUtils reads to same QS + QueryExecutionUtility
  pattern; replace canManage check with SecurityAdminUtils.userIsAdmin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AssignAppUserProfile, AssignAppUserSubgroup, and AssignUserPlatformProfile
now accept a list of userIds so the frontend can batch-assign in one call.
Each user is processed independently and the response buckets assigned,
skipped (already in profile), and errors (user not found or DB error).

Platform profiles remain one-per-user by design — assigning a user already
in a different platform profile replaces that assignment (DELETE + INSERT).
App profiles continue to be additive — a user can be in multiple profiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the entire IS_GROUP/subgroup hierarchy from the app profile system.
All profiles are now flat — features and users are managed directly on the
profile, with no subgroup layer.

Deleted (9 files):
- subgroup/AssignAppUserSubgroupReactor.java
- subgroup/CreateAppSubgroupReactor.java
- subgroup/DeleteAppSubgroupReactor.java
- subgroup/GetAppSubgroupFeaturesReactor.java
- subgroup/GetAppSubgroupUsersReactor.java
- subgroup/GetAppSubgroupsReactor.java
- subgroup/RemoveAppUserSubgroupReactor.java
- subgroup/SetAppSubgroupFeatureReactor.java
- subgroup/UpdateAppSubgroupReactor.java

Modified:
- AppProfileUtils: removed all subgroup CRUD/query methods; createProfile/
  updateProfile signatures drop isGroup param (always writes false to DB);
  getProfiles removes IS_GROUP from SELECT/GROUP BY; deleteProfile removes
  subgroup cascade; getUserFeatures returns Map<String,Boolean>; feature
  resolution is now explicit-profile → default-profile (no subgroup step);
  getExplicitUserProfiles rewritten as two-query in-memory join; adds
  duplicate profile name guard on createProfile; removes canEvaluateFeatures
- CreateAppProfileReactor: removed IS_GROUP key from keysToGet/keyRequired
- UpdateAppProfileReactor: removed IS_GROUP key from keysToGet/keyRequired
- CheckAppFeatureReactor, CheckFeatureReactor, GetAppUserFeaturesReactor:
  removed canEvaluateFeatures access guard (any app viewer can evaluate)
- GetUserAppProfilesReactor: updated return type to List<Map<String,Object>>,
  removed canEvaluateFeatures guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants