The Node Class

In Java terms, an authentication node is a class that implements the Node interface, org.forgerock.openam.auth.node.api.Node.

The Node class may access and modify the persisted state that is shared between the nodes within a tree, and may request input by using callbacks. The class also defines the possible exit paths from the node.

The class is annotated with org.forgerock.openam.auth.node.api.Node.Metadata. The annotation has two main attributes - configClass and outcomeProvider. Typically, the configClass attribute is an inner interface in the node implementation class.

For simple use cases, the abstract implementations of the node interface, org.forgerock.openam.auth.node.api.SingleOutcomeNode and org.forgerock.openam.auth.node.api.AbstractDecisionNode, have their own outcome providers that can be used. For more complex use cases you can provide your own implementation.

Conceptually, the Node class is structured as follows:

  1. The annotation

    The annotation specifies the outcome provider and configuration class. The outcome provider can use the default SingleOutcomeNode or MultipleOutcomeNode, or a custom OutcomeProvider can be created and referenced from the annotation.

    See the Choice Collector Node for an example of a custom outcome provider.

  2. Any private constants

  3. The config

    The config interface defines the configuration data for a node. A node cannot have state, but it can have configuration data.

    Note that you do not need to provide the implementation class for the Config interface you define. AM will create these automatically, as required.

    An example is the Account lockout Node. The node can be configured to lock or unlock the users' account, based on it’s configuration.

    Configuration is per-node. Different nodes of the same type in the same tree have their own configuration.

    The config interface configures values using methods. To provide no default value to the tree administrator, provide the method's signature but not the implementation. To provide a default value to the tree administrator, mark the method as default and provide both a method and a value. 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;
      }
    }

    The Config above would resemble the following in the tree designer view:

    The output from an example Config interface.

    The @Attribute annotation is required. It can be configured with an order value, which determines the position of the attribute in the UI, and with validators, to validate the values being set.

    In the example above, a custom enum called LockStatus is returned. The options are displayed to the user automatically.

  4. The constructor

    Dependencies should be injected by using Guice as this makes it easier to unit test your node. For example, you should accept the config as a parameter.

    You may also wish to obtain AM core classes, such as CoreWrapper, instances of third-party dependencies, or your own types.

    @Inject
    public AccountLockoutNode(CoreWrapper coreWrapper, @Assisted Config config)
    throws NodeProcessException {
      this.coreWrapper = coreWrapper;
      this.config = config;
    }
  5. The process method

    The process method takes a TreeContext parameter, does some processing, and returns an Action object.

    An action encapsulates changes to state and flow control. The TreeContext parameter is used to access the request, callbacks, shared state and other input.

    The process method is where state is retrieved and stored. The returning Action can be a response of callback to the user, an update of state, or a choice of outcome.

    The choice of outcome in a simple decision node would be true or false, resulting in the authentication tree flow moving from the current node to a node at the relevant connection.

  6. Any private methods

  7. Optionally, a custom outcome provider

The following example is the SetSessionPropertiesNode class, taken from the Set Session Properties Node:

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2017-2018 ForgeRock AS.
 */

package org.forgerock.openam.auth.nodes;

import java.util.Map;

import javax.inject.Inject;

import org.forgerock.openam.annotations.sm.Attribute;
import org.forgerock.openam.auth.node.api.Action;
import org.forgerock.openam.auth.node.api.Node;
import org.forgerock.openam.auth.node.api.SingleOutcomeNode;
import org.forgerock.openam.auth.node.api.TreeContext;
import org.forgerock.openam.auth.nodes.validators.SessionPropertyValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.assistedinject.Assisted;

/**
 * A node which contributes a configurable set of properties to be added to the user's session, if/when it is created.
 */
@Node.Metadata(outcomeProvider = SingleOutcomeNode.OutcomeProvider.class,
        configClass = SetSessionPropertiesNode.Config.class)  (1)
public class SetSessionPropertiesNode extends SingleOutcomeNode {  (2)

    /**
     * Configuration for the node.
     */
    public interface Config {  (3)
        /**
         * A map of property name to value.
         * @return a map of properties.
         */
        @Attribute(order = 100, validators = SessionPropertyValidator.class)
        Map<String, String> properties();
    }

    private final Config config;
    private final Logger logger = LoggerFactory.getLogger("amAuth");

    /**
     * Constructs a new SetSessionPropertiesNode instance.
     * @param config Node configuration.
     */
    @Inject  (4)
    public SetSessionPropertiesNode(@Assisted Config config) {
        this.config = config;
    }

    @Override
    public Action process(TreeContext context) {  (5)
        logger.debug("SetSessionPropertiesNode started");
        Action.ActionBuilder actionBuilder = goToNext();
        config.properties().entrySet().forEach(property -> {
            actionBuilder.putSessionProperty(property.getKey(), property.getValue());
            logger.debug("set session property {}", property);
        });
        return actionBuilder.build();
    }
}

1

The @Node.Metadata annotation. See "The Meta Data Annotation".

2

Implementing the Node interface. See "The Node Interface".

3

Implementing the Config interface. See "The Config Interface".

4

Injecting the Node instance. See "Injecting Objects Into a Node Instance".

5

Creating an Action instance. See "The Action Interface".

Read a different version of :