Checking in changes prior to tagging of version 1.009. Changelog diff is:
[catagits/Catalyst-Authentication-Credential-HTTP.git] / t / basic.t
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4 use Test::More tests => 35;
5 use Test::MockObject::Extends;
6 use Test::MockObject;
7 use Test::Exception;
8 use HTTP::Headers;
9
10 my $m; BEGIN { use_ok($m = "Catalyst::Authentication::Credential::HTTP") }
11 can_ok( $m, "authenticate" );
12 can_ok( $m, "authorization_required_response" );
13
14 my $req = Test::MockObject->new;
15 my $req_headers = HTTP::Headers->new;
16 $req->set_always( headers => $req_headers );
17 my $res = Test::MockObject->new;
18 my $status;
19 $res->mock(status => sub { $status = $_[1] });
20 my $content_type;
21 $res->mock(content_type => sub { $content_type = $_[1] });
22 my $body;
23 my $headers;
24 $res->mock(body => sub { $body = $_[1] });
25 my $res_headers = HTTP::Headers->new;
26 $res->set_always( headers => $res_headers );
27 my $user = Test::MockObject->new;
28 $user->set_isa('Catalyst::Authentication::User');
29 $user->mock(get => sub { return shift->{$_[0]} });
30 my $find_user_opts;
31 my $realm = Test::MockObject->new;
32 $realm->mock( find_user => sub { $find_user_opts = $_[1]; return $user; });
33 $realm->mock( name => sub { 'foo' } );
34 my $c = Test::MockObject->new;
35 my $cache = Test::MockObject->new;
36 $cache->mock(set => sub { shift->{$_[0]} = $_[1] });
37 $cache->mock(get => sub { return shift->{$_[0]} });
38 my $uri_for_called = 0;
39 $c->mock(uri_for => sub { my ($c, $uri) = @_; $uri_for_called++; return 'uri_for:' . $uri} );
40 $c->mock(cache => sub { $cache });
41 $c->mock(debug => sub { 0 });
42 my @login_info;
43 $c->mock( login => sub { shift; @login_info = @_; 1 } );
44 my $authenticated = 0;
45 $c->mock( set_authenticated => sub { $authenticated++; } );
46 $c->set_always( config => {} );
47 $c->set_always( req => $req );
48 $c->set_always( res => $res );
49 $c->set_always( request => $req );
50 $c->set_always( response => $res );
51
52 sub new_self {
53     my $config = { @_ };
54     my $raw_self = $m->new($config, $c, $realm);
55     return Test::MockObject::Extends->new( $raw_self );
56 }
57
58 # Normal auth, simple as possible.
59 # No credentials
60 my $self = new_self( type => 'any', password_type => 'clear', password_field => 'password' );
61 throws_ok {
62     $self->authenticate( $c, $realm );
63 } qr/^ $Catalyst::DETACH $/x, 'Calling authenticate for http auth without header detaches';
64 $user->{password} = 'bar';
65
66 # Wrong credentials
67 $req_headers->authorization_basic( qw/foo quux/ );
68 throws_ok {
69     $self->authenticate( $c, $realm );
70 } qr/^ $Catalyst::DETACH $/x, 'Calling authenticate for http auth without header detaches';
71
72 # Correct credentials
73 $req_headers->authorization_basic( qw/foo bar/ );
74 {
75     my $user = $self->authenticate($c, $realm);
76     ok($user, "auth successful with header");
77     isa_ok $user, 'Catalyst::Authentication::User';
78 }
79 is($authenticated, 0, 'Not called set_authenticated');
80 is_deeply( $find_user_opts, { username => 'foo'}, "login delegated");
81
82 # Test all the headers look good.
83 $req_headers->clear;
84 $res_headers->clear;
85 $c->clear;
86 throws_ok {
87     $self->authenticate( $c, $realm );
88 } qr/^ $Catalyst::DETACH $/x, "detached on no authorization required with bad auth";
89 is( $status, 401, "401 status code" );
90 is( $content_type, 'text/plain' );
91 is( $body, 'Authorization required.' );
92 like( ($res_headers->header('WWW-Authenticate'))[0], qr/^Digest/, "WWW-Authenticate header set: digest");
93 like( ($res_headers->header('WWW-Authenticate'))[0], qr/realm="foo"/, "WWW-Authenticate header set: digest realm");
94 like( ($res_headers->header('WWW-Authenticate'))[1], qr/^Basic/, "WWW-Authenticate header set: basic");
95 like( ($res_headers->header('WWW-Authenticate'))[1], qr/realm="foo"/, "WWW-Authenticate header set: basic realm");
96
97 $res_headers->clear;
98 # Check password_field works
99 {
100     my $self = new_self( type => 'any', password_type => 'clear', password_field => 'the_other_password' );
101     local $user->{password} = 'bar';
102     local $user->{the_other_password} = 'the_other_password';
103     $req_headers->authorization_basic( qw/foo the_other_password/ );
104     ok($self->authenticate($c, $realm), "auth successful with header and alternate password field");
105     $c->clear;
106     $req_headers->authorization_basic( qw/foo bar/ );
107     throws_ok {
108         $self->authenticate( $c, $realm );
109     } qr/^ $Catalyst::DETACH $/x, "detached on bad password (different password field)";
110 }
111
112 $req_headers->clear;
113 $res_headers->clear;
114 throws_ok {
115     $self->authenticate( $c, $realm, { realm => 'myrealm' }); # Override realm object's name method by doing this.
116 } qr/^ $Catalyst::DETACH $/x, "detached on no authorization supplied, overridden realm value";
117 is( $status, 401, "401 status code" );
118 is( $content_type, 'text/plain' );
119 is( $body, 'Authorization required.' );
120 like( ($res_headers->header('WWW-Authenticate'))[0], qr/realm="myrealm"/, "WWW-Authenticate header set: digest realm overridden");
121 like( ($res_headers->header('WWW-Authenticate'))[1], qr/realm="myrealm"/, "WWW-Authenticate header set: basic realm overridden");
122
123 # Check authorization_required_message works
124 $req_headers->clear;
125 $res_headers->clear;
126 $c->clear;
127 {
128     my $self = new_self( type => 'any', password_type => 'clear',
129         authorization_required_message => 'foobar'
130     );
131     throws_ok {
132         $self->authenticate( $c, $realm );
133     } qr/^ $Catalyst::DETACH $/x, "detached";
134     is( $body, 'foobar', 'Body is supplied auth message');
135 }
136
137 # Check undef authorization_required_message suppresses crapping in
138 # the body.
139 $req_headers->clear;
140 $res_headers->clear;
141 $c->clear;
142 $body = 'quuux';
143 {
144     my $self = new_self( type => 'any', password_type => 'clear',
145         authorization_required_message => undef
146     );
147     throws_ok {
148         $self->authenticate( $c, $realm );
149     } qr/^ $Catalyst::DETACH $/x, "detached";
150     is( $body, 'quuux', 'Body is not set - user overrode auth message');
151 }
152
153 # Check domain config works
154 $req_headers->clear;
155 $res_headers->clear;
156 $c->clear;
157 {
158     my $self = new_self( type => 'any', password_type => 'clear');
159     throws_ok {
160         $self->authenticate( $c, $realm, {domain => [qw/dom1 dom2/]} );
161     } qr/^ $Catalyst::DETACH $/x, "detached";
162     like( ($res_headers->header('WWW-Authenticate'))[0], qr/domain="dom1 dom2"/, "WWW-Authenticate header set: digest domains set");
163     like( ($res_headers->header('WWW-Authenticate'))[1], qr/domain="dom1 dom2"/, "WWW-Authenticate header set: basic domains set");
164 }
165
166 # Check domain config works with use_uri_for option
167 $req_headers->clear;
168 $res_headers->clear;
169 $c->clear;
170 {
171     my $self = new_self( type => 'any', password_type => 'clear', use_uri_for => 1);
172     throws_ok {
173         $self->authenticate( $c, $realm, {domain => [qw/dom1 dom2/]} );
174     } qr/^ $Catalyst::DETACH $/x, "detached";
175     like( ($res_headers->header('WWW-Authenticate'))[0], qr/domain="uri_for:dom1 uri_for:dom2"/, 
176         "WWW-Authenticate header set: digest domains set with use_uri_for");
177     like( ($res_headers->header('WWW-Authenticate'))[1], qr/domain="uri_for:dom1 uri_for:dom2"/, 
178         "WWW-Authenticate header set: basic domains set with use_uri_for");
179 }