mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noel Rouvignac
12.00.2013 ab5ee2d4ce27ec1e0efb8e41957bda50cedae187
OPENDJ-830 (CR-1538) Implement authentication and authorization for HTTP connection handler

Added the possibility for the HTTP Connection Handler to accept unauthenticated requests.

CollectClientConnectionsFilter.java:
Extracted method getAuthenticationInfo() and added support for unauthenticated requests here.

CollectClientConnectionsFilterTest.java:
Added tests.

HTTPConnectionHandler.java:
Added acceptUnauthenticatedRequests().

config.ldif, 02-config.ldif, HTTPConnectionHandlerConfiguration.xml, HTTPConnectionHandlerCfgDefn.properties:
Added property 'ds-cfg-authentication-required' with default 'true' to HTTP Connection Handler
7 files modified
152 ■■■■ changed files
opends/resource/config/config.ldif 1 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 9 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml 32 ●●●●● patch | view | raw | blame | history
opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties 4 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java 66 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java 14 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilterTest.java 26 ●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -498,6 +498,7 @@
ds-cfg-ssl-client-auth-policy: optional
ds-cfg-ssl-cert-nickname: server-cert
ds-cfg-config-file: config/http-config.json
ds-cfg-authentication-required: true
dn: cn=LDIF Connection Handler,cn=Connection Handlers,cn=config
objectClass: top
opends/resource/schema/02-config.ldif
@@ -3671,6 +3671,12 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.119
  NAME 'ds-cfg-authentication-required'
  EQUALITY booleanMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -3848,7 +3854,8 @@
        ds-cfg-ssl-cipher-suite $
        ds-cfg-max-blocked-write-time-limit $
        ds-cfg-buffer-size $
        ds-cfg-config-file )
        ds-cfg-config-file $
        ds-cfg-authentication-required )
  X-ORIGIN 'OpenDJ Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.14
  NAME 'ds-cfg-entry-cache'
opends/src/admin/defn/org/opends/server/admin/std/HTTPConnectionHandlerConfiguration.xml
@@ -217,7 +217,7 @@
  <adm:property name="max-request-size" advanced="true">
    <adm:synopsis>
      Specifies the size in bytes of the largest HTTP request message that will
      be allowed by this HTTP Connection handler.
      be allowed by the <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:description>
      This can help prevent denial-of-service attacks by clients that indicate 
@@ -429,7 +429,7 @@
  </adm:property>
  <adm:property name="config-file" mandatory="true">
    <adm:synopsis>
      Specifies the name of the configuration file for the HTTP Connection Handler.
      Specifies the name of the configuration file for the <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
@@ -453,4 +453,32 @@
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="authentication-required" mandatory="true">
    <adm:synopsis>
      Specifies whether only authenticated requests can be processed by the
      <adm:user-friendly-name />.
    </adm:synopsis>
    <adm:description>
      If true, only authenticated requests will be processed by the
      <adm:user-friendly-name />. If false, both authenticated requests and
      unauthenticated requests will be processed. All requests are subject
      to ACI limitations and unauthenticated requests are subject to server
      limits like maximum number of entries returned. Note that setting
      ds-cfg-reject-unauthenticated-requests to true will override the current
      setting.
    </adm:description>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>true</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-authentication-required</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/messages/HTTPConnectionHandlerCfgDefn.properties
@@ -12,6 +12,8 @@
property.allowed-client.requires-admin-action.synopsis=Changes to this property take effect immediately and do not interfere with connections that may have already been established.
property.allow-tcp-reuse-address.synopsis=Indicates whether the HTTP Connection Handler should reuse socket descriptors.
property.allow-tcp-reuse-address.description=If enabled, the SO_REUSEADDR socket option is used on the server listen socket to potentially allow the reuse of socket descriptors for clients in a TIME_WAIT state. This may help the server avoid temporarily running out of socket descriptors in cases in which a very large number of short-lived connections have been established from the same client system.
property.authentication-required.synopsis=Specifies whether only authenticated requests can be processed by the HTTP Connection Handler.
property.authentication-required.description=If true, only authenticated requests will be processed by the HTTP Connection Handler. If false, both authenticated requests and unauthenticated requests will be processed. All requests are subject to ACI limitations and unauthenticated requests are subject to server limits like maximum number of entries returned. Note that setting ds-cfg-reject-unauthenticated-requests to true will override the current setting.
property.buffer-size.synopsis=Specifies the size in bytes of the HTTP response message write buffer.
property.buffer-size.description=This property specifies write buffer size allocated by the server for each client connection and used to buffer HTTP response messages data when writing.
property.config-file.synopsis=Specifies the name of the configuration file for the HTTP Connection Handler.
@@ -31,7 +33,7 @@
property.listen-port.description=Only a single port number may be provided.
property.max-blocked-write-time-limit.synopsis=Specifies the maximum length of time that attempts to write data to HTTP clients should be allowed to block.
property.max-blocked-write-time-limit.description=If an attempt to write data to a client takes longer than this length of time, then the client connection is terminated.
property.max-request-size.synopsis=Specifies the size in bytes of the largest HTTP request message that will be allowed by this HTTP Connection handler.
property.max-request-size.synopsis=Specifies the size in bytes of the largest HTTP request message that will be allowed by the HTTP Connection Handler.
property.max-request-size.description=This can help prevent denial-of-service attacks by clients that indicate they send extremely large requests to the server causing it to attempt to allocate large amounts of memory.
property.ssl-cert-nickname.synopsis=Specifies the nickname (also called the alias) of the certificate that the HTTP Connection Handler should use when performing SSL communication.
property.ssl-cert-nickname.description=This is only applicable when the HTTP Connection Handler is configured to use SSL.
opends/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilter.java
@@ -134,29 +134,24 @@
      Connection connection = new SdkConnectionAdapter(clientConnection);
      String[] userPassword = extractUsernamePassword(request);
      if (userPassword != null && userPassword.length == 2)
      AuthenticationInfo authInfo = getAuthenticationInfo(request, connection);
      if (authInfo != null)
      {
        AuthenticationInfo authInfo =
            authenticate(userPassword[0], userPassword[1], connection);
        if (authInfo != null)
        {
          clientConnection.setAuthenticationInfo(authInfo);
        clientConnection.setAuthenticationInfo(authInfo);
          /*
           * WARNING: This action triggers 3-4 others: Set the connection for
           * use with this request on the HttpServletRequest. It will make
           * Rest2LDAPContextFactory create an AuthenticatedConnectionContext
           * which will in turn ensure Rest2LDAP uses the supplied Connection
           * object
           */
          request.setAttribute(
              Rest2LDAPContextFactory.ATTRIBUTE_AUTHN_CONNECTION, connection);
        /*
         * WARNING: This action triggers 3-4 others: Set the connection for use
         * with this request on the HttpServletRequest. It will make
         * Rest2LDAPContextFactory create an AuthenticatedConnectionContext
         * which will in turn ensure Rest2LDAP uses the supplied Connection
         * object.
         */
        request.setAttribute(
            Rest2LDAPContextFactory.ATTRIBUTE_AUTHN_CONNECTION, connection);
          // send the request further down the filter chain or pass to servlet
          chain.doFilter(request, response);
          return;
        }
        // send the request further down the filter chain or pass to servlet
        chain.doFilter(request, response);
        return;
      }
      // The user could not be authenticated. Send an HTTP Basic authentication
@@ -227,6 +222,37 @@
  }
  /**
   * Returns an {@link AuthenticationInfo} object if the request is accepted. An
   * {@link AuthenticationInfo} object will be returned if authentication
   * credentials were valid or if unauthenticated requests are allowed on this
   * server.
   *
   * @param request
   *          the request used to extract the {@link AuthenticationInfo}
   * @param connection
   *          the connection used to retrieve the {@link AuthenticationInfo}
   * @return an {@link AuthenticationInfo} if the request is accepted, null if
   *         the request was rejected
   * @throws Exception
   *           if any problem occur
   */
  AuthenticationInfo getAuthenticationInfo(ServletRequest request,
      Connection connection) throws Exception
  {
    String[] userPassword = extractUsernamePassword(request);
    if (userPassword != null && userPassword.length == 2)
    {
      return authenticate(userPassword[0], userPassword[1], connection);
    }
    else if (this.connectionHandler.acceptUnauthenticatedRequests())
    {
      // return unauthenticated
      return new AuthenticationInfo();
    }
    return null;
  }
  /**
   * Extracts the username and password from the request using one of the
   * enabled authentication mechanism: HTTP Basic authentication or HTTP Custom
   * headers. If no username and password can be obtained, then send back an
opends/src/server/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -186,6 +186,20 @@
    super(DEFAULT_FRIENDLY_NAME);
  }
  /**
   * Returns whether unauthenticated HTTP requests are allowed. The server
   * checks whether unauthenticated requests are allowed server-wide first then
   * for the HTTP Connection Handler second.
   *
   * @return true if unauthenticated requests are allowed, false otherwise.
   */
  public boolean acceptUnauthenticatedRequests()
  {
    // the global setting overrides the more specific setting here.
    return !DirectoryServer.rejectUnauthenticatedRequests()
        && !this.currentConfig.isAuthenticationRequired();
  }
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/http/CollectClientConnectionsFilterTest.java
@@ -28,6 +28,7 @@
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.opends.server.protocols.http.CollectClientConnectionsFilter.*;
import java.io.IOException;
@@ -36,15 +37,15 @@
import javax.servlet.http.HttpServletResponse;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.util.Base64;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@SuppressWarnings("javadoc")
public class CollectClientConnectionsFilterTest extends DirectoryServerTestCase
{
  private static final String AUTHORIZATION =
      CollectClientConnectionsFilter.HTTP_BASIC_AUTH_HEADER;
  private static final String USERNAME = "Aladdin";
  private static final String PASSWORD = "open sesame";
  private static final String BASE64_USERPASS = Base64
@@ -131,7 +132,7 @@
    authConfig.setBasicAuthenticationSupported(true);
    HttpServletRequest request = mock(HttpServletRequest.class);
    when(request.getHeader(AUTHORIZATION)).thenReturn(
    when(request.getHeader(HTTP_BASIC_AUTH_HEADER)).thenReturn(
        "Basic " + BASE64_USERPASS);
    assertThat(filter.extractUsernamePassword(request)).containsExactly(
@@ -156,4 +157,23 @@
        USERNAME, PASSWORD);
  }
  /**
   * Tests that getAuthenticationInfo() without basic auth header or custom
   * headers returns an unauthenticated info when the server accepts
   * unauthenticated requests.
   */
  @Test
  public void getAuthenticationInfoReturnsUnauthenticatedInfo()
      throws Exception
  {
    HttpServletRequest request = mock(HttpServletRequest.class);
    HTTPConnectionHandler cfg = mock(HTTPConnectionHandler.class);
    when(cfg.acceptUnauthenticatedRequests()).thenReturn(true);
    filter = new CollectClientConnectionsFilter(cfg, authConfig);
    AuthenticationInfo authInfo = filter.getAuthenticationInfo(request, null);
    assertThat(authInfo).isNotNull();
    assertThat(authInfo.isAuthenticated()).isFalse();
  }
}