Metadata API Apex fails to create fields in Connected App

I think the second problem you listed is causing the first problem, and unfortunately it's a bit of a layered answer. I'm also assuming that the MetadataService layer you've generated is all correct.

The first part to solve is getting the ConnectedAppOauthConfig.isAdminApproved = true. Now I couldn't get this to set on creation of the Connected App. However, when I update the Connected App just created with the setting of ConnectedAppOauthConfig.isAdminApproved = true it sticks. So you will need to create the Connected App first then update the same app to make the setting stick.

Now going back to the Connected App documentation, the 'permissionSetName' field requires the 'isAdminApproved' to be set to true so we can't set the permission sets on create. I was able to set unmanaged permission sets on the update, but got the following error when trying to set the managed package permission set:

Cannot modify managed object: entity=SetupEntityAccess, component=0J0O000000Ceu83, field=KeyPrefix, state=installed

The ConnectedAppPlugin class might be something you will need to explore as an authorisation alternative if you don't have control of the managed package setup.


Re 1

In order to get the ConnectedApp.permissionSetName set, you can use SetupEntityAccess and regular DML commands to associate a connected app with a permission set. Only caveat - you can't adjust a managed permission set, so you'll have to create your own new permission set.

Code Sample:

//== (1) Create new permission set ==//
PermissionSet connectedPs = new PermissionSet(
    Label='Connected App User',
    Name='Connected_App_User',
    Description='Gives permissions to use the managed connected app'
);

insert connectedPs;

// Get Managed Connected App
ConnectedApplication myApp = [SELECT Id, Name, OptionsAllowAdminApprovedUsersOnly FROM ConnectedApplication WHERE Name like 'MyManagedAppName' LIMIT 1];

//== (2) Associate the new perm set with the connected app ==//
SetupEntityAccess sea = new SetupEntityAccess(
    ParentId = connectedPs.Id,
    SetupEntityId = app.Id
);

upsert seas;

Re 2

You can't set isAdminApproved via MetadataService API classes because you literally can't read the managed package via the readMetadata() method. As per my comment on the question in this post

I was able to configure and do a readMetadata on isAdminApproved within the packaging org itself, but then ran into the same issue in production - readMetadata doesn't return managed connected apps results, and interestingly enough, workbench also doesn't show managed connected apps (as mentioned in the retrieveConnectedApp() method in the MetadataServicesExamples file (line 1517 at time of posting). I don't know if this is a deficiency in Metadata API or not

So it seems this will be a manual step that's unavoidable for Production environments.


Regardless, here's the script that I ran that would work in non-production environments but didn't run correctly in a production environment, because, again, readMetadata just wouldn't retrieve managed packages :-/

Note: I had to update a few extra properties (like isAdminApproved and permissionSetName) within MetadataService in order to properly read and set these properties. Also needed to update the version to 47 or later.

See all the changes to MetadataService that I made in this gist. Simply search for BMiller and you'll see all the places that I modified code.

Below is the script using the modified version of MetadataService

// Modified and taken from MetadataServicesExamples
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
String appFullName = 'myNamespace__My_Connected_App';
MetadataService.ConnectedApp connectedApp =
    (MetadataService.ConnectedApp)service.readMetadata('ConnectedApp', new String[] {appFullName}).getRecords()[0];

System.debug('connectedApp = ' + connectedApp);
System.debug(connectedApp.oauthConfig.isAdminApproved);
System.debug(connectedApp.permissionSetName);
System.debug(connectedApp.description);

// Attempting to set the isAdminApproved setting.
connectedApp.oauthConfig.isAdminApproved = true;

// Works in non-production environments.  In production environments, connectedApp is showing up null
List<MetadataService.UpsertResult> results =
    service.upsertMetadata(new MetadataService.Metadata[] { connectedApp });

MetadataService.UpsertResult upsertResult = results[0];
if(upsertResult.errors!=null) {
    List<String> messages = new List<String>();
    messages.add(
        (upsertResult.errors.size()==1 ? 'Error ' : 'Errors ') +
        'occured processing component ' + upsertResult.fullName + '.');
    for(MetadataService.Error error : upsertResult.errors)
        messages.add(
            error.message + ' (' + error.statusCode + ').' +
            ( error.fields!=null && error.fields.size()>0 ?
             ' Fields ' + String.join(error.fields, ',') + '.' : '' ) );
    if(messages.size()>0)
        System.debug(String.join(messages, ' '));
}
if(!upsertResult.success)
    System.debug('Request failed with no specified error.');