See the WebSphere eXtreme Scale Wiki for links to eXtreme Scale Version 7.0 documentation.
If you log in
with your developerWorks ID, you can leave comments and feedback for the development team.
Use this topic to learn about the ObjectGrid client authentication mechanism.
ObjectGrid client server security has the following important aspects:
Enabling client server security
Security must be enabled on both the client and server in order to be able to successfully authenticate with teh ObjectGrid.
To enable security, modify the following properties:
Enabling client security
To enable the security on the client side, set the securityEnabled property in the security.ogclient.props file to true. ObjectGrid ships a client security property template file, the security.ogclient.props file, in the [WAS_HOME]/optionalLibraries/ObjectGrid/properties directory for a WebSphere Extended Deployment installation, or the /ObjectGrid/properties directory in a mixed server installation. You can modify this template file with appropriate values.
The description of the securityEnabled property follows:
- securityEnabled (true, false+): This property indicates if security is enabled. When a client connects to a server, the securityEnabled value on the client and server side must be both true or bothfalse. For example, if the connected server security is enabled, the client has to set this property to true to connect to the server.
The com.ibm.websphere.objectgrid.security.config.ClientSecurityConfiguration
interface represents the security.ogclient.props file. You can use the com.ibm.websphere.objectgrid.security.config.ClientSecurityConfigurationFactory
public API to create an instance of this interface with default values, or you can create an instance by passing the ObjectGrid client security property file. The security.ogclient.props file contains other properties. Refer to ClientSecurityConfiguration API Documentation
and ClientSecurityConfigurationFactory API Documentation
for more details.
Enabling server security
To enable the security on the server side, you can set the securityEnabled property in the cluster XML or the security.xml to true. Here is an example:
<cluster>
<objectGrid name="cluster" securityEnabled="true"
singleSignOnEnabled="true" loginSessionExpirationTime="300">
Use a security descriptor XML file to specify the cluster security configuration. This better isolates the cluster wide security configuration from the non-security configuration.
For dynamic deployment, the catalog server cluster XML is hidden from users so you cannot directly modify the cluster XML. The only way to enable a cluster security is to use a security descriptor XML file.
Creating a credential and credential generator
When connecting a server, a client needs to present its own credential. A client credential is represented by a com.ibm.websphere.objectgrid.security.plugins.Credential
interface. We have shown this interface in the section J2SE security tutorial step 2 - client authentication. The credential can contain a user password pair, a Kerberos ticket, or data in any format the client and server agrees upon.
ObjectGrid provides three default implementations for the Credential interfaces:
ObjectGrid also provides a plug-in to generate a credential. This plug-in is represented by the com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator interface. We have shown this interface in the section J2SE security tutorial step 2 - client authentication.
ObjectGrid provides two default built-in implementations:
- The com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator
constructor takes a user ID and a password. When the getCredential method is called, it returns a UserPasswordCredential object that contains the user ID and password.
- The com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator
represents a credential (security token) generator when running in WebSphere Application Server. When the getCredential method is called, the Subject that is associated with the current thread is retrieved. Then the security information in this Subject object is converted into a WSTokenCredential object. You can specify whether to retrieve a runAs subject or a caller subject from the thread by using the constant WSTokenCredentialGenerator.RUN_AS_SUBJECT or WSTokenCredentialGenerator.CALLER_SUBJECT.
Refer to the API documentation for more details.
Connecting to the ObjectGrid securely
If an ObjectGrid client wants to connect to a server securely, you can use any connect method in the ObjectGridManager
interface, which takes a ClientSecurityConfiguration object. Take the following connect method as an example:
/**
* This method allows client to connect to a Remote ObjectGrid
* The RemoteObject Grid is hosted as specified by the parameters
* @param clusterName: The name of the cluster to which this client
* will attach itself
* @param host: The host on which to connect to
* @param port: The clientAceess port which is listening.
* @param ClientSecurityConfiguration: Security configuration. It can be null
* if security is not configured
* @param overRideObjectGrid XML. This parameter can be null. If it is not
* null, the client side configuration of ObjectGrid plug-in is overridden.
* Not all plug-ins can be overridden. For details, see the ObjectGrid documents.
* @throws ConnectException
* @ibm-api
*/
public ClientClusterContext connect(String clusterName, String host, String port,
ClientSecurityConfiguration securityProps, URL overRideObjectGrid) throws
ConnectException ;
This method takes a parameter of the ClientSecurityConfiguration type, among others. This interface represents a client security configuration. You can usecom.ibm.websphere.objectgrid.security.config.ClientSecurityConfigurationFactory
public API to create an instance with default values, or you can create an instance by passing the ObjectGrid client security property file. This file contains the following properties that are related to authentication. The value marked with a plus sign + is the default value.
- securityEnabled (true, false+): This property indicates if the security is enabled. When a client connects to a server, the securityEnabled value on the client and server side must be both true or both false. For example, if the connected server security is enabled, the client has to set this property to true to connect to the server.
- credentialAuthentication (Never, Supported+, Required): This property indicates if the client supports credential authentication.
- If the property value is Never, no credential authentication is supported by this client.
- If the property value is Supported, client authentication is performed when communicating with any server that supports or requires credential authentication. Client credential authentication transmits a credential or a single sign-on (SSO) token.
- If the property value is Required, the client must send a credential to the server for authentication.
- authenticationRetryCount (an integer value, 0+). This property determines how many retries are attempted for login when a credential is expired. If the value is 0, no retries are attempted. The authentication retry only applies to the case when the credential is expired. If the credential is not valid, there is not any retry. Your application is responsible for retrying the operation.
- clientCertificateAuthentication (Never+, Supported, Required): This property indicates if the client supports client certificate authentication.
- If the property value is Never, no client certification authentication is supported on the client side.
- If the property value is Supported, transport layer client authentication can be performed and the client sends digital certificate to the server during the authentication stage.
- If the property value is Required, the client only authenticates with servers that support transport-layer client authentication.
- SSOEnabled: Specifies if the client supports passing single sign-on tokens to the server. Set this property to false if the client authenticates to every server. Set this property to true if the client only authenticates to one server. If you set SSOEnabled true on the client, verify that the single-sign-on-enabled property in the cluster XML configuration is also set to true.
You can also set these properties using setters in the ClientSecurityConfiguration interface.
After you create a com.ibm.websphere.objectgrid.security.config.ClientSecurityConfiguration
object, set the credentialGenerator object on the client using the following method:
/**
* Set the {@link CredentialGenerator} object for this client.
* @param generator the CredentialGenerator object associated with this client
*/
void setCredentialGenerator(CredentialGenerator generator);
You can set the CredentialGenerator object in the ObjectGrid client security property file too. Here are the properties:
- credentialGeneratorClass: The class implementation name for the CredentialGenerator object. It must have a default constructor.
- credentialGeneratorProps: The properties for the CredentialGenerator class. If the value is not null, it is set to the constructed CredentialGenerator object using the setProperties(String) method.
Here is a sample to instantiate a ClientSecurityConfiguration and then use it to connect to the server.
/**
* Get a secure ClientClusterContext
* @return a secure ClientClusterContext object
*/
protected ClientClusterContext connect() throws ConnectException {
ClientSecurityConfiguration csConfig = ClientSecurityConfigurationFactory
.getClientSecurityConfiguration("/properties/security.ogclient.props");
UserPasswordCredentialGenerator gen= new
UserPasswordCredentialGenerator("manager", "manager1");
csConfig.setCredentialGenerator(gen);
return objectGridManager.connect(csConfig, null);
}
When the connect is called, the ObjectGrid client calls the CredentialGenerator.getCredential()
method to get the client credential. This credential is sent along with the connect request to the server for authentication.
Using a different CredentialGenerator instance per session
In some cases, an ObjectGrid client represents just one client identity; in other cases, it might represent multiple identities. Here is one scenario for the latter case: An ObjectGrid client is created and shared in a Web server. All servlets in this Web server use this one ObjectGrid client. Because every servlet represents a different Web client, use different credentials when sending requests to ObjectGrid servers.
ObjectGrid provides for changing the credential on the session level. Every session can uses a different CredentialGenerator object. Therefore, the previous scenarios can be done by letting the servlet get a session with a different CredentialGenerator object. The following example illustrates the ObjectGrid.getSession(CredentialGenerator)
method in the ObjectGridManager interface.
/**
* Get a session with a CredentialGenerator. This method can only be called
* by the ObjectGrid client in a client server environment.
*
* If ObjectGrid is used in a core model, that is, within the same JVM with
* no client or server existing, getSession(Subject) should be used to secure
* the ObjectGrid.
*
* @since WebSphere Extended Deployment Version 6.0.1
*/
Session getSession(CredentialGenerator credGen) throws
ObjectGridException, TransactionCallbackException;
Here is an example:
ObjectGridManager ogManager = ObjectGridManagerFactory.getObjectGridManager();
CredentialGenerator credGenManager = new UserPasswordCredentialGenerator("manager", "xxxxxx");
CredentialGenerator credGenEmployee = new UserPasswordCredentialGenerator("employee", "xxxxxx");
ObjectGrid og = ogManager.getObjectGrid(ctx, "accounting");
Session session = og.getSession(credGenManager );
ObjectMap om = session.getMap("employee");
session.begin();
Object rec1 = map.get("xxxxxx");
session.commit();
session = og.getSession(credGenEmployee );
om = session.getMap("employee");
session.begin();
Object rec2 = map.get("xxxxx");
session.commit();
If you use the ObjectGird.getSession method to get a Session object, the session uses the CredentialGenerator object set on the ClientConfigurationSecurity object. The ObjectGird.getSession(CredentialGenerator) method overrides the CredentialGenerator set in the ClientSecurityConfiguration object.
If you can reuse the Session object, a performance gain results. However, calling the ObjectGrid.getSession(CredentialGenerator) method is not very expensive; the major overhead is the increased object garbage collection time. Make sure that you release the references after you are done with the Session objects. In summary, if your Session object can share the identity, try to reuse the Session object; if your Session object cannot share the identity, use the ObjectGrid.getSession(CredentialGenerator) method.
Creating an authenticator
After the ObjectGrid client retrieves the Credential object using the CredentialGenerator object, the Credential object is sent along with the client request to the ObjectGrid server. The ObjectGrid server authenticates the Credential object before processing the request. If the Credential object is authenticated successfully, a Subject object is returned to represent this Credential object. This Subject object is then used for authorizing the request.
This Subject object is also cached. The object expires after its lifetime reaches the session timeout value. The login session timeout value can be set using the loginSessionExpirationTime property in the cluster XML file. For example, setting loginSessionExpirationTime="300" makes the Subject object expire in 300 seconds.
The ObjectGrid server uses the Authenticator
plug-in to authenticate the Credential object. We have shown this interface in the J2SE security tutorial step 2 - client authentication section.
An implementation of the Authenticator interface gets the Credential object and then authenticates it to a user registry, for example, a Lightweight Directory Access Protocol (LDAP) server, etc. ObjectGrid does not provide an out-of-box user registry configuration. Connecting to a user registry and authenticating to it must be implemented in this plug-in.
For example, one Authenticator implementation extracts the user ID and password from the credential, uses them to connect and validate to an LDAP server, and creates a Subject object as a result of the authentication. The implementation can utilize Java Authentication and Authorization Service (JAAS) login modules. A Subject object is returned as a result of authentication.
Notice that this method creates two exceptions: InvalidCredentialException
and ExpiredCredentialException
. The InvalidCredentialException exception indicates that the credential is not valid. The ExpiredCredentialException exception indicates that the credential expired. If one of these two exceptions result from the authenticate method, the exceptions are sent back to the client. However, the client run time deals with these two exceptions differently:
- If the error is an InvalidCredentialException exception, the client run time displays this exception. Your application must handle the exception. You can correct the CredentialGenerator, for example, and then retry the operation.
- If the error is an ExpiredCredentialException exception, and the retry count is not 0, the client run time calls the CredentialGenerator.getCredential method again, and sends the new Credential object to the server. If the new credential authentication succeeds, the server processes the request. If the new credential authentication fails, the exception is sent back to the client. If the number of authentication retries reaches the supported value and the client still gets an ExpiredCredentialException exception, the ExpiredCredentialException exception results. Your application must handle the error.
The Authenticator interface provides great flexibility. You can implement the Authenticator interface in your specific way. For example, you can implement this interface to do both credential authentication and client certificate authentication, to support both authentications. Or you can implement the interface to support two different user registries.
ObjectGrid supports two kinds of authentications: credential authentication and client certificate authentication. Which mechanism you use depends on the client and server side security property setting. These properties follow:
For dynamic deployment, the client certificate authentication is not supported yet.
Remember that you can also set these properties using programming APIs.
The following two tables display which authentication mechanism to use under different settings.
Table 1. Credential authentication under client and server settings
| Client credential authentication |
Server credential authentication |
Result |
| No |
Never |
Disabled |
| No |
Supported |
Disabled |
| No |
Required |
Error case |
| Supported |
Never |
Disabled |
| Supported |
Supported |
Enabled |
| Supported |
Required |
Enabled |
| Required |
Never |
Error case |
| Required |
Supported |
Enabled |
| Required |
Required |
Enabled |
When no credential authentication exists (the result is disabled), the client certificate authentication can occur if the server is statically configured.
The following table shows whether client certificate authentication is used under different settings. Notice the client certificate authentication is only possible if Secure Sockets Layer (SSL) is used as the communication protocol, and the credential authentication is not used.
Table 2. Client certificate authentication under client and server settings.
| Client clientCertificate authentication |
Server clientCertificate authentication |
Result |
| No |
Never |
Disabled |
| No |
Supported |
Disabled |
| No |
Required |
Error case |
| Supported |
Never |
Disabled |
| Supported |
Supported |
Enabled* |
| Supported |
Required |
Enabled* |
| Required |
Never |
Error case |
| Required |
Supported |
Enabled* |
| Required |
Required |
Enabled* |
* ClientCertificateAuthentication happens only when SSL is used as the protocol and CredentialAuthentication is not used.
Notice that subtlety exists: When both credential authentication and client certificate authentication are used, but the credential sent from the client is null, the client certificate authentication is used.
The authenticator can be configured in the cluster XML file of a statically configured server or a catalog server. An example follows:
<cluster name="cluster1" securityEnabled="true" singleSignOnEnabled="true"
loginSessionExpirationTime="300" statisticsEnabled="true" statisticsSpec="map.all=enabled">
<serverDefinition name="server1" host="localhost" clientAccessPort="12503" peerAccessPort="12500"
workingDirectory="" traceSpec="ObjectGrid=all=disabled" systemStreamToFileEnabled="true" />
<serverDefinition name="server2" host="localhost" clientAccessPort="12504" peerAccessPort="12501"
workingDirectory="" traceSpec="ObjectGrid=all=disabled" systemStreamToFileEnabled="true" />
<authenticator className ="com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenAuthenticator">
</authenticator>
</cluster>
ObjectGrid provides four default authentication built-in implementations for:
- User ID and password authentication to a key file user registry
- User ID and password authentication to a Lightweight Directory Access Protocol (LDAP) server
- SSL client certificate simple mapping authentication
- WebSphere Application Server security mechanism
Except for the Authenticator implementation for the WebSphere Application Server security mechanism, other built-in implementations are for testing or sample purposes only. The main purpose of these two built-ins is to allow you to do simple testing without writing any code. WebSphere Application Server Authenticator implementation is an out-of-box implementation that can be plugged in when both ObjectGrid servers and clients are in the same security domain.
You can use WebSphere Application Server APIs to get the user registry configured in the application server, and then use that in your authenticator implementation. However, this implementation is out of scope of this programming guide.
Using the ObjectGrid LDAP authenticator plug-in
ObjectGrid provides the com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthenticator
default implementation for this plug-in to handle the user name and password authentication to an LDAP server. This implementation uses the LDAPLogin login module to log the user into an Lightweight Directory Access Protocol (LDAP) server.The following snippet demonstrates how the authenticate method is implemented:
/**
* @see com.ibm.ws.objectgrid.security.plugins.Authenticator#
* authenticate(LDAPLogin)
*/
public Subject authenticate(Credential credential) throws
InvalidCredentialException, ExpiredCredentialException {
UserPasswordCredential cred = (UserPasswordCredential) credential;
LoginContext lc = null;
try {
lc = new LoginContext("LDAPLogin",
new UserPasswordCallbackHandlerImpl(cred.getUserName(),
cred.getPassword().toCharArray()));
lc.login();
Subject subject = lc.getSubject();
return subject;
}
catch (LoginException le) {
throw new InvalidCredentialException(le);
}
catch (IllegalArgumentException ile) {
throw new InvalidCredentialException(ile);
}
}
ObjectGrid ships a login module com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule for this purpose. You must provide the following two options in the JAAS login configuration file:
- providerURL: The LDAP server provider URL
- factoryClass: The LDAP context factory implementation class
The LDAPLoginModule module calls the com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthentcationHelper.authenticate method. The following code snippet shows how the authenticate method of the LDAPAuthenticationHelper is implemented:
/**
* Authenticate the user to the LDAP directory.
* @param user the user ID, e.g., uid=xxxxxx,c=us,ou=bluepages,o=ibm.com
* @param pwd the password
*
* @throws NamingException
*/
public String[] authenticate(String user, String pwd)
throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, factoryClass);
env.put(Context.PROVIDER_URL, providerURL);
env.put(Context.SECURITY_PRINCIPAL, user);
env.put(Context.SECURITY_CREDENTIALS, pwd);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
InitialContext initialContext = new InitialContext(env);
DirContext dirCtx = (DirContext) initialContext.lookup(user);
String uid = null;
int iComma = user.indexOf(",");
int iEqual = user.indexOf("=");
if (iComma > 0 && iComma > 0) {
uid = user.substring(iEqual + 1, iComma);
}
else {
uid = user;
}
Attributes attributes = dirCtx.getAttributes("");
String thisUID = (String) (attributes.get(UID).get());
String thisDept = (String) (attributes.get(HR_DEPT).get());
if (thisUID.equals(uid)) {
return new String[] { thisUID, thisDept };
}
else {
return null;
}
}
If authentication succeeds, the ID and password are considered valid. Then the login module gets the UID information and department information from this authenticate method. The login module creates two principals: SimpleUserPrincipal and SimpleDeptPrincipal. You can use the authenticated subject for group authorization (in this case, the department is a group) and individual authorization.
The following example shows a login module configuration that is used to log in to the LDAP server:
LDAPLogin { com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule required
providerURL="ldap:
factoryClass="com.sun.jndi.ldap.LdapCtxFactory";
};
In the previous configuration, the LDAP server points to theldap://directory.acme.com:389/server. Change this setting to your LDAP server. This login module uses the provided user ID and password to connect to the LDAP server. This implementation is for testing purposes only.
Using the WebSphere Application Server authenticator plug-in
ObjectGrid also provides the com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenAuthenticator
built-in implementation to use the WebSphere Application Server security infrastructure. This built-in implementation can be used when the following conditions exist:
- WebSphere Application Server global security is turned on.
- Both ObjectGrid clients and ObjectGrid servers are launched in the Java virtual machines of WebSphere Application Server.
- These application servers are in the same security domain.
- The ObjectGrid client is already authenticated in WebSphere Application Server.
The ObjectGrid client can use the com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator class to generate a credential. The ObjectGrid server uses this Authenticator implementation class to authenticate the credential. If the token is authenticated successfully, a Subject object returns.
This scenario takes advantage of the fact that the ObjectGrid client has already been authenticated. Because the application servers that have the ObjectGrid servers are in the same security domain as the application servers that house the ObjectGrid clients, the security tokens can be propagated from the ObjectGrid client to the ObjectGrid server so the same user registry does not need to be authenticated again.
Using the simple certificate mapping authenticator plug-in
ObjectGrid also provides a built-in com.ibm.websphere.objectgrid.security.plugins.builtins.CertificateMappingAuthenticator implementation to map the certificate to an Subject object. The implementation extracts the distinguished name (DN) of the first certificate in the chain and creates a principal with that name. This implementation is for testing purposes only.
Using the Tivoli Access Manager authenticator plug-in
Tivoli Access Manager is used widely as a security server. You can also implement Authenticator using the Tivoli Access Manager-provided login modules.
To authenticate a user using Tivoli Access Manager, use the com.tivoli.mts.PDLoginModule login module, that requires that the calling application provide the following information:
- A principal name, specified as either a short name or an X.500 name (DN)
- A password
The login module authenticates the principal and returns the Tivoli Access Manager credential. The login module expects the calling application to provide the following information:
- The user name, through a javax.security.auth.callback.NameCallback object.
- The password, through a javax.security.auth.callback.PasswordCallback object.
When the Tivoli Access Manager credential is successfully retrieved, the JAAS LoginModule creates a Subject and a PDPrincipal. No built-in for Tivoli Access Manager authentication is provided, because it is just with the PDLoginModule module. Refer to theIBM Tivoli Access Manager Authorization Java Classes Developer Referencefor more details.
Using single sign-on
After an ObjectGrid client successfully authenticates to a server, the ObjectGrid server creates a Subject object. If both the client and server support single sign-on (SSO), this Subject object is then converted to an SSO token. This token is passed back to the client side to associate with the socket. This SSO token can be passed to a new server for authentication so there is no need to authenticate again on a different server.
SSO token is implemented using ObjectGrid secure token manager mechanism. For more details about secure token manager, see ObjectGrid cluster security. Basically, the secure token mechanism uses cryptographic keys (secret keys) to encrypt and decrypt user data that passes between the servers, and public-private keys to sign the data.
The SSO token also contains an expiration time. All product servers that participate in a protection domain must have their time, date, and time zone synchronized. If not, the SSO tokens seem prematurely expired and cause authentication or validation failures. This synchronization is not necessary if universal time is used.
When an ObjectGrid client connects to a different server, this SSO token can be passed to the new server. This server validates the SSO token by unsigning and decrypting it to make sure it has not been tampered with. The server also checks the SSO token. If the token is valid, the client does not need to authenticate to this server.
If an SSO token is expired, the server has to reauthenticate the client. The server asks the client to provide the credential again.
Enabling single sign-on for a client
Enabling client single sign-on can be done in two ways:
- Configuration. Use the SSOEnabled property in the security.ogclient.props file to enable single sign-on on the client side.
- Programming. Use the ClientSecurityConfiguration attribute to enable single sign-on with the following method.
/**
* Set whether single sign-on is enabled.
* @param enabled whether single sign on is enabled for this
* client or not.
*/
void setSingleSignOnEnabled(boolean enabled);
Notice that single sign-on is enabled only if security is enabled.
Enabling single sign-on for the server
To enable single sign-on on the statically configured server or a catalog server, set the singleSignOnEnabled attribute to true in the cluster XML file. An example follows:
<cluster securityEnabled="true" singleSignOnEnabled="true" loginSessionExpirationTime="300">
Notice that single sign-on is enabled only if security is enabled.
If the catalog server single sign-on is enabled, then all container servers have single sign-on enabled
Creating a system credential generator
Starting from XD 6.1, we introduce a new concept called system credential generator. This is due to the new architecture of dynamic configuration. In the dynamic configuration model, the dynamic container server connects to the catalog server as an ObjectGrid client and the catalog server can connect to the ObjectGrid container server as client too. This system credential generator is used to represent a factory for the system credential.
Consider a system credential something like a administrator credential. It should be securely configured and managed.
The SystemCredentialGenerator can be configured in the catalog cluster XML. Here is an example:
<systemCredentialGenerator className ="com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator">
<property name="properties" type="java.lang.String" value="manager manager1" description="username password" />
</systemCredentialGenerator>
For demonstration purposes, the user name and password are stored in clear text. Do not store the user name and password in clear text in a production environment.
ObjectGrid provides a default system credential generator, which utilizes the server credentials (Refer to ObjectGrid cluster security for server credentials) as the credential. If users do not explicitely specify the system credential generator, this default system credential generator will be used.
© Copyright IBM Corporation 2007,2009. All Rights Reserved.