[Rt-commit] rt branch, 4.2/web-external-auth, created. rt-4.0.6-459-g69a5ebc

Thomas Sibley trs at bestpractical.com
Mon Aug 13 14:19:51 EDT 2012


The branch, 4.2/web-external-auth has been created
        at  69a5ebce9f522149acaf87b601fa11287b1ad287 (commit)

- Log -----------------------------------------------------------------
commit d305cf6f6107cc763fb80b675ad11c175bc6c266
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jul 25 16:31:19 2012 -0700

    Rename and comment test file for a more accurate description

diff --git a/t/web/remote_user.t b/t/web/basic_auth.t
similarity index 89%
rename from t/web/remote_user.t
rename to t/web/basic_auth.t
index edad6ef..c6f2fb3 100644
--- a/t/web/remote_user.t
+++ b/t/web/basic_auth.t
@@ -13,9 +13,12 @@ sub auth {
 }
 
 my ( $url, $m ) = RT::Test->started_ok( basic_auth => 1 );
+
+# This tests the plack middleware, not RT
 $m->get($url);
 is($m->status, 401, "Initial request with no creds gets 401");
 
+# This tests the plack middleware, not RT
 $m->get($url, auth( root => "wrong" ));
 is($m->status, 401, "Request with wrong creds gets 401");
 
@@ -28,6 +31,7 @@ $m->content_like(
 );
 $m->content_unlike(qr/Logout/i, "Has no logout button, no WebFallbackToInternalAuth");
 
+# Again, testing the plack middleware
 $m->get($url);
 is($m->status, 401, "Subsequent requests without credentials aren't still logged in");
 

commit 7e0cd1409b8ee96309cc49c3b575fd4a8fe8fc12
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 26 10:03:09 2012 -0700

    Anonymous basic auth support in tests
    
    To use, supply basic_auth => 'anon' to RT::Test->started_ok.
    
    This provides a way to pass arbitrary REMOTE_USER values into RT from
    tests, which enables testing of RT's responses in mixed auth
    environments.  Without this, all tests for auth declined responses
    (401/403 statuses) come from the auth layer rather than RT.

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index d9c43ab..385ddf7 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1342,7 +1342,7 @@ sub test_app {
         require Plack::Middleware::Auth::Basic;
         $app = Plack::Middleware::Auth::Basic->wrap(
             $app,
-            authenticator => sub {
+            authenticator => $server_opt{basic_auth} eq 'anon' ? sub { 1 } : sub {
                 my ($username, $password) = @_;
                 return $username eq 'root' && $password eq 'password';
             }
diff --git a/lib/RT/Test/Apache.pm b/lib/RT/Test/Apache.pm
index b2733ea..28f2615 100644
--- a/lib/RT/Test/Apache.pm
+++ b/lib/RT/Test/Apache.pm
@@ -83,6 +83,23 @@ sub basic_auth {
 EOT
 }
 
+sub basic_auth_anon {
+    my $self = shift;
+
+    return <<"EOT";
+    AuthType Basic
+    AuthName "restricted area"
+    AuthBasicProvider anon
+
+    Anonymous *
+    Anonymous_NoUserID On
+    Anonymous_MustGiveEmail Off
+    Anonymous_VerifyEmail Off
+
+    Require valid-user
+EOT
+}
+
 sub start_server {
     my ($self, %config) = @_;
     my %tmp = %{$config{tmp}};
@@ -108,8 +125,14 @@ sub start_server {
         rt_sbin_path   => $RT::SbinPath,
         rt_site_config => $ENV{'RT_SITE_CONFIG'},
         load_modules   => $info{load_modules},
-        basic_auth     => $config{basic_auth} ? $self->basic_auth : "",
     );
+    if ($config{basic_auth}) {
+        if ($config{basic_auth} eq 'anon') {
+            $opt{basic_auth} = $self->basic_auth_anon;
+        } else {
+            $opt{basic_auth} = $self->basic_auth;
+        }
+    }
     foreach (qw(log pid lock)) {
         $opt{$_ .'_file'} = File::Spec->catfile(
             "$tmp{'directory'}", "apache.$_"
@@ -193,7 +216,10 @@ sub apache_server_info {
     ) unless exists $MODULES{$res{version}}{$res{variant}};
 
     my @mlist = @{$MODULES{$res{version}}{$res{variant}}};
-    push @mlist, "authn_file", "auth_basic", "authz_user" if $res{basic_auth};
+    if ($res{basic_auth}) {
+        push @mlist, "auth_basic", "authz_user";
+        push @mlist, $res{basic_auth} eq 'anon' ? "authn_anon" : "authn_file";
+    }
 
     $res{'load_modules'} = '';
     foreach my $mod ( @mlist ) {

commit c7945e593c75b099675cbaf3cd0d9de6c77134a8
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Jul 26 13:38:29 2012 -0700

    Failing tests for deauthenticating when REMOTE_USER disappears

diff --git a/t/web/remote_user.t b/t/web/remote_user.t
new file mode 100644
index 0000000..314bdba
--- /dev/null
+++ b/t/web/remote_user.t
@@ -0,0 +1,109 @@
+use strict;
+use warnings;
+use RT;
+use RT::Test plan => 'no_plan';
+use MIME::Base64 qw//;
+
+sub auth {
+    return Authorization => "Basic " .
+        MIME::Base64::encode( join(":", @_) );
+}
+
+sub logged_in_as {
+    my $mech = shift;
+    my $user = shift || '';
+
+    unless ($mech->status == 200) {
+        diag "Error: status is ". $mech->status;
+        return 0;
+    }
+
+    RT::Interface::Web::EscapeUTF8(\$user);
+    unless ($mech->content =~ m{<span class="current-user">\Q$user\E</span>}i) {
+        diag "Error: page has no user name";
+        return 0;
+    }
+    return 1;
+}
+
+sub stop_server {
+    my $mech = shift;
+
+    # Ensure we're logged in for the final warnings check
+    $$mech->default_header( auth("root") );
+
+    # Force the warnings check before we stop the server
+    undef $$mech;
+
+    RT::Test->stop_server;
+}
+
+diag "Continuous + Fallback";
+{
+    RT->Config->Set( DevelMode => 0 );
+    RT->Config->Set( WebExternalAuth => 1 );
+    RT->Config->Set( WebExternalAuthContinuous => 1 );
+    RT->Config->Set( WebFallbackToInternalAuth => 1 );
+    RT->Config->Set( WebExternalAuto => 0 );
+
+    my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' );
+
+    diag "Internal auth";
+    {
+        # Empty REMOTE_USER
+        $m->default_header( auth("") );
+
+        # First request gets the login form
+        $m->get_ok($url, "No basic auth is OK");
+        $m->content_like(qr/Login/, "Login form");
+
+        # Log in using RT's form
+        $m->submit_form_ok({
+            with_fields => {
+                user => 'root',
+                pass => 'password',
+            },
+        }, "Submitted login form");
+        ok logged_in_as($m, "root"), "Logged in as root";
+
+        # Still logged in on another request without REMOTE_USER
+        $m->follow_link_ok({ text => 'My Tickets' });
+        ok logged_in_as($m, "root"), "Logged in as root";
+
+        ok $m->logout, "Logged out";
+
+        # We're definitely logged out?
+        $m->get_ok($url);
+        $m->content_like(qr/Login/, "Login form");
+    }
+
+    diag "External auth";
+    {
+        # REMOTE_USER of root
+        $m->default_header( auth("root") );
+
+        # Automatically logged in as root without Login page
+        $m->get_ok($url);
+        ok logged_in_as($m, "root"), "Logged in as root";
+
+        # Still logged in on another request
+        $m->follow_link_ok({ text => 'My Tickets' });
+        ok logged_in_as($m, "root"), "Still logged in as root";
+
+        # Drop credentials and...
+        $m->default_header( auth("") );
+
+        # ...see if RT notices
+        $m->get($url);
+        is $m->status, 403, "403 Forbidden from RT";
+        is $m->content, '', "No content returned";
+        # XXX should this be a 403?
+
+        # Next request gets us the login form
+        $m->get_ok($url);
+        $m->content_like(qr/Login/, "Login form");
+    }
+
+    stop_server(\$m);
+}
+

commit 9354f0ae8f6330572574deed2fb59be905492b38
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jul 25 16:11:53 2012 -0700

    Cleanup WebExternalAuth handling
    
    This standardizes on a 403 Forbidden response and avoids ever sending a
    login page with no form.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 8a01217..dc8b4bc 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -623,14 +623,11 @@ sub AttemptExternalAuth {
                 }
                 $HTML::Mason::Commands::session{'CurrentUser'}->Load($user);
             } else {
-
-                # we failed to successfully create the user. abort abort abort.
-                delete $HTML::Mason::Commands::session{'CurrentUser'};
+                # we failed to successfully create the user!
+                _ForceLogout();
 
                 if (RT->Config->Get('WebFallbackToInternalAuth')) {
                     TangentForLoginWithError('Cannot create user: [_1]', $msg);
-                } else {
-                    $m->abort();
                 }
             }
         }
@@ -647,22 +644,24 @@ sub AttemptExternalAuth {
             # straight-up external auth would always redirect to /
             # when you first hit it.
         } else {
-            delete $HTML::Mason::Commands::session{'CurrentUser'};
+            # Couldn't auth with the REMOTE_USER provided, either because an RT
+            # user doesn't exist or we can't create one.  Bail unless we
+            # fallback to internal auth.
             $user = $orig_user;
+            AbortExternalAuth() unless RT->Config->Get('WebFallbackToInternalAuth');
         }
-    } elsif ( RT->Config->Get('WebFallbackToInternalAuth') ) {
-        unless ( defined $HTML::Mason::Commands::session{'CurrentUser'} ) {
-            # XXX unreachable due to prior defaulting in HandleRequest (check c34d108)
-            TangentForLoginWithError('You are not an authorized user');
-        }
-    } else {
-
-        # WebExternalAuth is set, but we don't have a REMOTE_USER. abort
-        # XXX: we must return AUTH_REQUIRED status or we fallback to
-        # internal auth here too.
-        delete $HTML::Mason::Commands::session{'CurrentUser'}
-            if defined $HTML::Mason::Commands::session{'CurrentUser'};
     }
+    elsif (not RT->Config->Get('WebFallbackToInternalAuth')) {
+        # No REMOTE_USER and we don't want to fallback internally.
+        AbortExternalAuth();
+    }
+}
+
+sub AbortExternalAuth {
+    _ForceLogout();
+
+    # Return a 403 Forbidden or we may fallback to a login page with no form
+    $HTML::Mason::Commands::m->abort(403);
 }
 
 sub AttemptPasswordAuthentication {

commit 154143c13462f95556f162aa5d1ecb0a2726d4a9
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Wed Jul 25 16:16:38 2012 -0700

    Deauthentication under continuous external auth with internal fallback
    
    Under WebExternalAuth configurations with both WebExternalAuthContinuous
    and WebFallbackToInternalAuth enabled, RT now properly logs out
    externally authed users who are deauthenticated outside of RT during their
    RT session.
    
    Previously this configuration checked for REMOTE_USER on every request,
    but still allowed users to remain logged in even after they stopped
    being authenticated by the external source.  WebFallbackToInternalAuth
    prevented WebExternalAuthContinuous from working properly, presumably in
    order to let internally authed users stay logged in despite their lack
    of REMOTE_USER.
    
    By setting a flag on externally authed users' sessions, we can
    differentiate between those users and users who logged into RT directly.
    This difference lets us enforce WebExternalAuthContinuous for only the
    external users, as intended.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index dc8b4bc..f8229fb 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -633,6 +633,7 @@ sub AttemptExternalAuth {
         }
 
         if ( _UserLoggedIn() ) {
+            $HTML::Mason::Commands::session{'WebExternallyAuthed'} = 1;
             $m->callback( %$ARGS, CallbackName => 'ExternalAuthSuccessfulLogin', CallbackPage => '/autohandler' );
             # It is possible that we did a redirect to the login page,
             # if the external auth allows lack of auth through with no
@@ -651,8 +652,12 @@ sub AttemptExternalAuth {
             AbortExternalAuth() unless RT->Config->Get('WebFallbackToInternalAuth');
         }
     }
-    elsif (not RT->Config->Get('WebFallbackToInternalAuth')) {
-        # No REMOTE_USER and we don't want to fallback internally.
+    elsif (not RT->Config->Get('WebFallbackToInternalAuth')
+            or (_UserLoggedIn() and $HTML::Mason::Commands::session{'WebExternallyAuthed'})) {
+        # No REMOTE_USER and...
+        #
+        # a) We don't want to fallback internally, or
+        # b) The logged in external user was deauthed and we should kick them out
         AbortExternalAuth();
     }
 }

commit 0d28defc69a10a77d5f6b0be809cb2d6170ef8a9
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Jul 30 17:04:55 2012 -0700

    Standard error pages for all reasons we abort external auth
    
    Instead of a blank page, show brief, friendlier error messages with
    information about why auth failed, contact information for the local
    admin, and an internal login link when fallback is on.  Rendering each
    error as an individual component allows easier site-specific overrides
    for customizing messages to the external authentication system in use.
    
    Behaviour under WebFallbackToInternalAuth is changed from showing login
    pages with no message on failure to showing an error message with a way
    to login.  Below is a summary of the new behaviour for each reason we
    abort and force logout during WebExternalAuth checking.  All error pages
    are 403s and provide a login link if Fallback is ON.
    
    User doesn't exist and auto-create is OFF
        Fallback  ON: Error page, since if they were supposed to login it would
                      likely be linked to remote user
    
        Fallback OFF: Error page
    
    Fail to auto-create a user (iff WebExternalAuto is ON)
        Fallback  ON: Error page with "couldn't create user message", since they're
                      unlikely to be able to do anything about it.
    
        Fallback OFF: Error page with "couldn't create user" message
    
    User logged in but deauthed by external system (iff WebExternalAuthContinuous is ON)
        Fallback  ON: Error page, suggesting refresh. Next page hit gets them
                      logged in again if it was a blip or the login form if it wasn't.
    
        Fallback OFF: Same error page as above, except they'll get the generic
                      error page below if it wasn't a blip when they refresh.
    
    No remote user
        Fallback  ON: Login page
        Fallback OFF: Error page, generic

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index f8229fb..acd532f 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -579,8 +579,6 @@ sub AttemptExternalAuth {
 
     # do we actually have a REMOTE_USER equivlent?
     if ( RT::Interface::Web::WebCanonicalizeInfo() ) {
-        my $orig_user = $user;
-
         $user = RT::Interface::Web::WebCanonicalizeInfo();
         my $load_method = RT->Config->Get('WebExternalGecos') ? 'LoadByGecos' : 'Load';
 
@@ -623,12 +621,8 @@ sub AttemptExternalAuth {
                 }
                 $HTML::Mason::Commands::session{'CurrentUser'}->Load($user);
             } else {
-                # we failed to successfully create the user!
-                _ForceLogout();
-
-                if (RT->Config->Get('WebFallbackToInternalAuth')) {
-                    TangentForLoginWithError('Cannot create user: [_1]', $msg);
-                }
+                RT->Logger->error("Couldn't auto-create user '$user' when attempting WebExternalAuth: $msg");
+                AbortExternalAuth( Error => "AutoCreate" );
             }
         }
 
@@ -645,28 +639,43 @@ sub AttemptExternalAuth {
             # straight-up external auth would always redirect to /
             # when you first hit it.
         } else {
-            # Couldn't auth with the REMOTE_USER provided, either because an RT
-            # user doesn't exist or we can't create one.  Bail unless we
-            # fallback to internal auth.
-            $user = $orig_user;
-            AbortExternalAuth() unless RT->Config->Get('WebFallbackToInternalAuth');
+            # Couldn't auth with the REMOTE_USER provided because an RT
+            # user doesn't exist and we're configured not to create one.
+            RT->Logger->error("Couldn't find internal user for '$user' when attempting WebExternalAuth and RT is not configured for auto-creation.");
+            AbortExternalAuth(
+                Error => "NoInternalUser",
+                User  => $user,
+            );
         }
     }
-    elsif (not RT->Config->Get('WebFallbackToInternalAuth')
-            or (_UserLoggedIn() and $HTML::Mason::Commands::session{'WebExternallyAuthed'})) {
-        # No REMOTE_USER and...
-        #
-        # a) We don't want to fallback internally, or
-        # b) The logged in external user was deauthed and we should kick them out
-        AbortExternalAuth();
+    elsif (_UserLoggedIn() and $HTML::Mason::Commands::session{'WebExternallyAuthed'}) {
+        # The logged in external user was deauthed by the auth system and we
+        # should kick them out.
+        AbortExternalAuth( Error => "Deauthorized" );
+    }
+    elsif (not RT->Config->Get('WebFallbackToInternalAuth')) {
+        # Abort if we don't want to fallback internally
+        AbortExternalAuth( Error => "NoRemoteUser" );
     }
 }
 
 sub AbortExternalAuth {
+    my %args  = @_;
+    my $error = $args{Error} ? "/Errors/WebExternalAuth/$args{Error}" : undef;
+    my $m     = $HTML::Mason::Commands::m;
+    my $r     = $HTML::Mason::Commands::r;
+
     _ForceLogout();
 
+    # Clear the decks, not that we should have partial content.
+    $m->clear_buffer;
+
+    $r->status(403);
+    $m->comp($error, %args)
+        if $error and $m->comp_exists($error);
+
     # Return a 403 Forbidden or we may fallback to a login page with no form
-    $HTML::Mason::Commands::m->abort(403);
+    $m->abort(403);
 }
 
 sub AttemptPasswordAuthentication {
diff --git a/share/html/Errors/WebExternalAuth/AutoCreate b/share/html/Errors/WebExternalAuth/AutoCreate
new file mode 100644
index 0000000..554ba54
--- /dev/null
+++ b/share/html/Errors/WebExternalAuth/AutoCreate
@@ -0,0 +1,3 @@
+<&| Wrapper, %ARGS, Title => loc("Automatic account setup failed") &>
+<p><&|/l&>Unfortunately, RT couldn't automatically setup an account for you. Your RT administator will find more information in the logs.</&></p>
+</&>
diff --git a/share/html/Errors/WebExternalAuth/Deauthorized b/share/html/Errors/WebExternalAuth/Deauthorized
new file mode 100644
index 0000000..e31068f
--- /dev/null
+++ b/share/html/Errors/WebExternalAuth/Deauthorized
@@ -0,0 +1,3 @@
+<&| Wrapper, %ARGS, Title => loc("No longer authorized") &>
+<p><&|/l&>You were logged out of RT by your authentication system.  This may be a temporary hiccup, in which case refreshing this page may help.</&></p>
+</&>
diff --git a/share/html/Errors/WebExternalAuth/NoInternalUser b/share/html/Errors/WebExternalAuth/NoInternalUser
new file mode 100644
index 0000000..8ffa295
--- /dev/null
+++ b/share/html/Errors/WebExternalAuth/NoInternalUser
@@ -0,0 +1,3 @@
+<&| Wrapper, %ARGS, Title => loc("Unauthorized") &>
+<p><&|/l, $ARGS{User} &>You ([_1]) are not authorized to use RT.</&></p>
+</&>
diff --git a/share/html/Errors/WebExternalAuth/NoRemoteUser b/share/html/Errors/WebExternalAuth/NoRemoteUser
new file mode 100644
index 0000000..81d8ae0
--- /dev/null
+++ b/share/html/Errors/WebExternalAuth/NoRemoteUser
@@ -0,0 +1,3 @@
+<&| Wrapper, %ARGS, Title => loc("Unauthorized") &>
+<p><&|/l&>You are not authorized to use RT.</&></p>
+</&>
diff --git a/share/html/Errors/WebExternalAuth/Wrapper b/share/html/Errors/WebExternalAuth/Wrapper
new file mode 100644
index 0000000..a5318d8
--- /dev/null
+++ b/share/html/Errors/WebExternalAuth/Wrapper
@@ -0,0 +1,34 @@
+<%args>
+$Title => loc("An error occurred")
+$Error => ''
+</%args>
+<%init>
+my $next = RT::Interface::Web::SetNextPage();
+my $login_url = RT->Config->Get('WebPath') . "/NoAuth/Login.html?next=$next";
+</%init>
+<html>
+  <head>
+    <title><% $Title %></title>
+  </head>
+  <body>
+    <h1><% $Title %></h1>
+    <!-- WebExternalAuth error: <% $Error %> -->
+    <% $m->content |n%>
+
+    <p id="contact-admin">
+% if (my $owner = RT->Config->Get('OwnerEmail')) {
+%     $owner = $m->interp->apply_escapes($owner, 'h');
+      <&|/l_unsafe, qq[<a href="mailto:$owner">], $owner, '</a>' &>Contact your RT administrator via [_1]email to [_2][_3].</&>
+% } else {
+      <&|/l&>Contact your RT administrator.</&>
+% }
+    </p>
+
+% if (RT->Config->Get('WebExternalAuth') and RT->Config->Get('WebFallbackToInternalAuth')) {
+    <p id="internal-auth">
+      <&|/l_unsafe, qq[<a href="$login_url">], '</a>' &>If you have an internal RT login, you may [_1]try it instead[_2].</&>
+    </p>
+% }
+    </p>
+  </body>
+</html>
diff --git a/t/web/remote_user.t b/t/web/remote_user.t
index 314bdba..d5837fd 100644
--- a/t/web/remote_user.t
+++ b/t/web/remote_user.t
@@ -96,14 +96,64 @@ diag "Continuous + Fallback";
         # ...see if RT notices
         $m->get($url);
         is $m->status, 403, "403 Forbidden from RT";
-        is $m->content, '', "No content returned";
-        # XXX should this be a 403?
 
         # Next request gets us the login form
         $m->get_ok($url);
         $m->content_like(qr/Login/, "Login form");
     }
 
+    diag "External auth with invalid user, login internally";
+    {
+        # REMOTE_USER of invalid
+        $m->default_header( auth("invalid") );
+
+        # Login internally via the login link
+        $m->get("$url/Search/Build.html");
+        is $m->status, 403, "403 Forbidden";
+        $m->follow_link_ok({ url_regex => qr'NoAuth/Login\.html' }, "follow logout link");
+        $m->content_like(qr/Login/, "Login form");
+
+        # Log in using RT's form
+        $m->submit_form_ok({
+            with_fields => {
+                user => 'root',
+                pass => 'password',
+            },
+        }, "Submitted login form");
+        ok logged_in_as($m, "root"), "Logged in as root";
+        like $m->uri, qr'Search/Build\.html', "at our originally requested page";
+
+        # Still logged in on another request
+        $m->follow_link_ok({ text => 'Tools' });
+        ok logged_in_as($m, "root"), "Logged in as root";
+
+        ok $m->logout, "Logged out";
+
+        $m->next_warning_like(qr/Couldn't find internal user for 'invalid'/, "found warning for first request");
+        $m->next_warning_like(qr/Couldn't find internal user for 'invalid'/, "found warning for second request");
+    }
+
+    stop_server(\$m);
+}
+
+diag "Fallback OFF";
+{
+    RT->Config->Set( DevelMode => 0 );
+    RT->Config->Set( WebExternalAuth => 1 );
+    RT->Config->Set( WebExternalAuthContinuous => 0 );
+    RT->Config->Set( WebFallbackToInternalAuth => 0 );
+    RT->Config->Set( WebExternalAuto => 0 );
+
+    my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' );
+
+    diag "No remote user";
+    {
+        $m->default_header( auth("") );
+        $m->get($url);
+        is $m->status, 403, "Forbidden";
+    }
+
     stop_server(\$m);
 }
 
+# XXX TODO: test WebExternalAuto and AutoCreate

commit 4ae71e0ea63a265130c3a0c1888589d7f546b6c1
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 6 18:35:57 2012 -0700

    Ignore REMOTE_USER if we're logged in with internal auth
    
    This prevents logged in internally-authed users from being logged out if
    they pick up a REMOTE_USER.  Viewed another way, it lets users with an
    invalid REMOTE_USER still login with internal credentials.
    
    All tests in t/web/remote_user.t now pass.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index acd532f..58c3918 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -575,10 +575,17 @@ sub AttemptExternalAuth {
     my $user = $ARGS->{user};
     my $m    = $HTML::Mason::Commands::m;
 
+    my $logged_in_external_user = _UserLoggedIn() && $HTML::Mason::Commands::session{'WebExternallyAuthed'};
+
     # If RT is configured for external auth, let's go through and get REMOTE_USER
 
-    # do we actually have a REMOTE_USER equivlent?
-    if ( RT::Interface::Web::WebCanonicalizeInfo() ) {
+    # Do we actually have a REMOTE_USER or equivalent?  We only check auth if
+    # 1) we have no logged in user, or 2) we have a user who is externally
+    # authed.  If we have a logged in user who is internally authed, don't
+    # check remote user otherwise we may log them out.
+    if (RT::Interface::Web::WebCanonicalizeInfo()
+        and (not _UserLoggedIn() or $logged_in_external_user) )
+    {
         $user = RT::Interface::Web::WebCanonicalizeInfo();
         my $load_method = RT->Config->Get('WebExternalGecos') ? 'LoadByGecos' : 'Load';
 
@@ -648,7 +655,7 @@ sub AttemptExternalAuth {
             );
         }
     }
-    elsif (_UserLoggedIn() and $HTML::Mason::Commands::session{'WebExternallyAuthed'}) {
+    elsif ($logged_in_external_user) {
         # The logged in external user was deauthed by the auth system and we
         # should kick them out.
         AbortExternalAuth( Error => "Deauthorized" );

commit d188b4f70cb91bf2a5d0f88a033804b4553ab024
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Aug 7 17:08:56 2012 -0700

    Refactor the generation of login tangent links
    
    This avoids the error wrapper needing to know the details of how login
    tangents work.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 58c3918..a769dde 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -377,11 +377,23 @@ the next page.  Optionally takes a hash which is dumped into query params.
 =cut
 
 sub TangentForLogin {
+    my $login = TangentForLoginURL(@_);
+    Redirect( RT->Config->Get('WebBaseURL') . $login );
+}
+
+=head2 TangentForLoginURL [HASH]
+
+Returns a URL suitable for tangenting for login.  Optionally takes a hash which
+is dumped into query params.
+
+=cut
+
+sub TangentForLoginURL {
     my $hash  = SetNextPage();
     my %query = (@_, next => $hash);
-    my $login = RT->Config->Get('WebURL') . 'NoAuth/Login.html?';
+    my $login = RT->Config->Get('WebPath') . '/NoAuth/Login.html?';
     $login .= $HTML::Mason::Commands::m->comp('/Elements/QueryString', %query);
-    Redirect($login);
+    return $login;
 }
 
 =head2 TangentForLoginWithError ERROR
diff --git a/share/html/Errors/WebExternalAuth/Wrapper b/share/html/Errors/WebExternalAuth/Wrapper
index a5318d8..548d842 100644
--- a/share/html/Errors/WebExternalAuth/Wrapper
+++ b/share/html/Errors/WebExternalAuth/Wrapper
@@ -3,8 +3,7 @@ $Title => loc("An error occurred")
 $Error => ''
 </%args>
 <%init>
-my $next = RT::Interface::Web::SetNextPage();
-my $login_url = RT->Config->Get('WebPath') . "/NoAuth/Login.html?next=$next";
+my $login_url = $m->interp->apply_escapes(RT::Interface::Web::TangentForLoginURL(), 'h');
 </%init>
 <html>
   <head>

commit 933754e99dcc556f02d411b0554da7cf3b0a4f38
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Aug 7 18:26:03 2012 -0700

    Tests for the WebExternalAuto and AutoCreate options
    
    One test fails (creating privileged users by default) and is marked TODO
    because of a longstanding bug.

diff --git a/t/web/remote_user.t b/t/web/remote_user.t
index d5837fd..81b9aa7 100644
--- a/t/web/remote_user.t
+++ b/t/web/remote_user.t
@@ -156,4 +156,67 @@ diag "Fallback OFF";
     stop_server(\$m);
 }
 
-# XXX TODO: test WebExternalAuto and AutoCreate
+diag "AutoCreate";
+{
+    RT->Config->Set( DevelMode => 0 );
+    RT->Config->Set( WebExternalAuth => 1 );
+    RT->Config->Set( WebExternalAuthContinuous => 1 );
+    RT->Config->Set( WebFallbackToInternalAuth => 0 );
+    RT->Config->Set( WebExternalAuto => 1 );
+    RT->Config->Set( AutoCreate => { Organization => "BPS" } );
+
+    my ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' );
+
+    diag "New user";
+    {
+        $m->default_header( auth("anewuser") );
+        $m->get_ok($url);
+        ok logged_in_as($m, "anewuser"), "Logged in as anewuser";
+
+        my $user = RT::User->new( RT->SystemUser );
+        $user->Load("anewuser");
+        ok $user->id, "Found newly created user";
+        is $user->Organization, "BPS", "Found Organization from AutoCreate hash";
+        {
+            local $TODO = 'A bug from 2009 prevents Privileged from being set by WebExternalAutoInfo';
+            ok $user->Privileged, "Privileged by default";
+        }
+    }
+
+    stop_server(\$m);
+    RT->Config->Set(
+        AutoCreate => {
+            Privileged   => 0,
+            EmailAddress => 'foo at example.com',
+        },
+    );
+    ( $url, $m ) = RT::Test->started_ok( basic_auth => 'anon' );
+
+    diag "Create unprivileged users";
+    {
+        $m->default_header( auth("unpriv") );
+        $m->get_ok($url);
+        ok logged_in_as($m, "unpriv"), "Logged in as an unpriv user";
+        like $m->uri->path, RT->Config->Get('SelfServiceRegex'), "SelfService URL";
+
+        my $user = RT::User->new( RT->SystemUser );
+        $user->Load("unpriv");
+        ok $user->id, "Found newly created user";
+        ok !$user->Privileged, "Unprivileged per config";
+        is $user->EmailAddress, 'foo at example.com', "Email address per config";
+    }
+
+    diag "User creation failure";
+    {
+        $m->default_header( auth("conflicting") );
+        $m->get($url);
+        is $m->status, 403, "Forbidden";
+
+        my $user = RT::User->new( RT->SystemUser );
+        $user->Load("conflicting");
+        ok !$user->id, "Couldn't find conflicting user";
+    }
+
+    stop_server(\$m);
+}
+

commit 16dd2334c277e729f3bd0f5fe1bbd64955643327
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Tue Aug 7 18:30:49 2012 -0700

    Allow Privileged and Disabled to be set from WebExternalAutoInfo()
    
    Fixes a regression introduced by e2ec2c4c in 2009.  The change to
    WritableAttributes was the correct approach (and let us pick up new
    columns automatically), but it missed that Privileged and Disabled
    aren't true User columns handled by DBIx::SearchBuilder.
    
    Privileged is particularly important since the default implementation of
    WebExternalAutoInfo() expects to be able to return it in order to make
    externally authed auto-created users Privileged by default.  The bug
    meant the default behaviour didn't work and you had to include
    Privileged => 1 in $AutoCreate if you wanted privileged users.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index a769dde..f8d35e4 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -627,7 +627,7 @@ sub AttemptExternalAuth {
                 my $new_user_info = RT::Interface::Web::WebExternalAutoInfo($user);
 
                 # set the attributes that have been defined.
-                foreach my $attribute ( $UserObj->WritableAttributes ) {
+                foreach my $attribute ( $UserObj->WritableAttributes, qw(Privileged Disabled) ) {
                     $m->callback(
                         Attribute    => $attribute,
                         User         => $user,
diff --git a/t/web/remote_user.t b/t/web/remote_user.t
index 81b9aa7..4ad1702 100644
--- a/t/web/remote_user.t
+++ b/t/web/remote_user.t
@@ -177,10 +177,7 @@ diag "AutoCreate";
         $user->Load("anewuser");
         ok $user->id, "Found newly created user";
         is $user->Organization, "BPS", "Found Organization from AutoCreate hash";
-        {
-            local $TODO = 'A bug from 2009 prevents Privileged from being set by WebExternalAutoInfo';
-            ok $user->Privileged, "Privileged by default";
-        }
+        ok $user->Privileged, "Privileged by default";
     }
 
     stop_server(\$m);

commit 067e8b4e1cedbedf06b321e90d806b744250da5a
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Aug 9 17:53:59 2012 -0700

    Document the various external authentication solutions
    
    Focusing on $WebExternalAuth, but also touching on
    RT::Authen::ExternalAuth and RT::Extension::LDAPImport.  Hopefully this
    provides a useful overview of the options available, since most folks
    don't seem to realize RT can integrate with your web server's auth.

diff --git a/docs/external-auth.pod b/docs/external-auth.pod
new file mode 100644
index 0000000..cc3ee42
--- /dev/null
+++ b/docs/external-auth.pod
@@ -0,0 +1,112 @@
+=head1 External Authentication
+
+There are two primary types of external authentication: in one you type your
+username and password into RT's login form, and in the other your web server
+(such as Apache) handles authentication, often seamlessly, and tells RT the
+user logged in.
+
+The second is supported by RT out of the box under the configuration option
+C<$WebExternalAuth> and other related options.  The first is supported by an RT
+extension named L</RT::Authen::ExternalAuth>.  These two types may be used
+independently or together, and both can fallback to RT's internal
+authentication.
+
+No matter what type of external authentication you use, RT still maintains User
+records in it's database that correspond to your external source.  This is
+necessary so RT can link tickets, groups, rights, dashboards, etc. to users.
+Do not be concerned if you observe such behaviour.
+
+Another extension, L</RT::Extension::LDAPImport>, can be used to keep users,
+user data, and groups in sync (via cron) with an external LDAP source (such as
+an OpenLDAP or Active Directory server).  This can be used in tandem with any
+of the external authentication options as it does not provide any
+authentication itself.
+
+=head1 C<$WebExternalAuth>
+
+This type of external authentication is built-in to RT and bypasses the RT
+login form.  Instead, RT defers authentication to the web server which is
+expected to set a C<REMOTE_USER> environment variable.  Upon a request, RT
+checks C<REMOTE_USER> against its internal database and logs in the matched
+user.
+
+It is often used to provide single sign-on (SSO) support via Apache modules
+such as C<mod_auth_kerb> (to talk to Active Directory).  C<$WebExternalAuth> is
+widely used by organizations with existing authentication standards for web
+services that leverge web server modules for central authentication services.
+The flexibility of RT's C<$WebExternalAuth> support means that it can be setup
+with almost any authentication system.
+
+In order to keep user data in sync, this type of external auth is almost always
+used in combination with one or both of L</RT::Authen::ExternalAuth> and
+L</RT::Extension::LDAPImport>.
+
+=head2 Configuration options
+
+All of the following options control the behaviour of RT's built-in external
+authentication which relies on the web server.  They are documented in detail
+under the "Authorization and user configuration" section of C<etc/RT_Config.pm>
+and you can read the documentation by running C<perldoc /opt/rt4/etc/RT_Config.pm>.
+
+The list below is meant to make you aware of what's available.  You should read
+the full documentation as described above.
+
+=head3 C<$WebExternalAuth>
+
+Enables or disables RT's expectation that the web server will provide
+authentication using the C<REMOTE_USER> environment variable.
+
+=head3 C<$WebExternalAuthContinuous>
+
+Check C<REMOTE_USER> on every request rather than the initial request.
+
+=head3 C<$WebFallbackToInternalAuth>
+
+If true, allows internal logins as well as C<REMOTE_USER> by providing a login
+form if external authentication fails.
+
+=head3 C<$WebExternalAuto>
+
+Enables or disables auto-creation of RT users when a new C<REMOTE_USER> is
+encountered.
+
+=head3 C<$AutoCreate>
+
+Specifies the default properties of auto-created users.
+
+=head3 C<$WebExternalGecos>
+
+Tells RT to compare C<REMOTE_USER> to the C<Gecos> field of RT users instead of
+the C<Name> field.
+
+=head1 RT::Authen::ExternalAuth
+
+L<RT::Authen::ExternalAuth> is an RT extension which provides authentication
+B<using> RT's login form.  It can be configured to talk to an LDAP source (such
+as Active Directory), an external database, or an SSO cookie.
+
+The key difference between C<$WebExternalAuth> and L<RT::Authen::ExternalAuth>
+is the use of the RT login form and what part of the system talks to your
+authentication source (your web server vs. RT itself).
+
+=head2 Info mode and Authentication mode
+
+There are two modes of operation in L<RT::Authen::ExternalAuth>: info and auth.
+Usually you want to configure both so that successfully authenticated users
+also get their information pulled and updated from your external source.
+
+Auth-only configurations are rare, and generally not as useful.
+
+Info-only configurations are commonly setup in tandem with C<$WebExternalAuth>.
+This lets your web server handle authentication (usually for SSO) and
+C<RT::Authen::ExternalAuth> ensures user data is updated every time someone
+logs in.
+
+=head1 RT::Extension::LDAPImport
+
+L<RT::Extension::LDAPImport> provides no authentication, but is worth
+mentioning because it provides user data and group member synchronization from
+any LDAP source into RT.  It provides a similar but more complete sync solution
+than L<RT::Authen::ExternalAuth> (which only updates upon login and doesn't
+handle groups).  It may be used with either of RT's external authentication
+sources, or on it's own.

commit 708ed6c550c04392a53518bb397baf082e5bf22e
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 13 10:46:27 2012 -0700

    Refactor setting an Authorization header into RT::Test::Web

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index e3359e8..8a6f982 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -53,6 +53,7 @@ use warnings;
 
 use base qw(Test::WWW::Mechanize);
 use Scalar::Util qw(weaken);
+use MIME::Base64 qw//;
 
 BEGIN { require RT::Test; }
 require Test::More;
@@ -371,6 +372,17 @@ sub check_links {
     return Test::More::ok( 1, "expected links" );
 }
 
+sub auth {
+    my $self = shift;
+    $self->default_header( $self->auth_header(@_) );
+}
+
+sub auth_header {
+    my $self = shift;
+    return Authorization => "Basic " .
+        MIME::Base64::encode( join(":", @_) );
+}
+
 sub DESTROY {
     my $self = shift;
     if ( !$RT::Test::Web::DESTROY++ ) {
diff --git a/t/web/basic_auth.t b/t/web/basic_auth.t
index c6f2fb3..f8ac2ee 100644
--- a/t/web/basic_auth.t
+++ b/t/web/basic_auth.t
@@ -2,16 +2,10 @@ use strict;
 use warnings;
 use RT;
 use RT::Test tests => 9;
-use MIME::Base64 qw//;
 
 RT->Config->Set( DevelMode => 0 );
 RT->Config->Set( WebExternalAuth => 1 );
 
-sub auth {
-    return Authorization => "Basic " .
-        MIME::Base64::encode( join(":", @_) );
-}
-
 my ( $url, $m ) = RT::Test->started_ok( basic_auth => 1 );
 
 # This tests the plack middleware, not RT
@@ -19,10 +13,10 @@ $m->get($url);
 is($m->status, 401, "Initial request with no creds gets 401");
 
 # This tests the plack middleware, not RT
-$m->get($url, auth( root => "wrong" ));
+$m->get($url, $m->auth_header( root => "wrong" ));
 is($m->status, 401, "Request with wrong creds gets 401");
 
-$m->get($url, auth( root => "password" ));
+$m->get($url, $m->auth_header( root => "password" ));
 is($m->status, 200, "Request with right creds gets 200");
 
 $m->content_like(
@@ -37,4 +31,4 @@ is($m->status, 401, "Subsequent requests without credentials aren't still logged
 
 
 # Put the credentials back for the warnings check at the end
-$m->default_header( auth( root => "password" ));
+$m->auth( root => "password" );
diff --git a/t/web/remote_user.t b/t/web/remote_user.t
index 4ad1702..f7caa08 100644
--- a/t/web/remote_user.t
+++ b/t/web/remote_user.t
@@ -2,12 +2,6 @@ use strict;
 use warnings;
 use RT;
 use RT::Test plan => 'no_plan';
-use MIME::Base64 qw//;
-
-sub auth {
-    return Authorization => "Basic " .
-        MIME::Base64::encode( join(":", @_) );
-}
 
 sub logged_in_as {
     my $mech = shift;
@@ -30,7 +24,7 @@ sub stop_server {
     my $mech = shift;
 
     # Ensure we're logged in for the final warnings check
-    $$mech->default_header( auth("root") );
+    $$mech->auth("root");
 
     # Force the warnings check before we stop the server
     undef $$mech;
@@ -51,7 +45,7 @@ diag "Continuous + Fallback";
     diag "Internal auth";
     {
         # Empty REMOTE_USER
-        $m->default_header( auth("") );
+        $m->auth("");
 
         # First request gets the login form
         $m->get_ok($url, "No basic auth is OK");
@@ -80,7 +74,7 @@ diag "Continuous + Fallback";
     diag "External auth";
     {
         # REMOTE_USER of root
-        $m->default_header( auth("root") );
+        $m->auth("root");
 
         # Automatically logged in as root without Login page
         $m->get_ok($url);
@@ -91,7 +85,7 @@ diag "Continuous + Fallback";
         ok logged_in_as($m, "root"), "Still logged in as root";
 
         # Drop credentials and...
-        $m->default_header( auth("") );
+        $m->auth("");
 
         # ...see if RT notices
         $m->get($url);
@@ -105,7 +99,7 @@ diag "Continuous + Fallback";
     diag "External auth with invalid user, login internally";
     {
         # REMOTE_USER of invalid
-        $m->default_header( auth("invalid") );
+        $m->auth("invalid");
 
         # Login internally via the login link
         $m->get("$url/Search/Build.html");
@@ -148,7 +142,7 @@ diag "Fallback OFF";
 
     diag "No remote user";
     {
-        $m->default_header( auth("") );
+        $m->auth("");
         $m->get($url);
         is $m->status, 403, "Forbidden";
     }
@@ -169,7 +163,7 @@ diag "AutoCreate";
 
     diag "New user";
     {
-        $m->default_header( auth("anewuser") );
+        $m->auth("anewuser");
         $m->get_ok($url);
         ok logged_in_as($m, "anewuser"), "Logged in as anewuser";
 
@@ -191,7 +185,7 @@ diag "AutoCreate";
 
     diag "Create unprivileged users";
     {
-        $m->default_header( auth("unpriv") );
+        $m->auth("unpriv");
         $m->get_ok($url);
         ok logged_in_as($m, "unpriv"), "Logged in as an unpriv user";
         like $m->uri->path, RT->Config->Get('SelfServiceRegex'), "SelfService URL";
@@ -205,7 +199,7 @@ diag "AutoCreate";
 
     diag "User creation failure";
     {
-        $m->default_header( auth("conflicting") );
+        $m->auth("conflicting");
         $m->get($url);
         is $m->status, 403, "Forbidden";
 

commit 714b9b58c449e80a24b2c1d8bcd97fc5f551e0fa
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 13 11:03:58 2012 -0700

    Move logged_in_as into RT::Test::Web instead of copying parts of ->login

diff --git a/lib/RT/Test/Web.pm b/lib/RT/Test/Web.pm
index 8a6f982..8fae537 100644
--- a/lib/RT/Test/Web.pm
+++ b/lib/RT/Test/Web.pm
@@ -97,14 +97,24 @@ sub login {
 
     my $url = $self->rt_base_url;
     $self->get($url . "?user=$user;pass=$pass");
-    unless ( $self->status == 200 ) {
-        Test::More::diag( "error: status is ". $self->status );
-        return 0;
-    }
+
+    return 0 unless $self->logged_in_as($user);
+
     unless ( $self->content =~ m/Logout/i ) {
         Test::More::diag("error: page has no Logout");
         return 0;
     }
+    return 1;
+}
+
+sub logged_in_as {
+    my $self = shift;
+    my $user = shift || '';
+
+    unless ( $self->status == 200 ) {
+        Test::More::diag( "error: status is ". $self->status );
+        return 0;
+    }
     RT::Interface::Web::EscapeUTF8(\$user);
     unless ( $self->content =~ m{<span class="current-user">\Q$user\E</span>}i ) {
         Test::More::diag("Page has no user name");
diff --git a/t/web/remote_user.t b/t/web/remote_user.t
index f7caa08..69c672f 100644
--- a/t/web/remote_user.t
+++ b/t/web/remote_user.t
@@ -3,23 +3,6 @@ use warnings;
 use RT;
 use RT::Test plan => 'no_plan';
 
-sub logged_in_as {
-    my $mech = shift;
-    my $user = shift || '';
-
-    unless ($mech->status == 200) {
-        diag "Error: status is ". $mech->status;
-        return 0;
-    }
-
-    RT::Interface::Web::EscapeUTF8(\$user);
-    unless ($mech->content =~ m{<span class="current-user">\Q$user\E</span>}i) {
-        diag "Error: page has no user name";
-        return 0;
-    }
-    return 1;
-}
-
 sub stop_server {
     my $mech = shift;
 
@@ -58,11 +41,11 @@ diag "Continuous + Fallback";
                 pass => 'password',
             },
         }, "Submitted login form");
-        ok logged_in_as($m, "root"), "Logged in as root";
+        ok $m->logged_in_as("root"), "Logged in as root";
 
         # Still logged in on another request without REMOTE_USER
         $m->follow_link_ok({ text => 'My Tickets' });
-        ok logged_in_as($m, "root"), "Logged in as root";
+        ok $m->logged_in_as("root"), "Logged in as root";
 
         ok $m->logout, "Logged out";
 
@@ -78,11 +61,11 @@ diag "Continuous + Fallback";
 
         # Automatically logged in as root without Login page
         $m->get_ok($url);
-        ok logged_in_as($m, "root"), "Logged in as root";
+        ok $m->logged_in_as("root"), "Logged in as root";
 
         # Still logged in on another request
         $m->follow_link_ok({ text => 'My Tickets' });
-        ok logged_in_as($m, "root"), "Still logged in as root";
+        ok $m->logged_in_as("root"), "Still logged in as root";
 
         # Drop credentials and...
         $m->auth("");
@@ -114,12 +97,12 @@ diag "Continuous + Fallback";
                 pass => 'password',
             },
         }, "Submitted login form");
-        ok logged_in_as($m, "root"), "Logged in as root";
+        ok $m->logged_in_as("root"), "Logged in as root";
         like $m->uri, qr'Search/Build\.html', "at our originally requested page";
 
         # Still logged in on another request
         $m->follow_link_ok({ text => 'Tools' });
-        ok logged_in_as($m, "root"), "Logged in as root";
+        ok $m->logged_in_as("root"), "Logged in as root";
 
         ok $m->logout, "Logged out";
 
@@ -165,7 +148,7 @@ diag "AutoCreate";
     {
         $m->auth("anewuser");
         $m->get_ok($url);
-        ok logged_in_as($m, "anewuser"), "Logged in as anewuser";
+        ok $m->logged_in_as("anewuser"), "Logged in as anewuser";
 
         my $user = RT::User->new( RT->SystemUser );
         $user->Load("anewuser");
@@ -187,7 +170,7 @@ diag "AutoCreate";
     {
         $m->auth("unpriv");
         $m->get_ok($url);
-        ok logged_in_as($m, "unpriv"), "Logged in as an unpriv user";
+        ok $m->logged_in_as("unpriv"), "Logged in as an unpriv user";
         like $m->uri->path, RT->Config->Get('SelfServiceRegex'), "SelfService URL";
 
         my $user = RT::User->new( RT->SystemUser );

commit 69a5ebce9f522149acaf87b601fa11287b1ad287
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Mon Aug 13 11:16:48 2012 -0700

    Reference the new external auth docs from an error message
    
    Useful to know if you want to turn on auto-creation, or import from
    LDAP, etc.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index f8d35e4..006b2c5 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -660,7 +660,7 @@ sub AttemptExternalAuth {
         } else {
             # Couldn't auth with the REMOTE_USER provided because an RT
             # user doesn't exist and we're configured not to create one.
-            RT->Logger->error("Couldn't find internal user for '$user' when attempting WebExternalAuth and RT is not configured for auto-creation.");
+            RT->Logger->error("Couldn't find internal user for '$user' when attempting WebExternalAuth and RT is not configured for auto-creation. Refer to `perldoc $RT::BasePath/docs/external-auth.pod` if you want to allow auto-creation.");
             AbortExternalAuth(
                 Error => "NoInternalUser",
                 User  => $user,

-----------------------------------------------------------------------


More information about the Rt-commit mailing list