Compare commits

..

6 Commits

Author SHA1 Message Date
Adirelle 0cd05b468a
Merge pull request #2 from eisin/master
Use for content publishing, not for repository access
2018-02-12 19:46:40 +01:00
eisin c13f59b392 Clean condition checking 2018-02-12 12:37:47 +00:00
eisin 89ec4eea05 Update README.pod 2017-04-02 19:01:47 +09:00
eisin aff2c5c3d5 Update README.pod 2017-04-02 19:01:27 +09:00
eisin 054950f6e3 Added feature that sets firstname, lastname, email address to environment variables 2017-04-02 09:58:31 +00:00
eisin 3f35315ff5 supports projects with no repository 2017-03-05 13:14:50 +00:00
2 changed files with 70 additions and 119 deletions

View File

@ -97,18 +97,10 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
## Default: Off
# RedmineDenyNonMember On
## Allow authentication by API key
## Default: Off
# RedmineKeyAuthentication On
## Username for authentication by API key
## Default: api-key
# RedmineKeyUsername key
## Administrators have super-powers
## Default: On
# RedmineSuperAdmin Off
## Sets firstname, lastname, email address to environment variables.
## Default: Off
# RedmineSetUserAttributes Off
@ -144,4 +136,6 @@ S<them :>
And you need to upgrade at least reposman.rb (after r860).
=cut

View File

@ -63,7 +63,7 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
# RedmineDbWhereClause "and members.role_id IN (1,2)"
## SCM transport protocol, used to detecte write requests
## Valid values: Subversion, Git
## Valid values: Subversion, Git, None
## Default: Subversion
# RedmineRepositoryType Subversion
@ -99,14 +99,6 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
## Default: Off
# RedmineDenyNonMember On
## Allow authentication by API key
## Default: Off
# RedmineKeyAuthentication On
## Username for authentication by API key
## Default: api-key
# RedmineKeyUsername key
## Administrators have super-powers
## Default: On
# RedmineSuperAdmin Off
@ -245,19 +237,7 @@ my @directives = (
name => 'RedmineRepositoryType',
req_override => OR_AUTHCFG,
args_how => TAKE1,
errmsg => 'Indicate the type of Repository (Subversion or Git). This is used to properly detected write requests. Defaults to Subversion.',
},
{
name => 'RedmineKeyAuthentication',
req_override => OR_AUTHCFG,
args_how => FLAG,
errmsg => 'Allow authentication by API key. Defaults to no.'
},
{
name => 'RedmineKeyUsername',
req_override => OR_AUTHCFG,
args_how => TAKE1,
errmsg => 'USername to use for authenticiation with API KEY. Defaults to api-key.'
errmsg => 'Indicate the type of Repository (Subversion or Git or None). This is used to properly detected write requests. Defaults to Subversion.',
},
{
name => 'RedmineSetUserAttributes',
@ -289,8 +269,6 @@ sub DIR_CREATE {
DenyAnonymous => 0,
DenyNonMember => 0,
SuperAdmin => 1,
KeyAuthentication => 0,
KeyUsername => 'api-key',
SetUserAttributes => 0,
AttributesCacheCredsCount => 0,
}, $class;
@ -308,8 +286,6 @@ sub RedmineCacheCredsMaxAge { set_val('CacheCredsMaxAge', @_); }
sub RedmineDenyAnonymous { set_val('DenyAnonymous', @_); }
sub RedmineDenyNonMember { set_val('DenyNonMember', @_); }
sub RedmineSuperAdmin { set_val('SuperAdmin', @_); }
sub RedmineKeyAuthentication { set_val('KeyAuthentication', @_); }
sub RedmineKeyUsername { set_val('KeyUsername', @_); }
sub RedmineSetUserAttributes { set_val('SetUserAttributes', @_); }
sub RedmineDbWhereClause {
@ -324,8 +300,10 @@ sub RedmineRepositoryType {
$arg = trim($arg);
if($arg eq 'Subversion' || $arg eq 'Git') {
$cfg->{RepositoryType} = 'Repository::'.$arg;
} elsif($arg eq 'None') {
$cfg->{RepositoryType} = $arg;
} else {
die "Invalid RedmineRepositoryType value: $arg, choose either Subversion or Git !";
die "Invalid RedmineRepositoryType value: $arg, choose either Subversion or Git or None !";
}
}
@ -372,7 +350,6 @@ sub authen_handler {
if(defined $cache_key && !$cfg->{SetUserAttributes} && cache_get($r, $cache_key)) {
$r->log->debug("reusing cached credentials for user '", $r->user, "'");
$r->set_handlers(PerlAuthzHandler => undef);
attributes_cache_get($r, $cache_key);
} elsif(defined $cache_key && $cfg->{SetUserAttributes} && cache_get($r, $cache_key) && attributes_cache_get($r, $cache_key)) {
$r->log->debug("reusing cached credentials for user '", $r->user, "' including attributes");
@ -383,7 +360,7 @@ sub authen_handler {
my $dbh = connect_database($r)
or return SERVER_ERROR;
($res, $reason) = check_login($r, $dbh, $password);
($res, $reason) = check_login($r, $dbh, $password, $cfg);
$dbh->disconnect();
# Store the cache key for latter use
@ -413,40 +390,53 @@ sub authen_handler {
return $res;
}
sub check_login {
my ($r, $dbh, $password) = @_;
my ($r, $dbh, $password, $cfg) = @_;
my $user = $r->user;
my $status;
my $cfg = get_config($r);
my ($hashed_password, $auth_source_id, $salt, $id, $firstname, $lastname, $email_address);
if ($cfg->{KeyAuthentication} && $user eq $cfg->{KeyUsername}) {
# API key auth
($user, $status) = $dbh->selectrow_array('SELECT u.login, u.status FROM users u INNER JOIN tokens t ON (t.user_id = u.id) WHERE t.action = \'api\' AND t.value = ?', undef, $password)
or return (AUTH_REQUIRED, "unknown api-key '$password'");
$r->user($user);
} else {
# Login+password auth
($hashed_password, $status, $auth_source_id, $salt, $id, $firstname, $lastname, $email_address) =
my ($hashed_password, $status, $auth_source_id, $salt, $id, $firstname, $lastname, $email_address) =
$dbh->selectrow_array('SELECT users.hashed_password, users.status, users.auth_source_id, users.salt, users.id, users.firstname, users.lastname, email_addresses.address
FROM users
LEFT JOIN email_addresses on (email_addresses.user_id=users.id and email_addresses.is_default = true)
LEFT JOIN email_addresses on (email_addresses.user_id=users.id and email_addresses.is_default=1)
WHERE users.login = ?', undef, $user)
or return (AUTH_REQUIRED, "unknown user '$user'");
my ($res, $reason);
if ($auth_source_id) {
($res, $reason) = check_ldap_login($dbh, $auth_source_id, $user, $password);
} else {
($res, $reason) = check_db_login($user, $password, $hashed_password, $salt);
}
or return (AUTH_REQUIRED, "unknown user '$user'");
# Bail out if authentication failed
return ($res, $reason) unless $res == OK;
# Check password
if($auth_source_id) {
# LDAP authentication
# Ensure Authen::Simple::LDAP is available
return (SERVER_ERROR, "Redmine LDAP authentication requires Authen::Simple::LDAP")
unless $CanUseLDAPAuth;
# Get LDAP server informations
my($host, $port, $tls, $account, $account_password, $base_dn, $attr_login) = $dbh->selectrow_array(
"SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?",
undef,
$auth_source_id
)
or return (SERVER_ERROR, "Undefined authentication source for '$user'");
# Connect to the LDAP server
my $ldap = Authen::Simple::LDAP->new(
host => is_true($tls) ? "ldaps://$host:$port" : $host,
port => $port,
basedn => $base_dn,
binddn => $account || "",
bindpw => $account_password || "",
filter => '('.$attr_login.'=%s)'
);
# Finally check user login
return (AUTH_REQUIRED, "LDAP authentication failed (user: '$user', server: '$host')")
unless $ldap->authenticate($user, $password);
} else {
# Database authentication
my $pass_digest = Digest::SHA::sha1_hex($password);
return (AUTH_REQUIRED, "wrong password for '$user'")
unless $hashed_password eq Digest::SHA::sha1_hex($salt.$pass_digest);
}
# Password is ok, check if account if locked
@ -460,7 +450,6 @@ sub check_login {
}
$r->subprocess_env->set("REDMINE_FIRSTNAME" => $firstname);
$r->subprocess_env->set("REDMINE_LASTNAME" => $lastname);
$r->log->debug("successfully authenticated as active redmine user '$user'");
}
@ -468,52 +457,6 @@ sub check_login {
return OK;
}
sub check_ldap_login {
# Ensure Authen::Simple::LDAP is available
return (SERVER_ERROR, "Redmine LDAP authentication requires Authen::Simple::LDAP")
unless $CanUseLDAPAuth;
my ($dbh, $auth_source_id, $user, $password) = @_;
# Get LDAP server informations
my($host, $port, $tls, $account, $account_password, $base_dn, $attr_login) = $dbh->selectrow_array(
"SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?",
undef,
$auth_source_id
)
or return (SERVER_ERROR, "Undefined authentication source for '$user'");
# Connect to the LDAP server
my $ldap = Authen::Simple::LDAP->new(
host => is_true($tls) ? "ldaps://$host:$port" : $host,
port => $port,
basedn => $base_dn,
binddn => $account || "",
bindpw => $account_password || "",
filter => '('.$attr_login.'=%s)'
);
# Finally check user login
return (AUTH_REQUIRED, "LDAP authentication failed (user: '$user', server: '$host')")
unless $ldap->authenticate($user, $password);
# LDAP auth is ok
return OK;
}
sub check_db_login {
my ($user, $password, $hashed_password, $salt) = @_ ;
# Database authentication
my $pass_digest = Digest::SHA::sha1_hex($password);
return (AUTH_REQUIRED, "wrong password for '$user'")
unless $hashed_password eq Digest::SHA::sha1_hex($salt.$pass_digest);
# Database password is ok
return OK;
}
# check if authentication is forced
sub is_authentication_forced {
my $dbh = shift;
@ -535,7 +478,23 @@ sub authz_handler {
my ($identifier, $project_id, $is_public, $status);
if($identifier = $cfg->{Project}) {
if($cfg->{RepositoryType} eq 'None') {
$identifier = $cfg->{Project};
if(!$cfg->{Project}) {
return FORBIDDEN;
}
($project_id, $is_public, $status) = $dbh->selectrow_array(
"SELECT p.id, p.is_public, p.status
FROM projects p
WHERE p.identifier = ?",
undef, $identifier
);
unless(defined $project_id) {
$r->log_reason("No matching project for ${identifier}");
return NOT_FOUND;
}
} elsif($identifier = $cfg->{Project}) {
($project_id, $is_public, $status) = $dbh->selectrow_array(
"SELECT p.id, p.is_public, p.status
FROM projects p JOIN repositories r ON (p.id = r.project_id)
@ -548,12 +507,11 @@ sub authz_handler {
}
} elsif(my $repo_id = get_repository_identifier($r)) {
my @pr_id = split(/\./, $repo_id);
($identifier, $project_id, $is_public, $status) = $dbh->selectrow_array(
"SELECT p.identifier, p.id, p.is_public, p.status
FROM projects p JOIN repositories r ON (p.id = r.project_id)
WHERE ((r.is_default AND p.identifier = ?) OR r.identifier = ?) AND r.type = ?",
undef, $pr_id[0], $repo_id, $cfg->{RepositoryType}
undef, $repo_id, $repo_id, $cfg->{RepositoryType}
);
unless(defined $project_id) {
$r->log_reason("No matching project for ${repo_id}");
@ -739,9 +697,8 @@ sub attributes_cache_get {
return unless $cfg->{CacheCredsMax} && $cfg->{AttributesCacheCreds};
my $cache_text = $cfg->{AttributesCacheCreds}->get($key)
or return 0;
or return 0;
$r->log->error("cache_text:$cache_text");
my($time, $email_address, $firstname, $lastname) = split(":", $cache_text);
if($cfg->{CacheCredsMaxAge} && ($r->request_time - $time) > $cfg->{CacheCredsMaxAge}) {
$cfg->{AttributesCacheCreds}->unset($key);
@ -784,7 +741,7 @@ sub attributes_cache_set {
unless($cfg->{AttributesCacheCreds}) {
$cfg->{AttributesCachePool} = APR::Pool->new;
$cfg->{AttributesCacheCreds} = APR::Table::make($cfg->{AttributesCachePool}, $cfg->{CacheCredsMax});
$cfg->{AttributesCacheCreds} = APR::Table::make($cfg->{AttributesCachePool}, $cfg->{CacheCredsMax});
}
if($cfg->{AttributesCacheCredsCount} >= $cfg->{CacheCredsMax}) {
@ -802,4 +759,4 @@ sub attributes_cache_set {
1;
# vim: set noexpandtab ts=4