The Config Interface

The Config interface of a node contains the configuration values required by a particular node instance.

Note that you do not need to write a class that implements the interface you define, AM will create it automatically, as required.

Define the properties the node will use in the Config interface, by using the @Attribute annotation. The @Attribute annotation specifies the order of the properties in the tree designer view as well as providing a way to specify additional validators.

Example:

public interface Config {
    @Attribute(order = 1)
    String domain(); (1)

    @Attribute(order = 2, validators = RequiredValueValidator.class)
    boolean isVerificationRequired(); (2)

    @Attribute(order = 3)
    default YourCustomEnum action() {
        return YourCustomEnum.LockScreen; (3)
    };
}
Key:

1

The domain attribute is a string-typed value which can be provided in the tree designer view by the tree administrator. It can be read in the process method by using a reference to the config interface; for example, config.domain().

2

A boolean attribute with an additional parameter, validators, containing a reference to a validation class. In this case, a value is required to be provided by the tree administrator.

3

A custom enum attribute. This provides type safety and negates the misuse of Strings as generic type-unsafe value holders. The UI will correctly handle the enum and only let the tree administrator chose from the defined enum values.

The defined properties appear as configurable options in the tree designer view when adding a node of the relevant type. For example the configuration for the Scripted Decision Node appears as follows:

Note that attribute names are used when localizing the node's text, see Internationalization.

For more information, see the Config annotation type and the Attribute annotation type in the AM 7.0.2 Public API Javadoc.

Sharing Configuration Between Nodes

You can share configuration between nodes that have common properties. For example, a number of your nodes may call out to an external service that requires a username, password, IP address, and port setting.

Rather than repeat the same configuration in each of these nodes, you can create a shared, auxiliary service to hold the common properties in one of your nodes, and reference that service from multiple other nodes.

The following sections explain how to create this auxiliary service and reference it in your nodes. Also covered is how to run more than one instance of an auxiliary service if required, and how to obtain the configuration from services built-in to AM.

Creating a Shared Auxiliary Service

You can create a shared auxiliary service in the configuration interface defined as part of a node. Annotate the service with the org.forgerock.openam.annotations.sm.Config annotation to describe how the service functions.

Specify the scope of the service, either GLOBAL or REALM, as shown below:

@Config(scope = Config.Scope.REALM)
public interface MyAuxService {
   @Attribute(order = 1)
   String serviceUrl();
}

You can also specify other features of the service, such as whether the service is a singleton in its scope, or if it can have multiple instances. For information about supporting multiple instances, see "Allowing Multiple Instances of an Auxiliary Service".

Referencing a Shared Auxiliary Service Instance

To access the shared auxiliary service, add org.forgerock.openam.sm.AnnotatedServiceRegistry to the @Inject-annotated constructor of the node.

Obtain the instance using the get instance methods on that class, for example:

serviceRegistry.getRealmSingleton(MyAuxService.class, realm)

Reinstalling a Shared Auxiliary Service Instance

When developing a custom authentication node that references a shared auxiliary service, it can be useful for the node to be able to remove and then reinstall the auxiliary service during upgrade, so that any existing configuration is cleared.

In the upgrade function of your plugin class, use the following example code to remove and reinstall a service:

public void upgrade(String fromVersion) throws PluginException {
    SSOToken adminToken = AccessController.doPrivileged(AdminTokenAction.getInstance());
    if (fromVersion.equals(PluginTools.DEVELOPMENT_VERSION)) {
        ServiceManager sm = new ServiceManager(adminToken);
        if (sm.getServiceNames().contains("MyAuxService")) {
            sm.removeService("MyAuxService", "1.0");
        }
        pluginTools.install(MyAuxService.class);
    }
}

For more information on upgrading custom authentication nodes, see "Upgrading Nodes and Configuration Changes".

Allowing Multiple Instances of an Auxiliary Service

To enable configuration of multiple instances of the auxiliary service in either the same realm or at a global level, set the collection attribute to true in the Config annotation.

You can present the names of the instances of the service as a drop-down menu to the tree administrator.

To be able to present the names, make sure the service instance exposes its id, as follows:

@Config(scope = Config.Scope.REALM, collection = true)
public interface MyAuxService {
   @Id
   String id();

   @Attribute(order = 1)
   String serviceUrl();
}

Change the nodes that will be using a service instance to store the id it uses, and implement choiceValuesClass as shown below:

public class MyCustomNode implements Node {
   public interface Config {
       @Attribute(order = 1, choiceValuesClass = ExternalServiceValues.class)
       String serviceId();
   }

   public static class ExternalServiceValues extends ChoiceValues {

       @Override
       public Map<String, String> getChoiceValues() {
           return getChoiceValues(null);
       }

       @Override
       public Map<String, String> getChoiceValues(Map envParams) {
           String realmName = "/";
           if (envParams != null) {
               realmName = (String) envParams.getOrDefault(Constants.ORGANIZATION_NAME, "/");
           }
           try {
               return InjectorHolder.getInstance(AnnotatedServiceRegistry.class)
                       .getRealmInstances(MyAuxService.class, Realms.of(realmName))
                       .stream()
                       .collect(Collectors.toMap(MyAuxService::id, MyAuxService::id));
           } catch (SSOException | SMSException | RealmLookupException e) {
               LoggerFactory.getLogger("amAuth").error("Couldn't load realm {}", realmName, e);
               throw new IllegalStateException("Couldn't load realm that was passed", e);
           }
       }
   }
   // ...
}

Obtaining Configuration of Services Built-in to AM

You can obtain configuration from services built-in to AM. For example, you might want to access the Email Service configuration to obtain the SMTP settings for the realm.

AM services are defined by two methods:

  1. Most services are defined by using an annotated interface.

  2. Legacy services that are defined by using an XML file.

See the following sections for information on obtaining the configuration from services defined with either of the two methods.

Obtaining the Configuration from an Annotated Service

To obtain the configuration from a service that uses an annotated interface, add org.forgerock.openam.sm.AnnotatedServiceRegistry to your Guice constructor. If the configuration is realm-based, include the realm in the constructor, as follows:

public class MyCustomNode extends SingleOutcomeNode {

   private final AnnotatedServiceRegistry serviceRegistry;
   private final Realm realm;

   @Inject
   public MyCustomNode(@Assisted Realm realm, AnnotatedServiceRegistry serviceRegistry) {
      this.realm = realm;
      this.serviceRegistry = serviceRegistry;
   }
   // ...
}

Obtain an instance of the service using one of the get methods of AnnotatedServiceRegistry in the constructor.

If the calls you make depend on input from elsewhere in the tree you can add AnnotatedServiceRegistry to the process method. Note that the following example assumes that a previous node has stored the ID of the AM service to use in shared state:

public Action process(TreeContext context) throws NodeProcessException {
   String serviceId = context.getState.get("myAuxServiceId");
   MyAuxService instance = serviceRegistry.getRealmInstance(MyAuxService.class, realm, serviceId);
   // ...
}

Obtaining the Configuration from a Legacy Service

To obtain an instance of the configuration from a legacy service, use the APIs in the com.sun.identity.sm package.

For example, to obtain the configuration values from a realm instance of a service, use ServiceConfigManager as follows:

ServiceConfigManager scm = new ServiceConfigManager("legacyServiceName", token);
ServiceConfig sc = scm.getOrganizationConfig(realm.asPath(), null);
final Map<String, Set<String>> configMap = sc.getAttributes();

However, to obtain the configuration values from a global instance of a service, use ServiceSchemaManager as follows:

ServiceSchemaManager ssm = new ServiceSchemaManager("legacyServiceName", getAdminToken());
Map<String, Set<String>> configMap = ssm.getGlobalSchema().getAttributeDefaults();
Read a different version of :