Revision f7417f07
Von Moritz Bunkus vor etwa 2 Monaten hinzugefügt
SL/Auth.pm | ||
---|---|---|
use SL::Auth::ColumnInformation;
|
||
use SL::Auth::Constants qw(:all);
|
||
use SL::Auth::DB;
|
||
use SL::Auth::HTTPHeaders;
|
||
use SL::Auth::LDAP;
|
||
use SL::Auth::Password;
|
||
use SL::Auth::SessionValue;
|
||
... | ... | |
foreach my $module (split m{ +}, $self->{module}) {
|
||
my $config_name;
|
||
($module, $config_name) = split m{:}, $module, 2;
|
||
$config_name ||= $module eq 'DB' ? 'database' : lc($module);
|
||
$config_name ||= $module eq 'DB' ? 'database' : $module eq 'HTTPHeaders' ? 'http_headers' : lc($module);
|
||
my $config = $::lx_office_conf{'authentication/' . $config_name};
|
||
|
||
if (!$config) {
|
||
... | ... | |
} elsif ($module eq 'LDAP') {
|
||
push @{ $self->{authenticators} }, SL::Auth::LDAP->new($config);
|
||
|
||
} elsif ($module eq 'HTTPHeaders') {
|
||
push @{ $self->{authenticators} }, SL::Auth::HTTPHeaders->new($config);
|
||
|
||
} else {
|
||
my $locale = Locale->new('en');
|
||
$self->mini_error($locale->text('Unknown authenticantion module #1 specified in "config/kivitendo.conf".', $module));
|
||
... | ... | |
return $result;
|
||
}
|
||
|
||
sub set_session_authenticated {
|
||
my ($self, $login, $result) = @_;
|
||
|
||
$self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
|
||
}
|
||
|
||
sub authenticate {
|
||
my ($self, $login, $password) = @_;
|
||
|
||
... | ... | |
}
|
||
}
|
||
|
||
$self->set_session_value(SESSION_KEY_USER_AUTH() => $result, login => $login, client_id => $self->client->{id});
|
||
$self->set_session_authenticated($login, $result);
|
||
|
||
return $result;
|
||
}
|
||
|
SL/Auth/HTTPHeaders.pm | ||
---|---|---|
package SL::Auth::HTTPHeaders;
|
||
|
||
use List::MoreUtils qw(any);
|
||
|
||
use SL::Auth::Constants qw(:all);
|
||
|
||
use strict;
|
||
|
||
my @required_config_options = qw(secret_header secret user_header client_id_header);
|
||
|
||
sub new {
|
||
my $type = shift;
|
||
my $self = {};
|
||
$self->{config} = shift;
|
||
|
||
bless $self, $type;
|
||
|
||
return $self;
|
||
}
|
||
|
||
sub reset {
|
||
my ($self) = @_;
|
||
}
|
||
|
||
sub _env_var_for_header {
|
||
my ($header) = @_;
|
||
|
||
$header =~ s{-}{_}g;
|
||
return $ENV{'HTTP_' . uc($header)};
|
||
}
|
||
|
||
sub _authenticate {
|
||
my ($self, $type) = @_;
|
||
|
||
my $secret = _env_var_for_header($self->{config}->{secret_header}) // '';
|
||
if ($secret ne $self->{config}->{secret}) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: bad secret sent by upstream server: $secret");
|
||
return (ERR_BACKEND);
|
||
}
|
||
|
||
my $client_id = _env_var_for_header($self->{config}->{client_id_header});
|
||
if (!$client_id) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: no client ID header found");
|
||
return (ERR_PASSWORD);
|
||
}
|
||
|
||
# $::auth->set_client();
|
||
|
||
my $user = _env_var_for_header($self->{config}->{user_header});
|
||
if (!$user) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: no user name header found");
|
||
return (ERR_PASSWORD);
|
||
}
|
||
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "HTTPHeaders ${type}: OK client $client_id user $user");
|
||
|
||
return (OK, $client_id, $user);
|
||
}
|
||
|
||
sub authenticate {
|
||
my ($self) = @_;
|
||
|
||
my ($status, $client, $login) = $self->_authenticate('authenticate');
|
||
|
||
return $status;
|
||
}
|
||
|
||
sub can_change_password {
|
||
return 0;
|
||
}
|
||
|
||
sub requires_cleartext_password {
|
||
return 0;
|
||
}
|
||
|
||
sub change_password {
|
||
return ERR_BACKEND;
|
||
}
|
||
|
||
sub verify_config {
|
||
my $self = shift;
|
||
my $cfg = $self->{config};
|
||
|
||
if (!$cfg) {
|
||
die 'config/kivitendo.conf: Key "authentication/http_headers" is missing.';
|
||
}
|
||
|
||
foreach (@required_config_options) {
|
||
next if $cfg->{$_};
|
||
die 'config/kivitendo.conf: Missing parameter in "authentication/http_headers": ' . $_;
|
||
}
|
||
}
|
||
|
||
1;
|
SL/Dispatcher/AuthHandler/Base.pm | ||
---|---|---|
package SL::Dispatcher::AuthHandler::Base;
|
||
|
||
use strict;
|
||
use parent qw(Rose::Object);
|
||
|
||
use Encode ();
|
||
use MIME::Base64 ();
|
||
|
||
use SL::Layout::Dispatcher;
|
||
|
||
sub _env_var_for_header {
|
||
my ($header) = @_;
|
||
|
||
$header =~ s{-}{_}g;
|
||
return $ENV{'HTTP_' . uc($header)};
|
||
}
|
||
|
||
sub _parse_http_basic_auth {
|
||
my ($self) = @_;
|
||
|
||
my $cfg = $::lx_office_conf{'authentication/http_basic'};
|
||
|
||
return unless $cfg && $cfg->{enabled};
|
||
|
||
# See RFC 7617.
|
||
|
||
# Requires that the server passes the 'Authorization' header as the
|
||
# environment variable 'HTTP_AUTHORIZATION'. Example code for
|
||
# Apache:
|
||
|
||
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||
|
||
my $data = _env_var_for_header('Authorization');
|
||
|
||
return unless ($data // '') =~ m{^basic +(.+)}i;
|
||
|
||
$data = Encode::decode('utf-8', MIME::Base64::decode($1));
|
||
|
||
return unless $data =~ m{(.+?):(.+)};
|
||
|
||
return ($1, $2);
|
||
}
|
||
|
||
sub _parse_http_headers_auth {
|
||
my ($self) = @_;
|
||
|
||
my $cfg = $::lx_office_conf{'authentication/http_headers'};
|
||
|
||
return unless $cfg && ($::lx_office_conf{'authentication'}->{module} =~ m{HTTPHeaders});
|
||
|
||
foreach (qw(secret_header secret user_header client_id_header)) {
|
||
next if $cfg->{$_};
|
||
die 'config/kivitendo.conf: Missing parameter in "authentication/http_headers": ' . $_;
|
||
}
|
||
|
||
my $secret = _env_var_for_header($cfg->{secret_header}) // '';
|
||
if ($secret ne $cfg->{secret}) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: bad secret sent by upstream server: $secret");
|
||
return;
|
||
}
|
||
|
||
my $client_id = _env_var_for_header($cfg->{client_id_header});
|
||
if (!$client_id) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: no client ID header found");
|
||
return;
|
||
}
|
||
|
||
# $::auth->set_client();
|
||
|
||
my $user = _env_var_for_header($cfg->{user_header});
|
||
if (!$user) {
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: no user name header found");
|
||
return;
|
||
}
|
||
|
||
$::lxdebug->message(LXDebug->DEBUG2(), "_parse_http_headers_auth: OK client $client_id user $user");
|
||
|
||
return ($client_id, $user);
|
||
}
|
||
|
||
1;
|
SL/Dispatcher/AuthHandler/None.pm | ||
---|---|---|
package SL::Dispatcher::AuthHandler::None;
|
||
|
||
use strict;
|
||
use parent qw(SL::Dispatcher::AuthHandler::Base);
|
||
|
||
use parent qw(Rose::Object);
|
||
use SL::Auth::Constants;
|
||
|
||
sub handle {
|
||
%::myconfig = User->get_default_myconfig;
|
||
my ($self) = @_;
|
||
|
||
|
||
my ($http_auth_login, $http_auth_password) = $self->_parse_http_basic_auth;
|
||
my ($http_headers_client, $http_headers_login) = $self->_parse_http_headers_auth;
|
||
|
||
my $client_id = $http_headers_client // $::auth->get_default_client_id;
|
||
my $login = $http_headers_login // $http_auth_login;
|
||
|
||
if ($client_id && $login) {
|
||
$::auth->set_client($client_id);
|
||
%::myconfig = User->get_default_myconfig($::auth->read_user(login => $login));
|
||
|
||
$::auth->create_or_refresh_session;
|
||
$::auth->set_session_value('client_id', $client_id);
|
||
$::auth->set_session_value('login', $login);
|
||
|
||
$::auth->set_session_authenticated($login, SL::Auth::Constants::SESSION_OK());
|
||
|
||
} else {
|
||
%::myconfig = User->get_default_myconfig;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
SL/Dispatcher/AuthHandler/User.pm | ||
---|---|---|
package SL::Dispatcher::AuthHandler::User;
|
||
|
||
use strict;
|
||
use parent qw(Rose::Object);
|
||
|
||
use Encode ();
|
||
use MIME::Base64 ();
|
||
use parent qw(SL::Dispatcher::AuthHandler::Base);
|
||
|
||
use SL::Helper::UserPreferences::DisplayPreferences;
|
||
use SL::Layout::Dispatcher;
|
||
... | ... | |
sub handle {
|
||
my ($self, %param) = @_;
|
||
|
||
my ($http_auth_login, $http_auth_password) = $self->_parse_http_basic_auth;
|
||
my ($http_auth_login, $http_auth_password) = $self->_parse_http_basic_auth;
|
||
my ($http_headers_client, $http_headers_login) = $self->_parse_http_headers_auth;
|
||
|
||
my $login = $::form->{'{AUTH}login'} // $http_auth_login // $::auth->get_session_value('login');
|
||
my $login = $::form->{'{AUTH}login'} // $http_auth_login // $http_headers_login // $::auth->get_session_value('login');
|
||
|
||
return $self->_error(%param) if !defined $login;
|
||
|
||
my $client_id = $::form->{'{AUTH}client_id'} // $::auth->get_session_value('client_id') // $::auth->get_default_client_id;
|
||
my $client_id = $::form->{'{AUTH}client_id'} // $http_headers_client // $::auth->get_session_value('client_id') // $::auth->get_default_client_id;
|
||
|
||
return $self->_error(%param) if !$client_id || !$::auth->set_client($client_id);
|
||
|
||
... | ... | |
: SL::Layout::Dispatcher->new(style => $::myconfig{menustyle});
|
||
|
||
my $ok = $::auth->is_api_token_cookie_valid;
|
||
$ok ||= $http_headers_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, \'dummy!'));
|
||
$ok ||= $::form->{'{AUTH}login'} && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, $::form->{'{AUTH}password'}));
|
||
$ok ||= !$::form->{'{AUTH}login'} && $http_auth_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, $http_auth_password));
|
||
$ok ||= !$::form->{'{AUTH}login'} && !$http_auth_login && (SL::Auth::OK() == $::auth->authenticate($::myconfig{login}, undef));
|
||
... | ... | |
return 0;
|
||
}
|
||
|
||
sub _parse_http_basic_auth {
|
||
my ($self) = @_;
|
||
|
||
# See RFC 7617.
|
||
|
||
# Requires that the server passes the 'Authorization' header as the
|
||
# environment variable 'HTTP_AUTHORIZATION'. Example code for
|
||
# Apache:
|
||
|
||
# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
|
||
|
||
my $data = $ENV{HTTP_AUTHORIZATION};
|
||
|
||
return unless ($data // '') =~ m{^basic +(.+)}i;
|
||
|
||
$data = Encode::decode('utf-8', MIME::Base64::decode($1));
|
||
|
||
return unless $data =~ m{(.+?):(.+)};
|
||
|
||
return ($1, $2);
|
||
}
|
||
|
||
1;
|
config/kivitendo.conf.default | ||
---|---|---|
# interface.
|
||
admin_password = admin123
|
||
|
||
# Which modules to use for authentication. Valid values are 'DB' and
|
||
# 'LDAP'. You can use multiple modules separated by spaces.
|
||
# Which modules to use for authentication. Valid values are 'DB',
|
||
# 'LDAP', 'HTTPHeaders'. You can use multiple modules separated by spaces.
|
||
#
|
||
# Multiple LDAP modules with different configurations can be used by
|
||
# postfixing 'LDAP' with the name of the configuration section to use:
|
||
... | ... | |
timeout = 10
|
||
verify = require
|
||
|
||
# For use with module 'HTTPHeaders':
|
||
[authentication/http_basic]
|
||
enabled = 1
|
||
|
||
# For use with module 'HTTPHeaders':
|
||
[authentication/http_headers]
|
||
enabled = 0
|
||
client_id_header = X-Kivitendo-Client-ID
|
||
user_header = Auth-User
|
||
secret_header = X-Kivitendo-App-Secret
|
||
secret = ...
|
||
|
||
[system]
|
||
# Set language for login and admin forms. Currently "de" (German)
|
||
# and "en" (English, not perfect) are available.
|
Auch abrufbar als: Unified diff
Auth: automatisches SSO mittels gewisser HTTP-Header