edf613fedc02095241da46679cd919a4aa602c55
[catagits/Catalyst-Plugin-RequireSSL.git] / lib / Catalyst / Plugin / RequireSSL.pm
1 package Catalyst::Plugin::RequireSSL;
2
3 use strict;
4 use base qw/Class::Accessor::Fast/;
5 use NEXT;
6
7 our $VERSION = '0.07';
8
9 __PACKAGE__->mk_accessors( qw/_require_ssl _allow_ssl _ssl_strip_output/ );
10
11 sub require_ssl {
12     my $c = shift;
13
14     $c->_require_ssl(1);
15
16     if ( !$c->req->secure && $c->req->method ne "POST" ) {
17         my $redir = $c->_redirect_uri('https');
18         if ( $c->config->{require_ssl}->{disabled} ) {
19             $c->log->warn( "RequireSSL: Would have redirected to $redir" );
20         }
21         else {
22             $c->_ssl_strip_output(1);
23             $c->res->redirect( $redir );
24             $c->detach if $c->config->{require_ssl}->{detach_on_redirect};
25         }
26     }
27 }
28
29 sub allow_ssl {
30     my $c = shift;
31
32     $c->_allow_ssl(1);
33 }
34
35 sub finalize {
36     my $c = shift;
37     
38     # Do not redirect static files (only works with Static::Simple)
39     if ( $c->isa( "Catalyst::Plugin::Static::Simple" ) ) {
40         return $c->NEXT::finalize(@_) if $c->_static_file;
41     }
42     
43     # redirect back to non-SSL mode
44     REDIRECT:
45     {
46         # No redirect if:
47         # we're not in SSL mode
48         last REDIRECT if !$c->req->secure;
49         # it's a POST request
50         last REDIRECT if $c->req->method eq "POST";
51         # we're already required to be in SSL for this request
52         last REDIRECT if $c->_require_ssl;
53         # or the user doesn't want us to redirect
54         last REDIRECT if $c->config->{require_ssl}->{remain_in_ssl} || $c->_allow_ssl;
55         
56         $c->res->redirect( $c->_redirect_uri('http') );
57     }
58
59     # do not allow any output to be displayed on the insecure page
60     if ( $c->_ssl_strip_output ) {
61         $c->res->body( '' );
62     }
63
64     return $c->NEXT::finalize(@_);
65 }
66
67 sub setup {
68     my $c = shift;
69
70     $c->NEXT::setup(@_);
71
72     # disable the plugin when running under certain engines which don't
73     # support SSL
74     if ( $c->engine =~ /Catalyst::Engine::HTTP/ ) {
75         $c->config->{require_ssl}->{disabled} = 1;
76         $c->log->warn( "RequireSSL: Disabling SSL redirection while running "
77                      . "under " . $c->engine );
78     }
79 }
80
81 sub _redirect_uri {
82     my ( $c, $type ) = @_;
83
84     # XXX: Cat needs a $c->req->host method...
85     # until then, strip off the leading protocol from base
86     if ( !$c->config->{require_ssl}->{$type} ) {
87         my $host = $c->req->base;
88         $host =~ s/^http(s?):\/\///;
89         $c->config->{require_ssl}->{$type} = $host;
90     }
91
92     if ( $c->config->{require_ssl}->{$type} !~ /\/$/xms ) {
93         $c->config->{require_ssl}->{$type} .= '/';
94     }
95
96     my $redir
97         = $type . '://' . $c->config->{require_ssl}->{$type} . $c->req->path;
98         
99     if ( scalar $c->req->param ) {
100         my @params;
101         foreach my $arg ( sort keys %{ $c->req->params } ) {
102             if ( ref $c->req->params->{$arg} ) {
103                 my $list = $c->req->params->{$arg};
104                 push @params, map { "$arg=" . $_  } sort @{$list};
105             }
106             else {
107                 push @params, "$arg=" . $c->req->params->{$arg};
108             }
109         }
110         $redir .= '?' . join( '&', @params );
111     }  
112           
113     if ( $c->config->{require_ssl}->{no_cache} ) {        
114         delete $c->config->{require_ssl}->{$type};
115     }
116     
117     return $redir;
118 }
119
120 1;
121 __END__
122
123 =head1 NAME
124
125 Catalyst::Plugin::RequireSSL - Force SSL mode on select pages
126
127 =head1 SYNOPSIS
128
129     # in MyApp.pm
130     use Catalyst;
131     MyApp->setup( qw/RequireSSL/ );
132     
133     MyApp->config->{require_ssl} = {
134         https => 'secure.mydomain.com',
135         http => 'www.mydomain.com',
136         remain_in_ssl => 0,
137         no_cache => 0,
138         detach_on_redirect => 1,
139     };
140
141     # in any controller methods that should be secured
142     $c->require_ssl;
143
144 =head1 DESCRIPTION
145
146 Use this plugin if you wish to selectively force SSL mode on some of your web
147 pages, for example a user login form or shopping cart.
148
149 Simply place $c->require_ssl calls in any controller method you wish to be
150 secured. 
151
152 This plugin will automatically disable itself if you are running under the
153 standalone HTTP::Daemon Catalyst server.  A warning message will be printed to
154 the log file whenever an SSL redirect would have occurred.
155
156 =head1 WARNINGS
157
158 If you utilize different servers or hostnames for non-SSL and SSL requests,
159 and you rely on a session cookie to determine redirection (i.e for a login
160 page), your cookie must be visible to both servers.  For more information, see
161 the documentation for the Session plugin you are using.
162
163 =head1 CONFIGURATION
164
165 Configuration is optional.  You may define the following configuration values:
166
167     https => $ssl_host
168     
169 If your SSL domain name is different from your non-SSL domain, set this value.
170
171     http => $non_ssl_host
172     
173 If you have set the https value above, you must also set the hostname of your
174 non-SSL server.
175
176     remain_in_ssl
177     
178 If you'd like your users to remain in SSL mode after visiting an SSL-required
179 page, you can set this option to 1.  By default, this option is disabled and
180 users will be redirected back to non-SSL mode as soon as possible.
181
182     no_cache 
183
184 If you have a wildcard certificate you will need to set this option if you are
185 using multiple domains on one instance of Catalyst.
186
187     detach_on_redirect 
188
189 By default C<< $c->require_ssl >> only calls C<< $c->response->redirect >> but
190 does not stop request processing (so it returns and subsequent statements are
191 run). This is probably not what you want. If you set this option to a true
192 value C<< $c->require_ssl >> will call C<< $c->detach >> when it redirects.
193
194 =head1 METHODS
195
196 =head2 require_ssl
197
198 Call require_ssl in any controller method you wish to be secured.
199
200     $c->require_ssl;
201
202 The browser will be redirected to the same path on your SSL server.  POST
203 requests are never redirected.
204
205 =head2 allow_ssl
206
207 Call allow_ssl in any controller method you wish to access both in SSL and
208 non-SSL mode.
209
210     $c->allow_ssl;
211
212 The browser will not be redirected, independently of whether the request was
213 made to the SSL or non-SSL server.
214
215 =head2 setup
216
217 Disables this plugin if running under an engine which does not support SSL.
218
219 =head2 finalize
220
221 Performs the redirect to SSL url if required.
222
223 =head1 KNOWN ISSUES
224
225 When viewing an SSL-required page that uses static files served from the
226 Static plugin, the static files are redirected to the non-SSL path.
227
228 In order to get the correct behaviour where static files are not redirected,
229 you should use the Static::Simple plugin or always serve static files
230 directly from your web server.
231
232 =head1 SEE ALSO
233
234 L<Catalyst>, L<Catalyst::Plugin::Static::Simple>
235
236 =head1 AUTHOR
237
238 Andy Grundman, <andy@hybridized.org>
239
240 =head1 CONTRIBUTORS
241
242 Simon Elliott <simon@browsing.co.uk> (support for wildcards)
243
244 =head1 COPYRIGHT
245
246 This program is free software, you can redistribute it and/or modify it under
247 the same terms as Perl itself.
248
249 =cut