diff -rupN redmine-2.5.1-org/app/controllers/application_controller.rb redmine-2.5.1-new/app/controllers/application_controller.rb --- redmine-2.5.1-org/app/controllers/application_controller.rb 2014-05-21 17:15:58.890007112 +0200 +++ redmine-2.5.1-new/app/controllers/application_controller.rb 2014-06-03 22:19:00.306007981 +0200 @@ -419,6 +419,17 @@ class ApplicationController < ActionCont return false end + #by heagdl, added 2 error msgs + def render_ad(options={}) + render_error({:message => "Access to this repository denied! Please contact your system administrator for further information.", :status => 401}.merge(options)) + return false + end + + def render_rd(options={}) + render_error({:message => "Access to this revision denied! You are not authorized to see the associated paths. Please contact your system administrator for further information.", :status => 401}.merge(options)) + return false + end + # Renders an error response def render_error(arg) arg = {:message => arg} unless arg.is_a?(Hash) diff -rupN redmine-2.5.1-org/app/controllers/repositories_controller.rb redmine-2.5.1-new/app/controllers/repositories_controller.rb --- redmine-2.5.1-org/app/controllers/repositories_controller.rb 2014-05-21 17:15:58.889007112 +0200 +++ redmine-2.5.1-new/app/controllers/repositories_controller.rb 2014-06-04 02:21:26.728007983 +0200 @@ -19,6 +19,8 @@ require 'SVG/Graph/Bar' require 'SVG/Graph/BarHorizontal' require 'digest/sha1' require 'redmine/scm/adapters' +require 'open3' #may be unused +require 'svncheck' class ChangesetNotFound < Exception; end class InvalidRevisionParam < Exception; end @@ -32,6 +34,7 @@ class RepositoriesController < Applicati before_filter :find_repository, :only => [:edit, :update, :destroy, :committers] before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers] before_filter :find_changeset, :only => [:revision, :add_related_issue, :remove_related_issue] + before_filter :check_repo_access, :only => [:revision, :revisions, :show, :changes, :diff, :graph, :stats, :entry, :annotate, :raw] before_filter :authorize accept_rss_auth :revisions @@ -435,4 +438,31 @@ class RepositoriesController < Applicati ) graph.burn end + + #check if the current user is allowed to access the repository at the requested paths + def check_repo_access + if (@repository.type.eql? "Repository::Subversion") + begin + @svnchk = SVNCheck.new(@repository.url,@repository.root_url,User.current.login) + if params[:action] == "revision" + if !@svnchk.chkrev(@changeset.id) + render_rd; return false + end + else + if params[:path].nil? + reqpath = "/" + else + reqpath = "/"+params[:path] + end + logger.info 'SVN-Request for following path: ' + reqpath + if !(@svnchk.chkurl(reqpath)) + render_ad; return false + end + end + rescue Exception => e + logger.fatal "An error occured during svnchk!" + logger.info e.message + end + end + end end diff -rupN redmine-2.5.1-org/app/helpers/application_helper.rb redmine-2.5.1-new/app/helpers/application_helper.rb --- redmine-2.5.1-org/app/helpers/application_helper.rb 2014-05-21 17:15:58.893007112 +0200 +++ redmine-2.5.1-new/app/helpers/application_helper.rb 2014-06-03 22:24:07.820007037 +0200 @@ -771,7 +771,17 @@ module ApplicationHelper :repository_id => repository.identifier_param, :rev => changeset.revision}, :class => 'changeset', - :title => truncate_single_line_raw(changeset.comments, 100)) + :title => if (repository.type.eql? "Repository::Subversion") + require 'svncheck' + svnc = SVNCheck.new(repository.url,repository.root_url,User.current.login) + if !svnc.chkrev(changeset.id) + "Access to this revision denied!" + else + truncate_single_line_raw(changeset.comments, 100) + end + else + truncate_single_line_raw(changeset.comments, 100) + end) end end elsif sep == '#' diff -rupN redmine-2.5.1-org/app/models/changeset.rb redmine-2.5.1-new/app/models/changeset.rb --- redmine-2.5.1-org/app/models/changeset.rb 2014-05-21 17:15:58.886007112 +0200 +++ redmine-2.5.1-new/app/models/changeset.rb 2014-06-03 22:19:00.308007965 +0200 @@ -267,6 +267,15 @@ class Changeset < ActiveRecord::Base comments =~ /\A(.+?)\r?\n(.*)$/m @short_comments = $1 || comments @long_comments = $2.to_s.strip + #check if allowed to show by haegdl: + if (repository.type.eql? "Repository::Subversion") + require 'svncheck' + svnc = SVNCheck.new(repository.url,repository.root_url,User.current.login) + if !svnc.chkrev(id) + @short_comments = "Access to this revision denied!" + @long_comments = "You have no rights to access this path in your svn repository!" + end + end return @short_comments, @long_comments end diff -rupN redmine-2.5.1-org/lib/svncheck.rb redmine-2.5.1-new/lib/svncheck.rb --- redmine-2.5.1-org/lib/svncheck.rb 1970-01-01 01:00:00.000000000 +0100 +++ redmine-2.5.1-new/lib/svncheck.rb 2014-06-04 02:22:59.513007752 +0200 @@ -0,0 +1,230 @@ +################################################# +# INFO +# +# This file was written by Daniel Haeger (haegdl@idmt.fraunhofer.de) +# Its a helper for checking if a user is permitted to access a SVN +# repository protected by mod_authz_svn with path-based authz. +# +# In the same directory, a 'right to read' dominates a 'deny' +# For initialisation, the paths to the authz file of the repo +# and the user have to be specified! +# +# root_url is different that url if the svn should just +# access a subdir! +# +# TODO +# +# -wrap it in begin.rescue.end blocks +# -allow aliases +# -logging instead of puts using STDERR.puts "error" ? +# +# Last edited: 13.10.14 13:37 +# +################################################## + +class SVNCheck + def initialize(url, rooturl, user) + puts "Started SVNCheck with:\n " + url + "\n " + rooturl + "\n " + user + + status = 0 + currentpath = "" + @user = user.dup + @authzpath = rooturl.dup #"/opt/svn/systies_cp/conf/authz" + if @authzpath.start_with?("file://") + @authzpath.slice!("file://") #remove beginning (we need absolut path) + end + @authzpath = @authzpath.chomp("/") + "/conf/authz" + + usergroups = [] #all groups the user is in, groups have to be specified before beeing used + @ap = [] #paths with allowed access + @dp = [] #paths with denied access + + #set prefix path + @urlprefix = "" #if repository is a svn subdirectory, we need a prefix to match paths from authz file + + #catch if rooturl.nil? (when repo is just created!) Add/Change your name below to be able to browse the svn for the first time!!!! + if ((rooturl.size == 0) && !(user.eql?("haegdl") || user.eql?("admin"))) + puts "ERROR during SVNCheck, rooturl is nil, please access the repo from admin/haegdl account to activate it!" + else + #set prefix if needed + if url.length > rooturl.length + @urlprefix = url.sub(rooturl, "") + end + + File.open(@authzpath, "r") do |f| + f.each_line do |line| + line.gsub!(/\s+/, "") #.downcase! removes whitespace; lowercasing disabled! + if !(line.empty? || (line[0,1] == "#") || (line[0,9] == "[aliases]")) #comments or empty lines shall not be processed + nchanged = true #not changed; used to ignore section lines + + #section check + if line[0,8] == '[groups]' then + status = 1 #1=> group declaration + nchanged = false + elsif line[0,2] == '[/' then + status = 2 # path check mode + nchanged = false + end + + #PLACE FOR STRING OPERATION + case status + when 1 #check for groups + parts = line.split("=") + if ( !(parts.empty?) && (parts.size == 2) && nchanged) + if parts[1].split(",").include? user + usergroups << "@" + parts[0] + else + #allow groups in group creation + parts[1].split(",") do |x| + if usergroups.include? x + usergroups << parts[0] + break + end + end + end + end + when 2 + if !(nchanged) + #this line specifies path + currentpath = line.gsub!(/[\[\]]/, "") + else + #3 checks: first: groups_allowed?; second: userallowed + parts = line.split("=") + if (!(parts.empty?) && (parts.size == 2)) + #check groups + if (usergroups.include? parts[0]) #name matches a group name the user is in + case parts[1] + when "rw", "wr", "r" + @ap << currentpath + when "w" + puts "NOTE: just write-access, doing nothing!" + else + puts "ERROR, 2nd part could not be interpreted" + parts[1] + end + end + #check explicit mentioned user + if (parts[0].eql? user) #if username is mentiend + case parts[1] + when "rw", "wr", "r" + @ap << currentpath + when "w" + puts "NOTE: just write-access, not implemented yet, doing nothing!" + else + puts "ERROR, 2nd Part could not be interpreited" + "###" + parts[1] + "###" + end + end + #*= all user + if (parts[0].eql? "*") + case parts[1] + when "rw", "wr", "r" + @ap << currentpath + when "w" + puts "ERROR, not implemented yet!" + else + puts "ERROR, second part could not be interpreted" + end + end + end + #interpret denied access + if (parts.size == 1) + if ((parts[0].eql? user)|| (usergroups.include? parts[0]) || (parts[0].eql? "*") ) + @dp << currentpath + end + end + end + else + puts "ERROR!" + end + end + end + end + end + end + + # Erklaerung: + # + # Suche alle Pfade für die Richtlinien existieren, welche ein Teil des zu ueberpruefenden Pfades + # sind und gleichzeitig fuer den Benutzer gelten (andere werden ignoriert!) + # Bestimme den laengsten uebereinstimmenden Pfad fuer den es eine Regel gibt und wende sie an. + + def chk(chkurl) + chkp = chkurl#.sub("/","") # @urlprefix + ... + puts "PATH TO CHECK: " + chkp + " PREFIX IS: " + @urlprefix + + ap = @ap.dup + dp = @dp.dup + #remove tailing '/' + (ap + dp).each do |s| + if s.length > 1 + s.chomp!("/") + end + end + if chkp.length > 1 + chkp.chomp!("/") + end + #just keep paths matching to the path we have to check + dp.delete_if { |x| !dirorsubdir(chkp,x) } + ap.delete_if { |x| !dirorsubdir(chkp,x) } + puts "Authz rules found for following paths:" + puts (ap + dp) + path = (ap + dp).sort_by(&:length).reverse.first + puts "Winner Path = " + path unless path.nil? + + if (path.nil? || path.eql?("")) + puts "ACCESS DENIED! Checked path: " + chkp + " There is no rule for this person in the conf file!" + return false + else + if ap.include?(path) + #access granted! + puts "ACCESS GRANTED! Checked path: " + chkp + " Allowed by read-rule in: "+ path + return true + end + if dp.include?(path) + #access denied! + puts "ACCESS DENIED! Checked path: " + chkp + " Denied by rule in: " + path + return false + end + end + end + + def dirorsubdir(chkp, x) + if chkp.start_with?(x) + if ((chkp.length > x.length) && (chkp[x.length,1] == "/" )) + #chkp is in directory x or one of its subdirs (recursiv) + return true + elsif chkp == x + # path that was asked for + return true + elsif x == "/" + #is always valid (check if other paths match better is solved by sorting in chk + return true + else + #deny all other + return false + end + else + return false + end + end + + #resolve revisions to paths + def chkrev(csetid) + puts 'SVN-Request for Revision: ' + Changeset.find(csetid).revision + changes = Change.where(changeset_id: csetid) + changes.each do |x| + if !(chk(x.path)) + puts 'SVN-Request DENIED for ' + User.current.login + return false + end + end + #didnt returned false before -> no request denied + puts 'SVN-Request ALLOWED for ' + User.current.login + return true + end + + def chkurl(url) + #add prefix if svn-subdir (not needed @revisions) + return chk(@urlprefix.chomp("/") + url) + end + +end