File Coverage

blib/lib/Clustericious/Plugin/PlugAuth.pm
Criterion Covered Total %
statement 102 109 93.5
branch 21 28 75.0
condition 4 6 66.6
subroutine 12 12 100.0
pod 3 3 100.0
total 142 158 89.8


line stmt bran cond sub pod time code
1             package Clustericious::Plugin::PlugAuth;
2              
3 8     8   3493 use strict;
  8         20  
  8         206  
4 8     8   39 use warnings;
  8         17  
  8         184  
5 8     8   38 use Clustericious::Log;
  8         15  
  8         55  
6 8     8   5759 use Mojo::ByteStream qw/b/;
  8         28  
  8         369  
7 8     8   41 use Mojo::URL;
  8         14  
  8         54  
8 8     8   184 use Mojo::Base 'Mojolicious::Plugin';
  8         16  
  8         36  
9 8     8   1406 use JSON::MaybeXS qw( encode_json );
  8         19  
  8         9072  
10              
11             # ABSTRACT: Plugin for clustericious to use PlugAuth.
12             our $VERSION = '1.27'; # VERSION
13              
14              
15             has 'config_url';
16              
17             sub register {
18 7     7 1 228 my ($self, $app, $conf) = @_;
19 7         15 my $url = eval { $conf->{plug_auth}->url(default => '') };
  7         77  
20 7 100       27 $url =~ s{/$}{} if defined $url;
21 7 50       21 if ($@) {
22 0         0 WARN "unable to determine PlugAuth URL: $@";
23 0         0 return $self;
24             }
25 7         26 $self->config_url($url);
26 7         75 $self;
27             }
28              
29              
30             sub authenticate {
31 26     26 1 155 my $self = shift;
32 26         54 my $c = shift;
33 26         62 my $realm = shift;
34              
35 26         145 TRACE ("Authenticating for realm $realm");
36             # Everyone needs to send an authorization header
37 26 100       283 my $auth = $c->req->headers->authorization or do {
38 8         317 $c->res->headers->www_authenticate(qq[Basic realm="$realm"]);
39 8         394 $c->render(text => "auth required", layout => "", status => 401);
40 8         807 return;
41             };
42              
43 18         704 my $config_url = $self->config_url;
44              
45 18         167 my ($method,$str) = split / /,$auth;
46 18         121 my $userinfo = b($str)->b64_decode;
47 18         595 my ($user,$pw) = split /:/, $userinfo;
48              
49 18         150 my $self_plug_auth = 0;
50              
51             # VIP treatment for some hosts
52 18         61 my $ip = $c->tx->remote_address;
53             $c->ua->get("$config_url/host/$ip/trusted", sub {
54 18     18   110738 my $ua = shift;
55            
56 18         42 do {
57 18         40 my $tx = shift;
58 18 100       100 if ( my $res = $tx->success ) {
59 3 50       75 if ( $res->code == 200 ) {
60 3         40 TRACE "Host $ip is trusted, not authenticating";
61 3         45 $c->stash( user => $user );
62 3         73 $c->continue;
63 3         196 return;
64             }
65             else {
66 0         0 WARN "PlugAuth returned code " . $res->code;
67             }
68             } else {
69 15         279 my $error = $tx->error;
70 15 50       202 if ($error->{code}) {
71 15         69 TRACE "Not VIP $config_url/host/$ip/trusted : @{[ $error->{code} ]} @{[ $error->{message} ]}";
  15         67  
  15         86  
72             } else {
73 0         0 WARN "Error connecting to PlugAuth at $config_url/host/$ip/trusted : @{[ encode_json $error ]}";
  0         0  
74             }
75             }
76             };
77              
78             # Everyone else get in line
79 15         231 my $auth_url = Mojo::URL->new("$config_url/auth");
80 15         1697 $auth_url->userinfo($userinfo);
81              
82 15         94 my $check;
83             my $res;
84              
85             $ua->head($auth_url, sub {
86            
87 15         89664 my($ua, $tx) = @_;
88              
89 15         60 $res = $tx->res;
90 15         88 $check = $res->code();
91              
92 15 100 66     166 if(!defined $check || $check == 503) {
93 2         14 $c->res->headers->www_authenticate(qq[Basic realm="$realm"]);
94 2         56 my $error = $tx->error;
95 2 50       30 if ($error->{code}) {
96 2         13 WARN "Error connecting to PlugAuth at $auth_url : @{[ $error->{code} ]} @{[ $error->{message} ]}";
  2         430  
  2         15  
97             } else {
98 0         0 WARN "Error connecting to PlugAuth at $auth_url : @{[ encode_json $error ]}";
  0         0  
99             }
100 2         29 $c->render(text => "auth server down", status => 503); # "Service unavailable"
101 2         1445 return;
102             }
103 13 100       51 if ($check == 200) {
104 10         61 $c->stash(user => $user);
105 10         246 $c->continue;
106 10         3121 return;
107             }
108 3         25 INFO "Authentication denied for $user, status code : ".$check;
109 3 50       54 TRACE "Response was ".$res->to_string if defined $res;
110 3         41 $c->res->headers->www_authenticate(qq[Basic realm="$realm"]);
111 3         111 $c->render(text => "authentication failure", status => 401);
112 15         156 });
113            
114 18         510 });
115              
116 18         26770 return undef;
117             }
118              
119              
120             sub authorize {
121 8     8 1 20 my $self = shift;
122 8         18 my $c = shift;
123 8         24 my ($action,$resource) = @_;
124 8 50       28 my $user = $c->stash("user") or LOGDIE "missing user in authorize()";
125 8 50       113 LOGDIE "missing action or resource in authorize()" unless @_==2;
126 8         50 TRACE "Authorizing user $user, action $action, resource $resource";
127              
128 8         88 $resource =~ s[^/][];
129              
130 8         852 my $url = Mojo::URL->new( join '/', $self->config_url, "authz/user", $user, $action, $resource );
131              
132 8         981 my $tx = $c->ua->build_tx(HEAD => $url);
133            
134             $c->ua->start($tx, sub {
135 8     8   38162 my($ua, $tx) = @_;
136            
137 8         32 my $code = $tx->res->code;
138 8 100 66     108 if($code && $code == 200) {
139 5         30 $c->continue;
140 5         3770 return;
141             }
142            
143 3         24 INFO "Unauthorized access by $user to $action $resource";
144 3 100       35 if($code == 503) {
145 1         8 $c->render(text => "auth server down", status => 503); # "Service unavailable"
146             } else {
147 2         16 $c->render(text => "unauthorized", status => 403);
148             }
149 8         1428 });
150              
151 8         10918 return undef;
152             }
153              
154              
155             1;
156              
157             __END__
158              
159             =pod
160              
161             =encoding UTF-8
162              
163             =head1 NAME
164              
165             Clustericious::Plugin::PlugAuth - Plugin for clustericious to use PlugAuth.
166              
167             =head1 VERSION
168              
169             version 1.27
170              
171             =head1 SYNOPSIS
172              
173             MyApp.conf:
174              
175             ---
176             plug_auth:
177             url: http://plugauthserver:3000
178              
179             Application:
180              
181             package MyApp;
182            
183             use base qw( Clustericious::App );
184             use Clustericious::RouteBuilder;
185              
186             # unprotected
187             get '/public' => 'unprotected';
188            
189             # require PlugAuth username/password
190             authenticate;
191             get '/private1' => 'protected';
192            
193             # protected by PlugAuth an explicit realm
194             autheticate 'realm';
195             get '/private2' => 'realm protected';
196            
197             # check for permissions to do $action on $resource
198             authorize 'action', 'resource';
199             get '/restricted1' => 'authz_restricted';
200            
201             # check for premissions to do $action on the resource /restricted2
202             authorize 'action';
203             get '/restricted2';
204            
205             # HTTP method as the $action and /http_method_get as the resource
206             authorize '<method>';
207             get '/http_method_get';
208              
209             # HTTP method as the $action and "/prefix/http_method_with_prefix"
210             # as the resource.
211             authorize '<method>', '/myprefix/<path>';
212             get '/http_method_with_prefix';
213              
214             =head1 DESCRIPTION
215              
216             This provides authenticate and authorize methods which can be called from your applications
217             Route class.
218              
219             =head1 ATTRIBUTES
220              
221             =head2 config_url
222              
223             The URL of the PlugAuth server to authenticate against.
224              
225             =head1 METHODS
226              
227             =head2 authenticate [ $realm ]
228              
229             Require username and password authentication, optionally with a realm.
230             If a realm is not provided, '' is used.
231              
232             =head2 authorize [$action, [$resource]]
233              
234             Require the authenticated user have the authorization to perform
235             the given action on the given resource.
236              
237             =head1 SEE ALSO
238              
239             L<PlugAuth>, L<Clustericious>
240              
241             =head1 AUTHOR
242              
243             Original author: Brian Duggan
244              
245             Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>
246              
247             Contributors:
248              
249             Curt Tilmes
250              
251             Yanick Champoux
252              
253             =head1 COPYRIGHT AND LICENSE
254              
255             This software is copyright (c) 2013 by NASA GSFC.
256              
257             This is free software; you can redistribute it and/or modify it under
258             the same terms as the Perl 5 programming language system itself.
259              
260             =cut