File Coverage

blib/lib/Dancer2/Plugin/Auth/SAFE.pm
Criterion Covered Total %
statement 71 71 100.0
branch 4 4 100.0
condition 7 12 58.3
subroutine 18 18 100.0
pod 2 3 66.6
total 102 108 94.4


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::SAFE;
2              
3 3     3   1531133 use strict;
  3         16  
  3         94  
4 3     3   12 use warnings;
  3         5  
  3         121  
5              
6             our $VERSION = '0.002';
7              
8 3     3   1714 use Dancer2::Plugin;
  3         86466  
  3         21  
9 3     3   21241 use Dancer2::Core::Types qw( Str );
  3         3  
  3         157  
10 3     3   14 use Digest::MD5 qw( md5_hex );
  3         9  
  3         137  
11 3     3   1621 use HTTP::Status qw( :constants );
  3         9644  
  3         1186  
12 3     3   945 use DateTime;
  3         365325  
  3         94  
13 3     3   1567 use Const::Fast;
  3         2641  
  3         20  
14 3     3   230 use namespace::autoclean;
  3         5  
  3         26  
15              
16             const my $MAX_TIMESTAMP_DEVIANCE => 5;
17              
18             has safe_url => (
19             is => 'ro',
20             isa => Str,
21             from_config => 1,
22             );
23              
24             has shared_secret => (
25             is => 'ro',
26             isa => Str,
27             from_config => 1,
28             );
29              
30             plugin_keywords qw( require_login logged_in_user );
31              
32             sub BUILD {
33 2     2 0 3993 my ($plugin) = @_;
34              
35 2         14 return $plugin->app->add_route(
36             method => 'post',
37             regexp => '/safe',
38             code => _authenticate_user($plugin),
39             );
40             }
41              
42             sub require_login {
43 2     2 1 216 my ( $plugin, $coderef ) = @_;
44              
45             return sub {
46 3     3   91482 my $session = $plugin->app->session;
47 3         8670 my $user_info = $session->read('user_info');
48              
49 3 100       70 if ($user_info) {
50 2         12 return $coderef->( $plugin->app );
51             }
52             else {
53 1         8 _store_original_route( $session, $plugin->app->request );
54 1         7 return $plugin->app->redirect( $plugin->safe_url );
55             }
56             }
57 2         20 }
58              
59             sub logged_in_user {
60 2     2 1 15 my ( $plugin, $coderef ) = @_;
61              
62 2         33 my $user_info = $plugin->app->session->read('user_info');
63              
64 2         47 return $user_info;
65             }
66              
67             sub _authenticate_user {
68 2     2   3 my ($plugin) = @_;
69              
70             return sub {
71 3     3   129967 my ($self) = @_;
72              
73 3         20 my $params = $self->app->request->params;
74              
75 3         40 my ( $uid, $timestamp, $digest ) = @{$params}{qw( uid time digest )};
  3         11  
76              
77 3 100 33     77 if (
      33        
      66        
      100        
78             defined $uid
79             && defined $timestamp
80             && defined $digest
81              
82             && $digest eq md5_hex( $uid . $timestamp . $plugin->shared_secret )
83             && _timestamp_deviance($timestamp) < $MAX_TIMESTAMP_DEVIANCE
84             )
85             {
86             my $user_info = {
87 3         7 map { $_ => $params->{$_} }
88 1         722 grep { defined $params->{$_} }
  9         11  
89             qw( uid firstname lastname company costcenter
90             email marketgroup paygroup thomslocation )
91             };
92              
93 1         5 my $session = $self->app->session;
94              
95 1         998 $session->write( user_info => $user_info );
96              
97 1         80 $self->app->forward( _extract_original_route($session) );
98             }
99              
100 2         1708 return $self->app->send_error( 'Authentication error',
101             HTTP_UNAUTHORIZED );
102             }
103 2         17 }
104              
105             sub _timestamp_deviance {
106 2     2   667 my ($timestamp) = @_;
107              
108 2         4 my %date_time;
109 2         20 @date_time{qw( year month day hour minute second )} =
110             split /:/xms, $timestamp;
111              
112 2         12 my $current_time = DateTime->now;
113 2         410 my $digest_time = DateTime->new(%date_time);
114              
115 2         530 return $current_time->delta_ms($digest_time)->{minutes};
116             }
117              
118             sub _store_original_route {
119 1     1   2 my ( $session, $request ) = @_;
120              
121 1         4 $session->write( '__auth_safe_path' => $request->path );
122 1         544 $session->write( '__auth_safe_method' => lc $request->method );
123 1         69 $session->write( '__auth_safe_params' => \%{ $request->params } );
  1         6  
124              
125 1         69 return;
126             }
127              
128             sub _extract_original_route {
129 1     1   4 my ($session) = @_;
130              
131 1         5 my @route = (
132             $session->read('__auth_safe_path'),
133             $session->read('__auth_safe_params'),
134             { method => $session->read('__auth_safe_method') },
135             );
136              
137 1         60 for (qw( __auth_safe_path __auth_safe_params __auth_safe_method )) {
138 3         129 $session->delete($_);
139             }
140              
141 1         64 return @route;
142             }
143              
144             1;
145              
146             __END__
147              
148             =head1 NAME
149              
150             Dancer2::Plugin::Auth::SAFE - Thomson Reuters SAFE SSO authentication plugin for Dancer2
151              
152             =head1 VERSION
153              
154             version 0.002
155              
156             =head1 DESCRIPTION
157              
158             With this plugin you can easily integrate Thomson Reuters SAFE SSO authentication
159             into your application.
160              
161             =head1 SYNOPSIS
162              
163             Add plugin configuration into your F<config.yml>
164              
165             plugins:
166             Auth::SAFE:
167             safe_url: "https://safe-test.thomson.com/login/sso/SSOService?app=app"
168             shared_secret: "fklsjf5GlkKJ!gs/skf"
169              
170             Define that a user must be logged in to access a route - and find out who is
171             logged in with the C<logged_in_user> keyword:
172              
173             use Dancer2::Plugin::Auth::SAFE;
174              
175             get '/users' => require_login sub {
176             my $user = logged_in_user;
177             return "Hi there, $user->{firstname}";
178             };
179              
180             =head1 ATTRIBUTES
181              
182             =head2 safe_url
183              
184             =head2 shared_secret
185              
186             =head1 SUBROUTINES/METHODS
187              
188             =head2 require_login
189              
190             Used to wrap a route which requires a user to be logged in order to access
191             it.
192              
193             get '/profile' => require_login sub { .... };
194              
195             =head2 logged_in_user
196              
197             Returns a hashref of details of the currently logged-in user, if there is one.
198              
199             =head1 AUTHOR
200              
201             Konstantin Matyukhin E<lt>kmatyukhin@gmail.comE<gt>
202              
203             =head1 LICENSE AND COPYRIGHT
204              
205             Copyright (c) 2016 by Konstantin Matyukhin
206              
207             This is a free software; you can redistribute it and/or modify it
208             under the same terms as Perl itself.
209              
210             =cut