diff --git a/src/classes/HttpMockFactory.cls b/src/classes/HttpMockFactory.cls new file mode 100755 index 0000000..50e197f --- /dev/null +++ b/src/classes/HttpMockFactory.cls @@ -0,0 +1,25 @@ +@isTest +public class HttpMockFactory implements HttpCalloutMock { + protected Integer code; + protected String status; + protected String body; + protected Map responseHeaders; + + public HttpMockFactory(Integer code, String status, String body, Map responseHeaders) { + this.code = code; + this.status = status; + this.body = body; + this.responseHeaders = responseHeaders; + } + + public HTTPResponse respond(HTTPRequest req) { + HttpResponse res = new HttpResponse(); + for (String key : this.responseHeaders.keySet()) { + res.setHeader(key, this.responseHeaders.get(key)); + } + res.setBody(this.body); + res.setStatusCode(this.code); + res.setStatus(this.status); + return res; + } +} \ No newline at end of file diff --git a/src/classes/HttpMockFactory.cls-meta.xml b/src/classes/HttpMockFactory.cls-meta.xml new file mode 100755 index 0000000..252fbfd --- /dev/null +++ b/src/classes/HttpMockFactory.cls-meta.xml @@ -0,0 +1,5 @@ + + + 47.0 + Active + diff --git a/src/classes/UTIL_UnitTest.cls b/src/classes/UTIL_UnitTest.cls index c6ed0a0..94fdc0d 100644 --- a/src/classes/UTIL_UnitTest.cls +++ b/src/classes/UTIL_UnitTest.cls @@ -251,4 +251,11 @@ public class UTIL_UnitTest { Weekly_Occurrence__c = '1st'); } + public static void setupCaptchaSettings(Boolean enabled) { + VOL_SharedCode.VolunteersSettings.Enable_Site_Captcha__c = enabled; + VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Endpoint__c = 'www.salesforce.com'; + VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Site_Key__c = '1234567890'; + VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Secret_Key__c = '1234567890'; + } + } \ No newline at end of file diff --git a/src/classes/VOL_CTRL_PersonalSiteContactLookup.cls b/src/classes/VOL_CTRL_PersonalSiteContactLookup.cls index 0d0ef2f..69e966c 100644 --- a/src/classes/VOL_CTRL_PersonalSiteContactLookup.cls +++ b/src/classes/VOL_CTRL_PersonalSiteContactLookup.cls @@ -31,6 +31,9 @@ global with sharing class VOL_CTRL_PersonalSiteContactLookup { @TestVisible private static VOL_Access access = VOL_Access.getInstance(); + + @TestVisible + private static VOL_Captcha captcha = VOL_Captcha.getInstance(); // constructor global VOL_CTRL_PersonalSiteContactLookup() { @@ -72,12 +75,16 @@ global with sharing class VOL_CTRL_PersonalSiteContactLookup { } global string strResult { get; set; } - global string strLanguage { get; set; } + global string strLanguage { get; set; } // the action method called from the page to lookup the contact, and to send them email if found. - global PageReference LookupContact() { - - list listCon = VOL_SharedCode.LookupContact(contact, null); + global PageReference LookupContact() { + if (!captcha.verifyCaptcha()) { + strResult = System.Label.CaptchaFailure; + return null; + } + + list listCon = VOL_SharedCode.LookupContact(contact, null); if (listCon == null || listCon.size() == 0) { strResult = System.Label.labelContactLookupAmbiguous; } else { @@ -123,7 +130,7 @@ global with sharing class VOL_CTRL_PersonalSiteContactLookup { mail.setTemplateID(emailTemplateId); if (orgWideEmailId != null) { mail.setOrgWideEmailAddressId(orgWideEmailId); - } + } list listSER; listSER = Messaging.sendEmail(new Messaging.Email[] { mail }, false); if (listSER[0].isSuccess()) { @@ -183,5 +190,4 @@ global with sharing class VOL_CTRL_PersonalSiteContactLookup { null : closedTaskStatuses[0].ApiName; } - } \ No newline at end of file diff --git a/src/classes/VOL_CTRL_PersonalSiteContactLookup_TEST.cls b/src/classes/VOL_CTRL_PersonalSiteContactLookup_TEST.cls index 70723d7..5d3430e 100644 --- a/src/classes/VOL_CTRL_PersonalSiteContactLookup_TEST.cls +++ b/src/classes/VOL_CTRL_PersonalSiteContactLookup_TEST.cls @@ -77,7 +77,51 @@ private with sharing class VOL_CTRL_PersonalSiteContactLookup_TEST { System.assertEquals(defaultClosedStatus, VOL_CTRL_PersonalSiteContactLookup.getClosedTaskStatus(), 'Expected the system to find the default closed task status when ran as a guest user.'); } } + + @IsTest + private static void shouldReturnErrorOnCaptchaFailure() { + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + //point to our VF page + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + + Test.startTest(); + VOL_CTRL_PersonalSiteContactLookup ctrl = new VOL_CTRL_PersonalSiteContactLookup(); + ctrl.contact.Firstname = 'Test'; + ctrl.contact.Lastname = 'User'; + ctrl.contact.Email = 'testuser@salesforce.com'; + ctrl.LookupContact(); + Test.stopTest(); + + System.assertEquals(System.Label.CaptchaFailure, ctrl.strResult); + } + @IsTest + private static void shouldReturnSuccessMessageOnCaptchaSuccess() { + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":true}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + //point to our VF page + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + Test.startTest(); + VOL_CTRL_PersonalSiteContactLookup ctrl = new VOL_CTRL_PersonalSiteContactLookup(); + ctrl.contact.Firstname = 'Test'; + ctrl.contact.Lastname = 'User'; + ctrl.contact.Email = 'testuser@salesforce.com'; + ctrl.LookupContact(); + Test.stopTest(); + + System.assertEquals(System.Label.labelContactLookupAmbiguous, ctrl.strResult); + } + @isTest(SeeAllData=true) // SeeAllData so the CSS file is viewable /******************************************************************************************************* * @description test the visualforce page controller, running as the Sites Guest User, if such as user @@ -104,6 +148,8 @@ private with sharing class VOL_CTRL_PersonalSiteContactLookup_TEST { } private static void TestPersonalSiteContactLookup() { + //disable captcha + UTIL_UnitTest.setupCaptchaSettings(false); //point to our VF page PageReference p = new PageReference('Page.PersonalSiteContactLookup'); diff --git a/src/classes/VOL_CTRL_VolunteersJobListingFS.cls b/src/classes/VOL_CTRL_VolunteersJobListingFS.cls index 16e60fb..e6aa6b4 100644 --- a/src/classes/VOL_CTRL_VolunteersJobListingFS.cls +++ b/src/classes/VOL_CTRL_VolunteersJobListingFS.cls @@ -31,6 +31,10 @@ global virtual with sharing class VOL_CTRL_VolunteersJobListingFS { @TestVisible private static VOL_Access access = VOL_Access.getInstance(); + + @TestVisible + private static VOL_Captcha captcha = VOL_Captcha.getInstance(); + /** * Hard coding the subquery limit to 200 to prevent an error being displayed when * attempting to access the list of child records when more than 200 are present @@ -418,6 +422,11 @@ global virtual with sharing class VOL_CTRL_VolunteersJobListingFS { private class MyException extends Exception {} global virtual PageReference VolunteerShiftSignUp() { + if (!captcha.verifyCaptcha()) { + strSaveResult = System.Label.CaptchaFailure; + ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, System.Label.CaptchaFailure)); + return null; + } Savepoint sp = Database.setSavepoint(); try { @@ -449,8 +458,7 @@ global virtual with sharing class VOL_CTRL_VolunteersJobListingFS { dtStart = listShift[0].Start_Date_Time__c.date(); duration = listShift[0].Duration__c; } - } - + } // when used within the Personal Site, we should use the appropriate Contact Cookie cId = ApexPages.currentPage().getCookies().get('contactIdPersonalSite'); ID contactIdPersonalSite = null; diff --git a/src/classes/VOL_CTRL_VolunteersJobListingFS_TEST.cls b/src/classes/VOL_CTRL_VolunteersJobListingFS_TEST.cls index 049dca5..dcccf73 100644 --- a/src/classes/VOL_CTRL_VolunteersJobListingFS_TEST.cls +++ b/src/classes/VOL_CTRL_VolunteersJobListingFS_TEST.cls @@ -159,6 +159,68 @@ private with sharing class VOL_CTRL_VolunteersJobListingFS_TEST { System.assertEquals(QUERY_LIMIT, jobListingCtrl.listVolunteerJobs[0].Volunteer_Job_Slots__r.size(), 'The number of shifts returned was not as expected.'); } + @IsTest + private static void shouldNotContainPageMessageOnCaptchaSuccess() { + generateData(1); + UTIL_UnitTest.setupCaptchaSettings(true); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":true}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + Contact existingContact = UTIL_UnitTest.createContact('Signup Update ' + DateTime.now().getTime()); + insert existingContact; + + Id jobId = [SELECT Id FROM Volunteer_Job__c LIMIT 1].Id; + Id shiftId = [SELECT Id FROM Volunteer_Shift__c Where Volunteer_Job__c = :jobId LIMIT 1].Id; + + setupPage(new Map{'g-recaptcha-response' => '1234567890'}); + + Test.startTest(); + jobListingCtrl.shiftIdSignUp = shiftId; + jobListingCtrl.jobIdSignUp = jobId; + jobListingCtrl.contact = existingContact; + jobListingCtrl.vhours.Number_of_Volunteers__c = 2; + jobListingCtrl.VolunteerShiftSignUp(); + Test.stopTest(); + + List messages = ApexPages.getMessages(); + System.assertEquals(0, messages.size()); + } + + @IsTest + private static void shouldReturnErrorOnCaptchaFailure() { + generateData(1); + UTIL_UnitTest.setupCaptchaSettings(true); + + Contact existingContact = UTIL_UnitTest.createContact('Signup Update ' + DateTime.now().getTime()); + insert existingContact; + + Id jobId = [SELECT Id FROM Volunteer_Job__c LIMIT 1].Id; + Id shiftId = [SELECT Id FROM Volunteer_Shift__c Where Volunteer_Job__c = :jobId LIMIT 1].Id; + + setupPage(new Map()); + + Test.startTest(); + jobListingCtrl.shiftIdSignUp = shiftId; + jobListingCtrl.jobIdSignUp = jobId; + jobListingCtrl.contact = existingContact; + jobListingCtrl.vhours.Number_of_Volunteers__c = 2; + jobListingCtrl.VolunteerShiftSignUp(); + Test.stopTest(); + + Boolean expectedMessage = false; + List messages = ApexPages.getMessages(); + System.assertNotEquals(0, messages.size()); + + for (Apexpages.Message message : messages) { + if (message.getDetail().contains(System.Label.CaptchaFailure)) { + expectedMessage = true; + } + } + + System.assertEquals(true, expectedMessage); + } + private static void setupPage(Map params) { PageReference pageRef = Page.VolunteersJobListingFS; diff --git a/src/classes/VOL_CTRL_VolunteersReportHours.cls b/src/classes/VOL_CTRL_VolunteersReportHours.cls index c5632e6..338c8ae 100644 --- a/src/classes/VOL_CTRL_VolunteersReportHours.cls +++ b/src/classes/VOL_CTRL_VolunteersReportHours.cls @@ -29,10 +29,13 @@ */ global virtual with sharing class VOL_CTRL_VolunteersReportHours { - private VOL_SharedCode volSharedCode; - + private VOL_SharedCode volSharedCode; + @TestVisible private static VOL_Access access = VOL_Access.getInstance(); + + @TestVisible + private static VOL_Captcha captcha = VOL_Captcha.getInstance(); // constructor global VOL_CTRL_VolunteersReportHours() { @@ -255,9 +258,12 @@ global virtual with sharing class VOL_CTRL_VolunteersReportHours { private class MyException extends Exception {} - // action method for saving the the volunteer's hours. global virtual PageReference Save() { + if (!captcha.verifyCaptcha()) { + strSaveResult = System.Label.CaptchaFailure; + return null; + } Savepoint sp = Database.setSavepoint(); try { // because we need to use actionSupport immediate=false to support the combo's, diff --git a/src/classes/VOL_CTRL_VolunteersReportHours_TEST.cls b/src/classes/VOL_CTRL_VolunteersReportHours_TEST.cls index a222406..af66e6b 100644 --- a/src/classes/VOL_CTRL_VolunteersReportHours_TEST.cls +++ b/src/classes/VOL_CTRL_VolunteersReportHours_TEST.cls @@ -89,6 +89,60 @@ private with sharing class VOL_CTRL_VolunteersReportHours_TEST { accessMock.assertMethodCalled('updateRecords', Volunteer_Hours__c.SObjectType); } + @IsTest + private static void shouldReturnSuccessMessageOnCaptchaSuccess() { + UTIL_UnitTest.generateData(); + + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":true}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + Contact contactRecord = (Contact) UTIL_UnitTest.getSObject(Contact.SObjectType); + Id jobId = UTIL_UnitTest.getId(Volunteer_Job__c.SObjectType); + Volunteer_Hours__c hours = UTIL_UnitTest.createHours(contactRecord.Id, jobId, null); + insert hours; + + Test.setCurrentPage(reportHoursPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + Test.startTest(); + VOL_CTRL_VolunteersReportHours reportHoursCtrl = new VOL_CTRL_VolunteersReportHours(); + reportHoursCtrl.contact = contactRecord; + reportHoursCtrl.volunteerJobId = jobId; + reportHoursCtrl.vhours = hours; + reportHoursCtrl.Save(); + Test.stopTest(); + + System.assertEquals(System.Label.labelVolunteerReportHoursThankYou, reportHoursCtrl.strSaveResult); + } + + @IsTest + private static void shouldReturnErrorOnCaptchaFailure() { + UTIL_UnitTest.generateData(); + + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + Contact contactRecord = (Contact) UTIL_UnitTest.getSObject(Contact.SObjectType); + Id jobId = UTIL_UnitTest.getId(Volunteer_Job__c.SObjectType); + Volunteer_Hours__c hours = UTIL_UnitTest.createHours(contactRecord.Id, jobId, null); + insert hours; + + Test.setCurrentPage(reportHoursPage); + + Test.startTest(); + VOL_CTRL_VolunteersReportHours reportHoursCtrl = new VOL_CTRL_VolunteersReportHours(); + reportHoursCtrl.contact = contactRecord; + reportHoursCtrl.volunteerJobId = jobId; + reportHoursCtrl.vhours = hours; + reportHoursCtrl.Save(); + Test.stopTest(); + + System.assertEquals(System.Label.CaptchaFailure, reportHoursCtrl.strSaveResult); + } + /******************************************************************************************************* * @description test the visualforce page controller, running as the Sites Guest User, if such as user * exists. if not, will run under the current user. diff --git a/src/classes/VOL_CTRL_VolunteersSignupFS.cls b/src/classes/VOL_CTRL_VolunteersSignupFS.cls index e1cece7..a27bf15 100644 --- a/src/classes/VOL_CTRL_VolunteersSignupFS.cls +++ b/src/classes/VOL_CTRL_VolunteersSignupFS.cls @@ -29,7 +29,9 @@ */ global virtual with sharing class VOL_CTRL_VolunteersSignupFS { - + @TestVisible + private static VOL_Captcha captcha = VOL_Captcha.getInstance(); + // constructor global VOL_CTRL_VolunteersSignupFS() { @@ -96,6 +98,10 @@ global virtual with sharing class VOL_CTRL_VolunteersSignupFS { global Attachment attachment { get; set; } global virtual PageReference Save() { + if (!captcha.verifyCaptcha()) { + StrSaveResult = System.Label.CaptchaFailure; + return null; + } try { // save or update the contact ID contactId = VOL_SharedCode.CreateOrUpdateContactFS(null, contact, contact.Volunteer_Organization__c, listStrFields, true); diff --git a/src/classes/VOL_CTRL_VolunteersSignupFS_TEST.cls b/src/classes/VOL_CTRL_VolunteersSignupFS_TEST.cls index 3fd203c..cd00acf 100644 --- a/src/classes/VOL_CTRL_VolunteersSignupFS_TEST.cls +++ b/src/classes/VOL_CTRL_VolunteersSignupFS_TEST.cls @@ -59,6 +59,53 @@ private with sharing class VOL_CTRL_VolunteersSignupFS_TEST { //} } + @IsTest + private static void shouldReturnErrorOnCaptchaFailure() { + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + //point to our VF page + PageReference lookupPage = Page.VolunteersSignupFS; + Test.setCurrentPage(lookupPage); + + Test.startTest(); + VOL_CTRL_PersonalSiteContactLookup ctrl = new VOL_CTRL_PersonalSiteContactLookup(); + ctrl.contact.Firstname = 'Test'; + ctrl.contact.Lastname = 'User'; + ctrl.contact.Email = 'testuser@salesforce.com'; + ctrl.LookupContact(); + Test.stopTest(); + + System.assertEquals(System.Label.CaptchaFailure, ctrl.strResult); + } + + @IsTest + private static void shouldReturnSuccessMessageOnCaptchaSuccess() { + //enable captcha + UTIL_UnitTest.setupCaptchaSettings(true); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":true}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + //point to our VF page + PageReference lookupPage = Page.VolunteersSignupFS; + Test.setCurrentPage(lookupPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + Test.startTest(); + //instantiate the controller + VOL_CTRL_VolunteersSignupFS ctrl = new VOL_CTRL_VolunteersSignupFS(); + Contact contact = ctrl.contact; + contact.FirstName = 'TestFirstName'; + contact.LastName = 'TestLastName'; + contact.Email = 'foovolunteer@bar.com'; + contact.MailingState = 'wa'; + ctrl.Save(); + Test.stopTest(); + + System.assertEquals(System.Label.labelVolunteerSignupThankYou, ctrl.StrSaveResult); + } + private static void CodeCoverageTests() { // create test bucket account diff --git a/src/classes/VOL_Captcha.cls b/src/classes/VOL_Captcha.cls new file mode 100755 index 0000000..e0391c8 --- /dev/null +++ b/src/classes/VOL_Captcha.cls @@ -0,0 +1,69 @@ +public with sharing class VOL_Captcha { + private VOL_Captcha() {} + + @TestVisible + private static VOL_Captcha instance = null; + + public static VOL_Captcha getInstance() { + if (instance == null) { + instance = new VOL_Captcha(); + } + return instance; + } + + /******************************************************************************************************* + * @description If captcha is enabled, verifies that the token received is valid + * Return the Boolean verification of the captcha token (or return true if captcha is disabled) + * @return Boolean value for captcha check + ********************************************************************************************************/ + private Boolean isEnabled() { + return VOL_SharedCode.VolunteersSettings.Enable_Site_Captcha__c && + !String.isEmpty(VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Site_Key__c) && + !String.isEmpty(VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Secret_Key__c) && + !String.isEmpty(VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Endpoint__c); + } + + /******************************************************************************************************* + * @description If captcha is enabled, verifies that the token received is valid + * Return the Boolean verification of the captcha token (or return true if captcha is disabled) + * @return Boolean value for captcha check + ********************************************************************************************************/ + public Boolean verifyCaptcha() { + // If captcha is disabled, pass the check + if(!isEnabled()){ + return true; + } + + String captchaSecretToken = VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Secret_Key__c; + String captchaResponse = ApexPages.currentPage().getParameters().get('g-recaptcha-response'); + Boolean responseSuccess = false; + + // If captcha parameter is missing, fail the check + if (String.isEmpty(captchaResponse)) { + return false; + } + + try { + // Callout to reCaptcha with response token + HttpResponse response = VOL_SharedCode.makeHttpCallout(VOL_SharedCode.VolunteersSettings.Google_reCaptcha_Endpoint__c, + 'POST', + 'secret=' + captchaSecretToken + '&response='+ captchaResponse); + if(response == null) { + return responseSuccess; + } + + JSONParser parser = JSON.createParser(response.getBody()); + while (parser.nextToken() != null) { + if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getText() == 'success')) { + parser.nextToken(); + responseSuccess = parser.getBooleanValue(); + break; + } + } + } catch (Exception e) { + System.debug(e.getMessage()); + } + + return responseSuccess; + } +} \ No newline at end of file diff --git a/src/classes/VOL_Captcha.cls-meta.xml b/src/classes/VOL_Captcha.cls-meta.xml new file mode 100755 index 0000000..252fbfd --- /dev/null +++ b/src/classes/VOL_Captcha.cls-meta.xml @@ -0,0 +1,5 @@ + + + 47.0 + Active + diff --git a/src/classes/VOL_Captcha_TEST.cls b/src/classes/VOL_Captcha_TEST.cls new file mode 100755 index 0000000..46d7915 --- /dev/null +++ b/src/classes/VOL_Captcha_TEST.cls @@ -0,0 +1,89 @@ +@IsTest +public with sharing class VOL_Captcha_TEST { + + @IsTest + private static void testCaptchaResponseSuccess() { + UTIL_UnitTest.setupCaptchaSettings(true); + + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":true}', new Map()); + + Test.setMock(HttpCalloutMock.class, mock); + + Test.startTest(); + Boolean response = VOL_Captcha.getInstance().verifyCaptcha(); + Test.stopTest(); + + System.assertEquals(true, response); + } + + @IsTest + private static void testCaptchaResponseFailure() { + UTIL_UnitTest.setupCaptchaSettings(true); + + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":false}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + Test.startTest(); + Boolean response = VOL_Captcha.getInstance().verifyCaptcha(); + Test.stopTest(); + + System.assertEquals(false, response); + } + + @IsTest + private static void testCaptchaUnexpectedResponseFromEndpoint() { + UTIL_UnitTest.setupCaptchaSettings(true); + + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + System.currentPageReference().getParameters().put('g-recaptcha-response', '1234567890'); + + HttpMockFactory mock = new HttpMockFactory(404, '', '', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + Test.startTest(); + Boolean response = VOL_Captcha.getInstance().verifyCaptcha(); + Test.stopTest(); + + System.assertEquals(false, response); + } + + @IsTest + private static void captchaShouldSucceedIfDisabled() { + UTIL_UnitTest.setupCaptchaSettings(false); + + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + + Test.startTest(); + Boolean response = VOL_Captcha.getInstance().verifyCaptcha(); + Test.stopTest(); + + System.assertEquals(true, response); + } + + @IsTest + private static void captchaShouldFailIfNoTokenReceived() { + UTIL_UnitTest.setupCaptchaSettings(true); + + PageReference lookupPage = Page.PersonalSiteContactLookup; + Test.setCurrentPage(lookupPage); + + HttpMockFactory mock = new HttpMockFactory(200, 'OK', '{"success":false}', new Map()); + Test.setMock(HttpCalloutMock.class, mock); + + Test.startTest(); + Boolean response = VOL_Captcha.getInstance().verifyCaptcha(); + Test.stopTest(); + + System.assertEquals(false, response); + } +} diff --git a/src/classes/VOL_Captcha_TEST.cls-meta.xml b/src/classes/VOL_Captcha_TEST.cls-meta.xml new file mode 100755 index 0000000..252fbfd --- /dev/null +++ b/src/classes/VOL_Captcha_TEST.cls-meta.xml @@ -0,0 +1,5 @@ + + + 47.0 + Active + diff --git a/src/classes/VOL_SharedCode.cls b/src/classes/VOL_SharedCode.cls index 64578bc..d29a6db 100644 --- a/src/classes/VOL_SharedCode.cls +++ b/src/classes/VOL_SharedCode.cls @@ -202,6 +202,7 @@ global with sharing class VOL_SharedCode { VolunteersSettings.Personal_Site_Org_Wide_Email_Name__c = null; VolunteersSettings.Contact_Matching_Rule__c = 'Firstname;Lastname;Email'; VolunteersSettings.Personal_Site_Report_Hours_Filtered__c = false; + VolunteersSettings.Enable_Site_Captcha__c = false; if (UTIL_Describe.hasObjectCreateAccess(UTIL_Describe.StrTokenNSPrefix('Volunteers_Settings__c'))) { insert VolunteersSettings; } @@ -928,6 +929,22 @@ global with sharing class VOL_SharedCode { return new List(campaignsInHierarchy.keySet()); } + /******************************************************************************************************* + * @description Make web service callouts to external APIs + * Return the HttpResponse object + * @return HttpResponse value from the web service callout + ********************************************************************************************************/ + public static HttpResponse makeHTTPCallout(String endpoint, String httpMethod, String body) { + Http http = new Http(); + HttpResponse response; + HttpRequest req = new HttpRequest(); + req.setEndpoint(endpoint); + req.setMethod(httpMethod); + req.setBody(body); + response = http.send(req); + return response; + } + /******************************************************************************************************* * @description keeps references to old labels that have been packaged, that we no longer use. * @return void diff --git a/src/classes/VOL_SharedCode_TEST.cls b/src/classes/VOL_SharedCode_TEST.cls index 079e2a3..56fa48b 100644 --- a/src/classes/VOL_SharedCode_TEST.cls +++ b/src/classes/VOL_SharedCode_TEST.cls @@ -322,7 +322,6 @@ public with sharing class VOL_SharedCode_TEST { } - ///////////////// /// Helpers ///////////////// @@ -375,5 +374,5 @@ public with sharing class VOL_SharedCode_TEST { public static void setAccessMock() { VOL_SharedCode.access = (VOL_Access) Test.createStub(VOL_Access.class, accessMock); - } + } } \ No newline at end of file diff --git a/src/components/Captcha.component b/src/components/Captcha.component new file mode 100644 index 0000000..1232bc4 --- /dev/null +++ b/src/components/Captcha.component @@ -0,0 +1,11 @@ + + +
+ + +
+
\ No newline at end of file diff --git a/src/components/Captcha.component-meta.xml b/src/components/Captcha.component-meta.xml new file mode 100644 index 0000000..f6848d2 --- /dev/null +++ b/src/components/Captcha.component-meta.xml @@ -0,0 +1,5 @@ + + + 47.0 + + diff --git a/src/labels/CustomLabels.labels b/src/labels/CustomLabels.labels index f808624..21afe4a 100644 --- a/src/labels/CustomLabels.labels +++ b/src/labels/CustomLabels.labels @@ -1,5 +1,13 @@ + + CaptchaFailure + PageMessages + en_US + true + CaptchaFailure + Please complete the captcha challenge. + PageMessagesConfirm PageMessages diff --git a/src/objects/Volunteers_Settings__c.object b/src/objects/Volunteers_Settings__c.object index 378fdb5..a111c09 100644 --- a/src/objects/Volunteers_Settings__c.object +++ b/src/objects/Volunteers_Settings__c.object @@ -3,6 +3,18 @@ Hierarchy Custom settings used by Volunteers for Salesforce. false + + Google_reCaptcha_Endpoint__c + false + Google reCaptcha verification endpoint URL + Google reCaptcha verification endpoint URL + + 255 + false + false + Text + false + Contact_Match_Email_Fields__c false @@ -132,6 +144,40 @@ false Checkbox + + Enable_Site_Captcha__c + Enable captcha for Volunteers sites + Enable captcha for Volunteers sites + false + false + + false + Checkbox + + + Google_reCaptcha_Secret_Key__c + Secret Key for Google reCaptcha + false + Secret Key for Google reCaptcha + + 255 + false + false + Text + false + + + Google_reCaptcha_Site_Key__c + Site Key for Google reCaptcha + false + Site Key for Google reCaptcha + + 255 + false + false + Text + false + Public diff --git a/src/package.xml b/src/package.xml index 218d5b3..9a6ff74 100644 --- a/src/package.xml +++ b/src/package.xml @@ -5,6 +5,7 @@ ComponentControllerBase DatabaseDml DatabaseDml_TEST + HttpMockFactory InstallScript InstallScript_TEST PageControllerBase @@ -63,6 +64,8 @@ VOL_CTRL_VolunteersSignupFS VOL_CTRL_VolunteersSignupFS_TEST VOL_CTRL_VolunteersSignup_TEST + VOL_Captcha + VOL_Captcha_TEST VOL_JRS VOL_JRS_TEST VOL_SharedCode @@ -76,6 +79,7 @@ ApexClass + Captcha SoqlListView UTIL_FormField UTIL_HtmlOutput @@ -215,7 +219,11 @@ Volunteers_Settings__c.Contact_Match_Email_Fields__c Volunteers_Settings__c.Contact_Match_First_Name_Fields__c Volunteers_Settings__c.Contact_Matching_Rule__c + Volunteers_Settings__c.Enable_Site_Captcha__c Volunteers_Settings__c.Google_Maps_API_Key__c + Volunteers_Settings__c.Google_reCaptcha_Endpoint__c + Volunteers_Settings__c.Google_reCaptcha_Secret_Key__c + Volunteers_Settings__c.Google_reCaptcha_Site_Key__c Volunteers_Settings__c.Grant_Guest_Users_Update_Access__c Volunteers_Settings__c.Personal_Site_Org_Wide_Email_Name__c Volunteers_Settings__c.Personal_Site_Report_Hours_Filtered__c @@ -227,6 +235,7 @@ CustomField + CaptchaFailure PageMessagesConfirm PageMessagesError PageMessagesFatal diff --git a/src/pages/PersonalSiteContactLookup.page b/src/pages/PersonalSiteContactLookup.page index 48ca2af..75dd106 100644 --- a/src/pages/PersonalSiteContactLookup.page +++ b/src/pages/PersonalSiteContactLookup.page @@ -42,7 +42,7 @@ - + -

{!$Label.labelContactInfoLookupTitle}

@@ -139,15 +138,19 @@
- - +

- +
+ +
diff --git a/src/pages/VolunteersJobListingFS.page b/src/pages/VolunteersJobListingFS.page index 3a181b4..2c3a851 100644 --- a/src/pages/VolunteersJobListingFS.page +++ b/src/pages/VolunteersJobListingFS.page @@ -280,7 +280,7 @@ -

+



@@ -294,6 +294,9 @@ return false;" immediate="false" status="statusSignUp" styleClass="cssButton btn" /> +
+ +
diff --git a/src/pages/VolunteersReportHours.page b/src/pages/VolunteersReportHours.page index 7c349e6..dcbf2a2 100644 --- a/src/pages/VolunteersReportHours.page +++ b/src/pages/VolunteersReportHours.page @@ -30,7 +30,7 @@ - + @@ -52,7 +52,7 @@ @@ -115,7 +115,7 @@ - + @@ -123,6 +123,9 @@
- +    @@ -68,7 +68,7 @@ - +
- + +
+ +
\ No newline at end of file diff --git a/src/pages/VolunteersSignupFS.page b/src/pages/VolunteersSignupFS.page index 057fbad..c9d4dfa 100644 --- a/src/pages/VolunteersSignupFS.page +++ b/src/pages/VolunteersSignupFS.page @@ -66,8 +66,7 @@ - + @@ -76,6 +75,9 @@ +
+ +
\ No newline at end of file