See wam Menu

Using CamsClient Services

This section provides details on how to use each of the services available from the CamsClient.

Using the AuthenticationService

The AuthenticationService enables you to delegate user authentication to a specific security domain hosted on a WAM Policy Server. This service is extensibly designed to support whatever information is required by the LoginModules and CallbackHandlers registered with the security domain.

The AuthenticationService is generally used in conjunction with the AccessControlService, since the AccessControlResponse provides references to the: security domain name, login configuration entry, and login configuration parameters when access is denied because authentication is required.

The general process for using the AuthenticationService involves:

  1. Finding an AuthenticationService instance using the CamsClient.

  2. Getting an AuthRequest from the AuthenticationService.

  3. Populating the AuthRequest.

  4. Invoking the AuthenticationService.

  5. Handling the AuthResponse.

  6. Destroying the AuthRequest and AuthResponse.

Example 1 shows a code fragment that uses the WAM AuthenticationService. It assumes the CamsClient is already initialized and connected to a WAM Policy Server.

Exception Handling while Executing an Authentication Request

A CamsTransportException may be thrown during execution of the access control check if one or more WAM Policy Servers are unavailable.

Since WAM sessions are “sticky” to a given WAM Policy Server, proper exception handling depends on whether or not one or more SessionId objects were sent with the authentication request. This can happen in situations when “re-authentication” or “additional authentication” within an existing session are implemented.

The reason is that the original authentication request will have failed to the specific server associated with the SessionId(s), but another server may be available to provide authentication and create a new user session.

If another WAM Policy Server is available then authentication may succeed with the existing authentication request (after any existing session ids have been cleared). If existing session ids were not added to the request, then all connectivity to all possible WAM Policy Servers failed and your agent will likely log an error message and display an error message to the user.

Please note the code in Example 2 that handles the CamsTransportException. If the AuthenticationRequest has any SessionId objects, they are cleared. The authentication request is then tried again and if it succeeds, the AccessControlResponse handling proceeds normally. If the authentication fails, an exception is thrown. If no existing SessionId objects were available for the original request, then another request is not attempted as connectivity to all configured WAM Policy Servers has already failed.

  /**
   * Test the WAM authentication service.
   *
   * @return a WAM SessionId object if authentication was successful.
   */
  public SessionId testAuthenticationService()
  {
    SessionId sessionId = null;
    AuthenticationService authService = null;
    AuthRequest authRequest = null;
    AuthResponse authResponse = null;

    try
    {
      // Get the authentication service from the CamsClient.
      authService = (AuthenticationService)camsClient.find(
        "authentication", AuthenticationService.class);

      // Populate the authentication request.
      authRequest = authService.createAuthRequest();
      authRequest.setSecurityDomainName("system");
      authRequest.setLoginConfigEntryName("http");
      authRequest.setRemoteAddr("127.0.0.1");
      authRequest.setRemoteHost("localhost");
      authRequest.addCallbackValue("username", "guest");
      authRequest.addCallbackValue("password", "password");

      // NOTE: If your agent needs to implement "re-authentication" or
      // request additional authentication tokens, you can add an 
      // existing WAM SessionId object here.
      // For example:
      // if (sessionId != null)
      //    authRequest.addSessionId(sessionId);

      if (debug)
        authRequest.log(logger);

      try
      {
        // Authenticate the user.
        authResponse = authService.authenticate(authRequest);
      }
      catch(CamsTransportException cte)
      {
        // Transport exception occurred, which means that the request failed
        // to a specific WAM Policy Server chosen by the CamsClient. 

        // If request had no sessions it was anonymous and we must fail
        // because the CamsClient has already attempted authentication
        // with all configured policy servers.
        if (!authRequest.hasSessionIds())
          throw new AuthException("Failed to send request", cte);

        // If the request has existing WAM sessions, then we won't be
        // able to "re-authenticate" or get additional authentication 
        // credentials with the specific policy server for which the 
        // session was created. That's because WAM sessions are "sticky"
        // to a policy server.
        //
        // In this case, clear the existing WAM session and try a fresh
        // (anonymous) authentication, which might be handled by another
        // available WAM Policy Server.
        authRequest.clearSessionIds();

        try
        {
          // Attempt another authentication.
          authResponse = authService.authenticate(authRequest);
        }
        catch(CamsTransportException cte2)
        {
          // Authentication failure: all avenues have been exhausted.
          throw new AuthException("Failed to send request", cte2);
        }
      }

      // An authentication response was received.

      if (debug)
        authResponse.log(logger);

      // If authentication succeeded a session id will be returned.
      sessionId = authResponse.getSessionId();
      if (sessionId != null)
      {
        logger.info(this, "User authentication succeeded: session id=" +
          sessionId.toString());
      }
      else
      {
        logger.info(this, "User authentication failed: " +
          "status code=" + authResponse.getStatus() +
          ", reason code=" + authResponse.getReason() +
          ", message=" + authResponse.getMessage());
      }

      // Your agent should handle the authentication response as needed
      // for the agent environment. Sample code is provided in private
      // method: handleAuthResponse();
      handleAuthResponse(authRequest, authResponse);
    }
    catch(ServiceException se)
    {
      logger.error(this, "authentication service error", se);
    }
    catch(AuthException ae)
    {
      logger.error(this, "authentication error", ae);
    }
    finally
    {
      if (authService != null)
      {
        if (authRequest != null)
          authService.destroy(authRequest);
        if (authResponse != null)
          authService.destroy(authResponse);
        authService = null;
      }
    }

    return sessionId;
  }
...

Example 1 - Using the AuthenticationService

Handling the Authentication Response

Some key points for Example 2 include:

  • The AuthenticationService can pass any CallbackValues necessary to the security domain.

    It is up to the security domain administrator to register a CallbackHandler (within login-config.xml) that understands the CallbackValues. The encoding/decoding of CallbackValues is up to the agent and CallbackHandler.

  • The LoginConfigEntry set within the AuthRequest must exist within the specified security domain’s login-config.xml file.

  • Once a user is authenticated, a session will be managed within the WAM Policy Server.

    The identifier of that session is returned in the AuthResponse. Information about the session can be retrieved using the SessionAccessService and the user can effectively be “logged out” using the SessionControlService.

  • Whenever the AccessControlService is used, the session id of the associated authenticated user should be supplied if it is available.

    This will enable user/role-based access control rules to grant or deny access based on the user’s identity.

  • Handling of the AuthResponse is very agent-specific.

    If authentication fails, some agents may want to simply prompt the user for login credentials again. Other agents may want to provide some form of assistance or prompt the user to create/update account information.

  • The AuthenticationService is a factory for AuthRequest objects for use by agents.

    Because WAM is designed for high-performance environments, most service implementations cache/pool request/response objects. So, to get optimum performance from the WAM Agents APIs AuthenticationService, be sure to “destroy” the request/response objects returned to your agent.

  • For more information on the AuthenticationService, AuthRequest, AuthResponse, etc. see the WAM Javadoc.

/**
   * Handle the authentication response.
   *
   * @param authRequest the authentication request associated with the response.
   * @param authResponse the authentication response.
   */
  private void handleAuthResponse(AuthRequest authRequest, AuthResponse authResponse)
  {
    if (authResponse == null)
      throw new IllegalArgumentException(
        "AuthResponse is null, cannot handle null response");

    int statusCode = authResponse.getStatus();
    if (statusCode == AuthResponse.SC_SUCCESS)
    {
      // Authentication was successful.

      // Get the SessionId
      SessionId sessionId = authResponse.getSessionId();

      // NOTE: Handle successful authentication obligations here.
      // Obligations are actions that an agent is obligated (required)
      // to perform as a Policy Enforcement Point (PEP) based on a decision
      // by the Policy Decision Point (PDP), which in this case is the 
      // AuthenticationService at the WAM Policy Server.
      //
      // Obligations are specific to the type of agent. For a web agent,
      // an obligation may specify: to redirect the User-Agent to a 
      // specific URL, add an HTTP response header, respond with a 
      // speciic HTTP error code, etc. Using the Obligations API on
      // the WAM Policy Server, you can create/return obligations that
      // are very specific to the types of actions needed by your agent's
      // environment.
      //
      // See the WAM Obligations API for for more details.

      // Do whatever is necessary on successful authentication here.
      // For example: create a session cookie, return the session id
      // to a web service, save the session id in a Java client, etc.

      logger.info(this, "Authentication was successful: session id=" +
        sessionId.toString());
    }
    else if (statusCode == AuthResponse.SC_FAILED)
    {
      // Handle failed authentication obligations here.
      //
      // See the WAM Obligations API for for more details.

      // Handle based on the failed authentication reason.
      int reasonCode = authResponse.getReason();
      switch (reasonCode)
      {
        case AuthResponse.RC_LOGIN_FAILED:
        case AuthResponse.RC_EXPIRED_ACCOUNT:
        case AuthResponse.RC_EXPIRED_CREDENTIAL:

          // Login has failed due to invalid credentials, an expired
          // account, or expired credentials. Take appropriate action
          // here.

          logger.info(this, "Login failed: invalid credentials, expired account " +
            "or expired credentials: reason code=" + reasonCode);

          // Dump any login parameters that might have been returned.
          Map loginParametersMap = authResponse.getLoginParameters();
          if ((loginParametersMap != null) && (loginParametersMap.size() > 0))
          {
            Iterator iter = loginParametersMap.keySet().iterator();
            while (iter.hasNext())
            {
              String name = (String)iter.next();
              String value = (String)loginParametersMap.get(name);
              logger.info(this, "   Login parameter: " + name + "=" + value);
            }
          }

          break;

        case AuthResponse.RC_CALLBACK_HANDLER_ERROR:
        case AuthResponse.RC_GENERAL_SERVER_ERROR:
        case AuthResponse.RC_INVALID_REMOTE_HOST_NAME:
        case AuthResponse.RC_INVALID_REMOTE_IP_ADDRESS:
        case AuthResponse.RC_NOT_APPLICABLE:
        case AuthResponse.RC_UNAUTHORIZED_AGENT:
        case AuthResponse.RC_UNKNOWN_SECURITY_DOMAIN:
        case AuthResponse.RC_UNKNOWN_LOGIN_CONFIG:
        case AuthResponse.RC_LOGIN_ERROR:

          // An error occured.
          logger.error(this, "login error: reason code=" + reasonCode);

          // Take appropriate error action here.

        default:

          // An error occured.
          logger.error(this, "login error: unknown reason code=" + reasonCode);

          // Take appropriate error action here.

          break;
      }
    }
    else
    {
      logger.error(this, "login error: unknown status code=" + statusCode);
      
      // Take appropriate error action here.
    }
  }
...

Example 2 - Authentication response handling examples

Using the AccessControlService

The AccessControlService enables you to delegate access control checks to a WAM Policy Server. As a web access management product, WAM is generally used perform access control checks on http/https resource, but it is also used to control access to “cams” services by WAM agents. The AccessControlService is extensibly designed to support access control checks on new/custom resource types when used in conjunction with the WAM Permissions API.

The general process for using the AccessControlService includes:

  1. Creating a ResourceRequestFactory, usually at agent initialization time.

  2. Finding an AccessControlService instance using the CamsClient.

  3. Getting an AccessControlRequest from the AccessControlService.

  4. Populating the AccessControlRequest.

  5. Invoking the AccessControlService.

  6. Handling the AccessControlResponse.

  7. Destroying the AccessControlRequest and AccessControlResponse.

Creating a ResourceRequestFactory

In addition to other information, every access control request contains a “resource request,” which corresponds to a requested action on some object.

For example, an HTTP resource request may contain the action GET and the resource identifier http://localhost:8080/index.html. In this case, the action corresponds to a possible HTTP request method (GET, POST, PUT, DELETE, etc.) and a fully-qualified HTTP or HTTPS URL.

Though WAM is not limited to protecting resources identified by URLs, as a web access management product, it is typically used to protect access to HTTP resources, which are identfied by HTTP/HTTPS URLs.

In addition, WAM also commonly protects “cams” and “rmi” resources, which are also identified by URLs. The chances are, your agent will also be protecting access to resources identified using URLs and you’ll be able to use the WAM UrlResourceRequestFactory, which is initialized as shown in Example 3.

The configuration properties defaultPort.http and defaultPort.https are used to normalize HTTP/HTTPS URLs to a fully qualified form. For example, if HTTP URL http://localhost/index.html is specified, then the UrlResourceRequestFactory will normalize it to http://localhost:80/index.html.

As you can see, a URL like https://localhost/index.html will be normalized to https://localhost:443/index.html.

import com.cafesoft.cams.access.ResourceRequestFactory;
import com.cafesoft.cams.access.url.UrlResourceRequestFactory;

import java.util.Properties;

...
  // Create/initialize a factory for ResourceRequest instances.
  ResourceRequestFactory resReqFactory = 
    new UrlResourceRequestFactory();
  Properties p = new Properties();
  p.setProperty("defaultPort.http", "80");
  p.setProperty("defaultPort.https", "443");
  resReqFactory.initialize(p);
...

Example 3 - Creating a UrlResourceRequestFactory

NOTE: If you have a need to protect resources that are not or cannot be identified by a URL, you’ll need to implement a custom ResourceRequestFactory. Contact OneLogin support for more information.

Information on using the UrlResourceRequestFactory is provided in the next section.

Executing an Access Control Check

Executing an access control check requires the following steps:

  1. Using the CamsClient ServiceFinder to get a handle to the AccessControlService.

  2. Using the AccessControlService to create an AccessControlRequest.

  3. Creating a ResourceRequest using the ResourceRequestFactory (described in the previous section) and adding it to the AccessControlRequest.

  4. Setting common AccessControlRequest values, like: client remote address, client remote host name, etc.

    NOTE: WAM agents should also add standard agent “attributes” as described in this section and may also add other attributes, which may be specific to the environment in which the agent operates or the resource types protected by the agent.

  5. If one or more SessionId objects are available for an authenticated user (more than one may be available if the user is authenticated to multiple WAM security domains), add them to the AccessControlRequest.

  6. Use the AccessControlService to execute the access control check on the AccessControlResponse.

  7. Handle the AccessControlResponse.

    More details on access control response handling are provided later in this section.

  8. Use the AccessControlService to destroy the AccessControlRequest and AccessControlResponse objects.

    NOTE: The AccessControlService implementation may actually manage these objects in a pool to reduce Java garbage collection and boost performance. You must destroy these request/response objects to avoid a memory leak and eventual exhaustion of underlying object pools.

Exception Handling while Executing an Access Control Check

A CamsTransportException may be thrown during execution of the access control check if one or more WAM Policy Servers are unavailable. Because WAM sessions are “sticky” to a given WAM Policy Server, proper exception handling depends on whether or not one or more SessionId objects were sent with the access control check. The reason is, that the original access control check will have failed to the specific server associated with the SessionId(s), but another server may be available to provide a decision even without the user’s identity.

If another WAM Policy Server is avaiable and access to the resource does not require an authenticated user, then access may be granted.

If access is denied because an authenticated user is required, then your agent will need to take action to prompt for user login.

If another WAM Policy Server is not available, then your agent will likely log an error message and decide to deny access to the resource.

Please note the code in Example 4 that handles the CamsTransportException. If the AccessControlRequest has any SessionId object, they are cleared. The access control check is then tried again and if it succeeds, the AccessControlResponse handling proceeds normally. If the access control check fails, an exception is thrown.

import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.client.CamsTransportException;

import com.cafesoft.cams.access.AccessControlService;
import com.cafesoft.cams.access.AccessControlRequest;
import com.cafesoft.cams.access.AccessControlResponse;
import com.cafesoft.cams.access.AccessControlException;
import com.cafesoft.cams.access.InvalidResourceException;
import com.cafesoft.cams.access.ResourceRequest;
import com.cafesoft.cams.access.ResourceRequestFactory;
import com.cafesoft.cams.access.ResourceType;

import com.cafesoft.cams.session.SessionId;
import com.cafesoft.cams.session.MalformedSessionIdException;

import com.cafesoft.security.common.access.StandardResourceType;

import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;

import java.util.Properties;

...

  /**
   * The ResourceType name for sample access control checks on resources.
   */
  private static String RESOURCE_TYPE_NAME = "http";

  /**
   * The ResourceType description for sample resources.
   */
  private static String RESOURCE_TYPE_DESC = "HTTP Resource Type";

  /**
   * The ResourceType actions for sample resources.
   *
   * NOTE: THE ORDER OF THESE ELEMENTS MUST MATCH THE ORDERING OF THE
   * THE SAME ELEMENTS IN THE cams.conf FILE DEPLOYED ON WAM POLICY 
   * SERVER(s). FOR HISTORIC REASONS, EACH RESOURCE ACTION WITHIN A
   * GIVEN TYPE IS ASSIGNED A UNIQUE BIT VALUE BASED ON ITS POSITION IN 
   * THIS LIST, WHICH AIDS WITH EFFICIENT ACTION MATCHING.
   */
  private static String[] RESOURCE_TYPE_ACTIONS =
  { "GET","POST","PUT","DELETE","HEAD","OPTIONS","TRACE","PROPFIND",
    "PROPPATCH","MKCOL","COPY","MOVE","LOCK","UNLOCK","DEBUG"
  };

  /**
   * The "http" ResourceType object used when constructing a "resource
   * request".
   *
   * NOTE: This variable is static because it represents a ResourceType
   * supported by this example agent. There is no need to have more than
   * one instance. Existing WAM web agents support this ResourceType and
   * no other types.
   */
  private static ResourceType httpResourceType = new StandardResourceType(
    RESOURCE_TYPE_NAME, RESOURCE_TYPE_DESC, RESOURCE_TYPE_ACTIONS);

...

   /**
   * Test the WAM access control service.
   *
   * @param sessionId a WAM session identifier object representing
   *  an authenticated WAM user or null if no authenticated user.
   */
  public void testAccessControlService(SessionId sessionId)
  {
    AccessControlService acService = null;
    AccessControlRequest acRequest = null;
    AccessControlResponse acResponse = null;

    logger.info(this, "Starting access control service test");

    try
    {
      // Get the access control service from the CamsClient.
      acService = (AccessControlService)camsClient.find(
        "access-control", AccessControlService.class);

      // Get an AccessControlRequest object from the service.
      acRequest = acService.createAccessControlRequest();

      // Create an HTTP ResourceRequest.
      ResourceRequest resourceRequest =
        resReqFactory.createResourceRequest(httpResourceType,
          "http://localhost:8080/cams/camstest.jsp", "GET");
      acRequest.setResourceRequest(resourceRequest);
      acRequest.setRemoteAddr("127.0.0.1");
      acRequest.setRemoteHost("localhost");
      acRequest.setLoginConfigEntry("http");
      acRequest.setConfidential(false);

      if (sessionId != null)
        acRequest.addSessionId(sessionId);

      if (debug)
        acRequest.log(logger, true);

      try
      {
        acResponse = acService.checkAccess(acRequest);
      }
      catch(CamsTransportException cte)
      {
        if(!acRequest.hasSessionIds())
        {
          logger.error(this, "Error occurred checking access", cte);
          return;
        }

        acRequest.clearSessionIds();

        try
        {
          acResponse = acService.checkAccess(acRequest);
        }
        catch(CamsTransportException cte2)
        {
          throw new AccessControlException(
            "Error occurred sending request", cte2);
        }
      }

      if (debug)
        acResponse.log(logger);

      // Your agent should handle the access control response as needed
      // for the agent environment. Sample code is provided in private
      // method: handleAccessControlResponse();
      handleAccessControlResponse(acRequest, acResponse);
    }
    catch(ServiceException se)
    {
      logger.error(this, "Error occurred checking access", se);
    }
    catch(AccessControlException ace)
    {
      logger.error(this, "Error occurred checking access", ace);
    }
    catch(InvalidResourceException ire)
    {
      logger.error(this, "Error occurred checking access", ire);
    }
    finally
    {
      if (acService != null)
      {
        if (acRequest != null)
          acService.destroy(acRequest);
        if (acResponse != null)
          acService.destroy(acResponse);
        acService = null;
      }
    }

    logger.info(this, "Completed access control service test");
  }

Example 4 - Executing an Access Control Check

Handling the AccessControlResponse

Example 5 shows more examples of special AccessControlResponse handling your agent may need. Besides a “GRANTED”, “DENIED”, or “PENDING” status code, the AccessControlResponse also returns a finer-grained reason code that is helpful for handling possible issues such as:

  • Returning the requested resource because access was granted.

  • Displaying an access denied message.

  • Caching an access control check because access was unconditionally granted or denied.

  • Clearing cached access control checks.

  • Prompting the user for login credentials because access was denied for want of an authenticated user.

  • Removing previously cached RemoteSession information (see the WAM SessionAccessService).

  • Telling the user that the requested resource is only available via confidential means (e.g. SSL).

  • Displaying an error message.

The precise action taken by your agent may depend on the agent type, the type of client, and the way it integrates into its operating environment. Example 5 shows some example code that provides finer-grained handling based on the reason code. Contact OneLogin support if you have additional questions.

Handling AccessControlResponse Obligations

An “obligation” is and instruction sent from the WAM Policy Server (the Policy Decision Point), which your WAM agent (the Policy Enforcement Point) is obligated to handle. As of WAM 3.0, obligations are supported in the access control and authentication protocols.

Access control obligations can be returned with “GRANTED” and “DENIED” decisions and the type of obligation returned is usually very specific to the WAM agent type.

For example , a WAM web agent might be sent an obligation that tells it to redirect a web browser to a specific URL or add a specific header to an HTTP response. The agent must, of course, handle the obligations in such a way that is valid for the environment in which it operates.

For example, if an obligation instructs the agent to redirect a user’s browser, then it would be illegal to return other content in the same HTTP response and an error to receive a second redirect obligation in the same access control response.

If an agent cannot handle or does not understand an obligation, the recommended course of action is to “fail safe.” After all, the obligation may designate a critical action that denies access to a highly valuable resource.

The typical action in this case is to log a detailed error message and report an appropriate “friendly” error message to the user. Example 5 shows the typical location where obligation handling is performed and in this case simply displays information about the obligation.

import com.cafesoft.cams.client.CamsClient;

import com.cafesoft.cams.access.AccessControlService;
import com.cafesoft.cams.access.AccessControlRequest;
import com.cafesoft.cams.access.AccessControlResponse;
import com.cafesoft.cams.access.AccessControlException;
import com.cafesoft.cams.access.ResourceRequest;
import com.cafesoft.cams.access.ResourceRequestFactory;

import com.cafesoft.cams.access.http.HttpResourceRequestFactory;

import com.cafesoft.cams.session.SessionId;
import com.cafesoft.cams.session.MalformedSessionIdException;

/**
 * A timestamp used to remember when the last access control policy
 * modification time, which is returned with access control responses.
 * This value can be used to flush cached access control checks.
 */
private long lastModified = 0L;

...

  /**
   * Handle the access control response
   *
   * @param acRequest the access control response associated with the response.
   * @param acResponse the access control response associated with the request.
   */
  private void handleAccessControlResponse(
    AccessControlRequest acRequest, AccessControlResponse acResponse)
  {
    // Handle the access control response.
    int acStatus = acResponse.getStatus();
    if (acStatus == AccessControlResponse.SC_GRANTED)
    {
      if (debug)
      {
        SessionId sessionId = acResponse.getSessionId();
        logger.debug(this, "access GRANTED to resource id=" +
          acRequest.getResourceId().toString() + "'" +
          ", security domain=" + acResponse.getSecurityDomainName() +
          ", session id=" + 
          (sessionId != null ? sessionId.toString() : "null"));
      }

      // The server configuration "last modified time" is useful for clearing
      // cached state.
      if (lastModified != acResponse.getLastModificationTime())
      {
        // Clear out cached state

        // Update the lastModified time
        lastModified = acResponse.getLastModificationTime();
      }

      // NOTE: Handle successful access control obligations here.
      // Obligations are actions that an agent is obligated (required)
      // to perform as a Policy Enforcement Point (PEP) based on a decision
      // by the Policy Decision Point (PDP), which in this case is the 
      // AuthenticationService at the WAM Policy Server.
      //
      // Obligations are specific to the type of agent. For a web agent,
      // an obligation may specify: to redirect the User-Agent to a 
      // specific URL, add an HTTP response header, respond with a 
      // speciic HTTP error code, etc. Using the Obligations API on
      // the WAM Policy Server, you can create/return obligations that
      // are very specific to the types of actions needed by your agent's
      // environment.
      //
      // See the WAM Obligations API for for more details.
      Iterator obligationItr = acResponse.getObligations();
      while (obligationItr.hasNext())
      {
        Obligation obligation = (Obligation)obligationItr.next();
        logger.info(this, "Access Control Obligation: id=" + 
          obligation.getId() + ", description=" + 
          obligation.getDescription());

        // Handle Attributes returned by the Obligation here.
      }

      // Do whatever is necessary on successful authentication here.
      // For example: create a session cookie, return the session id
      // to a web service, save the session id in a Java client, etc.

      // Access to the requested resource is granted. Various other values are
      // returned, which may be useful to an agent. 
      // Take action based on the reason.
      switch (acResponse.getReason())
      {
        case AccessControlResponse.RC_ACCESS_GRANTED_UNCONDITIONALLY:
        case AccessControlResponse.RC_DEFAULT_BIAS_APPLIED:
          // Cache unconditionally granted access control checks?
          break;

        case AccessControlResponse.RC_ACCESS_GRANTED_CONDITIONALLY:
          // Access was granted by one or more conditional 
          // access control rules
        break;

        default:
          // Ignore all other reason codes
          break;
      }

      // Fall through, or do whatever else is necessary to return the resource
    }
    else if (acStatus == AccessControlResponse.SC_DENIED)
    {
      if (debug)
      {
        SessionId sessionId = acResponse.getSessionId();
        logger.debug(this, "access DENIED to resource id=" + 
          acRequest.getResourceId().toString() + "'" +
          ", message=" + acResponse.getMessage() +
          ", reason code=" + acResponse.getReason() +
          ", security domain=" + acResponse.getSecurityDomainName() +
          ", session id=" + (sessionId != null ? sessionId.toString() : "null"));
      }

      // The server configuration "last modified time" might be useful for
      // clearing cached state.
      if (lastModified != acResponse.getLastModificationTime())
      {
        // Clear out cached state?

        // Update the lastModified time
        lastModified = acResponse.getLastModificationTime();
      }

      // NOTE: Handle denied access control obligations here.
      // Obligations are actions that an agent is obligated (required)
      // to perform as a Policy Enforcement Point (PEP) based on a decision
      // by the Policy Decision Point (PDP), which in this case is the 
      // AuthenticationService at the WAM Policy Server.
      //
      // Obligations are specific to the type of agent. For a web agent,
      // an obligation may specify: to redirect the User-Agent to a 
      // specific URL, add an HTTP response header, respond with a 
      // speciic HTTP error code, etc. Using the Obligations API on
      // the WAM Policy Server, you can create/return obligations that
      // are very specific to the types of actions needed by your agent's
      // environment.
      //
      // See the WAM Obligations API for for more details.
      Iterator obligationItr = acResponse.getObligations();
      while (obligationItr.hasNext())
      {
        Obligation obligation = (Obligation)obligationItr.next();
        logger.info(this, "Access Control Obligation: id=" + 
          obligation.getId() + ", description=" + 
          obligation.getDescription());

        // Handle Attributes returned by the Obligation here.
      }

      // Access to the requested resource was denied.
      // Take action based on the reason.
      switch (acResponse.getReason())
      {
        case AccessControlResponse.RC_ACCESS_DENIED_UNCONDITIONALLY:
        case AccessControlResponse.RC_ACCESS_DENIED_CONDITIONALLY:
        case AccessControlResponse.RC_DEFAULT_BIAS_APPLIED:

          // Perform "access-denied" action.
          break;

        case AccessControlResponse.RC_ACCESS_DENIED_AUTHENTICATION_REQUIRED:
        case AccessControlResponse.RC_ACCESS_DENIED_SESSION_EXPIRED:

          // Get the "login parameters" returned with the response.
          // NOTE: These values are configured in the login-config.xml
          // file on the WAM Policy Server for a given security domain
          // and login-config-entry
    
          Map loginParamMap = acResponse.getLoginParameters();

          // Prompt user to authenticate? (agent-specific action)
          break;

        case AccessControlResponse.RC_ACCESS_DENIED_CONFIDENTIALITY_REQUIRED:
          // Notify that access to resource requires confidentiality.
          // e.g. Redirect to "https" URL?
          break;

        case AccessControlResponse.RC_ACCESS_DENIED_EVALUATION_ERROR:
        case AccessControlResponse.RC_GENERAL_SERVER_ERROR:
        case AccessControlResponse.RC_INVALID_REMOTE_HOST_NAME:
        case AccessControlResponse.RC_INVALID_REMOTE_IP_ADDRESS:
        case AccessControlResponse.RC_INVALID_RESOURCE_IDENTIFIER:
        case AccessControlResponse.RC_UNKNOWN_RESOURCE_ACTION:
        case AccessControlResponse.RC_UNKNOWN_RESOURCE_TYPE:
        case AccessControlResponse.RC_UNKNOWN_SECURITY_DOMAIN:
        case AccessControlResponse.RC_UNKNOWN_LOGIN_CONFIG:
        case AccessControlResponse.RC_UNAUTHORIZED_AGENT:

          // Configuration or runtime error.
          logger.error(this, "access control error: " + 
            acResponse.getMessage());

          // Take appropriate "error" action here.

          break;

        default:

          // Unhandled access denied reason code.
          logger.error(this, "Unknown access denied reason code: " +
            acResponse.getReason() + ", message=" + 
            acResponse.getMessage());

          // Take appropriate "error" action here.
  
          break;
      }
    }
    else if (acStatus == AccessControlResponse.SC_PENDING)
    {
      logger.warning(this, "Access control descision is: PENDING (request timed out)");

      // Try again or take appropriate "error" action here.
    }
    else
    {
      logger.error(this, "Access control descision is unrecognized: status=" + acStatus);

      // Take appropriate "error" action here.
    }
  }

Example 5 - Examples of finer-grained access control response handling

Using the SessionAccessService

The SessionAccessService enables agents to get authenticated user session information from a WAM Policy Server. The SessionAccessService is generally used with the AuthenticationService, since the AuthResponse provides the authenticated user’s session id.

Information available from a user’s session includes:

  • Name of the security domain associated with the session.

  • Session creation time.

  • Session “last touched” time (the date/time of the last session activity).

  • Session idle time (milliseconds since the last session activity).

  • Subject name (name of the authenticated user).

  • Array of principal names (roles associated with the authenticated user).

  • Session attributes (namespace-delimited values associated with the session).

  • Authentication method(s) used to originally authenticate the user.

The way an agent makes use of this information is agent-specific. For example:

  • The WAM TomcatWebAgent and ApacheWebAgent make various session values and attributes available to cgi-bin scripts, servlets, and JSPs as HTTP request headers. This enables applications and portals to customize content based on the identity of an authenticated user.

  • The WAM TomcatWebAgent stores the authenticated subject name (user) and principals (roles) in a custom Tomcat “Realm”, which enables the servlet API to use methods like: Principal getUserPrincipal() and boolean isUserInRole(rolename).

  • The WAM ApacheWebAgent make various session values and attributes available to cgi-bin scripts, mod_jk connector, Warp connector, etc. available as HTTP request headers.

The general procedure for using the SessionAccessService includes:

  1. Finding a SessionAccessService instance using the CamsClient.

  2. Getting a SessionAccessRequest from the SessionAccessService.

  3. Populating the SessionAccessRequest.

  4. Invoking the SessionAccessService.

  5. Handling the SessionAccessResponse.

  6. Destroying the SessionAccessRequest and SessionAccessResponse.

Example 6 shows a code fragment that uses the WAM SessionAccessService. It assumes the CamsClient is already initialized and connected to a WAM Policy Server.

Some key points for Example 6 include:

  • The SessionAccessRequest must set the remote address of the client supposedly associated with the session id.

    This is a safeguard against “hijacked session” attacks. If session hijacking protection is enabled and if the client IP address does not match the one associated with the WAM Policy Server-managed session, the SessionControlRequest will fail with reason code: RC_INVALID_REMOTE_IP_ADDRESS.

  • Each security domain’s session-manager-service sets a secret key used to uniquely hash the session identifier. Tampering with the session identifier can be detected and is reported with reason code: SessionAccessResponse.RC_POISONED_SESSION.

  • If the requested session expired or was explicitly closed by some other agent, status code: SessionAccessResponse.RC_SESSION_DOES_NOT_EXIST is returned.

  • The SessionAccessService is a factory for SessionAccessRequest objects for use by agents.

    Because WAM is designed for high-performance environments, most service implementations cache/pool request/response objects. So, to get optimum performance from the WAM Agents API’s SessionAccessService, be sure to “destroy” the request/response objects returned to your agent.

  • For more information on the SessionAccessService, SessionAccessRequest, SessionAccessResponse, etc. see the WAM Javadoc.

On success, the SessionAccessResponse object returns more information than most other services. In particular, a hierarchy of name/value pairs can be returned as session “attributes”. In addition, various time values are available. Example 6 shows how these values can be accessed and used.

import com.cafesoft.cams.client.CamsClient;

import com.cafesoft.cams.session.access.SessionAccessService;
import com.cafesoft.cams.session.access.SessionAccessException;
import com.cafesoft.cams.session.access.SessionAccessRequest;
import com.cafesoft.cams.session.access.SessionAccessResponse;

import com.cafesoft.cams.session.SessionId;
import com.cafesoft.cams.session.MalformedSessionIdException;
import com.cafesoft.cams.session.RemoteSession;

import java.util.Date;
...

  /**
   * Test the WAM session access service.
   *
   * @param sessionId the identifier for the WAM session to be accessed.
   */
  private void testSessionAccessService(SessionId sessionId)
  {
    SessionAccessService saService = null;
    SessionAccessRequest saRequest = null;
    SessionAccessResponse saResponse = null;

    logger.info(this, "Starting session access service test");

    try
    {
      // Get the session access service from the CamsClient.
      saService = (SessionAccessService)camsClient.find(
        "session-access", SessionAccessService.class);

      // Get a session access request object from the service.
      saRequest = saService.createSessionAccessRequest();

      // Set the session id and the current user IP address.
      saRequest.setSessionId(sessionId);
      saRequest.setRemoteAddr("127.0.0.1");

      if (debug)
        saRequest.log(logger);

      try
      {
        saResponse = saService.accessSession(saRequest);
      }
      catch(CamsTransportException cte)
      {
        throw new SessionAccessException(
          "Error occurred sending session access request", cte);
      }

      if (debug)
        saResponse.log(logger);

      // Handle the session access response.
      int saStatus = saResponse.getStatus();
      if (saStatus == SessionAccessResponse.SC_SUCCESS)
      {
        // SessionAccess succeeded
        RemoteSession session = saResponse.getSession();
        logger.info(this, "Session access status is: SUCCESS");
        logger.info(this, "Session id=" + session.getId());
        logger.info(this, "Session status=" + session.getStatus());
        logger.info(this, "Security Domain Name=" + session.getSecurityDomainName());
        logger.info(this, "Creation Time=" + new Date(session.getCreationTime()));
        logger.info(this, "Last Touch Time=" + new Date(session.getLastTouchTime()));
        logger.info(this, "Idle Time=" + session.getIdleTime());

        // Get the subject name
        logger.info(this, "Subject (user) name=" + session.getSubjectName());

        // Get the principal names (roles)
        String[] principalName = session.getPrincipalNames();
        for (int i = 0; i < principalName.length; i++)
        {
          logger.info(this, "Principal (role) name=" + principalName[i]);
        }

        // Get all the session attribute namespaces
        String[] namespace = session.getNamespaces();
        for (int i = 0; i < namespace.length; i++)
        {
          // Get all attribute names within a namespace
          String[] attrName = session.getAttributeNames(namespace[i]);
          for (int j = 0; j < attrName.length; j++)
          {
            // Dump each name/value pair within the namespace
            logger.info(this, "Session Attribute: " + 
              namespace[i] + "." + attrName[j] + "=" + 
              session.getAttribute(namespace[i], attrName[j]));
          }
        }
      }
      else if (saStatus == SessionAccessResponse.SC_FAILED)
      {
        logger.info(this, "Session access status is: FAILED, reason code=" +
          saResponse.getReason());

        // SessionAccess failed. Take action based on the reason.
        switch (saResponse.getReason())
        {
          case SessionAccessResponse.RC_SESSION_DOES_NOT_EXIST:
            // The session id probably expired, cleanup cached session info
            // if necessary
            break;

          case SessionAccessResponse.RC_GENERAL_SERVER_ERROR:
          case SessionAccessResponse.RC_INVALID_REMOTE_IP_ADDRESS:
          case SessionAccessResponse.RC_UNAUTHORIZED_AGENT:
          case SessionAccessResponse.RC_UNKNOWN_SECURITY_DOMAIN:
            // Misconfiguration of agent or security domain
            logger.error(this, "session access failed: reason(" +
              saResponse.getReason() + "): " + saResponse.getMessage());
            break;

          case SessionAccessResponse.RC_INVALID_SESSION_ID:
          case SessionAccessResponse.RC_POISONED_SESSION:
            // The session id is invalid or corrupted. The format of the
            // session id is not understood or an internal checksum 
            // indicates the session id was tampered with or is NOT
            // associated with the RemoteAddress.
            break;

          default:
            // Unhandled reason code
            logger.error(this, "Unknown session access reason code: " +
              saResponse.getReason() + ", message=" + saResponse.getMessage());
            break;
        }
      }
      else if (saStatus == SessionAccessResponse.SC_PENDING)
      {
        logger.warning(this, "Session access status is: PENDING (request timed out)");
      }
      else
      {
        logger.error(this, "Session access status is unrecognized: status=" + saStatus);
      }
    }
    catch(ServiceException se)
    {
      logger.error(this, "Error occurred accessing session", se);
    }
    catch(SessionAccessException sae)
    {
      logger.error(this, "Error occurred accessing session", sae);
    }
    finally
    {
      if (saService != null)
      {
        if (saRequest != null)
          saService.destroy(saRequest);
        if (saResponse != null)
          saService.destroy(saResponse);
        saService = null;
      }
    }

    logger.info(this, "Completed session access service test");
  }

Example 6 - Using the session access service

Using the SessionControlService

The SessionControlService enables agents to remotely control an authenticated user’s session being managed on a WAM Policy Server. The primary use of this service is for closing active sessions, which “logs out” the user associated with the session. Future enhancements may include the ability to “touch” a session and the ability to add/update/remote session attributes.

Example 7 shows a Java code fragment that uses the WAM SessionControlService to close an active WAM session.

Some key points for Example 7 include:

  • The SessionControlRequest must set the remote address of the client supposedly associated with the session id.

    This is a safeguard against “hijacked session” attacks. If the client IP address does not match the one associated with the WAM Policy Server-managed session, the SessionControlRequest will fail with reason code: RC_INVALID_REMOTE_IP_ADDRESS.

  • Each security domain’s session-manager-service sets a secret key used to uniquely hash the session identifier. Tampering with the session identifier can be detected and is reported with reason code: SessionControlResponse.RC_POISONED_SESSION.

  • If the requested session expired or was explicitly closed by some other agent, status code: SessionControlResponse.RC_SESSION_DOES_NOT_EXIST is returned.

  • The SessionControlService is a factory for SessionControlRequest objects for use by agents.

    Because WAM is designed for high-performance environments, most service implementations cache/pool request/response objects. So, to get optimum performance from the WAM Agents API’s SessionControlService, be sure to “destroy” the request/response objects returned to your agent.

  • For more information on the SessionControlService, SessionControlRequest, SessionControlResponse, etc see the WAM Javadoc.

import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.session.control.SessionControlService;
import com.cafesoft.cams.session.control.SessionControlException;
import com.cafesoft.cams.session.control.SessionControlRequest;
import com.cafesoft.cams.session.control.SessionControlResponse;
import com.cafesoft.cams.session.control.SessionControlAction;

...

  /**
   * Test the WAM session control service.
   *
   * @param sessionId the WAM session identifier.
   */
  private void testSessionControlService(SessionId sessionId)
  {
    SessionControlService scService = null;
    SessionControlRequest scRequest = null;
    SessionControlResponse scResponse = null;

    logger.info(this, "Starting session control service test");

    try
    {
      // Get the access control service from the CamsClient.
      scService = (SessionControlService)camsClient.find(
        "session-control", SessionControlService.class);

      scRequest = scService.createSessionControlRequest();
      scRequest.setSessionId(sessionId);
      scRequest.setRemoteAddr("127.0.0.1");
      scRequest.setAction(SessionControlAction.CLOSE);

      if (debug)
        scRequest.log(logger);

      try
      {
        scResponse = scService.controlSession(scRequest);
      }
      catch(CamsTransportException cte)
      {
        throw new SessionControlException(
          "Error occurred sending session control request", cte);
      }

      if (debug)
        scResponse.log(logger);

      // Handle the session control response.
      int scStatus = scResponse.getStatus();
      if (scStatus == SessionControlResponse.SC_SUCCESS)
      {
        logger.info(this, "Session Control status is: SUCCESS");
      }
      else if (scStatus == SessionControlResponse.SC_FAILED)
      {
        logger.info(this, "Session Control status is: FAILED, reason code=" +
          scResponse.getReason());

        switch (scResponse.getReason())
        {
          case SessionControlResponse.RC_NOT_APPLICABLE:
            logger.info(this, "reason=RC_NOT_APPLICABLE");
            break;

          case SessionControlResponse.RC_GENERAL_SERVER_ERROR:
            logger.info(this, "reason=RC_GENERAL_SERVER_ERROR");
            break;

          case SessionControlResponse.RC_UNKNOWN_SECURITY_DOMAIN:
            logger.info(this, "reason=RC_GENERAL_SERVER_ERROR");
            break;

          case SessionControlResponse.RC_INVALID_SESSION_ID:
            logger.info(this, "reason=RC_GENERAL_SERVER_ERROR");
            break;

          case SessionControlResponse.RC_POISONED_SESSION:
            logger.info(this, "reason=RC_GENERAL_SERVER_ERROR");
            break;

          case SessionControlResponse.RC_SESSION_DOES_NOT_EXIST:
            logger.info(this, "reason=RC_GENERAL_SERVER_ERROR");
            break;

          default:
            logger.info(this, "reason is unrecognized");
            break;
        }
      }
      else if (scStatus == SessionControlResponse.SC_PENDING)
      {
        logger.warning(this, "Session Control status is: PENDING (request timed out)");
      }
      else
      {
        logger.error(this, "Session Control status is unrecognized: status=" + scStatus);
      }
    }
    catch(ServiceException se)
    {
      logger.error(this, "Session Control service error on close", se);
    }
    catch(SessionControlException sce)
    {
      logger.error(this, "Session Control exception on close", sce);
    }
    finally
    {
      if (scService != null)
      {
        if (scRequest != null)
          scService.destroy(scRequest);
        if (scResponse != null)
          scService.destroy(scResponse);
        scService = null;
      }
    }

    logger.info(this, "Completed session control service test");
  }

Example 7 - Using session control service

Using the PingService

The PingService enables agents to test connectivity, agent authentication, and agent access control with a WAM Policy Server. The primary use of this service is for testing connectivity during agent deployment and configuration, but it can might also be used to poll a WAM Policy Server for “liveness.”

Example 8 shows a Java code fragment that uses the WAM PingService.

Some key points for Example 8 include:

  • The PingService will probably be most useful when executed as standalone utility program.

    This will enable Ping connection setup to be isolated from other service connections. Any ConnectionExceptions delivered to ConnectionExceptionListeners can then be isolated to the PingService.

  • The PingService is a factory for PingRequest objects for use by agents.

    Because WAM is designed for high-performance environments, most service implementations cache/pool request/response objects. So, to get optimum performance from the WAM Agents API’s PingService, be sure to “destroy” the request/response objects returned to your agent.

  • For more information on the PingService, PingRequest, PingResponse, etc see the WAM Javadoc.

import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.client.ConnectionExceptionListener;
import com.cafesoft.cams.ping.PingService;
import com.cafesoft.cams.ping.PingService;
import com.cafesoft.cams.ping.PingServiceRequest;
import com.cafesoft.cams.ping.PingServiceResponse;

...

  /**
   * Test the WAM ping service.
   */
  private void testPingService()
  {
    PingService pingService = null;
    PingRequest pingRequest = null;
    PingResponse pingResponse = null;

    logger.info(this, "Starting ping service test");

    try
    {
      // Get the ping service from the CamsClient.
      pingService = (PingService)camsClient.find("ping", PingService.class);

      // Create a PingRequest object.
      pingRequest = pingService.createPingRequest();

      // Set the IP address for this agent.
      pingRequest.setRemoteAddr("127.0.0.1");

      // Set the WAM Policy Server name to be pinged.
      pingRequest.setServerName("MyCamsServer");

      // Ping the WAM Policy Server.
      pingResponse = pingService.ping(pingRequest);

      // Handle the PingResponse.
      int pingStatus = pingResponse.getStatus();
      if (pingStatus == PingResponse.SC_SUCCESS)
      {
        logger.info(this, "Ping status is: SUCCESS");
        logger.info(this, "Ping message is: " + pingResponse.getMessage());
        logger.info(this, "Ping server name is: " + pingResponse.getServerName());
        logger.info(this, "Ping server address is: " + pingResponse.getServerAddr());
        logger.info(this, "Ping server port is: " + pingResponse.getServerPort());
      }
      else if (pingStatus == PingResponse.SC_FAILED)
      {
        logger.info(this, "Ping status is: FAILED");
        logger.info(this, "Ping message is: " + pingResponse.getMessage());
        logger.info(this, "Ping server name is: " + pingResponse.getServerName());
        logger.info(this, "Ping server address is: " + pingResponse.getServerAddr());
        logger.info(this, "Ping server port is: " + pingResponse.getServerPort());
      }
      else if (pingStatus == PingResponse.SC_PENDING)
      {
        logger.warning(this, "Ping status is: PENDING (request timed out)");
      }
      else
      {
        logger.error(this, "Ping status is unrecognized: status=" + pingStatus);
      }
    }
    catch(ServiceException se)
    {
      logger.error(this, "Error occurred pinging server", se);
    }
    catch(CamsTransportException cte)
    {
      logger.error(this, "Error occurred pinging server", cte);
    }
    catch(PingException pe)
    {
      logger.error(this, "Error occurred pinging server", pe);
    }
    finally
    {
      if (pingService != null)
      {
        if (pingRequest != null)
          pingService.destroy(pingRequest);
        if (pingResponse != null)
          pingService.destroy(pingResponse);
        pingService = null;
      }
    }
  }

Example 8 - Using the Ping Service


Have a Question?

Have a how-to question? Seeing a weird error? Contact us.

Found a bug? Submit a support ticket.

Have a product idea or request? Share it with us in our Ideas Portal.