[Bps-public-commit] rt-extension-rest2 branch, master, updated. e0ddc9ef455296f8a3fa1c0a1a25b973bcb104a6

Shawn Moore shawn at bestpractical.com
Fri Jul 7 11:39:31 EDT 2017


The branch, master has been updated
       via  e0ddc9ef455296f8a3fa1c0a1a25b973bcb104a6 (commit)
      from  5159c3fdefab807730854e41250f09ff9e4de0b4 (commit)

Summary of changes:
 Makefile.PL                               |   1 -
 lib/RT/Extension/REST2/Middleware/Auth.pm | 114 ++++++++++++++++++++++++++----
 t/root.t                                  |   1 -
 3 files changed, 102 insertions(+), 14 deletions(-)

- Log -----------------------------------------------------------------
commit e0ddc9ef455296f8a3fa1c0a1a25b973bcb104a6
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Fri Jul 7 15:22:48 2017 +0000

    Allow web session and token-based auth
    
    If you have a valid session cookie, we now use it. This makes it
    possible to (among other things) use REST2 from JS, instead of having to
    write one-off /Helpers/ endpoints.
    
    There's also a new RT::Authen::Token extension which REST2 can use as
    well. Since this plugin is likely destined for core, it doesn't check
    @Plugins but instead checks to see if the feature is there.
    
    If it looks like you're using a browser, we shuttle you over to / to log
    you in rather than provide a 401 response.
    
    Finally, the test demanding that unauthorized requests send a
    WWW-Authenticate be present is removed; we don't want to prompt a
    browser for basic auth. The 401 status code and response body should
    suffice for non-browser clients. A sampling of other APIs shows
    WWW-Authenticate isn't widely used.

diff --git a/Makefile.PL b/Makefile.PL
index ab30f79..10cad18 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -18,7 +18,6 @@ requires 'Plack::Builder';
 requires 'Scalar::Util';
 requires 'Sub::Exporter';
 requires 'Web::Machine' => '0.12';
-requires 'Class::Method::Modifiers';
 requires 'Plack::Middleware::RequestHeaders';
 requires 'Plack::Middleware::ReverseProxyPath';
 requires 'Module::Path';
diff --git a/lib/RT/Extension/REST2/Middleware/Auth.pm b/lib/RT/Extension/REST2/Middleware/Auth.pm
index 18587de..3745b6c 100644
--- a/lib/RT/Extension/REST2/Middleware/Auth.pm
+++ b/lib/RT/Extension/REST2/Middleware/Auth.pm
@@ -3,27 +3,117 @@ package RT::Extension::REST2::Middleware::Auth;
 use strict;
 use warnings;
 
-use base 'Plack::Middleware::Auth::Basic';
+use base 'Plack::Middleware';
 
-use Class::Method::Modifiers;
+our @auth_priority = qw(
+    login_from_cookie
+    login_from_authtoken
+    login_from_basicauth
+);
 
-before prepare_app => sub {
-    my $self = shift;
-    $self->realm( RT->Config->Get('rtname') . ' REST API' );
+sub call {
+    my ($self, $env) = @_;
+
+    for my $method (@auth_priority) {
+        last if $env->{'rt.current_user'} = $self->$method($env);
+    }
+
+    if ($env->{'rt.current_user'}) {
+        return $self->app->($env);
+    }
+    else {
+        return $self->unauthorized($env);
+    }
+}
+
+sub login_from_cookie {
+    my ($self, $env) = @_;
+
+    # allow reusing authentication from the ordinary web UI so that
+    # among other things our JS can use REST2
+    if ($env->{HTTP_COOKIE}) {
+
+        # this is foul but LoadSessionFromCookie doesn't have a hook for
+        # saying "look up cookie in my $env". this beats duplicating
+        # LoadSessionFromCookie
+        no warnings 'redefine';
+        local *RT::Interface::Web::RequestENV = sub { return $env->{$_[0]} };
+
+        local *HTML::Mason::Commands::session;
+
+        RT::Interface::Web::LoadSessionFromCookie();
+        if (RT::Interface::Web::_UserLoggedIn) {
+            return $HTML::Mason::Commands::session{CurrentUser};
+        }
+    }
+
+    return;
+}
+
+sub login_from_authtoken {
+    my ($self, $env) = @_;
 
-    $self->authenticator(sub {
-        my ($user, $pass, $env) = @_;
+    # needs RT::Authen::Token extension
+    return unless RT::AuthToken->can('Create');
+
+    if (($env->{HTTP_AUTHORIZATION}||'') =~ /^token (.*)$/i) {
+        my ($user_obj, $token) = RT::Authen::Token->UserForAuthString($1);
+        return $user_obj;
+    }
+
+    return;
+}
+
+sub login_from_basicauth {
+    my ($self, $env) = @_;
+
+    require MIME::Base64;
+    if (($env->{HTTP_AUTHORIZATION}||'') =~ /^basic (.*)$/i) {
+        my($user, $pass) = split /:/, (MIME::Base64::decode($1) || ":"), 2;
         my $cu = RT::CurrentUser->new;
         $cu->Load($user);
         if ($cu->id and $cu->IsPassword($pass)) {
-            $env->{'rt.current_user'} = $cu;
-            return 1;
+            return $cu;
         }
         else {
             RT->Logger->info("Failed login for $user");
-            return 0;
+            return;
         }
-    });
-};
+    }
+
+    return;
+}
+
+sub _looks_like_browser {
+    my $self = shift;
+    my $env = shift;
+
+    return 1 if $env->{HTTP_COOKIE};
+    return 1 if $env->{HTTP_USER_AGENT} =~ /Mozilla/;
+    return 0;
+}
+
+sub unauthorized {
+    my $self = shift;
+    my $env = shift;
+
+    if ($self->_looks_like_browser($env)) {
+        my $url = RT->Config->Get('WebPath') . '/';
+        return [
+            302,
+            [ 'Location' => $url ],
+            [ "Login required" ],
+        ];
+    }
+    else {
+        my $body = 'Authorization required';
+        return [
+            401,
+            [ 'Content-Type' => 'text/plain',
+              'Content-Length' => length $body ],
+            [ $body ],
+        ];
+    }
+}
 
 1;
diff --git a/t/root.t b/t/root.t
index be7d640..9ed51e8 100644
--- a/t/root.t
+++ b/t/root.t
@@ -11,7 +11,6 @@ my $rest_base_path = '/REST/2.0';
 {
     my $res = $mech->get($rest_base_path);
     is($res->code, 401, 'Unauthorized');
-    is($res->header('www-authenticate'), 'Basic realm="example.com REST API"');
     is($mech->json_response->{message}, 'Unauthorized');
 }
 

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


More information about the Bps-public-commit mailing list