Apache2-AuthenNTLM-Cookie

 view release on metacpan or  search on metacpan

lib/Apache2/AuthenNTLM/Cookie.pm  view on Meta::CPAN

# see doc at end of file

package Apache2::AuthenNTLM::Cookie;

use strict;
use warnings;

use Apache2::RequestRec        ();
use Apache2::Request;
use Apache2::Cookie;
use Apache2::Directive         ();
use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED) ;
use Digest::SHA1               qw(sha1_hex);
use MIME::Base64               ();

use Apache2::AuthenNTLM;

our $VERSION = '1.02';

# constants from NTLM protocol
use constant NEGOTIATE_UNICODE     => 0x00000001;
use constant NEGOTIATE_NTLM        => 0x00000200;
use constant TARGET_TYPE_DOMAIN    => 0x00010000;
use constant NEGOTIATE_TARGET_INFO => 0x00800000;
use constant NTLM_SIGNATURE        => "NTLMSSP";
use constant NTLM_FORMAT           => "a8 V a8 V a8 a8 a8";

# named fields corresponding to format above
my @NTLM_FIELDS = qw/signature msg_type target_name flags 
                     challenge context target_info/;

# cookie format: digest(40); time_created(12); username
use constant COOKIE_FORMAT         => "A40 A12 A*"; 


sub handler : method  {
  my ($class, $r) = @_ ;

  # create an instance
  my $self = bless {
    request     => $r,
    secret      => $r->dir_config('secret')      || $class->default_secret,
    refresh     => $r->dir_config('refresh')     || 14400, # in seconds
    cookie_name => $r->dir_config('cookie_name') || 'NTLM_AUTHEN',
   }, $class;

  my $result;

  # get the cookie
  my $jar    = Apache2::Cookie::Jar->new($r);
  my $cookie = $jar->cookies($self->{cookie_name});
  my $has_valid_cookie = $cookie && $self->validate_cookie($cookie->value);

  # if cookie is present and valid
  if ($has_valid_cookie) {
    $result = Apache2::Const::OK;

    # if MSIE "optimization" is activated, i.e. if this is a POST with an
    # NTLM type1 message and without body ... 
    if ($r->method eq 'POST' && $self->has_empty_body && $self->is_NTLM_msg1) {

      # ... then we must fake a type2 msg so that MSIE will post again
      $r->log->debug("AuthenNTLM::Cookie: creating fake type2 msg");
      $self->add_auth_header($self->fake_NTLM_msg2);
      $result = Apache2::Const::HTTP_UNAUTHORIZED;
    }
  }

  # otherwise (if cookie is absent or invalid)
  else {

    # if no NTLM message, directly ask for authentication (avoid calling
    # Apache2::AuthenNTLM because it pollutes the error log)
    if (!$self->get_NTLM_msg && $self->is_ntlmauthoritative) {
      $self->ask_for_authentication;
      $result = Apache2::Const::HTTP_UNAUTHORIZED;
    }

    # else invoke Apache2::AuthenNTLM to go through the NTLM handshake    
    else {
      my $msg = $cookie ? "cookie invalidated" : "no cookie";
      $r->log->debug("AuthenNTLM::Cookie: $msg, calling Apache2::AuthenNTLM");
      $result = Apache2::AuthenNTLM->handler($r); # will set $r->user

      # create the cookie if NTLM succeeded
      $self->set_cookie if $result == Apache2::Const::OK;
    }
  }

  return $result;
}


sub validate_cookie {
  my ($self, $cookie_val) = @_;

  # unpack cookie information
  my ($sha, $time_created, $username) = unpack COOKIE_FORMAT, $cookie_val;

  # valid if not too old and matches the SHA1 digest
  my $now = time;
  my $is_valid 
    =  ($now - $time_created) < $self->{refresh}
    && $sha eq sha1_hex($time_created, $username, $self->{secret});

  # if valid, set the username
  $self->{request}->user($username) if $is_valid;

  $self->{request}->log->debug("cookie $cookie_val is " . 
                                 ($is_valid ? "valid" : "invalid"));
  return $is_valid;
}


sub set_cookie {
  my ($self) = @_;

  # prepare a new cookie from current time and current user
  my $r           = $self->{request};
  my $username    = $r->user; # was just set from the parent handler
  my $now         = time;
  my $sha         = sha1_hex($now, $username, $self->{secret});
  my $cookie_val  = pack COOKIE_FORMAT, $sha, $now, $username;
  my @cookie_args = (-name => $self->{cookie_name}, -value => $cookie_val);

  # other cookie args may come from apache config
 ARG:
  foreach my $arg (qw/expires domain path/) {
    my $val = $r->dir_config($arg) or next ARG;
    push @cookie_args, -$arg => $val;
  }

  # send cookie
  my $cookie = Apache2::Cookie->new($r, @cookie_args);
  $cookie->bake($r);

  $r->log->debug("AuthenNTLM::Cookie: baked cookie $cookie_val");
}


sub default_secret {
  my ($class) = @_;

  # default secret : mtime and i-node of Apache configuration file
  my $config_file     = Apache2::Directive::conftree->filename;
  my ($mtime, $inode) = (stat $config_file)[9, 1];
  return $mtime . $inode;
}


sub has_empty_body {
  my $self = shift;

  my $content_length = $self->{request}->headers_in->{"Content-Length"};

lib/Apache2/AuthenNTLM/Cookie.pm  view on Meta::CPAN

generation, in order to prevent any attempt to forge a fake cookie.

Details about the NTLM authentication protocol can be found at
L<http://davenport.sourceforge.net/ntlm.html#ntlmHttpAuthentication>.

=head1 CONFIGURATION

Configuration directives for NTLM authentication are 
just inherited from L<Apache2::AuthenNTLM>; see that module's
documentation. These are most probably all you need, namely
the minimal information for setting the handler, 
specifying the C<AuthType> and specifying the names
of domain controllers :

  <Location /my/secured/URL>
    PerlAuthenHandler Apache2::AuthenNTLM::Cookie
    AuthType ntlm
    PerlAddVar ntdomain "domain primary_domain_controller other_controller"
  </Location>

In addition to the inherited directives, some
optional C<PerlSetVar> directives 
allow you to control various details of cookie generation :

   PerlSetVar cookie_name my_cookie_name    # default is NTLM_AUTHEN
   PerlSetVar domain      my_cookie_domain  # default is none
   PerlSetVar expires     my_cookie_expires # default is none
   PerlSetVar path        my_cookie_path    # default is none
   PerlSetVar refresh     some_seconds      # default is 14400 (4 hours)
   PerlSetVar secret      my_secret_string  # default from stat(config file)

See L<Apache2::Cookie> for explanation of variables
C<cookie_name>, C<domain>, C<expires>, and C<path>.
The only variables specific to the present module are

=over

=item refresh

This is the number of seconds after which the cookie becomes invalid
for authentication : it complements the C<expires> parameter.  The
C<expires> value is a standard HTTP cookie mechanism which tells how
long a cookie will be kept on the client side; its default
value is 0, which means that this is a session cookie, staying as long
as the browser is open. But if the Windows account gets disabled,
the cookie will never reflect the new situation : therefore we 
must impose a periodic refresh of the cookie. The default refresh 
value is 14400 seconds (four hours).

=item secret

This is a secret phrase for generating a SHA1 digest that will be
incorporated into the cookie. The digest also incorporates the
username and cookie creation time, and is checked at each request :
therefore it is impossible to forge a fake cookie without knowing the
secret.

The default value for the secret is the concatenation of modification
time and inode of the F<httpd.conf> file on the server; therefore if
the configuration file changes, authentication cookies are
automatically invalidated.

=back

=head1 SPECIAL NOTE ABOUT INTERNET EXPLORER

Microsoft Internet Explorer (MSIE) has an "optimization" when sending
POST requests to an NTLM-secured site : the browser does not send the
request body because it expects to receive a 401 HTTP_UNAUTHORIZED
response, and then would send the body only at the second
request. This is a problem with the present module, because if
authorization is granted on the basis of a cookie, instead of NTLM
handshake, then control goes to the HTTP response handler ... but that
handler gets no parameters since the request body is empty !

One way to fix the problem is to set the registry entry 
C<HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/InternetSettings/DisableNTLMPreAuth>;
but this is only feasible if one has control over the registry settings of all clients.
Otherwise, the present module will forge a fake "NTLM type2" message, so that
Internet will try again, sending a new NTLM type3 request with a proper body.
See 
L<http://lists.samba.org/archive/jcifs/2006-September/006554.html>
for more details about this issue.

=head1 AUTHOR

Laurent Dami, C<< <la_____.da__@etat.ge.ch> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-apache2-authenntlm-cookie at rt.cpan.org>, or through the web
interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Apache2-AuthenNTLM-Cookie>.
I will be notified, and then you'll automatically be notified of
progress on your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Apache2::AuthenNTLM::Cookie

You can also look for information at:

=over 4

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Apache2-AuthenNTLM-Cookie>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Apache2-AuthenNTLM-Cookie>

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Apache2-AuthenNTLM-Cookie>

=item * Search CPAN



( run in 1.503 second using v1.01-cache-2.11-cpan-140bd7fdf52 )