Lots of entangled changes...
This commit is contained in:
		
							parent
							
								
									57f4784158
								
							
						
					
					
						commit
						a47ee921ec
					
				
							
								
								
									
										203
									
								
								Redmine.pm
									
									
									
									
									
								
							
							
						
						
									
										203
									
								
								Redmine.pm
									
									
									
									
									
								
							@ -145,6 +145,18 @@ my @directives = (
 | 
				
			|||||||
    req_override => OR_AUTHCFG,
 | 
					    req_override => OR_AUTHCFG,
 | 
				
			||||||
    args_how     => TAKE1,
 | 
					    args_how     => TAKE1,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							name         => 'RedmineReadPermissions',
 | 
				
			||||||
 | 
							req_override => OR_AUTHCFG,
 | 
				
			||||||
 | 
							args_how     => ITERATE,
 | 
				
			||||||
 | 
							errmsg       => 'list of permissions to allow read access',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							name         => 'RedmineWritePermissions',
 | 
				
			||||||
 | 
							req_override => OR_AUTHCFG,
 | 
				
			||||||
 | 
							args_how     => ITERATE,
 | 
				
			||||||
 | 
							errmsg       => 'list of permissions to allow other than read access',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name         => 'RedmineCacheCredsMax',
 | 
					    name         => 'RedmineCacheCredsMax',
 | 
				
			||||||
    req_override => OR_AUTHCFG,
 | 
					    req_override => OR_AUTHCFG,
 | 
				
			||||||
@ -160,40 +172,50 @@ my @directives = (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub RedmineDSN {
 | 
					sub RedmineDSN {
 | 
				
			||||||
  my ($self, $parms, $arg) = @_;
 | 
					  my ($cfg, $parms, $arg) = @_;
 | 
				
			||||||
  $self->{RedmineDSN} = $arg;
 | 
					  $cfg->{DSN} = $arg;
 | 
				
			||||||
  my $query = "SELECT 
 | 
					  $cfg->{Query} = trim("
 | 
				
			||||||
                 hashed_password, salt, auth_source_id, permissions
 | 
							SELECT permissions FROM users, members, member_roles, roles
 | 
				
			||||||
              FROM members, projects, users, roles, member_roles
 | 
							WHERE users.login = ?
 | 
				
			||||||
              WHERE 
 | 
					 | 
				
			||||||
                projects.id=members.project_id
 | 
					 | 
				
			||||||
                AND member_roles.member_id=members.id
 | 
					 | 
				
			||||||
		  AND users.id = members.user_id
 | 
							  AND users.id = members.user_id
 | 
				
			||||||
                AND roles.id=member_roles.role_id
 | 
					 | 
				
			||||||
		  AND users.status = 1
 | 
							  AND users.status = 1
 | 
				
			||||||
                AND login=? 
 | 
							  AND	members.project_id = ?
 | 
				
			||||||
                AND identifier=? ";
 | 
							  AND members.id = member_roles.member_id
 | 
				
			||||||
  $self->{RedmineQuery} = trim($query);
 | 
							  AND member_roles.role_id = roles.id
 | 
				
			||||||
 | 
						");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub RedmineDbUser { set_val('RedmineDbUser', @_); }
 | 
					sub RedmineDbUser { set_val('DbUser', @_); }
 | 
				
			||||||
sub RedmineDbPass { set_val('RedmineDbPass', @_); }
 | 
					sub RedmineDbPass { set_val('DbPass', @_); }
 | 
				
			||||||
 | 
					sub RedmineCacheCredsMaxAge { set_val('CacheCredsMaxAge', @_); }
 | 
				
			||||||
 | 
					sub RedmineProject { set_val('Project', @_); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub RedmineDbWhereClause {
 | 
					sub RedmineDbWhereClause {
 | 
				
			||||||
  my ($self, $parms, $arg) = @_;
 | 
					  my ($cfg, $parms, $arg) = @_;
 | 
				
			||||||
  $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
 | 
					  $cfg->{Query} = trim($cfg->{Query}.($arg || "")." ");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub RedmineCacheCredsMax {
 | 
					sub RedmineCacheCredsMax {
 | 
				
			||||||
  my ($self, $parms, $arg) = @_;
 | 
					  my ($cfg, $parms, $arg) = @_;
 | 
				
			||||||
  if ($arg) {
 | 
					  if ($arg) {
 | 
				
			||||||
    $self->{RedmineCachePool} = APR::Pool->new;
 | 
					    $cfg->{CachePool} = APR::Pool->new;
 | 
				
			||||||
    $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
 | 
					    $cfg->{CacheCreds} = APR::Table::make($cfg->{CachePool}, $arg);
 | 
				
			||||||
    $self->{RedmineCacheCredsCount} = 0;
 | 
					    $cfg->{CacheCredsCount} = 0;
 | 
				
			||||||
    $self->{RedmineCacheCredsMax} = $arg;
 | 
					    $cfg->{CacheCredsMax} = $arg;
 | 
				
			||||||
 | 
					    $cfg->{CacheCredsMaxAge} ||= 300;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub RedmineCacheCredsMaxAge { set_val('RedmineCacheCredsMaxAge', @_); }
 | 
					
 | 
				
			||||||
 | 
					sub RedmineReadPermissions {
 | 
				
			||||||
 | 
						my ( $cfg, $parms, $arg ) = @_;
 | 
				
			||||||
 | 
						push @{ $cfg->{ReadPermissions} }, $arg;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sub RedmineWritePermissions {
 | 
				
			||||||
 | 
						my ( $cfg, $parms, $arg ) = @_;
 | 
				
			||||||
 | 
						push @{ $cfg->{WritePermissions} }, $arg;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub trim {
 | 
					sub trim {
 | 
				
			||||||
  my $string = shift;
 | 
					  my $string = shift;
 | 
				
			||||||
@ -208,7 +230,9 @@ sub set_val {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Apache2::Module::add(__PACKAGE__, \@directives);
 | 
					Apache2::Module::add(__PACKAGE__, \@directives);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
my %read_only_methods = map { $_ => ':browse_repository' } qw/GET PROPFIND REPORT OPTIONS/;
 | 
					my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
 | 
				
			||||||
 | 
					my @default_read_permissions = ( ':browse_repository' );
 | 
				
			||||||
 | 
					my @default_write_permissions = ( ':commit_access' );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub authen_handler {
 | 
					sub authen_handler {
 | 
				
			||||||
  my $r = shift;
 | 
					  my $r = shift;
 | 
				
			||||||
@ -264,7 +288,7 @@ sub check_login {
 | 
				
			|||||||
	my ($r, $dbh, $password) = @_;
 | 
						my ($r, $dbh, $password) = @_;
 | 
				
			||||||
	my $user = $r->user;
 | 
						my $user = $r->user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	my ($hashed_password, $status, $auth_source_id, $salt) = query_fetch_first($dbh, 'SELECT hashed_password, status, auth_source_id, salt FROM users WHERE login = ?', $user);
 | 
						my ($hashed_password, $status, $auth_source_id, $salt) = $dbh->selectrow_arrayref('SELECT hashed_password, status, auth_source_id, salt FROM users WHERE login = ?', $user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Not found
 | 
						# Not found
 | 
				
			||||||
	return (AUTH_REQUIRED, "unknown user '$user'") unless defined($hashed_password);
 | 
						return (AUTH_REQUIRED, "unknown user '$user'") unless defined($hashed_password);
 | 
				
			||||||
@ -321,7 +345,7 @@ sub check_login {
 | 
				
			|||||||
# check if authentication is forced
 | 
					# check if authentication is forced
 | 
				
			||||||
sub is_authentication_forced {
 | 
					sub is_authentication_forced {
 | 
				
			||||||
	my $dbh = shift;
 | 
						my $dbh = shift;
 | 
				
			||||||
  return is_true(query_fetch_first($dbh, "SELECT value FROM settings WHERE settings.name = 'login_required'"));
 | 
					  return is_true($dbh->selectrow_arrayref("SELECT value FROM settings WHERE settings.name = 'login_required'"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sub authz_handler {
 | 
					sub authz_handler {
 | 
				
			||||||
@ -344,7 +368,7 @@ sub authz_handler {
 | 
				
			|||||||
  	$res = DECLINED;
 | 
					  	$res = DECLINED;
 | 
				
			||||||
  	$reason = "not a redmine project";
 | 
					  	$reason = "not a redmine project";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  } elsif($status ne 1 && !defined($read_only_methods{$r->method})) {
 | 
					  } elsif($status != 1 && !is_read_request($r)) {
 | 
				
			||||||
  	# Write operation on archived project is forbidden
 | 
					  	# Write operation on archived project is forbidden
 | 
				
			||||||
  	$reason = "write operations on inactive project '$identifier' are forbidden";
 | 
					  	$reason = "write operations on inactive project '$identifier' are forbidden";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -355,9 +379,8 @@ sub authz_handler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if($is_public) {
 | 
							if($is_public) {
 | 
				
			||||||
			# Check anonymous permissions
 | 
								# Check anonymous permissions
 | 
				
			||||||
	 		my $required = required_permission($r);
 | 
								my ($permissions) = $dbh->selectrow_arrayref("SELECT permissions FROM roles WHERE builtin = 2");
 | 
				
			||||||
			my ($id) = query_fetch_first($dbh, "SELECT id FROM roles WHERE builtin = 2 AND permissions LIKE ?", '%'.$required.'%');
 | 
								$res = OK if check_permissions($r, $permissions);
 | 
				
			||||||
			$res = OK if defined $id;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  	# Force login if failed
 | 
					  	# Force login if failed
 | 
				
			||||||
@ -365,26 +388,22 @@ sub authz_handler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
  	# Logged in user
 | 
					  	# Logged in user
 | 
				
			||||||
 		my $required = required_permission($r);
 | 
							my @permissions = ();
 | 
				
			||||||
 		my $user = $r->user;
 | 
					 		my $user = $r->user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 		# Look for membership with required role
 | 
							# Membership permissions
 | 
				
			||||||
 		my($id) = query_fetch_first($dbh, q{
 | 
							if(my @membership = $dbh->selectcol_arrayref($cfg->{Query}, $user, $project_id)) {
 | 
				
			||||||
			SELECT roles.id FROM users, members, member_roles, roles
 | 
								push @permissions, @membership;
 | 
				
			||||||
			WHERE users.login = ?
 | 
					 | 
				
			||||||
			  AND users.id = members.user_id
 | 
					 | 
				
			||||||
			  AND	members.project_id = ?
 | 
					 | 
				
			||||||
			  AND members.id = member_roles.member_id
 | 
					 | 
				
			||||||
			  AND member_roles.role_id = roles.id
 | 
					 | 
				
			||||||
			  AND roles.permissions LIKE ?
 | 
					 | 
				
			||||||
		}, $user, $project_id, '%'.$required.'%');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if(!defined($id) && $is_public) {
 | 
					 | 
				
			||||||
			# Fallback to non-member role for public projects
 | 
					 | 
				
			||||||
			$id = query_fetch_first($dbh, "SELECT id FROM roles WHERE builtin = 1 AND permissions LIKE ?", '%'.$required.'%');
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 		if(defined($id)) {
 | 
							if($is_public) {
 | 
				
			||||||
 | 
								# Add non-member permissions for public projects
 | 
				
			||||||
 | 
									if(my ($non_member) = $dbh->selectrow_arrayref("SELECT permissions FROM roles WHERE builtin = 1")) {
 | 
				
			||||||
 | 
									push @permissions, $non_member;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if(check_permissions($r, @permissions)) {
 | 
				
			||||||
			$res = OK;
 | 
								$res = OK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			my $cache_key = $r->pnotes("RedmineCacheKey");
 | 
								my $cache_key = $r->pnotes("RedmineCacheKey");
 | 
				
			||||||
@ -402,35 +421,63 @@ sub authz_handler {
 | 
				
			|||||||
  return $res;
 | 
					  return $res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# get project identifier
 | 
					# get the project identifier
 | 
				
			||||||
sub get_project_identifier {
 | 
					sub get_project_identifier {
 | 
				
			||||||
	my $r = shift;
 | 
						my ($r, $dbh) = @_;
 | 
				
			||||||
	my $dbh = shift;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						my $cfg = get_config($r);
 | 
				
			||||||
 | 
						my $identifier = $cfg->{Project};
 | 
				
			||||||
 | 
						unless($identifier) {
 | 
				
			||||||
		my $location = $r->location;
 | 
							my $location = $r->location;
 | 
				
			||||||
  my ($identifier) = $r->uri =~ m{^\Q$location\E/*([^/]+)};
 | 
							($identifier) = $r->uri =~ m{^\Q$location\E/*([^/]+)};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return $identifier;
 | 
					  return $identifier;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# tell if the given request is a read operation
 | 
				
			||||||
 | 
					sub is_read_request {
 | 
				
			||||||
 | 
						my $r = shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return defined $read_only_methods{$r->method};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# check if one of the required permissions is in the passed list
 | 
				
			||||||
 | 
					sub check_permissions {
 | 
				
			||||||
 | 
						my $r = shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						my $permissions = join(' ', @_)
 | 
				
			||||||
 | 
							or return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						$cfg = get_config($r);
 | 
				
			||||||
 | 
						my @required;
 | 
				
			||||||
 | 
						if(is_read_request($r)) {
 | 
				
			||||||
 | 
							@required = $cfg->{ReadPermissions} || @default_read_permissions;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							@required = $cfg->{WritePermissions} || @default_write_permissions;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						foreach (@required) {
 | 
				
			||||||
 | 
							return 1 if $permissions =~ m{\Q$_\E};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# get information about the project
 | 
					# get information about the project
 | 
				
			||||||
sub get_project_data {
 | 
					sub get_project_data {
 | 
				
			||||||
	my $r = shift;
 | 
						my $r = shift;
 | 
				
			||||||
	my $dbh = shift;
 | 
						my $dbh = shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  my $identifier = get_project_identifier($r);
 | 
					  my $identifier = get_project_identifier($r);
 | 
				
			||||||
	return $identifier, query_fetch_first($dbh, "SELECT id, is_public, status FROM projects WHERE identifier = ?", $identifier);
 | 
						return $identifier, $dbh->selectrow_arrayref("SELECT id, is_public, status FROM projects WHERE identifier = ?", $identifier);
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# get redmine permission based on HTTP method
 | 
					 | 
				
			||||||
sub required_permission {
 | 
					 | 
				
			||||||
	my $r = shift;
 | 
					 | 
				
			||||||
	$read_only_methods{$r->method} || ':commit_access';
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# return module configuration for current directory
 | 
					# return module configuration for current directory
 | 
				
			||||||
sub get_config {
 | 
					sub get_config {
 | 
				
			||||||
	my $r = shift;
 | 
						my $r = shift;
 | 
				
			||||||
	Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
 | 
					
 | 
				
			||||||
 | 
						return Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# get a connection to the redmine database
 | 
					# get a connection to the redmine database
 | 
				
			||||||
@ -438,21 +485,7 @@ sub connect_database {
 | 
				
			|||||||
	my $r = shift;
 | 
						my $r = shift;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	my $cfg = get_config($r);
 | 
						my $cfg = get_config($r);
 | 
				
			||||||
	return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
 | 
						return DBI->connect($cfg->{DSN}, $cfg->{DbUser}, $cfg->{DbPass});
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# execute a query and return the first row
 | 
					 | 
				
			||||||
sub query_fetch_first {
 | 
					 | 
				
			||||||
	my $dbh = shift;
 | 
					 | 
				
			||||||
	my $query = shift;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  my $sth = $dbh->prepare($query);
 | 
					 | 
				
			||||||
  $sth->execute(@_);
 | 
					 | 
				
			||||||
	my @row = $sth->fetchrow_array();
 | 
					 | 
				
			||||||
  $sth->finish();
 | 
					 | 
				
			||||||
  undef $sth;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@row;	
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# tell if a value returned from SQL is "true"
 | 
					# tell if a value returned from SQL is "true"
 | 
				
			||||||
@ -464,36 +497,42 @@ sub is_true {
 | 
				
			|||||||
# build credential cache key
 | 
					# build credential cache key
 | 
				
			||||||
sub get_cache_key {
 | 
					sub get_cache_key {
 | 
				
			||||||
	my ($r, $password) = @_;
 | 
						my ($r, $password) = @_;
 | 
				
			||||||
	return Digest::SHA1::sha1_hex(join(':', get_project_identifier($r), $r->user, $password, required_permission($r)));
 | 
						return Digest::SHA1::sha1_hex(join(':', get_project_identifier($r), $r->user, $password, is_read_request($r) ? 'read' : 'write');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# check if credentials exist in cache
 | 
					# check if credentials exist in cache
 | 
				
			||||||
sub cache_get {
 | 
					sub cache_get {
 | 
				
			||||||
	my($r, $key) = @_;
 | 
						my($r, $key) = @_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	my $cfg = get_config($r);
 | 
						my $cfg = get_config($r);
 | 
				
			||||||
	my $cache = $cfg->{RedmineCacheCreds};
 | 
						my $cache = $cfg->{CacheCreds};
 | 
				
			||||||
	return unless $cache;
 | 
						return unless $cache;
 | 
				
			||||||
	my $time = $cache->get($key) or return 0;
 | 
					
 | 
				
			||||||
	if($cfg->{RedmineCacheCredsMaxAge} && ($r->request_time - $time) > $cfg->{RedmineCacheCredsMaxAge}) {
 | 
						my $time = $cache->get($key)
 | 
				
			||||||
 | 
							or return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if($cfg->{CacheCredsMaxAge} && ($r->request_time - $time) > $cfg->{CacheCredsMaxAge}) {
 | 
				
			||||||
		$cache->unset($key);
 | 
							$cache->unset($key);
 | 
				
			||||||
		$cfg->{RedmineCacheCredsCount}--;
 | 
							$cfg->{CacheCredsCount}--;
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	1;
 | 
						return 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# put credentials in cache
 | 
					# put credentials in cache
 | 
				
			||||||
sub cache_set {
 | 
					sub cache_set {
 | 
				
			||||||
	my($r, $key) = @_;
 | 
						my($r, $key) = @_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	my $cfg = get_config($r);
 | 
						my $cfg = get_config($r);
 | 
				
			||||||
	my $cache = $cfg->{RedmineCacheCreds};
 | 
						my $cache = $cfg->{CacheCreds};
 | 
				
			||||||
	return unless $cache;
 | 
						return unless $cache;
 | 
				
			||||||
	if($cfg->{RedmineCacheCredsCount} >= $cfg->{RedmineCacheCredsMax}) {
 | 
					
 | 
				
			||||||
 | 
						if($cfg->{CacheCredsCount} >= $cfg->{CacheCredsMax}) {
 | 
				
			||||||
		$cache->clear;
 | 
							$cache->clear;
 | 
				
			||||||
		$cfg->{RedmineCacheCredsCount} = 0;
 | 
							$cfg->{CacheCredsCount} = 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	$cache->set($key, $r->request_time);
 | 
						$cache->set($key, $r->request_time);
 | 
				
			||||||
	$cfg->{RedmineCacheCredsCount}++;
 | 
						$cfg->{CacheCredsCount}++;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1;
 | 
					1;
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user