diff --git a/README.pod b/README.pod index 11e14e6..489a5f5 100644 --- a/README.pod +++ b/README.pod @@ -61,9 +61,9 @@ 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: dav-svn, git-smart-http - ## Default: dav-svn - # RedmineSCMProtocol dav-svn + ## Valid values: Subversion, Git, None + ## Default: Subversion + # RedmineRepositoryType Subversion ## Credentials cache size ## Default: 0 (disabled) @@ -101,6 +101,10 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): ## Default: On # RedmineSuperAdmin Off + ## Sets firstname, lastname, email address to environment variables. + ## Default: Off + # RedmineSetUserAttributes Off + To be able to browse repository inside redmine, you must add something @@ -132,4 +136,6 @@ S And you need to upgrade at least reposman.rb (after r860). + =cut + diff --git a/Redmine.pm b/Redmine.pm index fed503e..8f73eb4 100644 --- a/Redmine.pm +++ b/Redmine.pm @@ -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 @@ -237,7 +237,13 @@ 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.', + errmsg => 'Indicate the type of Repository (Subversion or Git or None). This is used to properly detected write requests. Defaults to Subversion.', + }, + { + name => 'RedmineSetUserAttributes', + req_override => OR_AUTHCFG, + args_how => FLAG, + errmsg => 'Sets firstname, lastname, email address to environment variables. Defaults to no.', }, ); @@ -263,6 +269,8 @@ sub DIR_CREATE { DenyAnonymous => 0, DenyNonMember => 0, SuperAdmin => 1, + SetUserAttributes => 0, + AttributesCacheCredsCount => 0, }, $class; } @@ -278,6 +286,7 @@ sub RedmineCacheCredsMaxAge { set_val('CacheCredsMaxAge', @_); } sub RedmineDenyAnonymous { set_val('DenyAnonymous', @_); } sub RedmineDenyNonMember { set_val('DenyNonMember', @_); } sub RedmineSuperAdmin { set_val('SuperAdmin', @_); } +sub RedmineSetUserAttributes { set_val('SetUserAttributes', @_); } sub RedmineDbWhereClause { my ($cfg, $parms, $arg) = @_; @@ -291,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 !"; } } @@ -335,16 +346,21 @@ sub authen_handler { # Used cached credentials if possible my $cache_key = get_cache_key($r, $password); - if(defined $cache_key && cache_get($r, $cache_key)) { + my $cfg = get_config($r); + 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); + } 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"); + $r->set_handlers(PerlAuthzHandler => undef); + } else { # Else check them 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 @@ -376,10 +392,14 @@ sub authen_handler { sub check_login { - my ($r, $dbh, $password) = @_; + my ($r, $dbh, $password, $cfg) = @_; my $user = $r->user; - my ($hashed_password, $status, $auth_source_id, $salt) = $dbh->selectrow_array('SELECT hashed_password, status, auth_source_id, salt FROM users WHERE login = ?', undef, $user) + 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=1) + WHERE users.login = ?', undef, $user) or return (AUTH_REQUIRED, "unknown user '$user'"); # Check password @@ -422,7 +442,16 @@ sub check_login { # Password is ok, check if account if locked return (FORBIDDEN, "inactive account: '$user'") unless $status == 1; - $r->log->debug("successfully authenticated as active redmine user '$user'"); + if ($cfg->{SetUserAttributes}) { + if (defined $email_address) { + $r->subprocess_env->set("REDMINE_DEFAULT_EMAIL_ADDRESS" => $email_address); + } else { + $r->subprocess_env->set("REDMINE_DEFAULT_EMAIL_ADDRESS" => ""); + } + $r->subprocess_env->set("REDMINE_FIRSTNAME" => $firstname); + $r->subprocess_env->set("REDMINE_LASTNAME" => $lastname); + $r->log->debug("successfully authenticated as active redmine user '$user'"); + } # Everything's ok return OK; @@ -449,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) @@ -530,6 +575,7 @@ sub authz_handler { # Put successful credentials in cache if(my $cache_key = $r->pnotes("RedmineCacheKey")) { cache_set($r, $cache_key); + attributes_cache_set($r, $cache_key); } } else { @@ -603,7 +649,10 @@ sub connect_database { my $r = shift; my $cfg = get_config($r); - my $dbh = DBI->connect($cfg->{DSN}, $cfg->{DbUser}, $cfg->{DbPass}) + my $dbh = DBI->connect($cfg->{DSN}, $cfg->{DbUser}, $cfg->{DbPass}, { + pg_enable_utf8 => 1, + mysql_enable_utf8 => 1, + }) or $r->log->error("Connection to database failed: $DBI::errstr."); return $dbh; @@ -641,6 +690,29 @@ sub cache_get { return 1; } +sub attributes_cache_get { + my($r, $key) = @_; + + my $cfg = get_config($r); + return unless $cfg->{CacheCredsMax} && $cfg->{AttributesCacheCreds}; + + my $cache_text = $cfg->{AttributesCacheCreds}->get($key) + or return 0; + + my($time, $email_address, $firstname, $lastname) = split(":", $cache_text); + if($cfg->{CacheCredsMaxAge} && ($r->request_time - $time) > $cfg->{CacheCredsMaxAge}) { + $cfg->{AttributesCacheCreds}->unset($key); + $cfg->{AttributesCacheCredsCount}--; + return 0; + } + + $r->subprocess_env->set("REDMINE_DEFAULT_EMAIL_ADDRESS", $email_address . ""); + $r->subprocess_env->set("REDMINE_FIRSTNAME", $firstname . ""); + $r->subprocess_env->set("REDMINE_LASTNAME", $lastname . ""); + + return 1; +} + # put credentials in cache sub cache_set { my($r, $key) = @_; @@ -661,6 +733,30 @@ sub cache_set { $cfg->{CacheCredsCount}++; } +sub attributes_cache_set { + my($r, $key) = @_; + + my $cfg = get_config($r); + return unless $cfg->{CacheCredsMax}; + + unless($cfg->{AttributesCacheCreds}) { + $cfg->{AttributesCachePool} = APR::Pool->new; + $cfg->{AttributesCacheCreds} = APR::Table::make($cfg->{AttributesCachePool}, $cfg->{CacheCredsMax}); + } + + if($cfg->{AttributesCacheCredsCount} >= $cfg->{CacheCredsMax}) { + $cfg->{AttributesCacheCreds}->clear; + $cfg->{AttributesCacheCredsCount} = 0; + } + my $cache_text = join(":", $r->request_time, + $r->subprocess_env->get("REDMINE_DEFAULT_EMAIL_ADDRESS"), + $r->subprocess_env->get("REDMINE_FIRSTNAME"), + $r->subprocess_env->get("REDMINE_LASTNAME"), + ); + $cfg->{AttributesCacheCreds}->set($key, $cache_text); + $cfg->{AttributesCacheCredsCount}++; +} + 1;