File Coverage

blib/lib/Dancer2/Plugin/Auth/SAFE.pm
Criterion Covered Total %
statement 56 56 100.0
branch 4 4 100.0
condition 7 12 58.3
subroutine 16 16 100.0
pod 2 3 66.6
total 85 91 93.4


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::SAFE;
2              
3 3     3   1751774 use strict;
  3         5  
  3         123  
4 3     3   20 use warnings;
  3         6  
  3         216  
5              
6             our $VERSION = '0.001';
7              
8 3     3   2297 use Dancer2::Plugin;
  3         101154  
  3         28  
9 3     3   24492 use Dancer2::Core::Types qw( Str );
  3         7  
  3         220  
10 3     3   19 use Digest::MD5 qw( md5_hex );
  3         16  
  3         179  
11 3     3   2238 use HTTP::Status qw( :constants );
  3         13319  
  3         1629  
12 3     3   951 use DateTime;
  3         384254  
  3         207  
13 3     3   2129 use Const::Fast;
  3         3638  
  3         28  
14 3     3   287 use namespace::autoclean;
  3         7  
  3         34  
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 6172 my ($plugin) = @_;
34              
35 2         23 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 213 my ( $plugin, $coderef ) = @_;
44              
45             return sub {
46 2     2   140093 my $user_info = $plugin->app->session->read('user_info');
47              
48 2 100       16779 if ($user_info) {
49 1         25 return $coderef->( $plugin->app );
50             }
51             else {
52 1         26 return $plugin->app->redirect( $plugin->safe_url );
53             }
54             }
55 2         23 }
56              
57             sub logged_in_user {
58 1     1 1 11 my ( $plugin, $coderef ) = @_;
59              
60 1         80 my $user_info = $plugin->app->session->read('user_info');
61              
62 1         53 return $user_info;
63             }
64              
65             sub _authenticate_user {
66 2     2   18 my ($plugin) = @_;
67              
68             return sub {
69 3     3   154089 my ($self) = @_;
70              
71 3         20 my $params = $self->app->request->params;
72              
73 3         55 my ( $uid, $timestamp, $digest ) = @{$params}{qw( uid time digest )};
  3         28  
74              
75 3 100 33     85 if (
      33        
      66        
      100        
76             defined $uid
77             && defined $timestamp
78             && defined $digest
79              
80             && $digest eq md5_hex( $uid . $timestamp . $plugin->shared_secret )
81             && _timestamp_deviance($timestamp) < $MAX_TIMESTAMP_DEVIANCE
82             )
83             {
84             my $user_info = {
85 3         17 map { $_ => $params->{$_} }
86 1         978 grep { defined $params->{$_} }
  9         27  
87             qw( uid firstname lastname company costcenter
88             email marketgroup paygroup thomslocation )
89             };
90              
91 1         12 return $self->app->session->write( user_info => $user_info );
92             }
93             else {
94 2         1849 return $self->app->send_error( 'Authentication error',
95             HTTP_UNAUTHORIZED );
96             }
97             }
98 2         28 }
99              
100             sub _timestamp_deviance {
101 2     2   1024 my ($timestamp) = @_;
102              
103 2         5 my %date_time;
104 2         24 @date_time{qw( year month day hour minute second )} =
105             split /:/xms, $timestamp;
106              
107 2         21 my $current_time = DateTime->now;
108 2         573 my $digest_time = DateTime->new(%date_time);
109              
110 2         557 return $current_time->delta_ms($digest_time)->{minutes};
111             }
112              
113             1;
114              
115             __END__
116              
117             =head1 NAME
118              
119             Dancer2::Plugin::Auth::SAFE - Thomson Reuters SAFE SSO authentication plugin for Dancer2
120              
121             =head1 VERSION
122              
123             version 0.001
124              
125             =head1 DESCRIPTION
126              
127             With this plugin you can easily integrate Thomson Reuters SAFE SSO authentication
128             into your application.
129              
130             =head1 SYNOPSIS
131              
132             Add plugin configuration into your F<config.yml>
133              
134             plugins:
135             Auth::SAFE:
136             safe_url: "https://safe-test.thomson.com/login/sso/SSOService?app=app"
137             shared_secret: "fklsjf5GlkKJ!gs/skf"
138              
139             Define that a user must be logged in to access a route - and find out who is
140             logged in with the C<logged_in_user> keyword:
141              
142             use Dancer2::Plugin::Auth::SAFE;
143              
144             get '/users' => require_login sub {
145             my $user = logged_in_user;
146             return "Hi there, $user->{firstname}";
147             };
148              
149             =head1 ATTRIBUTES
150              
151             =head2 safe_url
152              
153             =head2 shared_secret
154              
155             =head1 SUBROUTINES/METHODS
156              
157             =head2 require_login
158              
159             Used to wrap a route which requires a user to be logged in order to access
160             it.
161              
162             get '/profile' => require_login sub { .... };
163              
164             =head2 logged_in_user
165              
166             Returns a hashref of details of the currently logged-in user, if there is one.
167              
168             =head1 AUTHOR
169              
170             Konstantin Matyukhin E<lt>kmatyukhin@gmail.comE<gt>
171              
172             =head1 LICENSE AND COPYRIGHT
173              
174             Copyright (c) 2016 by Konstantin Matyukhin
175              
176             This is a free software; you can redistribute it and/or modify it
177             under the same terms as Perl itself.
178              
179             =cut