Back | Next | Contents Cams Programmer's Guide

Programming Cams Agents

This chapter describes the Cams Agents API, which enables you to implement custom Cams agents that communicate with a Cams Policy Server to authenticate users, check access control permissions, retrieve session attributes, and control Cams sessions. In addition, the Cams Agents API works in conjunction with the Cams Permissions API enabling you to extend Cams to support new/custom resource types.

The CamsClient is at the core of the Cams Agents API. It provides a way for agents to lookup and remotely invoke Cams agent services hosted on a Cams Policy Server by use of client-side service stubs. Typical services used by agents include:

Agent-level code need not be concerned with the underlying network protocol(s) used by Cams client-side service stubs to communicate with a Cams Policy Server, only the business methods available from services to enforce security checks.

Programming Languages and Network Protocol Support

Currently, the Cams Agents API is available only from the Java programming language. A language binding for C/C++ is also under consideration along with support for popular platforms like Linux/i386, Windows/i386, Solaris/sparc, and others. If you have need to implement an agent in an operating environment that does not support Java, please contact Cafésoft.

In addition to possible support for different programming languages and operating systems, the Cams Policy Server has an architecture which enables addition of server-side connectors for new network interfaces. Although this must currently be done by Cafésoft engineering, if a particular networking interface like: RMI, CORBA, Web Services, DCOM, etc. is important to you, please be sure to let is know. Cams is designed to be flexible and extensible and we're interested in supporting access to Cams services wherever need arises.

Using the CamsClient

This section describes the basics of using the CamsClient, including:

  1. Creating a CamsClient
  2. Connecting to the Cams Policy Server(s)
  3. Finding a Cams Service
  4. Disconnecting from the Cams Policy Server
  5. Destroying the CamsClient

Details on how to use each of the services available from the CamsClient are available later in this document on a service-by-service basis.

Creating a CamsClient

The first step to using a CamsClient is creating the Config object required to initialize it. The Config object contains initialization parameters and a Logger instance. The CamsClient uses a Java Properties object for configuration with contents that may vary depending on the referenced classes. Without digging into specific configuration Properties yet, Example 1 shows the general approach for creating and initializing a CamsClient.

 
import com.cafesoft.cams.client.CamsClient;

import com.cafesoft.cams.Config;
import com.cafesoft.cams.ConfigException;
import com.cafesoft.cams.StandardConfig;

import com.cafesoft.core.log.Logger;
import com.cafesoft.core.log.StderrLogger;

import com.cafesoft.security.common.client.StandardCamsClientFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.Properties;

...

CamsClient camsClient = null;

try
{
   ...

	// Create a Config object.
	Config config = createConfig(new File("./cams-client-example.conf"));

	// Create and initialize a CamsClient instance
	camsClient = createCamsClient(config);

	...
}
catch (ConfigException e)
{
	// Error initializing CamsClient
	e.printStackTrace();
}
catch (FileNotFoundException e)
{
	// Configuration file does not exist
	e.printStackTrace();
}
catch (IOException e)
{
	// Error reading configuration file
	e.printStackTrace();
}

...
Example 1 - Creating and Initializing the CamsClient

Creating a Config Object

The CamsClient uses a Config object to access initialization parameters, a Logger, and basic information about the context in which it operates. Example 2 shows how a generic Config object can be created from a Properties object and a Logger instance.

	/**
	 * Create a Config object from a File.
	 *
	 * @param configFile the configuration file.
	 * @return an initialized CamsClient instance.
	 * @exception ConfigException if a CamsClient configuration error.
	 */
	public static Config createConfig(File configFile)
		throws ConfigException
	{
		// Load configuration properties from a file.
		Properties properties = loadPropertiesFromFile(configFile);

		// Create the Logger.
		Logger logger = createLogger(properties);

		return new StandardConfig(properties, "", null, logger);
	}
Example 2 - Creating the Config Object

Loading Configuration Properties from a File

Example 3 shows generic code for loading configuration properties from a file. The configuration file path is generally configured in a platform-specific way. For example, a the Cams Apache 2 web agent configures the path using a directive in the Apache "httpd.conf" file, the Cams web agent for Tomcat uses the "server.xml" file and "catalina.home" system property, and the Cams IIS web agent uses a path relative to the "cams" virtual directory configured in the IIS admin GUI.

The configuration file contents may vary widely based on the number of Cams policy servers configured, the type of Logger configured, the services needed, the agent type, etc. Consequently, this documention shows small segments of possible configuration properties in the client API contexts in which they apply. You may want to review the complete cams-client-example.conf file provided with the Cams Client API example code and specific Cams web agent configuration files. Contact Cafésoft support for answers to specific configuration property questions.

	/**
	 * Load the configuration Properties from a File.
	 *
	 * @param configFile the configuration File object.
	 * @return a new Properties object with all of the appropriate properties.
	 */
	private static Properties loadPropertiesFromFile(File configFile)
		throws ConfigException
	{
		Properties properties = new Properties();
		FileInputStream fis = null;

		try
		{
			fis = new FileInputStream(configFile);
			properties.load(fis);
		}
		catch (FileNotFoundException e)
		{
			throw new ConfigException("Error loading configuration file: " + 
				configFile.getAbsolutePath(), e);
		}
		catch (IOException e)
		{
			throw new ConfigException("Error loading configuration file: " + 
				configFile.getAbsolutePath(), e);
		}
		finally
		{
			try
			{
				if (fis != null)
					fis.close();
			}
			catch (Exception e)
			{
			}
		}

		return properties;
	}
Example 3 - Loading Configuration Properties from a File

Creating a Logger

The CamsClient depends on access to a Cams Logger object from within the Config object. The Logger is used for logging FATAL, ERROR, WARNING, INFO, and DEBUG-level messages and is generally the same Logger used by the enclosing Cams agent. The code in Example 4 shows how a Logger can be created and initialized using Properties loaded from the configuration file.

The Logger implementation class is specified by property logger.class, which generally uses the CamsTraceLogger implementation as shown below.

logger.class=com.cafesoft.cams.log.CamsTraceLogger
logger.file.path=./logs/cams-client-example.log
logger.file.append=true
logger.file.bufferedIO=true
logger.file.bufferSize=8192
logger.file.maxSize=10MB
logger.file.maxBackupIndex=100
logger.enableConsole=true
logger.enableDebugFilter=false
logger.verbose=false
logger.debug=false

The CamsTraceLogger is a specialized Logger implementation that will send INFO and DEBUG-level messages to a configured file and WARNING, ERROR, and FATAL messages to the file and to the System.err stream. Configuration properties for this logger are documented in the sample cams-client-example.conf file and the Cams javadocs.

Example 4 shows how the Cams StderrLogger implementation can be returned if creation of the Logger specified in the configuration file fails. See the javadocs for com.cafesoft.core.log.StderrLogger for more details. NOTE: Additional Logger implementations are available for use in the com.cafesoft.core.log Java package.

	/**
	 * Create the Logger for use by the CamsClient.
	 *
	 * @param properties the configuration properties.
	 * @return a new Logger instance.
	 */
	private static Logger createLogger(Properties properties)
	{
		Logger logger = StderrLogger.DEFAULT_STDERR_LOGGER;
		String className = properties.getProperty("logger.class");

		try
		{
			properties.put("logger.name", "cams-client-example");
			logger = (Logger)ClassUtils.createInstance(className);
			logger.initialize(properties);
		}
		catch (ClassInstantiationException cie)
		{
			StringBuffer buffer = new StringBuffer();
			buffer.append("Could not create Logger object, reason=");
			buffer.append(cie.getMessage());
			buffer.append(" creating StderrLogger");
			logger = StderrLogger.DEFAULT_STDERR_LOGGER;
			logger.warning(CamsClientExample.class, buffer.toString());
		}
		catch (LoggerException le)
		{
			StringBuffer sb = new StringBuffer();
			sb.append("Could not initialize Logger object, ");
			sb.append("reason=");
			sb.append(le.getMessage());
			sb.append(", Creating StderrLogger");
			logger = StderrLogger.DEFAULT_STDERR_LOGGER;
			logger.warning(CamsClientExample.class, sb.toString());
		}

		return logger;
	}
Example 4 - Creating the Logger

Creating and Initializing a CamsClient

Once a Config object has been created, creation and initialization of a CamsClient instance is quite simple using a CamsClientFactory as shown in Example 5. The StandardCamsClientFactory creates a CamsClient instance using the value of initialization parameter cams.client.class, which is generally provided in the agent configuration file.

cams.client.class=com.cafesoft.security.common.client.StandardCamsClient

The CamsClient instance is initialized in Example 5 with:

camsClient.initialize(config);

which throws a ConfigException if any required initialization parameters are missing or any supplied parameters are invalid.

	/**
	 * Create and initialize a CamsClient instance.
	 *
	 * @param config the Config object.
	 * @return an initialized CamsClient instance.
	 * @exception ConfigException if a CamsClient configuration error.
	 */
	public static CamsClient createCamsClient(Config config)
		throws ConfigException
	{
		CamsClient camsClient = null;

		CamsClientFactory clientFactory = new StandardCamsClientFactory();
		camsClient = clientFactory.create(config);
		camsClient.initialize(config);

		return camsClient;
	}
Example 5 - Creating and Initializing a CamsClient

Connecting to the Cams Policy Server(s)

Using a CamsClient, connecting to Cams Policy Server(s) is accomplished using the connect() method as shown in Example 6. If the client cannot connect, an ERROR is logged and a ConnectionException is delivered to registered ConnectionExceptionListeners. The StandardCamsClient implementation is designed to:

 
import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.client.ConnectionException;
import com.cafesoft.cams.client.ConnectionExceptionListener;

...

   // Connect to the Cams Policy Server(s)
   camsClient.connect(); 

...
Example 6 - Connecting to the Cams Policy Server using CamsClient

If the CamsClient cannot communicate with the Cams Policy Server when using one of the Cams services due to a broken or nonexistent connection, a service-specific Exception will be thrown. See the service-specific documentation for more details.

The following sections describe how the StandardCamsClient is configured to connect with one or more Cams Policy Servers.

Configuring the Cams Policy Server URL(s)

Using the StandardCamsClient implementation, connections to Cams Policy Server(s) are configured using initialization parameters of the form:

cams.server.url.<name>=<url>

where <name> is a registered Cams Policy Server name and <url> is the fully-qualified URL to the host and port where the Cams Policy Server is listening for client connections. For example:

cams.server.url.MyCamsServer=cams://localhost:9191

uses <name>=MyCamsServer and <url>=cams://localhost:9191. This is the default value that you'll find in all cams-webagent.conf files. Please note that the protocol is "cams", not "http" or "https". The "cams" protocol is an optimized, proprietary, binary format designed for speed and security. Future CamsClient implementations may support other protocols, including https and other secure protocols.

The following Cams Policy Server initialization parameters configure two Cams Policy Servers named "Orville" and "Wilbur" on remote hosts:

cams.server.uri.Orville=cams://orville.mydomain.com:9191
cams.server.url.Wilbur=cams://wilbur.mydomain.com:9191

Configuring the Cams Policy Server Cluster Name

All Cams Policy Servers configured for a given CamsClient instance must be members of the same cluster, which is configured using the cams.cluster.name initialization parameter:

cams.cluster.name=MyCamsCluster

This value must be identical for the CamsClient and the Cams Policy Server to which a connection is attempted. A mismatch causes a connection authentication failure.

Configuring the Number of Connections per Policy Server

From a system resource perspective, TCP/IP sockets are a rather expensive resource to create and clean up. Consequently, the Cams messaging framework was designed to enable many client threads to multiplex messages over a single Cams connection. This saves system resources and increases system scalability and performance. Each connection is capabable of handling all Cams service protocols and multiplexing requests and responses for many concurrent client threads. Each client thread uses a thread-safe "Cams Message Socket", which is described in the next section.

Connections to Cams Policy Servers are managed in an "object pool", which is configured with a minimum and maximum number of connections using the following values:

connection.pool.minium=2
connection.pool.maximum=5

As additional connections are needed, they are added in increments configured by the following parameter:

connection.pool.increment=1

The details on how and when new connections are created and existing connections are shutdown based on connection pool minimum and maximum sizes are implementation specific and cannot be controlled via the CamsClient API (or any other existing API). The actual number of connections needed for a given client environment may vary widely based on the number of Cams Policy Servers in a cluster, system load, agent type, the mix of Cams services used, and the other network services on which they depend (like LDAP servers, RDBMS servers, etc). At present the only reliable way to determine suitable values for "worst case" scenarios is to load test in a simulated environment. That said, the following guidelines usually apply:

Connections may be established using an "eager" or a "lazy" paradigmn depending on the value of the following initialization parameter:

connection.pool.lazyConnect=false

A value of "false" indicates use of eager connections (which are created when CamsClient.connect() is invoked). A value of "true" causes delayed connection creation until the first Cams service request is attempted. Use of an eager connection paradigm is recommended as connectivity problems are more easily diagnosed and minimal connections resources will be ready and waiting to expedite Cams service requests.

Configuring the Number of Message Sockets per Connection

The Cams message framework multiplexes messages from many different client threads using the concept of a "Message Socket". A single Cams connection may establish 10 or 20 reusable message sockets. During the scope of a client service request, a message socket is transparently reserved and used as a endpoint for request/response messages. A service request sent by a Cams client will be transported over the connection associated with a given message socket and will be handled by the receiving end of the message socket by the Cams Policy Server, where it is dispatched to a service based on content type. Though communications are asynchronous, the Cams Client API hides this detail and appears to block until a response is received or a configurable timeout period is exceeded.

The number of message sockets per Cams connection is configured using the following initialization parameter:

connection.pool.socket.maximum=10

The default value of 10 message sockets per connection is rarely changed, though values of 5-30 have been used depending on various environment performance characteristics. Using multiple Cams connections, each with multiple message sockets, the Cams message framework attempts to load balance requests across connections. Additional connections and message sockets are created as needed based on system load.

From a resource allocation perspective, the goal is to allocate a sufficient number of connections and message sockets for the typical load handled through a given agent, while providing additional capacity for traffic spikes. For example, the following connection related initialization parameters generally sufficient for an environment that handles 20 requests per second, but may handle up to 200 requests per second:

connection.pool.lazyConnect=false
connection.pool.minimum=2
connection.pool.maximum=20
connection.pool.increment=1
connection.pool.socket.maximum=10

Configuring the Client Authentication Credentials

Cams clients are authenticated by a Cams Policy Server whenever a new connection is established. The Cams message framework sends encrypted username, password, and the Cams cluster name as encrypted parameters using the configured secret key parameters configured for the Cams client (see section: Configuring Secret Key Parameters).

connection.authentication.type=EncryptedParameters
connection.authentication.principal=cams-web-agent
connection.authentication.credential=password

The Cams "system" security domain generally authenticates agents and by default the "principal" name and "credential" correspond to a username/password configured in the "cams-users.xml" configuration file, though agent accounts can be setup and managed through any Cams LoginModule (e.g. LDAP, RDBMS, etc.).

Configuring Secret Key Parameters

Cams uses symmetric secret key PKI to encrypt/decrypt sensitive values (like authentication credentials) sent between Cams web agents and a Cams policy server. The following initialization parameters must match those configured for your Cams Policy Server(s):

cams.skey.algorithm=Blowfish
cams.skey.key=ed28f2c7b60e978277d125d774bd25c1cad3c5c1a7f02757
cams.skey.iv=1a5dce1235fd429e

NOTE: the displayed secret key values MUST be changed a production environment to secure communications in your environment.

Please consult Securing Cams Communications using Secret Keys for more details.

Handling ConnectionExceptions

If your agent needs to detect and handle connection problems, you can create and register a ConnectionExceptionListener with the CamsClient. Regardless of whether you do so, INFO, WARNING, ERROR, and FATAL messages will be logged. Example 4 shows how you might create and handle a ConnectionException by registering a ConnectionExceptionListener.

Due to the intrinsic connection management, load balancing, and connection recovery capabilities of the Cams message framework, ConnectionExceptionListener implementations are generally used to notify administratators that a connectivity problem may exist. For example: if a single Cams Policy Server nodes temporarily becomes unavailable due to a system failure or networking issue, a ConnectionException will be delivered to registered ConnectionExceptionListeners, even though a second Cams Policy Server node is still accessible.

 
import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.client.ConnectionException;
import com.cafesoft.cams.client.ConnectionExceptionListener;

...

   // Add a ConnectionExceptionListener
   camsClient.addConnectionExceptionListener(new MyConnectionExceptionListener());

   // Connect to the Cams Policy Server
   camsClient.connect(); 


public class MyConnectionExceptionListener implements ConnectionExceptionListener
{
   public handleConnectionException(ConnectionException e)
   {
      System.err.println("[ERROR] Received ConnectionException: msg=" + e.getMessage());

      // Notify an administrator via e-mail?
      ...
   }
}
Example 7: Handling ConnectionExceptions using a ConnectionExceptionListener

Testing the CamsClient Connection State

The CamsClient API provides a way to check the "connection state", which really means that the client:

This may seem a little confusing, but the Cams client and message framework was designed to provide reliable messages services, including failover and recovery transparently at the client API level. If necessary, the recommended way for Cams agent-level code to test for connectivity to specific Cams Policy Servers is using the Cams "ping" service. NOTE: the standard CamsClient Context implementation transparently uses the "ping" service to monitor Cams Policy Servers and provide failover/recovery functionality.

Example 8 shows how the CamsClient API can be used to test the connection state. The .isConnected() method will return true after invocation of .connect() and before invocation of .disconnect(), whether or not an active TCP/IP connect to a Cams Policiy Server exists.

import com.cafesoft.cams.client.CamsClient;

...

   // Test the CamsClient for connection state
   boolean isConnected = camsClient.isConnected(); 

...
Example 8: Checking the CamsClient connection state

Finding a Cams Service

The services available via a CamsClient are generally configured in its initialization properties and are looked up via the CamsClient by:

  1. type - Specified in the Java API as a Java Class
  2. id - a String token

Before an agent can lookup and use a Cams service, the CamsClient must be initialized and connected. Example 9 contains a code fragment showing how an agent can lookup the Cams service used for authenticating users.

 
import com.cafesoft.cams.client.CamsClient;
import com.cafesoft.cams.client.ConnectionException;
import com.cafesoft.cams.client.ConnectionExceptionListener;

import com.cafesoft.cams.ConfigException;

import com.cafesoft.cams.auth.AuthenticationService;

import com.cafesoft.core.service.ServiceException;

import com.cafesoft.core.log.Logger;

...

try
{
...
   // Connect to the Cams Policy Server
   camsClient.connect();

   // Lookup the Cams AuthenticationService
   AuthenticationService authService = (AuthenticationService) camsClient.find(
"authentication", AuthenticationService.class); ...
} catch (ConfigException e) { logger.error(this, "CamsClient configuration error", e); } catch (ServiceException e) { logger.error(this, "Error finding a Cams service", e); }
Example 9 - Finding the Cams AuthenticationService by id and type

The common services (including the service identifier and type) available from a CamsClient via the Java API may include:

Service id
Service type
Description
access-control com.cafesoft.cams.access.AccessControlService Used to check access to protected resources
authentication com.cafesoft.cams.auth.AuthenticationService Used to authenticate users
session-access com.cafesoft.cams.session.access.SessionAccessService Used to get information about authenticated user sessions
session-control com.cafesoft.cams.session.control.SessionControlService Used to close authenticated user sessions
ping com.cafesoft.cams.ping.PingService Used to check connectivity with a Cams Policy Server

Table 1 - Summary of common services in the Cams Agents Java API

Check the CamsClient configuration for the actual services registered, their identifiers and types. Sections later in this document describe how to use each of these services.

Disconnecting from the Cams Policy Server

The CamsClient should be gracefully disconnected from the Cams Policy Server when an agent exits or restarts. This gives the CamsClient a chance to:

  1. Disconnect active network connections
  2. Cleanup allocated service resources

Example 10 shows a CamsClient disconnecting.

import com.cafesoft.cams.client.CamsClient;

...

   // Disconnect from the Cams Policy Server
   camsClient.disconnect();

...
Example 10 - Disconnecting a CamsClient

Once disconnected, it is illegal to lookup and use services. It is, however, possible to reconnect by invoking the CamsClient "connect()" method.

Destroying the CamsClient

Destruction of the CamsClient causes disconnection and cleanup of all network connections, services, and other resources. Once destroyed, it is illegal to use the CamsClient and any of its services. A destroyed CamsClient cannot be reinitialized and reconnected. Example 11 shows how to destroy a CamsClient.

 
import com.cafesoft.cams.client.CamsClient;

...

   // Gracefully disconnect
   camsClient.disconnect();

   // Destroy the CamsClient
   camsClient.destroy(); 
   camsClient = null;

...
Example 11 - Disconnecting a CamsClient

Using the AuthenticationService

The AuthenticationService enables you to delegate user authentication to a specific security domain hosted on a Cams 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 includes:

  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 12 shows a code fragment that uses the Cams AuthenticationService . It assumes the CamsClient is already initialized and connected to a Cams 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 Cams Policy Servers are unavailable. Since Cams sessions are "sticky" to a given Cams 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 Cams 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 Cams 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 13 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 Cams Policy Servers has already failed.

 

	/**
	 * Test the Cams authentication service.
	 *
	 * @return a Cams 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 Cams 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 Cams 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 Cams 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 Cams sessions are "sticky"
				// to a policy server.
				//
				// In this case, clear the existing Cams session and try a fresh
				// (anonymous) authentication, which might be handled by another
				// available Cams 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 12 - Using the AuthenticationService

Handling the Authentication Response

NKK: TBD

Some key points for Example 13 include:

 

	/**
	 * 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 Cams 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 Cams Policy Server, you can create/return obligations that
			// are very specific to the types of actions needed by your agent's
			// environment.
			//
			// See the Cams 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 Cams 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 13 - Authentication response handling examples

Using the AccessControlService

The AccessControlService enables you to delegate access control checks to a Cams Policy Server. As a web access management product, Cams is generally used perform access control checks on http/https resource, but it is also used to control access to "cams" services by Cams agents. The AccessControlService is extensibly designed to support access control checks on new/custom resource types when used in conjunction with the Cams 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 Cams 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, Cams 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 Cams UrlResourceRequestFactory, which is initialized as shown in Example 14.

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 14 - 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 Cafésoft 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: Cams agents should also add standard agent "attributes" as described in section (NKK: TBD) 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 Cams security domains), add them to the AccessControlRequest.
  6. Use the AccessControlService to execute the access control check on the AccessControlResponse.
  7. Handle the AccessControlResponse. NOTE: More details on access control response handling is provided later in this document.
  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 Cams Policy Servers are unavailable. Since Cams sessions are "sticky" to a given Cams 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 users identity. If another Cams 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 Cams 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 15 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 CAMS 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 Cams 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 Cams access control service.
	 *
	 * @param sessionId a Cams session identifier object representing
	 * 	an authenticated Cams 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 15 - Executing an Access Control Check

Handling the AccessControlResponse

Example 16 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 like:

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 16 shows some example code that provides finer-grained handling based on the reason code. Contact Cafésoft support if you have additional questions.

Handling AccessControlResponse Obligations

An "obligation" is and instruction sent from the Cams Policy Server (the Policy Decision Point) which your Cams agent (the Policy Enforcement Point) is obligated to handle. As of Cams 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 Cams agent type. For example , a Cams web agent might be sent an obligation that tell 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 16 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 Cams 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 Cams Policy Server, you can create/return obligations that
			// are very specific to the types of actions needed by your agent's
			// environment.
			//
			// See the Cams 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 Cams 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 Cams Policy Server, you can create/return obligations that
			// are very specific to the types of actions needed by your agent's
			// environment.
			//
			// See the Cams 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 Cams 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 16 - Examples of finer-grained access control response handling

Using the SessionAccessService

The SessionAccessService enables agents to get authenticated user session information from a Cams 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:

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

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 17 shows a code fragment that uses the Cams SessionAccessService . It assumes the CamsClient is already initialized and connected to a Cams Policy Server. Some key points for Example 17 include:

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 17 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 Cams session access service.
	 *
	 * @param sessionId the identifier for the Cams 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 17 - Using the session access service

Using the SessionControlService

The SessionControlService enables agents to remotely control an authenticated user's session being managed on a Cams 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, the ability to add/update/remote session attributes.

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

Some key points for Example 18 include:

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 Cams session control service.
	 *
	 * @param sessionId the Cams 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 18 - Using session control service

Using the PingService

The PingService enables agents to test connectivity, agent authentication, and agent access control with a Cams 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 Cams Policy Server for "liveness".

Example 19 shows a Java code fragment that uses the Cams PingService.

Some key points for Example 19 include:

 
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 Cams 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 Cams Policy Server name to be pinged.
			pingRequest.setServerName("MyCamsServer");

			// Ping the Cams 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 19 - Using the Ping Service

 

Back | Next | Contents