ship
SalesForce Simplified

Your Go-To Resource for Streamlined Solutions and Expert Guidance

mountains
Empower Your Business
Dive deep into the world of CRM excellence, where innovation meets practicality, and transform your Salesforce experience with Forceshark's comprehensive resources

Crafting a Custom Permission Viewer in Salesforce LWC

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 the handleRowAction 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>&nbsp;<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.