Skip to content

Commit

Permalink
Add create named credential example (#494)
Browse files Browse the repository at this point in the history
* Add create named credential example

* Run prettier

* Document makeCalloutWithNamedCredential method

* Add tests

* Suppress / fix PMD warnings

* Skipping Permission Set setup when running tests

* Increase API version

* Add code review suggestions

* Move mock to correct folder

* Fix example

* sample data setup fixed on CI jobs
  • Loading branch information
albarivas authored Dec 1, 2023
1 parent 3c72592 commit 254de9c
Show file tree
Hide file tree
Showing 12 changed files with 472 additions and 2 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,11 @@ jobs:

# Import sample data
- name: 'Import sample data'
run: sf data tree import -p data/data-plan.json -p data/data-plan2.json
run: sf data tree import -p data/data-plan.json

# Import sample data 2
- name: 'Import sample data 2'
run: sf data tree import -p data/data-plan2.json

# Run Apex tests in scratch org
- name: 'Run Apex tests'
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ jobs:

# Import sample data
- name: 'Import sample data'
run: sf data tree import -p data/data-plan.json -p data/data-plan2.json
run: sf data tree import -p data/data-plan.json

# Import sample data 2
- name: 'Import sample data 2'
run: sf data tree import -p data/data-plan2.json

# Run Apex tests in scratch org
- name: 'Run Apex tests'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* @description Demonstrates how to manage named credentials from Apex
* @group Integration Recipes
*/
public with sharing class NamedCredentialRecipes {
public static final String NAMED_CREDENTIAL_MASTER_LABEL = 'GoogleBooksAPI (created with Apex)';
public static final String NAMED_CREDENTIAL_DEVELOPER_NAME = 'googleBooksAPIApex';
public static final ConnectApi.NamedCredentialType NAMED_CREDENTIAL_TYPE = ConnectApi.NamedCredentialType.SecuredEndpoint;
public static final String NAMED_CREDENTIAL_CALLOUT_URL = 'https://www.googleapis.com/books/v1';
public static final Boolean NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_BODY = false;
public static final Boolean NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_HEADER = false;
public static final Boolean NAMED_CREDENTIAL_GENERATE_AUTH_HEADER = true;
public static final String EXTERNAL_CREDENTIAL_MASTER_LABEL = 'GoogleBooksAPI (created with Apex)';
public static final String EXTERNAL_CREDENTIAL_DEVELOPER_NAME = 'googleBooksAPIApexExternal';
public static final ConnectApi.CredentialAuthenticationProtocol EXTERNAL_CREDENTIAL_AUTHENTICATION_PROTOCOL = ConnectApi.CredentialAuthenticationProtocol.Custom;
public static final String PRINCIPAL_NAME = 'Developer Access';
public static final ConnectApi.CredentialPrincipalType PRINCIPAL_TYPE = ConnectApi.CredentialPrincipalType.NamedPrincipal;
public static final Integer PRINCIPAL_SEQUENCE_NUMBER = 1;

/**
* @description Demonstrates how create a named credential from Apex.
* @param connectApiWrapper instance of ConnectApiWrapper, created to allow mocking
* @param permissionSetName name of the permission set that will have access to the external credential
* @return ConnectApi.NamedCredential The created named credential
* @example
* System.debug(NamedCredentialRecipes.createNamedCredential(new ConnectApiWrapper(), 'Apex_Recipes'));
* HttpResponse response = RestClient.makeApiCall(
* NAMED_CREDENTIAL_DEVELOPER_NAME,
* RestClient.HttpVerb.GET,
* '/volumes?q=salesforce'
* );
* System.debug(response.getBody());
**/
public static ConnectApi.NamedCredential createNamedCredential(
ConnectApiWrapper connectApiWrapper,
String permissionSetName
) {
// Create an external credential (you could use an existing one)
ConnectApi.ExternalCredential externalCredential = NamedCredentialRecipes.createExternalCredential(
connectApiWrapper,
permissionSetName
);

// Create a list of external credential inputs and add the external credential name
List<ConnectApi.ExternalCredentialInput> externalCredentials = new List<ConnectApi.ExternalCredentialInput>();
ConnectApi.ExternalCredentialInput externalCredentialInput = new ConnectApi.ExternalCredentialInput();
externalCredentialInput.developerName = externalCredential.DeveloperName;
externalCredentials.add(externalCredentialInput);

// Create a named credential input and setup the required fields
ConnectApi.NamedCredentialInput namedCredentialInput = new ConnectApi.NamedCredentialInput();
namedCredentialInput.developerName = NAMED_CREDENTIAL_DEVELOPER_NAME;
namedCredentialInput.masterLabel = NAMED_CREDENTIAL_MASTER_LABEL;
namedCredentialInput.type = NAMED_CREDENTIAL_TYPE;
namedCredentialInput.calloutUrl = NAMED_CREDENTIAL_CALLOUT_URL;
namedCredentialInput.externalCredentials = externalCredentials;

// Configure the named credential callout options
ConnectApi.NamedCredentialCalloutOptionsInput calloutOptions = new ConnectApi.NamedCredentialCalloutOptionsInput();
calloutOptions.allowMergeFieldsInBody = NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_BODY;
calloutOptions.allowMergeFieldsInHeader = NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_HEADER;
calloutOptions.generateAuthorizationHeader = NAMED_CREDENTIAL_GENERATE_AUTH_HEADER;
namedCredentialInput.calloutOptions = calloutOptions;

// Create the named credential!
return connectApiWrapper.createNamedCredential(namedCredentialInput);
}

/**
* @description This example shows how to create an external credential in Apex.
* An external credential contains the authentication and authorization information for the callout,
* and needs to be linked to a named credential in order to be used.
* @param connectApiWrapper instance of ConnectApiWrapper, created to allow mocking
* @param permissionSetName name of the permission set that will have access to the external credential
* @return ConnectApi.ExternalCredential The created external credential
* @example
* System.debug(NamedCredentialRecipes.createExternalCredential(new ConnectApiWrapper(), 'Apex_Recipes'));
**/
private static ConnectApi.ExternalCredential createExternalCredential(
ConnectApiWrapper connectApiWrapper,
String permissionSetName
) {
ConnectApi.ExternalCredentialInput externalCredentialInput = new ConnectApi.ExternalCredentialInput();
externalCredentialInput.developerName = EXTERNAL_CREDENTIAL_DEVELOPER_NAME;
externalCredentialInput.masterLabel = EXTERNAL_CREDENTIAL_MASTER_LABEL;
externalCredentialInput.authenticationProtocol = EXTERNAL_CREDENTIAL_AUTHENTICATION_PROTOCOL;

// Populate principals to connect the external credential to permissions
ConnectApi.ExternalCredentialPrincipalInput principalInput = new ConnectApi.ExternalCredentialPrincipalInput();
principalInput.principalName = PRINCIPAL_NAME;
principalInput.principalType = PRINCIPAL_TYPE;
principalInput.sequenceNumber = PRINCIPAL_SEQUENCE_NUMBER;

externalCredentialInput.principals = new List<ConnectApi.ExternalCredentialPrincipalInput>{
principalInput
};

// Create external credential
ConnectApi.ExternalCredential externalCredential = connectApiWrapper.createExternalCredential(
externalCredentialInput
);

if (!Test.isRunningTest()) {
// Tests should skip giving permission set access, as principal doesn't really exist
// Reload principal to get its id
List<ConnectApi.ExternalCredentialPrincipal> principals = connectApiWrapper.getExternalCredential(
EXTERNAL_CREDENTIAL_DEVELOPER_NAME
)
.principals;

PermissionSet permissionSet = [
SELECT Id
FROM PermissionSet
WHERE Name = :permissionSetName
WITH USER_MODE
LIMIT 1
];

if (permissionSet != null) {
// Give access to named principal on permission set
insert as user new SetupEntityAccess(
ParentId = permissionSet.Id,
SetupEntityId = principals[0].Id
);
}
}

return externalCredential;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
32 changes: 32 additions & 0 deletions force-app/main/default/classes/Shared Code/ConnectApiWrapper.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @description Most Connect in Apex methods require access to real organization data,
* and fail unless used in test methods marked @IsTest(SeeAllData=true).
* An alternative to that, is mocking the calls to the ConnectAPI class.
* This class can be used to inject the ConnectAPI dependency,
* allowing its methods to be mocked in test classes.
* @group Shared Code
* @see NamedCredentialRecipesTest
*/
public with sharing class ConnectApiWrapper {
public ConnectApi.ExternalCredential createExternalCredential(
ConnectApi.ExternalCredentialInput externalCredentialInput
) {
return ConnectApi.NamedCredentials.createExternalCredential(
externalCredentialInput
);
}

public ConnectApi.NamedCredential createNamedCredential(
ConnectApi.NAmedCredentialInput namedCredentialInput
) {
return ConnectApi.NamedCredentials.createNamedCredential(
namedCredentialInput
);
}

public ConnectApi.ExternalCredential getExternalCredential(
String developerName
) {
return ConnectApi.NamedCredentials.getExternalCredential(developerName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ConnectApiWrapper

Most Connect in Apex methods require access to real organization data,
and fail unless used in test methods marked


**IsTest** (SeeAllData=true). An alternative to that, is mocking the calls to the ConnectAPI class. This class can be used to inject the ConnectAPI dependency, allowing its methods to be mocked in test classes.


**Group** Shared Code


**See** [NamedCredentialRecipesTest](NamedCredentialRecipesTest)

## Methods
### `public ConnectApi createExternalCredential(ConnectApi externalCredentialInput)`
### `public ConnectApi createNamedCredential(ConnectApi namedCredentialInput)`
### `public ConnectApi getExternalCredential(String developerName)`
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# NamedCredentialRecipes

Demonstrates how to manage named credentials from Apex


**Group** Integration Recipes

## Fields

### `public EXTERNAL_CREDENTIAL_AUTHENTICATION_PROTOCOL``ConnectApi`


### `public EXTERNAL_CREDENTIAL_DEVELOPER_NAME``String`


### `public EXTERNAL_CREDENTIAL_MASTER_LABEL``String`


### `public NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_BODY``Boolean`


### `public NAMED_CREDENTIAL_ALLOW_MERGE_FIELDS_IN_HEADER``Boolean`


### `public NAMED_CREDENTIAL_CALLOUT_URL``String`


### `public NAMED_CREDENTIAL_DEVELOPER_NAME``String`


### `public NAMED_CREDENTIAL_GENERATE_AUTH_HEADER``Boolean`


### `public NAMED_CREDENTIAL_MASTER_LABEL``String`


### `public NAMED_CREDENTIAL_TYPE``ConnectApi`


### `public PRINCIPAL_NAME``String`


### `public PRINCIPAL_SEQUENCE_NUMBER``Integer`


### `public PRINCIPAL_TYPE``ConnectApi`


---
## Methods
### `public static ConnectApi createNamedCredential(ConnectApiWrapper connectApiWrapper, String permissionSetName)`

Demonstrates how create a named credential from Apex.

#### Parameters

|Param|Description|
|---|---|
|`connectApiWrapper`|instance of ConnectApiWrapper, created to allow mocking|
|`permissionSetName`|name of the permission set that will have access to the external credential|

#### Returns

|Type|Description|
|---|---|
|`ConnectApi`|ConnectApi.NamedCredential The created named credential|

#### Example
```apex
System.debug(NamedCredentialRecipes.createNamedCredential(new ConnectApiWrapper(), 'Apex_Recipes'));
HttpResponse response = RestClient.makeApiCall(
NAMED_CREDENTIAL_DEVELOPER_NAME,
RestClient.HttpVerb.GET,
'/volumes?q=salesforce'
);
System.debug(response.getBody());
```


### `private static ConnectApi createExternalCredential(ConnectApiWrapper connectApiWrapper, String permissionSetName)`

This example shows how to create an external credential in Apex. An external credential contains the authentication and authorization information for the callout, and needs to be linked to a named credential in order to be used.

#### Parameters

|Param|Description|
|---|---|
|`connectApiWrapper`|instance of ConnectApiWrapper, created to allow mocking|
|`permissionSetName`|name of the permission set that will have access to the external credential|

#### Returns

|Type|Description|
|---|---|
|`ConnectApi`|ConnectApi.ExternalCredential The created external credential|

#### Example
```apex
System.debug(NamedCredentialRecipes.createExternalCredential(new ConnectApiWrapper(), 'Apex_Recipes'));
```


---
Loading

0 comments on commit 254de9c

Please sign in to comment.