Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 7c5c23fb

Von Moritz Bunkus vor 7 Tagen hinzugefügt

  • ID 7c5c23fbf6f55a5b428f7d58bddc08938881a31f
  • Vorgänger 222efaf4
  • Nachfolger 82648acc

[LINET] Auth: automatisches SSO mittels gewisser HTTP-Header

Unterschiede anzeigen:

SL/Auth.pm
12 12
use SL::Auth::ColumnInformation;
13 13
use SL::Auth::Constants qw(:all);
14 14
use SL::Auth::DB;
15
use SL::Auth::HTTPHeaders;
15 16
use SL::Auth::LDAP;
16 17
use SL::Auth::Password;
17 18
use SL::Auth::SessionValue;
......
152 153
  foreach my $module (split m{ +}, $self->{module}) {
153 154
    my $config_name;
154 155
    ($module, $config_name) = split m{:}, $module, 2;
155
    $config_name          ||= $module eq 'DB' ? 'database' : lc($module);
156
    $config_name          ||= $module eq 'DB' ? 'database' : $module eq 'HTTPHeaders' ? 'http_headers' : lc($module);
156 157
    my $config              = $::lx_office_conf{'authentication/' . $config_name};
157 158

  
158 159
    if (!$config) {
......
166 167
    } elsif ($module eq 'LDAP') {
167 168
      push @{ $self->{authenticators} }, SL::Auth::LDAP->new($config);
168 169

  
170
    } elsif ($module eq 'HTTPHeaders') {
171
      push @{ $self->{authenticators} }, SL::Auth::HTTPHeaders->new($config);
172

  
169 173
    } else {
170 174
      my $locale = Locale->new('en');
171 175
      $self->mini_error($locale->text('Unknown authenticantion module #1 specified in "config/kivitendo.conf".', $module));
......
228 232
  return $result;
229 233
}
230 234

  
235
sub set_session_authenticated {
236
  my ($self, $login, $result) = @_;
237

  
238
  $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
239
}
240

  
231 241
sub authenticate {
232 242
  my ($self, $login, $password) = @_;
233 243

  
......
252 262
    }
253 263
  }
254 264

  
255
  $self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
265
  $self->set_session_authenticated($login, $result);
266

  
256 267
  return $result;
257 268
}
258 269

  
SL/Auth/HTTPHeaders.pm
1
package SL::Auth::HTTPHeaders;
2

  
3
use List::MoreUtils qw(any);
4

  
5
use SL::Auth::Constants qw(:all);
6

  
7
use strict;
8

  
9
my @required_config_options = qw(secret_header secret user_header client_id_header);
10

  
11
sub new {
12
  my $type        = shift;
13
  my $self        = {};
14
  $self->{config} = shift;
15

  
16
  bless $self, $type;
17

  
18
  return $self;
19
}
20

  
21
sub reset {
22
  my ($self) = @_;
23
}
24

  
25
sub _env_var_for_header {
26
  my ($header) = @_;
27

  
28
  $header =~ s{-}{_}g;
29
  return $ENV{'HTTP_' . uc($header)};
30
}
31

  
32
sub _authenticate {
33
  my ($self, $type) = @_;
34

  
35
  my $secret = _env_var_for_header($self->{config}->{secret_header}) // '';
36
  if ($secret ne $self->{config}->{secret}) {
37
    $::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: bad secret sent by upstream server: $secret");
38
    return (ERR_BACKEND);
39
  }
40

  
41
  my $client_id = _env_var_for_header($self->{config}->{client_id_header});
42
  if (!$client_id) {
43
    $::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: no client ID header found");
44
    return (ERR_PASSWORD);
45
  }
46

  
47
  # $::auth->set_client();
48

  
49
  my $user = _env_var_for_header($self->{config}->{user_header});
50
  if (!$user) {
51
    $::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: no user name header found");
52
    return (ERR_PASSWORD);
53
  }
54

  
55
  $::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: OK client $client_id user $user");
56

  
57
  return (OK, $client_id, $user);
58
}
59

  
60
sub authenticate {
61
  my ($self) = @_;
62

  
63
  my ($status, $client, $login) = $self->_authenticate('authenticate');
64

  
65
  return $status;
66
}
67

  
68
sub can_change_password {
69
  return 0;
70
}
71

  
72
sub requires_cleartext_password {
73
  return 0;
74
}
75

  
76
sub change_password {
77
  return ERR_BACKEND;
78
}
79

  
80
sub verify_config {
81
  my $self = shift;
82
  my $cfg  = $self->{config};
83

  
84
  if (!$cfg) {
85
    die 'config/kivitendo.conf: Key "authentication/http_headers" is missing.';
86
  }
87

  
88
  foreach (@required_config_options) {
89
    next if $cfg->{$_};
90
    die 'config/kivitendo.conf: Missing parameter in "authentication/http_headers": ' . $_;
91
  }
92
}
93

  
94
1;
SL/Dispatcher/AuthHandler/Base.pm
1
package SL::Dispatcher::AuthHandler::Base;
2

  
3
use strict;
4
use parent qw(Rose::Object);
5

  
6
use Encode ();
7
use MIME::Base64 ();
8

  
9
use SL::Layout::Dispatcher;
10

  
11
sub _env_var_for_header {
12
  my ($header) = @_;
13

  
14
  $header =~ s{-}{_}g;
15
  return $ENV{'HTTP_' . uc($header)};
16
}
17

  
18
sub _parse_http_basic_auth {
19
  my ($self) = @_;
20

  
21
  my $cfg = $::lx_office_conf{'authentication/http_basic'};
22

  
23
  return unless $cfg && $cfg->{enabled};
24

  
25
  # See RFC 7617.
26

  
27
  # Requires that the server passes the 'Authorization' header as the
28
  # environment variable 'HTTP_AUTHORIZATION'. Example code for
29
  # Apache:
30

  
31
  # SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
32

  
33
  my $data = _env_var_for_header('Authorization');
34

  
35
  return unless ($data // '') =~ m{^basic +(.+)}i;
36

  
37
  $data = Encode::decode('utf-8', MIME::Base64::decode($1));
38

  
39
  return unless $data =~ m{(.+?):(.+)};
40

  
41
  return ($1, $2);
42
}
43

  
44
sub _parse_http_headers_auth {
45
  my ($self) = @_;
46

  
47
  my $cfg = $::lx_office_conf{'authentication/http_headers'};
48

  
49
  return unless $cfg && ($::lx_office_conf{'authentication'}->{module} =~ m{HTTPHeaders});
50

  
51
  foreach (qw(secret_header secret user_header client_id_header)) {
52
    next if $cfg->{$_};
53
    die 'config/kivitendo.conf: Missing parameter in "authentication/http_headers": ' . $_;
54
  }
55

  
56
  my $secret = _env_var_for_header($cfg->{secret_header}) // '';
57
  if ($secret ne $cfg->{secret}) {
58
    $::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: bad secret sent by upstream server: $secret");
59
    return;
60
  }
61

  
62
  my $client_id = _env_var_for_header($cfg->{client_id_header});
63
  if (!$client_id) {
64
    $::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: no client ID header found");
65
    return;
66
  }
67

  
68
  # $::auth->set_client();
69

  
70
  my $user = _env_var_for_header($cfg->{user_header});
71
  if (!$user) {
72
    $::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: no user name header found");
73
    return;
74
  }
75

  
76
  $::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: OK client $client_id user $user");
77

  
78
  return ($client_id, $user);
79
}
80

  
81
1;
SL/Dispatcher/AuthHandler/None.pm
1 1
package SL::Dispatcher::AuthHandler::None;
2 2

  
3 3
use strict;
4
use parent qw(SL::Dispatcher::AuthHandler::Base);
4 5

  
5
use parent qw(Rose::Object);
6
use SL::Auth::Constants;
6 7

  
7 8
sub handle {
8
  %::myconfig = User->get_default_myconfig;
9
  my ($self) = @_;
10

  
11

  
12
  my ($http_auth_login,     $http_auth_password) = $self->_parse_http_basic_auth;
13
  my ($http_headers_client, $http_headers_login) = $self->_parse_http_headers_auth;
14

  
15
  my $client_id = $http_headers_client // $::auth->get_default_client_id;
16
  my $login     = $http_headers_login  // $http_auth_login;
17

  
18
  if ($client_id && $login) {
19
    $::auth->set_client($client_id);
20
    %::myconfig = User->get_default_myconfig($::auth->read_user(login => $login));
21

  
22
    $::auth->create_or_refresh_session;
23
    $::auth->set_session_value('client_id', $client_id);
24
    $::auth->set_session_value('login',     $login);
25

  
26
    $::auth->set_session_authenticated($login, SL::Auth::Constants::SESSION_OK());
27

  
28
  } else {
29
    %::myconfig = User->get_default_myconfig;
30
  }
31

  
9 32
  return 1;
10 33
}
11 34

  
SL/Dispatcher/AuthHandler/User.pm
1 1
package SL::Dispatcher::AuthHandler::User;
2 2

  
3 3
use strict;
4
use parent qw(Rose::Object);
5

  
6
use Encode ();
7
use MIME::Base64 ();
4
use parent qw(SL::Dispatcher::AuthHandler::Base);
8 5

  
9 6
use SL::Helper::UserPreferences::DisplayPreferences;
10 7
use SL::Layout::Dispatcher;
......
12 9
sub handle {
13 10
  my ($self, %param) = @_;
14 11

  
15
  my ($http_auth_login, $http_auth_password) = $self->_parse_http_basic_auth;
12
  my ($http_auth_login,     $http_auth_password) = $self->_parse_http_basic_auth;
13
  my ($http_headers_client, $http_headers_login) = $self->_parse_http_headers_auth;
16 14

  
17
  my $login = $::form->{'{AUTH}login'} // $http_auth_login // $::auth->get_session_value('login');
15
  my $login = $::form->{'{AUTH}login'} // $http_auth_login // $http_headers_login // $::auth->get_session_value('login');
18 16

  
19 17
  return $self->_error(%param) if !defined $login;
20 18

  
21
  my $client_id = $::form->{'{AUTH}client_id'} // $::auth->get_session_value('client_id') // $::auth->get_default_client_id;
19
  my $client_id = $::form->{'{AUTH}client_id'} // $http_headers_client // $::auth->get_session_value('client_id') // $::auth->get_default_client_id;
22 20

  
23 21
  return $self->_error(%param) if !$client_id || !$::auth->set_client($client_id);
24 22

  
......
37 35
    : SL::Layout::Dispatcher->new(style => $::myconfig{menustyle});
38 36

  
39 37
  my $ok   =  $::auth->is_api_token_cookie_valid;
38
  $ok    ||=                            $http_headers_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, \'dummy!'));
40 39
  $ok    ||=  $::form->{'{AUTH}login'}                      && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, $::form->{'{AUTH}password'}));
41 40
  $ok    ||= !$::form->{'{AUTH}login'} &&  $http_auth_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, $http_auth_password));
42 41
  $ok    ||= !$::form->{'{AUTH}login'} && !$http_auth_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, undef));
......
59 58
  return 0;
60 59
}
61 60

  
62
sub _parse_http_basic_auth {
63
  my ($self) = @_;
64

  
65
  # See RFC 7617.
66

  
67
  # Requires that the server passes the 'Authorization' header as the
68
  # environment variable 'HTTP_AUTHORIZATION'. Example code for
69
  # Apache:
70

  
71
  # SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
72

  
73
  my $data = $ENV{HTTP_AUTHORIZATION};
74

  
75
  return unless ($data // '') =~ m{^basic +(.+)}i;
76

  
77
  $data = Encode::decode('utf-8', MIME::Base64::decode($1));
78

  
79
  return unless $data =~ m{(.+?):(.+)};
80

  
81
  return ($1, $2);
82
}
83

  
84 61
1;
config/kivitendo.conf.default
77 77
timeout       = 10
78 78
verify        = require
79 79

  
80
[authentication/http_basic]
81
enabled = 1
82

  
83
[authentication/http_headers]
84
client_id_header = X-Kivitendo-Client-ID
85
user_header = Auth-User
86
secret_header = X-Kivitendo-App-Secret
87
secret = ...
88

  
80 89
[system]
81 90
# Set language for login and admin forms. Currently "de" (German)
82 91
# and "en" (English, not perfect) are available.

Auch abrufbar als: Unified diff