I was able to resolve a similar problem with System.resetPassword() by ensuring that this call is made from a class declared WITHOUT SHARING. The reason it works is because this class is executing in the System Context with elevated permissions.
/**
* An apex page controller that exposes the site forgot password functionality
* but performs a hard reset on the existing password to prevent further logins without a reset
*/
public with sharing class ForgotPasswordHardResetController {
// Username is set by the Visual Force page
public String username {get; set;}
public ForgotPasswordHardResetController() {}
public PageReference forgotPassword() {
// Reset Password Requires the Userid
try {
List<User> matchingUsers = [SELECT id,username,email from User where UserName = :username];
if (matchingUsers.size()>0) {
User currentUser = matchingUsers[0];
// Method is delegated to another class which uses without sharing to workaround permissions
new ResetHelper().resetPassword(currentUser);
PageReference pr = Page.ForgotPasswordConfirm;
pr.setRedirect(true);
System.debug('Password reset initiated');
return pr;
} else {
ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, 'Username not found');
ApexPages.addMessage(msg);
return null;
}
} catch (System.Exception ex) {
ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, 'Unexpected error');
ApexPages.addMessage(msg);
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,ex.getMessage()));
return null;
}
}
/**
* Helper class must be declared without sharing to force it to run in System Context
**/
public without sharing class ResetHelper {
public void resetPassword(User currentUser) {
System.resetPassword(currentUser.id,false); // force immediate password reset, no email
Site.forgotPassword(currentUser.Username); // repeat the reset and send the templated community email
}
}
}