From 3f35315ff5ac8c4f32b5a9adf6329e5e9d251c6a Mon Sep 17 00:00:00 2001 From: eisin Date: Sun, 5 Mar 2017 13:14:50 +0000 Subject: [PATCH 1/5] supports projects with no repository --- README.pod | 8 +++++--- Redmine.pm | 24 +++++++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/README.pod b/README.pod index 11e14e6..f3f38d6 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) @@ -132,4 +132,6 @@ S And you need to upgrade at least reposman.rb (after r860). + =cut + diff --git a/Redmine.pm b/Redmine.pm index fed503e..a47f8b7 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,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.', + errmsg => 'Indicate the type of Repository (Subversion or Git or None). This is used to properly detected write requests. Defaults to Subversion.', }, ); @@ -291,8 +291,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 !"; } } @@ -449,7 +451,7 @@ sub authz_handler { my ($identifier, $project_id, $is_public, $status); - if($identifier = $cfg->{Project}) { + if($identifier = $cfg->{Project} and $cfg->{RepositoryType} ne 'None') { ($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) @@ -461,7 +463,19 @@ sub authz_handler { return NOT_FOUND; } - } elsif(my $repo_id = get_repository_identifier($r)) { + } elsif($identifier = $cfg->{Project} and $cfg->{RepositoryType} eq 'None') { + ($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($cfg->{RepositoryType} ne 'None' and my $repo_id = get_repository_identifier($r)) { ($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) From 054950f6e3542b6446b3f704cfbddc23af4d050e Mon Sep 17 00:00:00 2001 From: eisin Date: Sun, 2 Apr 2017 09:58:31 +0000 Subject: [PATCH 2/5] Added feature that sets firstname, lastname, email address to environment variables --- Redmine.pm | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/Redmine.pm b/Redmine.pm index a47f8b7..5f23bde 100644 --- a/Redmine.pm +++ b/Redmine.pm @@ -239,6 +239,12 @@ my @directives = ( args_how => TAKE1, 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.', + }, ); # Initialize defaults configuration @@ -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) = @_; @@ -337,9 +346,15 @@ 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); + 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"); + $r->set_handlers(PerlAuthzHandler => undef); } else { # Else check them @@ -381,7 +396,7 @@ sub check_login { my ($r, $dbh, $password) = @_; 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) = $dbh->selectrow_array('SELECT hashed_password, status, auth_source_id, salt, id, firstname, lastname FROM users WHERE login = ?', undef, $user) or return (AUTH_REQUIRED, "unknown user '$user'"); # Check password @@ -424,6 +439,20 @@ sub check_login { # Password is ok, check if account if locked return (FORBIDDEN, "inactive account: '$user'") unless $status == 1; + my($email_address) = $dbh->selectrow_array( + "SELECT address + FROM email_addresses + WHERE email_addresses.user_id=? and is_default=1", + undef, + $id + ); + 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 @@ -544,6 +573,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 { @@ -617,7 +647,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; @@ -655,6 +688,30 @@ 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; + + $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); + $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) = @_; @@ -675,6 +732,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; From aff2c5c3d5a8f6b71180a45ae1c61223837848d6 Mon Sep 17 00:00:00 2001 From: eisin Date: Sun, 2 Apr 2017 19:01:27 +0900 Subject: [PATCH 3/5] Update README.pod --- README.pod | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.pod b/README.pod index f3f38d6..ed6b700 100644 --- a/README.pod +++ b/README.pod @@ -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 From 89ec4eea05a233ed1facbff08ecc18d9cc65e384 Mon Sep 17 00:00:00 2001 From: eisin Date: Sun, 2 Apr 2017 19:01:47 +0900 Subject: [PATCH 4/5] Update README.pod --- README.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.pod b/README.pod index ed6b700..489a5f5 100644 --- a/README.pod +++ b/README.pod @@ -101,7 +101,7 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used): ## Default: On # RedmineSuperAdmin Off - ## Sets firstname, lastname, email address to environment variables + ## Sets firstname, lastname, email address to environment variables. ## Default: Off # RedmineSetUserAttributes Off From c13f59b392c4d42d242561dc54583135141c956f Mon Sep 17 00:00:00 2001 From: eisin Date: Sat, 8 Apr 2017 08:46:46 +0000 Subject: [PATCH 5/5] Clean condition checking --- Redmine.pm | 65 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Redmine.pm b/Redmine.pm index 5f23bde..8f73eb4 100644 --- a/Redmine.pm +++ b/Redmine.pm @@ -350,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"); @@ -361,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 @@ -393,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, $id, $firstname, $lastname) = $dbh->selectrow_array('SELECT hashed_password, status, auth_source_id, salt, id, firstname, lastname 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 @@ -439,21 +442,16 @@ sub check_login { # Password is ok, check if account if locked return (FORBIDDEN, "inactive account: '$user'") unless $status == 1; - my($email_address) = $dbh->selectrow_array( - "SELECT address - FROM email_addresses - WHERE email_addresses.user_id=? and is_default=1", - undef, - $id - ); - if (defined $email_address) { - $r->subprocess_env->set("REDMINE_DEFAULT_EMAIL_ADDRESS" => $email_address); - } else { - $r->subprocess_env->set("REDMINE_DEFAULT_EMAIL_ADDRESS" => ""); + 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'"); } - $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; @@ -480,19 +478,11 @@ sub authz_handler { my ($identifier, $project_id, $is_public, $status); - if($identifier = $cfg->{Project} and $cfg->{RepositoryType} ne 'None') { - ($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) - WHERE p.identifier = ? AND r.is_default AND r.type = ?", - undef, $identifier, $cfg->{RepositoryType} - ); - unless(defined $project_id) { - $r->log_reason("No matching project for ${identifier}"); - return NOT_FOUND; + if($cfg->{RepositoryType} eq 'None') { + $identifier = $cfg->{Project}; + if(!$cfg->{Project}) { + return FORBIDDEN; } - - } elsif($identifier = $cfg->{Project} and $cfg->{RepositoryType} eq 'None') { ($project_id, $is_public, $status) = $dbh->selectrow_array( "SELECT p.id, p.is_public, p.status FROM projects p @@ -504,7 +494,19 @@ sub authz_handler { return NOT_FOUND; } - } elsif($cfg->{RepositoryType} ne 'None' and my $repo_id = get_repository_identifier($r)) { + } 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) + WHERE p.identifier = ? AND r.is_default AND r.type = ?", + undef, $identifier, $cfg->{RepositoryType} + ); + unless(defined $project_id) { + $r->log_reason("No matching project for ${identifier}"); + return NOT_FOUND; + } + + } elsif(my $repo_id = get_repository_identifier($r)) { ($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) @@ -697,7 +699,6 @@ sub attributes_cache_get { my $cache_text = $cfg->{AttributesCacheCreds}->get($key) 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);