In this comprehensive tutorial, we'll guide you through the process of building the AccessAuditUserApp
Salesforce Lightning Web Component (LWC). This component is designed to audit and monitor users with a custom permission called FSRK_ViewSensitiveData
. Additionally, we'll create two sub-components, AccessAuditUserDetail
and AccessAuditUserListTable
, each serving a specific purpose in displaying user information.
Use Case: Compliance Monitoring System
Imagine a company that has implemented custom permissions to control access to specific data within Salesforce. For example, there might be a custom permission called FSRK_ViewSensitiveData
that grants users the ability to view sensitive financial data.
The challenge is to regularly audit and monitor which users within the organization have been assigned this custom permission. This information is crucial for compliance reporting, internal audits, and ensuring that only authorized personnel can access sensitive data.
Salesforce Solution
Salesforce, with its robust user management features, can help address this challenge. Here's how the system can be configured:
Custom Permissions
Define custom permissions such as FSRK_ViewSensitiveData
within Salesforce. These permissions should be associated with specific profiles or permission sets.
User Profiles and Permission Sets
Assign the custom permissions to relevant user profiles or permission sets based on the roles and responsibilities of different users.
Monitoring Dashboard
Let's create a custom AccessAuditUserApp
LWC component that displays real-time information on users with the FSRK_ViewSensitiveData
custom permission. The component provides a central hub for auditing users with access to sensitive data. Let's break down the key elements of this component:
HTML Template
<template>
<lightning-card title="Sensitive Data Access Audit" icon-name="standard:user">
<!-- Notification about the purpose of the component -->
<div class="slds-scoped-notification slds-is-info slds-m-around_medium" role="status">
<div class="slds-media">
<div class="slds-media__figure">
<span class="slds-icon_container slds-icon-utility-info slds-current-color" title="Info">
<lightning-icon icon-name="utility:info" alternative-text="Info" size="small"></lightning-icon>
</span>
</div>
<div class="slds-media__body">
<p class="slds-text-body_small">
<!-- Notification content -->
Explore users with special access to sensitive data. The table displays users who have been
granted the <b>FSRK_ViewSensitiveData</b> custom permission. For more detailed information,
utilize the "View Details" option in the table actions.
</p>
</div>
</div>
</div>
<!-- Sub-component displaying user information -->
<c-fsrk_-access-audit-user-list-table
user-permission-list={userPermissionList}
onview_details={handleViewDetails}
></c-fsrk_-access-audit-user-list-table>
</lightning-card>
</template>
JS Controller
import {LightningElement, track, wire} from 'lwc';
import getUsersWithCustomPermission from '@salesforce/apex/FSRK_CustomPermUserTracker.getUsersWithCustomPermission';
import userDetailModal from 'c/fsrk_AccessAuditUserDetail';
export default class FsrkAccessAuditUserApp extends LightningElement {
userPermissionList;
selectedUserId;
@wire(getUsersWithCustomPermission, { customPermName: 'FSRK_ViewSensitiveData' })
wiredUserPermission({ error, data }) {
// Logic to handle retrieved user permissions
if (data) {
this.userPermissionList = data;
console.log('FsrkAccessAuditUserApp::wiredUserPermission', JSON.parse(JSON.stringify(data)));
} else if (error) {
console.error('FsrkAccessAuditUserApp::Error fetching user permissions', error);
}
}
handleViewDetails(event) {
// Logic to handle the "View Details" action
this.selectedUserId = event.detail.userId;
userDetailModal.open({
size: 'small',
userPermissionDto: this.getSelectedUser()
});
}
getSelectedUser() {
// Logic to retrieve selected user details
return this.userPermissionList.find(user => user.userId === this.selectedUserId);
}
}
Data Flow
- The Apex controller fetches user data based on the custom permission and passes it to the main component.
- The main component passes the user data to the table sub-component for display.
- Users can interact with the table and trigger the "View Details" event to explore more information about a specific user.
This approach ensures a modular and organized structure, making it easier to maintain and extend the functionality of the access audit component.
User Permissions Table
The FsrkAccessAuditUserListTable
component serves as the primary interface for displaying users in a tabular format. It provides a structured view with specific columns and allows users to initiate actions, such as viewing detailed information.
HTML Template
<template>
<!-- Lightning Datatable to display user information -->
<lightning-datatable
key-field="userId"
data={userPermissionList}
columns={columns}
onrowaction={handleRowAction}
></lightning-datatable>
</template>
JS Controller
import {api, LightningElement} from 'lwc';
// Definition of actions for the row in the datatable
const actions = [
{ label: 'View details', name: 'view_details' }
];
// Definition of columns for the datatable
const columns = [
{ label: 'Name', fieldName: 'name', type: 'text', sortable: false },
{ label: 'Alias', fieldName: 'alias', type: 'text', sortable: false },
{ label: 'Username', fieldName: 'userName', type: 'text', sortable: false },
{ label: 'Created Date', fieldName: 'createdDate', type: 'date', sortable: false },
{ label: 'Active', fieldName: 'isActive', type: 'boolean', sortable: false },
{
type: 'action',
typeAttributes: { rowActions: actions },
}
];
export default class FsrkAccessAuditUserListTable extends LightningElement {
@api userPermissionList;
columns = columns;
// Handler for row actions in the datatable
handleRowAction(event) {
const actionName = event.detail.action.name;
const row = event.detail.row;
if (actionName === 'view_details') {
const detailEvent = new CustomEvent('view_details', {
detail: { userId: row.userId },
});
this.dispatchEvent(detailEvent);
}
}
}
Data Flow
- The component receives the list of users
userPermissionList
as an attribute. - The Lightning Datatable is configured with specific columns (columns) and renders the user data.
- Row actions, specifically
View details
trigger thehandleRowAction
method. - The method dispatches a custom event (
view_details
) with the selected user's ID for further processing by the parent component. - This approach ensures an organized and interactive presentation of user data, allowing users to easily explore and initiate actions for detailed information.
Viewing User Information
The AccessAuditUserDetail
component serves as a detailed view of user information, providing a structured presentation of relevant details, including permission sets and profile information.
HTML Template
<template>
<!-- Lightning Modal Header with dynamic label -->
<lightning-modal-header label={userDetailTitle}></lightning-modal-header>
<!-- Lightning Modal Body -->
<lightning-modal-body>
<div class="slds-p-around_medium">
<!-- User information display -->
<p><b>Name:</b> {userPermissionDto.name}</p>
<p><b>Username:</b> {userPermissionDto.userName}</p>
<p><b>Alias:</b> {userPermissionDto.alias}</p>
<p><b>Created Date:</b> {userPermissionDto.createdDate}</p>
<p><b>Active:</b> {userPermissionDto.isActive}</p>
<p lwc:if={userPermissionDto.role}><b>Role:</b> {userPermissionDto.role.Name}</p>
<!-- Info Notification -->
<div class="slds-scoped-notification slds-is-info slds-m-around_medium" role="status">
<div class="slds-media">
<div class="slds-media__figure">
<span class="slds-icon_container slds-icon-utility-info slds-current-color" title="Info">
<lightning-icon icon-name="utility:info" alternative-text="Info" size="small"></lightning-icon>
</span>
</div>
<div class="slds-media__body">
<p class="slds-text-body_small">
Sections "Permission Sets" and "Profile" display information about the user's profile
and/or permission sets that grant the user the FSRK_ViewSensitiveData custom permission.
</p>
</div>
</div>
</div>
<!-- User information display: Permission Sets -->
<p><b>Permission Sets:</b></p>
<!-- Display permission sets as a list (or "none" if no permission sets) -->
<template if:true={userPermissionDto.permissionSets}>
<ul>
<template for:each={permSetDtoList} for:item="dto">
<li key={dto.Id}>
<a href={dto.setupUrl} target="_blank">{dto.Name}</a>
</li>
</template>
</ul>
</template>
<template if:false= {userPermissionDto.permissionSets}>
<ul><li>none</li></ul>
</template>
<!-- User information display: Profile -->
<template if:true={userPermissionDto.profile}>
<!-- Display profile with a link to setup URL -->
<p><b>Profile:</b> <a href={profileDto.setupUrl} target="_blank">{profileDto.Name}</a></p>
</template>
<!-- Display "none" if no profile -->
<template if:false= {userPermissionDto.profile}>
<p><b>Profile:</b> none</p>
</template>
</div>
</lightning-modal-body>
</template>
JS Controller
import {api} from 'lwc';
import LightningModal from "lightning/modal";
export default class FsrkAccessAuditUserDetail extends LightningModal {
// Exposing a public property to receive user permission data
@api userPermissionDto;
// Getter function to dynamically generate the user detail title
get userDetailTitle() {
return `User Detail - ${this.userPermissionDto.name}`;
}
// Getter function to transform permission sets into DTO list with setup URLs
get permSetDtoList() {
if (this.userPermissionDto.permissionSets) {
return this.userPermissionDto.permissionSets.map(permissionSet => ({
Id: permissionSet.Id,
Name: permissionSet.Name,
setupUrl: '/' + permissionSet.Id,
}));
}
return [];
}
// Getter function to transform profile data into DTO with a setup URL
get profileDto() {
if (this.userPermissionDto.profile) {
return {
Id: this.userPermissionDto.profile.Id,
Name: this.userPermissionDto.profile.Name,
setupUrl: '/' + this.userPermissionDto.profile.Id,
};
}
return null;
}
}
Data Flow
- The component receives the
userPermissionDto
attribute, containing detailed user information. The modal dynamically constructs its title based on the user's name. - The body of the modal displays various user details, including permission sets and profile information.
- Permission sets and profile information are presented as clickable links, facilitating navigation to the respective setup pages.
This approach ensures a comprehensive and user-friendly presentation of detailed user information within a modal component.
Apex Controller
The FSRK_CustomPermUserTracker
class is designed to track and retrieve information about users who have been granted a specific custom permission (FSRK_ViewSensitiveData
).
public with sharing class FSRK_CustomPermUserTracker {
private final String customPermName;
private final Set parentIdSet;
// AuraEnabled method to get users with a custom permission
@AuraEnabled(Cacheable = true)
public static List getUsersWithCustomPermission(String customPermName) {
FSRK_CustomPermUserTracker tracker = new FSRK_CustomPermUserTracker(customPermName);
List usersWithProfiles = tracker.getUsersWithPermInProfiles();
Map userIdPermSetDtoMap = tracker.getUsersWithPermInPermSet();
Map userIdPermissionDto = new Map();
for (User user : usersWithProfiles) {
userIdPermissionDto.put(user.Id, new UserPermissionDto(user, user.Profile, null));
}
for (UserPermissionSetDto dto : userIdPermSetDtoMap.values()) {
UserPermissionDto existingDto = userIdPermissionDto.get(dto.user.Id);
if (existingDto != null) {
existingDto.permissionSets = dto.permissionSets;
} else {
userIdPermissionDto.put(dto.user.Id, new UserPermissionDto(dto.user, null, dto.permissionSets));
}
}
return userIdPermissionDto.values();
}
// Constructor to initialize the custom permission name and parent IDs
public FSRK_CustomPermUserTracker(String customPermName) {
this.customPermName = customPermName;
this.parentIdSet = getSetupEntityAccessParentIds();
}
// Query to retrieve users with the custom permission in their profiles
public List getUsersWithPermInProfiles() {
return [
SELECT Id, Name, Username, Alias, IsActive, CreatedDate, ProfileId, Profile.Name, UserRole.Name
FROM User
WHERE ProfileId IN (
SELECT ProfileId
FROM PermissionSet
WHERE Id IN :parentIdSet
)
];
}
// Method to retrieve users with the custom permission in their permission sets
public Map getUsersWithPermInPermSet() {
Map userPermissionSetMap = new Map();
for (PermissionSetAssignment psa : [
SELECT AssigneeId, Assignee.Name, Assignee.Username, Assignee.Alias, Assignee.IsActive,
Assignee.CreatedDate, Assignee.UserRole.Name, PermissionSetId, PermissionSet.Name,
PermissionSet.ProfileId
FROM PermissionSetAssignment
WHERE PermissionSetId IN :parentIdSet
]) {
if (psa.PermissionSet.ProfileId != null) continue;
Id userId = psa.AssigneeId;
if (!userPermissionSetMap.containsKey(userId)) {
userPermissionSetMap.put(userId, new UserPermissionSetDto(psa.Assignee, new List()));
}
userPermissionSetMap.get(userId).permissionSets.add(psa.PermissionSet);
}
return userPermissionSetMap;
}
// Method to get parent IDs for the custom permission
private Set getSetupEntityAccessParentIds() {
Set parentIdSet = new Set();
for (SetupEntityAccess sea : [
SELECT ParentId
FROM SetupEntityAccess
WHERE SetupEntityId IN (
SELECT Id
FROM CustomPermission
WHERE DeveloperName = :customPermName
)
]) {
parentIdSet.add(sea.ParentId);
}
return parentIdSet;
}
public class UserPermissionDto {
@AuraEnabled
public Id userId;
@AuraEnabled
public String name;
@AuraEnabled
public String userName;
@AuraEnabled
public String alias;
@AuraEnabled
public Profile profile;
@AuraEnabled
public List permissionSets;
@AuraEnabled
public Datetime createdDate;
@AuraEnabled
public Boolean isActive;
@AuraEnabled
public UserRole role;
public UserPermissionDto(User user, Profile profile, List permissionSets) {
this.userId = user.Id;
this.name = user.Name;
this.userName = user.Username;
this.alias = user.Alias;
this.profile = profile;
this.permissionSets = permissionSets;
this.createdDate = user.CreatedDate;
this.isActive = user.IsActive;
this.role = user.UserRole;
}
}
public class UserPermissionSetDto {
public User user;
public List permissionSets;
public UserPermissionSetDto(User user, List permissionSets) {
this.user = user;
this.permissionSets = permissionSets;
}
}
}
Methods
getUsersWithCustomPermission(String customPermName)
- Entry point method to fetch and organize user information based on the specified custom permission.
- Utilizes the helper methods getUsersWithPermInProfiles and getUsersWithPermInPermSet.
getUsersWithPermInProfiles()
- Queries and retrieves users with the specified custom permission from their profiles.
- Populates a UserPermissionDto object for each user with relevant details.
getUsersWithPermInPermSet()
- Queries and retrieves users with the specified custom permission from permission sets.
- Constructs a UserPermissionSetDto for each user with associated permission sets.
getSetupEntityAccessParentIds()
- Retrieves the parent IDs associated with the custom permission from SetupEntityAccess and returns them as a set.
Conclusion
Congratulations! You've successfully explored the creation of the FsrkAccessAuditUserApp
component. This tutorial covered the essential elements, including the notification, sub-component, and controller logic. The FSRK_CustomPermUserTracker
Apex controller complements the LWC by handling data retrieval.
Feel free to customize and enhance the components further based on your specific business requirements. For more advanced features and capabilities, refer to the Salesforce documentation.