Auth.AuthProviderPluginClass to configure an external Salesforce authentication provider
global class B2BTIDAuthPlugin extends Auth.AuthProviderPluginClass {
private String customMetadataTypeApiName;
private String redirectUrl;
private String consumerKey;
private String consumerSecret;
private String authorizationUrl;
private String accessTokenUrl;
private String userInfoUrl;
private String scope;
/**
* @description This method is responsible for returning the custom metadata that
stores the api credentials and other details
* @author Raja Patnaik | 01-24-2022
* @return String Returns the custom metadata type API name for a custom
OAuth-based authentication provider for single sign-on to Salesforce
**/
global String getCustomMetadataType() {
String methodName = 'getCustomMetadataType';
System.debug('Entered Method: ' + methodName);
customMetadataTypeApiName = 'B2BTIDCredential__mdt';
System.debug(LoggingLevel.DEBUG, 'Auth Provider Custom metadata Type: ' +
customMetadataTypeApiName);
System.debug('Exited Method: ' + methodName);
return customMetadataTypeApiName;
}
/* Step 1 */
/**
* @description This method is responsible to initiate the authorization code flow
* @author Raja Patnaik | 01-24-2022
* @param authProviderConfiguration The configuration for the custom authentication provider
* @param stateToPropagate The state passed in to initiate the authentication request for the user
* @return PageReference Returns the URL where the user is redirected for authentication
**/
global PageReference initiate(Map<string,string> authProviderConfiguration,
String stateToPropagate) {
String methodName = 'initiate';
System.debug('Entered Method: ' + methodName);
for(String key : authProviderConfiguration.keySet()) {
System.debug('AuthProviderConfiguration Key: ' + key + ';
AuthProviderConfiguration Value: ' + authProviderConfiguration.get(key));
}
consumerKey = authProviderConfiguration.get('Client_Id__c');
authorizationUrl = authProviderConfiguration.get('Authorization_URL__c');
scope = authProviderConfiguration.get('Scope__c');
redirectUrl = authProviderConfiguration.get('Callback_URL__c');
String urlToRedirect = authorizationUrl
+ '?client_id=' + consumerKey
+ '&redirect_uri=' + redirectUrl
+ '&scope=' + scope
+ '&state=' + stateToPropagate
+ '&response_type=code';
System.debug('URL To Redirect: ' + urlToRedirect);
PageReference pageRef = new PageReference(urlToRedirect);
System.debug('Exited Method: ' + methodName);
return pageRef;
}
/* Step 2 */
/**
* @description This method is responsible to handle the callback from authorization
code flow and sets the access token, refresh token and other parameters
* Uses the authentication provider’s supported authentication protocol
to return an OAuth access token, OAuth secret or refresh token
* and the state passed in when the request for the current user was
initiated.
* @author Raja Patnaik | 01-24-2022
* @param authProviderConfiguration The configuration for the custom authentication
provider
* @param state The state passed in to initiate the authentication request for
the user
* @return Auth.AuthProviderTokenResponse
**/
global Auth.AuthProviderTokenResponse handleCallback(Map<string,string>
authProviderConfiguration, Auth.AuthProviderCallbackState state) {
String methodName = 'handleCallback';
System.debug('Entered Method: ' + methodName);
for(String key : authProviderConfiguration.keySet()) {
System.debug('AuthProviderConfiguration Key: ' + key + ';
AuthProviderConfiguration Value: ' + authProviderConfiguration.get(key));
}
// This will contain an optional accessToken and refreshToken
consumerKey = authProviderConfiguration.get('Client_Id__c');
consumerSecret = authProviderConfiguration.get('Client_Secret__c');
accessTokenUrl = authProviderConfiguration.get('Access_Token_URL__c');
redirectUrl = authProviderConfiguration.get('Callback_URL__c');
Map<String,String> queryParams = state.queryParameters;
for(String key : queryParams.keySet()) {
System.debug('AuthProviderCallbackState Key: ' + key + ';
AuthProviderCallbackState Value: ' + queryParams.get(key));
}
if(queryParams.containsKey('error_description')) {
throw new Auth.AuthProviderPluginException(queryParams.get('error_description')
);
}
String code = queryParams.get('code');
String sfdcState = queryParams.get('state');
Blob headerValue = Blob.valueOf(consumerKey + ':' + consumerSecret);
String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
Http http = new Http();
HttpRequest httpRequest = new HttpRequest();
/*String requestBody = 'client_id=' + consumerKey + '&client_secret=' +
consumerSecret + '&code=' + code
+ '&redirect_uri=' + redirectUrl + '&grant_type=
authorization_code';*/
String requestBody = 'client_id=' + consumerKey + '&code=' + code
+ '&redirect_uri=' + redirectUrl + '&grant_type=
authorization_code';
httpRequest.setEndpoint(accessTokenUrl);
httpRequest.setHeader('Authorization', authorizationHeader);
//httpRequest.setHeader('Content-Type','application/x-www-form-urlencoded');
httpRequest.setHeader('Accept', 'application/json');
httpRequest.setMethod('POST');
httpRequest.setBody(requestBody);
HTTPResponse httpResponse = http.send(httpRequest);
String responseBody = httpResponse.getBody();
System.debug('Callback Response Body: ' + responseBody);
Map<String, Object> responseMap = (Map<String, Object>)JSON.deserializeUntyped
(responseBody);
for(String key : responseMap.keySet()) {
System.debug('Callback Response Key: ' + key + '; Callback Response Value: '
+ responseMap.get(key));
}
B2BTIDCallbackWrapper tidB2CCallbackWrapper = (B2BTIDCallbackWrapper)
System.JSON.deserialize(responseBody, B2BTIDCallbackWrapper.class);
System.debug('Exited Method: ' + methodName);
authProviderConfiguration.put('id_token',tidB2CCallbackWrapper.id_token);
return new Auth.AuthProviderTokenResponse(tidB2CCallbackWrapper.id_token,
tidB2CCallbackWrapper.access_token, 'refreshToken', sfdcState);
}
/* Step 3 */
/**
* @description This method is responsible to get the user information used for
authentication from the external api
* @author Raja Patnaik | 01-24-2022
* @param authProviderConfiguration The configuration for the custom authentication
provider
* @param response The OAuth access token, OAuth secret or refresh token, and
state provided by the authentication provider to authenticate the current user
* @return Auth.UserData Creates a new instance of the Auth.UserData class.
**/
global Auth.UserData getUserInfo(Map<string,string> authProviderConfiguration,
Auth.AuthProviderTokenResponse response) {
String methodName = 'getUserInfo';
System.debug('Entered Method: ' + methodName);
for(String key : authProviderConfiguration.keySet()) {
System.debug('AuthProviderConfiguration Key: ' + key + ';
AuthProviderConfiguration Value: ' + authProviderConfiguration.get(key));
}
userInfoUrl = authProviderConfiguration.get('User_Info_URL__c');
String oAuthtoken = response.oauthToken;
System.debug('AuthProviderTokenResponse: ' + response);
String[] base64EncodedJwtList = oAuthtoken.split('\\.');
String jwtEncodedPayload = base64EncodedJwtList[1];
System.debug('JWT Encoded Payload: ' + jwtEncodedPayload);
jwtEncodedPayload = jwtEncodedPayload.replace('-', '+');
jwtEncodedPayload = jwtEncodedPayload.replace('_', '/');
Blob jwtPayloadBlob = EncodingUtil.base64Decode(jwtEncodedPayload);
String jwtPayload = jwtPayloadBlob.toString();
System.debug('JWT Payload: ' + jwtPayload);
// You might choose to get user information in the response from the handleCallback
method or by another method.
// However, you must still call getUserInfo in the custom authentication handler to
avoid getting errors about mixing objects.
// For example, if you don’t call getUserInfo, and then try to insert a contact in
the Auth.RegistrationHandler.createUser method,
// you get the error, “You cannot mix EntityObjects with different UddInfos within
one transaction.”
// To avoid this error, call getUserInfo with dummy user information as follows.
HttpRequest httpRequest = new HttpRequest();
httpRequest.setHeader('Authorization', 'Bearer ' + oAuthtoken);
String url = userInfoUrl;
httpRequest.setEndpoint(url);
httpRequest.setMethod('GET');
Http http = new Http();
HTTPResponse httpResponse = http.send(httpRequest);
// Below code is not needed
if(httpResponse.getStatusCode() == 200) {
System.debug('User Info Call Status Code: ' + httpResponse.getStatusCode());
System.debug('User Info Call Response Body: ' + httpResponse.getBody());
}
else {
System.debug('User Info Call Error Status Code: ' + httpResponse.getStatusCode());
System.debug('User Info Call Error Response Body: ' + httpResponse.getBody());
}
//Convert JWT Payload to wrapper object
B2BTIDUserWrapper userInfo = (B2BTIDUserWrapper) System.JSON.
deserialize(httpResponse.getBody(), B2BTIDUserWrapper.class);
String userFullName = userInfo.given_name + ' ' + userInfo.family_name;
Map<String,String> attributeMap = new Map<String,String>();
attributeMap.put('id_token', response.provider);
//attributeMap.put('email', userInfo.emails.get(0));
//attributeMap.put('audience', userInfo.aud);
//attributeMap.put('scope', userInfo.scp);
System.debug('Exited Method: ' + methodName);
return new Auth.UserData(userInfo.sub, userInfo.given_name, userInfo.family_name,
userFullName,
userInfo.email, userInfo.iss, userInfo.email, userInfo.locale,
'SelfServe_TID', null ,attributeMap);
}
public class B2BTIDCallbackWrapper {
public String access_token;
public String id_token;
public String token_type;
public Integer expires_in;
public String resource;
public B2BTIDCallbackWrapper parse(String json) {
return (B2BTIDCallbackWrapper) System.JSON.deserialize(json,
B2BTIDCallbackWrapper.class);
}
}
public class B2BTIDUserWrapper {
public String iss;
public String sub;
public String identity_type;
public String given_name;
public String family_name;
public String email;
public Boolean email_verified;
public String locale;
public String preferred_mfa_setting;
public String picture;
public B2BTIDUserWrapper parse(String json) {
return (B2BTIDUserWrapper) System.JSON.deserialize(json,
B2BTIDUserWrapper.class);
}
}
}
=============================================================================================
/**
* @description :
* @author : Raja Patnaik
* @group :
* @last modified on : 12-01-2021
* @last modified by :
* Modifications Log
* Ver Date Author Modification
* 1.0 08-28-2020 Raja Patnaik Initial Version
*
/* Raja Patnaik 01-18-2021 */
global class TNVB2B_TIDRegistrationHandler implements Auth.RegistrationHandler{
class RegHandlerException extends Exception {}
global boolean canCreateUser(Auth.UserData data) {
System.debug('new user data'+ JSON.serialize(data));
Boolean isUserCanCreate = (data != null && data.email != null && data.lastName
!= null && data.firstName != null);
return isUserCanCreate;
}
global User createUser(Id portalId, Auth.UserData data){
System.debug('new user data'+ JSON.serialize(data));
User communityUser = new User();
String email = data.email;
String fname = data.firstName;
String lname = data.lastName;
String tid = data.identifier;//data.attributeMap.get('sub');
String id_token = data.attributeMap.containsKey('id_token')?data.attributeMap.
get('id_token'):null;
Id b2bStoreGroupId = [SELECT Id FROM ccrz__E_AccountGroup__c WHERE Name =:
Label.B2B_Account_Group limit 1].Id;
Id accountContactOwnerId = [SELECT Id FROM User WHERE Name =:
Label.B2B_Account_Contact_Owner limit 1].Id;
String sCartId = (Apexpages.currentPage() != null ? Apexpages.currentPage().
getParameters().get('cartID') : '');
String sErrorMessage='';
if(!canCreateUser(data)) {
return null;
}
System.debug('-----------email----------' + email);
System.debug('-----------sfdc_networkid----------' + data.attributeMap.
containsKey('sfdc_networkid'));
if(data.email != null) {
List<User> userLst = [SELECT Id,Firstname,Lastname,UserName,ProfileId,
Profile.Name FROM User WHERE email =: email OR FederationIdentifier=:tid LIMIT 1];
if(userLst.size() == 1){
User loggedUser = userLst[0];
loggedUser.email = email;
loggedUser.FederationIdentifier = tid;
Profile oProfile = [Select Id,Name from Profile where Name =:
Label.B2B_Community_User_Profile];
if(loggedUser.Profile.Name != Label.B2B_Community_User_Profile){
//loggedUser.ProfileId =oProfile.Id;
loggedUser.TNVB2B_License_Upgraded_To_B2B__c = true;
}
loggedUser.Process_Community_Permissions__c = true;
loggedUser.Id_Token__c = id_token;
update loggedUser;
assignPermissionSet([SELECT Id, Label FROM PermissionSet WHERE Name =:
Label.B2B_Existing_User_Permission_Set].Id,userLst.get(0).Id);
return loggedUser;
}
else {
Map<Id,Contact> mapContacts = new Map<Id,Contact>([SELECT Id, salutation,
Other_Job_Title__c, Job_Function__c, Job_Role__c, FirstName, LastName,
Email, Phone, MobilePhone, AccountId, MarketingNews__c,
Marketing_List_MEP_NA__c, Marketing_List_FTG__c FROM Contact WHERE
email =: email]);
System.debug('mapContacts::::: '+mapContacts);
if(mapContacts.size() == 1){
Contact oContact = mapContacts.values().get(0);
System.debug('oContact::::: '+oContact);
Account oAccount = getAccount(oContact);
if(String.isNotBlank(oAccount.Id)){
oContact.FirstName = fname;
oContact.LastName = lname;
update oContact;
if(String.isblank(oAccount.Account_Division__c)){
oAccount.Account_Division__c = 'MEP NA';
}
else if(String.isnotblank(oAccount.Account_Division__c) &&
!oAccount.Account_Division__c.contains('MEP NA')) {
oAccount.Account_Division__c = oAccount.Account_Division__c +
';MEP NA';
}
oAccount.Account_Site2__c = 'Headquarters';
oAccount.ccrz__E_AccountGroup__c = b2bStoreGroupId;
ContactAddressAllHandler.dontresetValidationFlag = True;
Database.saveResult oSaveResult = Database.update(oAccount,false);
if(!oSaveResult.isSuccess()){
sErrorMessage = 'Exception : ';
for(Database.Error oError : oSaveResult.getErrors()) {
sErrorMessage += oError.getMessage();
}
System.debug('------sErrorMessage-------'+sErrorMessage);
}
}
communityUser = registerUser(oAccount, oContact, email, tid,
sCartId,id_token);
return communityUser;
}
else{
throw new RegHandlerException ('Account Details Not Found.
Please complete the registration process : ' +
+'&tid='+tid+'&email='+email+'
&firstName='+fname+'&lastName='+lname+'&id_token='+id_token);
}
}
}
else{
throw new RegHandlerException ('Account Details Not Found.
Please complete the registration process : ' +
+'&tid='+tid+'&email='+email+'
&firstName='+fname+'&lastName='+lname+'&id_token='+id_token);
}
}
global void updateUser(Id userId, Id portalId, Auth.UserData data){
System.debug('new user data'+ JSON.serialize(data));
User u = new User(id=userId);
u.email = data.email;
u.lastName = data.lastName;
u.firstName = data.firstName;
u.Id_Token__c = data.attributeMap.get('id_token');
update(u);
}
@future
public static void assignPermissionSet(Id permissionSetId, Id userId){
PermissionSetAssignment oPermissionSetAssign = new PermissionSetAssignment
(PermissionSetId = permissionSetId, AssigneeId = userId);
Database.insert(oPermissionSetAssign ,false);
}
global Account getAccount(Contact oContact) {
Account oAccount = [SELECT Id, Validation_Status__c,Validation_Status_Code__c,
Account_Division__c, ccrz__E_AccountGroup__c, Account_Site2__c, Name,
BillingStreet, BillingCity, BillingCountry, BillingPostalCode, BillingState,
County__c,
Phone, EBS_Account_Number__c, Industry, NumberOfEmployees,
AnnualRevenue, Type, ccrz__TaxExemptAccount__c
FROM Account WHERE Id =: oContact.AccountId];
return oAccount;
}
global User registerUser(Account oAccount, Contact oContact, String email, String tid,
String sCartId,String id_token){
if(String.isnotBlank(oContact.Id)){
String sErrorMessage='';
Savepoint oSavepoint = Database.setSavepoint();
try{
User oNewUser = createNewUser(oAccount, oContact, sCartId, tid, email,
id_token);
return oNewUser;
}
catch(Site.ExternalUserCreateException oExternalUserCreateException){
sErrorMessage = String.join(oExternalUserCreateException.
getDisplayMessages() ,', ');
System.debug('------sErrorMessage-------'+sErrorMessage);
Database.rollback(oSavepoint);
}
catch(Exception oException){
sErrorMessage = oException.getMessage();
System.debug('------sErrorMessage-------'+sErrorMessage);
Database.rollback(oSavepoint);
}
}
return null;
}
public static User createNewUser(Account oAccount, Contact oContact, String sCartId,
String tid, String email,String id_token){
User oNewUser = new User();
if(oAccount.BillingCountry=='Canada' && oAccount.BillingState=='Quebec'){
oNewUser = createB2BCommunityUserCanada(oContact,'fr_CA');
}
else if(oAccount.BillingCountry=='Canada' && oAccount.BillingState!='Quebec'){
oNewUser = createB2BCommunityUserCanada(oContact,'en_CA');
}
else{
oNewUser = B2BUtility.createB2BCommunityUser(oContact);
}
oNewUser.FederationIdentifier = tid;
oNewUser.Id_Token__c = id_token;
insert oNewUser;
}
return oNewUser;
}
public static User createB2BCommunityUserCanada(Contact oContact, String userLocale){
Boolean RemoveResetPasswordMessageFlag = true;
String sHashString = '1000' + String.valueOf(Datetime.now().formatGMT
('yyyy-MM-dd HH:mm:ss.SSS'));
Blob blobHash = Crypto.generateDigest('MD5', Blob.valueOf(sHashString));
String sHexDigest = 'User' + EncodingUtil.convertToHex(blobHash);
String sCommunityNickName = sHexDigest.left(40);
Profile oProfile = [Select Id from Profile where Name =:
Label.B2B_Community_User_Profile];
User oNewUser = new User(
ProfileId = oProfile.Id,
Username = oContact.Email,
Alias = 'sfdc',
Email = oContact.Email,
EmailEncodingKey = 'UTF-8',
Firstname = oContact.FirstName,
Lastname = oContact.LastName,
Phone = oContact.Phone,
MobilePhone = oContact.MobilePhone,
ContactId = oContact.Id,
LanguageLocaleKey=userLocale,
LocaleSidKey=userLocale,
TimeZoneSidKey='America/Chicago',
ccrz__CC_CurrencyCode__c = 'CAD',
CurrencyIsoCode = 'CAD',
IsActive = TRUE,
CommunityNickname = sCommunityNickName,
RemoveResetPasswordMessage__c = RemoveResetPasswordMessageFlag
);
return oNewUser;
}
}
Comments
Post a Comment