Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ jobs:

# Run PMD scan
- name: 'Run PMD scan'
run: ~/pmd/bin/pmd check -d sfdx-source/LabsActionPlans -R pmd/deployRules.xml -f text --cache .pmdCache -min 2
run: ~/pmd/bin/pmd check -d sfdx-source/LabsActionPlans -R pmd/deployRules.xml -f text --cache .pmdCache --minimum-priority 2
16 changes: 8 additions & 8 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
- name: 'Checkout source code'
uses: actions/checkout@v3

# Install PMD
- name: 'Install PMD'
# Install Latest PMD
- name: 'Install Latest PMD'
run: |
PMD_VERSION=$(curl -s https://api.github.com/repos/pmd/pmd/releases/latest | grep '.tag_name' | sed 's:.*/::' | sed 's:",::')
wget https://github.com/pmd/pmd/releases/download/pmd_releases%2F$PMD_VERSION/pmd-dist-$PMD_VERSION-bin.zip
Expand Down Expand Up @@ -67,9 +67,9 @@ jobs:
run: sf org login sfdx-url --sfdx-url-file ./DEVHUB_SFDX_URL.txt --alias devhub --set-default-dev-hub

# Add namespace to project config
- name: Add namespace to project config
run: |
sed -i 's,"namespace": "","namespace": "LabsActionPlans",' sfdx-project.json
# - name: Add namespace to project config
# run: |
# sed -i 's,"namespace": "","namespace": "LabsActionPlans",' sfdx-project.json

# Create scratch org
- name: 'Create scratch org'
Expand Down Expand Up @@ -104,6 +104,6 @@ jobs:
run: sf org delete scratch --no-prompt --target-org ActionPlans

# Remove namespace from project config
- name: Remove namespace from project config
run: |
sed -i 's,"namespace": "LabsActionPlans","namespace": "",' sfdx-project.json
# - name: Remove namespace from project config
# run: |
# sed -i 's,"namespace": "LabsActionPlans","namespace": "",' sfdx-project.json
11 changes: 6 additions & 5 deletions pmd/deployRules.xml
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
<!-- <rule ref="category/apex/errorprone.xml/OverrideBothEqualsAndHashcode" /> -->

<!-- VISUALFORCE RULES -->
<rule ref="category/vf/security.xml/VfHtmlStyleTagXss" message="Dynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate">
<rule ref="category/visualforce/security.xml/VfHtmlStyleTagXss" message="Dynamic EL content within URL in style tag should be URLENCODED or JSINHTMLENCODED as appropriate">
<!--
<apex:page>
<style>
Expand All @@ -324,12 +324,13 @@
<priority>3</priority>
</rule>

<!--Example <apex:outputText value="Potential XSS is {! here }" escape="false" /> -->
<!-- <rule ref="category/vf/security.xml/VfUnescapeEl" message="Avoid unescaped user controlled content in EL as it results in XSS.">
<!--Example
<apex:outputText value="Potential XSS is {! here }" escape="false" /> -->
<!-- <rule ref="category/visualforce/security.xml/VfUnescapeEl" message="Avoid unescaped user controlled content in EL as it results in XSS.">
<priority>3</priority>
</rule> -->

<!-- Error on apex:page action usage -->
<!-- <apex:page controller="AcRestActionsController" action="{!csrfInitMethod}" > -->
<rule ref="category/vf/security.xml/VfCsrf" />
</ruleset>
<rule ref="category/visualforce/security.xml/VfCsrf" />
</ruleset>
4 changes: 2 additions & 2 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"ancestorVersion": "HIGHEST",
"releaseNotesUrl": "https://github.com/SalesforceLabs/ActionPlansV4/blob/main/README.md",
"definitionFile": "config/install-scratch-def.json",
"versionDescription": "Action Plans now suppports Tasks owned by Queues and Professional Edition",
"versionDescription": "Action Plans now supports Tasks owned by Queues and Professional Edition",
"postInstallScript": "ActionPlansPostInstallScript",
"postInstallUrl": "https://salesforcelabs.github.io/ActionPlansV4/"
},
Expand All @@ -20,7 +20,7 @@
"name": "Action Plans",
"namespace": "",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "56.0",
"sourceApiVersion": "65.0",
"packageAliases": {
"Action Plans": "0Ho5f000000oLlrCAE",
"ActionPlans": "0Ho5f000000oLlrCAE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ SPDX-License-Identifier: BSD-3-Clause
For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
/**
* Invocable class for creating Action Plans from templates.
*
* This class uses `inherited sharing` to respect the calling context's sharing rules:
* - When invoked from Flows/Process Builder (system context), it executes without sharing restrictions
* - When invoked from Apex code with sharing rules, it respects those rules
*
* This approach provides flexibility for automation while maintaining security when called from user context.
*
* @group Invocable
* @author {@link [David Schach](https://github.com/dschach)}
* @since 2022
*/
global without sharing class ActionPlanCreateInvocable {
global inherited sharing class ActionPlanCreateInvocable {
/**
* Invocable Apex for creating Action Plans from a template, a parent ID, and days from now to start the task due dates
* @param requests Wrapper of `CreateActionPlanRequest`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,76 @@ public inherited sharing class ActionPlansSectionHeaderController {
}

/**
* Main code for lightning lookup. Included in this class because we use this as the controller for our lookups
* <br> the code is duplicated in AciontPlanCreationController because there is a lookup directly in that page (for now)
* <br>This is a different approach than in ActionPlanCreationController, as we pass in the full query here, but there it is
* done by passing only the search key.
* Query a specific record by Id for lightning lookup component.
* <br>This method uses binding variables and Id casting to prevent SOQL injection.
* <br>The sObject type is derived directly from the Id for enhanced security.
*
* @param queryText SOQL query to return records for the lookup
* @param recId The Id of the record to query
* @param displayField The field to retrieve from the record
* @return `List<SObject>` Returned records
* @see ActionPlanCreationController
*/
@RemoteAction
public static List<SObject> queryRecords(String queryText) {
//System.debug('querytext ' + queryText);
public static List<SObject> queryRecords(String recId, String displayField) {
// Validate and cast recId to Id type to prevent injection
Id recordId;
try {
recordId = (Id) recId;
} catch (Exception e) {
throw new IllegalArgumentException('Invalid record Id: ' + recId);
}

// Get sObjectType directly from the Id - more secure than accepting as parameter
Schema.SObjectType sObjType = recordId.getSObjectType();
String sObjectType = sObjType.getDescribe().getName();

// Build query using binding variables - field name is validated above
String queryText = 'SELECT Id, ' + String.escapeSingleQuotes(displayField) + ' FROM ' + String.escapeSingleQuotes(sObjectType) + ' WHERE Id = :recordId';

return Database.query(queryText);
}

/**
* Search records by name/field for lightning lookup component.
* <br>This method validates all inputs to prevent SOQL injection.
*
* @param sObjectType The API name of the sObject to query
* @param displayField The field to search and retrieve
* @param searchKey The search term to look for (will be used with LIKE)
* @param additionalWhere Optional WHERE clause (must start with AND or OR)
* @return `List<SObject>` Returned records (max 8)
*/
@RemoteAction
public static List<SObject> searchRecords(String sObjectType, String displayField, String searchKey, String additionalWhere) {
// Validate additionalWhere clause if provided
if (String.isNotBlank(additionalWhere)) {
additionalWhere = additionalWhere.trim();
if (!additionalWhere.startsWithIgnoreCase('AND') && !additionalWhere.startsWithIgnoreCase('OR')) {
throw new IllegalArgumentException('Additional WHERE clause must start with AND or OR');
}
} else {
additionalWhere = '';
}

// Escape the search key to prevent SOQL injection
String safeSearchKey = String.escapeSingleQuotes(searchKey);

// Build query with validated and escaped inputs
String queryText =
'SELECT Id, ' +
String.escapeSingleQuotes(displayField) +
' FROM ' +
String.escapeSingleQuotes(sObjectType) +
' WHERE ' +
String.escapeSingleQuotes(displayField) +
' LIKE \'%' +
safeSearchKey +
'%\' ' +
additionalWhere +
' ORDER BY ' +
String.escapeSingleQuotes(displayField) +
' ASC LIMIT 8';

return Database.query(queryText);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ For full license text, see the LICENSE file in the repo root or https://opensour
* @since 2022
* @group Trigger Handlers
*/
global without sharing class ActionPlansTriggerHandlers {
global inherited sharing class ActionPlansTriggerHandlers {
/**
* Use only when we want to delete a Task while preserving the APTask (such as when making an APTask newly-dependent on another)<br>
* Unused currently, but may be used in future version
Expand Down Expand Up @@ -51,7 +51,7 @@ global without sharing class ActionPlansTriggerHandlers {
*/
// prettier-ignore
public static void actionPlansSObjectTriggerHandler(List<SObject> newRecords, List<SObject> oldRecords, Map<Id, SObject> newRecordsMap, Map<Id, SObject> oldRecordsMap, System.TriggerOperation triggerEvent, String sObjectName) {


String relationshipName = null;

Expand Down Expand Up @@ -179,7 +179,7 @@ global without sharing class ActionPlansTriggerHandlers {
}
//Execution when an Action Plan is deleted: delete all asociated Tasks
when BEFORE_DELETE {

Set<Id> apTaskIDs = new Set<Id>();
if (!forceSynchronous()) {
Set<Id> taskIds = new Set<Id>();
Expand All @@ -196,7 +196,7 @@ global without sharing class ActionPlansTriggerHandlers {
//if (!taskIds.isEmpty()) {
//ActionPlansTaskTriggerUtilities.futureDeleteTasks(taskIds);
//}

} else {
for (APTask__c apt : [SELECT Id FROM APTask__c WHERE Action_Plan__c IN :oldRecordsMap.keyset()]){
apTaskIDs.add(apt.Id);
Expand Down Expand Up @@ -261,7 +261,7 @@ global without sharing class ActionPlansTriggerHandlers {
public static void triggerhandlerActionPlanTask(List<APTask__c> newRecords, List<APTask__c> oldRecords, Map<Id, APTask__c> newRecordsMap,Map<Id, APTask__c> oldRecordsMap, System.TriggerOperation triggerEvent) {
switch on triggerEvent {
when BEFORE_INSERT, BEFORE_UPDATE {

Set<Id> parentIDs = new Set<Id>();
for(APTask__c apt : newRecords){
parentIDs.add(apt.Action_Plan__c);
Expand Down Expand Up @@ -313,7 +313,7 @@ global without sharing class ActionPlansTriggerHandlers {
}
}
}

when AFTER_UPDATE {
Map<Id, Id> openControlledAPTasks = new Map<Id, Id>();
Set<Id> parentControllingTaskIDs = new Set<Id>();
Expand All @@ -331,7 +331,7 @@ global without sharing class ActionPlansTriggerHandlers {
apTasksNeedingTaskDeletion.add(i);
}
}

try {
bypassTaskTrigger = true;
Database.delete([SELECT Id FROM Task WHERE TaskAPTask__c IN :apTasksNeedingTaskDeletion]);
Expand All @@ -342,7 +342,7 @@ global without sharing class ActionPlansTriggerHandlers {
}
}
}

when BEFORE_DELETE {
try {
bypassTaskTrigger = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<apiVersion>65.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading