AM 7.5.0

Config interface

The Config interface defines the configuration data for a node. A node can’t have state, but it can have configuration data. Configuration is per node; different nodes of the same type in the same tree have their own configuration.

You don’t need to write a class that implements the interface you define. AM automatically creates this as required.

Define node properties

Configure the node properties using methods. To provide a default value to the tree administrator, mark the method as default and define both a method and a value. To omit a default value, define the method’s signature but not the implementation.

For example:

public interface Config {

  //This will have no default value for the UI
  @Attribute(order = 10)
  String noDefaultAttribute();

  //This will default to the value LOCK.
  @Attribute(order = 20)
  default LockStatus lockAction() {
    return LockStatus.LOCK;
  }
}

For this Config example, a custom enum named LockStatus is returned.

The defined properties appear as configurable options in the tree designer view when adding a node of the relevant type. The options display to the user automatically.

Attribute names are used when localizing the node’s text. For further information, refer to Internationalization.

The output from an example Config interface

For more information, refer to the Config annotation type in the AM Public API Javadoc.

The @Attribute annotation

The @Attribute annotation is required. You must specify an integer value for order, which determines the position of the attribute in the UI.

Optionally, specify one or more validators to validate the attribute values provided.

To include a validator:

  • Use an existing validator class from the org.forgerock.openam.auth.nodes.validators package, such as DecimalValidator or HMACKeyLengthValidator.

  • Create your own validator by implementing the ServiceAttributeValidator interface.

For example:

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

  @Attribute(order = 2, validators = {RequiredValueValidator.class,   2
                                      GreaterThanZeroValidator.class})
  int exampleNumber();

  @Attribute(order = 3, requiredValue = true)
  boolean isVerificationRequired();                                   3

  @Attribute(order = 4)
  @Password                                                           4
  char[] clientSecret();

  @Attribute(order = 5)
  default YourCustomEnum action() {
    return YourCustomEnum.LockScreen;                                 5

  @Attribute(order = 6, requiredValue = true, resourceName = "secretLabelIdentifier")
  @SecretPurpose("am.authentication.nodes.customauth.%s.secret")
  Purpose<GenericSecret> secretValuePurpose();                        6
  };
}

1 The domain attribute defines a String-typed node property for display in the UI. Access the attribute in the process method by using a reference to the config interface; for example, config.domain().

2 Specify one or more validator classes as the validators parameter.

3 The boolean attribute is defined as a required value. Specifying requiredValue=true is equivalent to including the parameter validators = RequiredValueValidator.class.

4 Use the Password annotation to mask the input characters and encrypt the value of the attribute.

5 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 choose from the defined enum values.

For more information, refer to the Attribute annotation type in the AM Public API Javadoc.

6 An identifier used to create a secret label for the node that maps to a secret in a secret store. The default custom authentication node secret label is am.authentication.nodes.customauth.%s.secret where %s is the value of the identifier.

For more information, refer to Map and rotate secrets and the SecretPurpose annotation.

Share configuration between nodes

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

Instead of repeating the same configuration in each of these nodes, create a shared, auxiliary service to hold the common properties in one node. You can then reference that service from other nodes.

The following sections explain how to create and reference this auxiliary service. The sections also describe 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.

Create 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, refer to Allow multiple instances of an auxiliary service.

Reference 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)

Reinstall 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 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, refer to Upgrade nodes and change node configuration.

Allow 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);
      }
    }
  }
  // ...
}

Get configuration of built-in services

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

AM services are defined by two methods:

  1. An annotated interface (most services)

  2. An XML file (legacy services)

The following sections describe how to obtain the configuration from services defined using these two methods.

Get 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);
  // ...
}

Get 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();
Copyright © 2010-2024 ForgeRock, all rights reserved.