Commit | Line | Data |
2022b950 |
1 | #!/usr/bin/perl |
2022b950 |
2 | use strict; |
3 | use warnings; |
b5402c9e |
4 | use Test::More tests => 35; |
2022b950 |
5 | use Test::MockObject::Extends; |
6 | use Test::MockObject; |
d9914dd2 |
7 | use Test::Exception; |
2022b950 |
8 | use HTTP::Headers; |
9 | |
513d8ab6 |
10 | my $m; BEGIN { use_ok($m = "Catalyst::Authentication::Credential::HTTP") } |
11 | can_ok( $m, "authenticate" ); |
2022b950 |
12 | can_ok( $m, "authorization_required_response" ); |
490754a8 |
13 | |
2022b950 |
14 | my $req = Test::MockObject->new; |
15 | my $req_headers = HTTP::Headers->new; |
2022b950 |
16 | $req->set_always( headers => $req_headers ); |
2022b950 |
17 | my $res = Test::MockObject->new; |
2022b950 |
18 | my $status; |
19 | $res->mock(status => sub { $status = $_[1] }); |
f366138e |
20 | my $content_type; |
21 | $res->mock(content_type => sub { $content_type = $_[1] }); |
22 | my $body; |
513d8ab6 |
23 | my $headers; |
f366138e |
24 | $res->mock(body => sub { $body = $_[1] }); |
2022b950 |
25 | my $res_headers = HTTP::Headers->new; |
26 | $res->set_always( headers => $res_headers ); |
513d8ab6 |
27 | my $user = Test::MockObject->new; |
b5402c9e |
28 | $user->set_isa('Catalyst::Authentication::User'); |
490754a8 |
29 | $user->mock(get => sub { return shift->{$_[0]} }); |
30 | my $find_user_opts; |
31 | my $realm = Test::MockObject->new; |
513d8ab6 |
32 | $realm->mock( find_user => sub { $find_user_opts = $_[1]; return $user; }); |
33 | $realm->mock( name => sub { 'foo' } ); |
34 | my $c = Test::MockObject->new; |
007935b8 |
35 | my $cache = Test::MockObject->new; |
36 | $cache->mock(set => sub { shift->{$_[0]} = $_[1] }); |
37 | $cache->mock(get => sub { return shift->{$_[0]} }); |
c5a1fa88 |
38 | my $uri_for_called = 0; |
39 | $c->mock(uri_for => sub { my ($c, $uri) = @_; $uri_for_called++; return 'uri_for:' . $uri} ); |
007935b8 |
40 | $c->mock(cache => sub { $cache }); |
4e8cbd42 |
41 | $c->mock(debug => sub { 0 }); |
2022b950 |
42 | my @login_info; |
43 | $c->mock( login => sub { shift; @login_info = @_; 1 } ); |
513d8ab6 |
44 | my $authenticated = 0; |
45 | $c->mock( set_authenticated => sub { $authenticated++; } ); |
2022b950 |
46 | $c->set_always( config => {} ); |
47 | $c->set_always( req => $req ); |
48 | $c->set_always( res => $res ); |
513d8ab6 |
49 | $c->set_always( request => $req ); |
50 | $c->set_always( response => $res ); |
490754a8 |
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 |
2022b950 |
73 | $req_headers->authorization_basic( qw/foo bar/ ); |
b5402c9e |
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'); |
513d8ab6 |
80 | is_deeply( $find_user_opts, { username => 'foo'}, "login delegated"); |
490754a8 |
81 | |
82 | # Test all the headers look good. |
2022b950 |
83 | $req_headers->clear; |
bf399285 |
84 | $res_headers->clear; |
2022b950 |
85 | $c->clear; |
d9914dd2 |
86 | throws_ok { |
513d8ab6 |
87 | $self->authenticate( $c, $realm ); |
d9914dd2 |
88 | } qr/^ $Catalyst::DETACH $/x, "detached on no authorization required with bad auth"; |
2022b950 |
89 | is( $status, 401, "401 status code" ); |
f366138e |
90 | is( $content_type, 'text/plain' ); |
91 | is( $body, 'Authorization required.' ); |
007935b8 |
92 | like( ($res_headers->header('WWW-Authenticate'))[0], qr/^Digest/, "WWW-Authenticate header set: digest"); |
513d8ab6 |
93 | like( ($res_headers->header('WWW-Authenticate'))[0], qr/realm="foo"/, "WWW-Authenticate header set: digest realm"); |
007935b8 |
94 | like( ($res_headers->header('WWW-Authenticate'))[1], qr/^Basic/, "WWW-Authenticate header set: basic"); |
513d8ab6 |
95 | like( ($res_headers->header('WWW-Authenticate'))[1], qr/realm="foo"/, "WWW-Authenticate header set: basic realm"); |
6afc3665 |
96 | |
bf399285 |
97 | $res_headers->clear; |
490754a8 |
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; |
bf399285 |
113 | $res_headers->clear; |
513d8ab6 |
114 | throws_ok { |
115 | $self->authenticate( $c, $realm, { realm => 'myrealm' }); # Override realm object's name method by doing this. |
490754a8 |
116 | } qr/^ $Catalyst::DETACH $/x, "detached on no authorization supplied, overridden realm value"; |
513d8ab6 |
117 | is( $status, 401, "401 status code" ); |
118 | is( $content_type, 'text/plain' ); |
119 | is( $body, 'Authorization required.' ); |
bf399285 |
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 | |
05512a69 |
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 | |
f1f73b53 |
137 | # Check undef authorization_required_message suppresses crapping in |
138 | # the body. |
05512a69 |
139 | $req_headers->clear; |
140 | $res_headers->clear; |
141 | $c->clear; |
afe44be8 |
142 | $body = 'quuux'; |
05512a69 |
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"; |
afe44be8 |
150 | is( $body, 'quuux', 'Body is not set - user overrode auth message'); |
f1f73b53 |
151 | } |
152 | |
153 | # Check domain config works |
154 | $req_headers->clear; |
155 | $res_headers->clear; |
156 | $c->clear; |
157 | { |
c5a1fa88 |
158 | my $self = new_self( type => 'any', password_type => 'clear'); |
f1f73b53 |
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 | } |
c5a1fa88 |
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 | } |