|
1 |
#################################################
|
|
2 |
# INFO
|
|
3 |
#
|
|
4 |
# This file was written by Daniel Haeger (haegdl@idmt.fraunhofer.de)
|
|
5 |
# Its a helper for checking if a user is permitted to access a SVN
|
|
6 |
# repository protected by mod_authz_svn with path-based authz.
|
|
7 |
#
|
|
8 |
# In the same directory, a 'right to read' dominates a 'deny'
|
|
9 |
# For initialisation, the paths to the authz file of the repo
|
|
10 |
# and the user have to be specified!
|
|
11 |
#
|
|
12 |
# root_url is different that url if the svn should just
|
|
13 |
# access a subdir!
|
|
14 |
#
|
|
15 |
# TODO
|
|
16 |
#
|
|
17 |
# -wrap it in begin.rescue.end blocks
|
|
18 |
# -allow aliases
|
|
19 |
# -logging instead of puts using STDERR.puts "error" ?
|
|
20 |
#
|
|
21 |
# Last edited: 13.10.14 13:37
|
|
22 |
#
|
|
23 |
##################################################
|
|
24 |
|
|
25 |
class SVNCheck
|
|
26 |
def initialize(url, rooturl, user)
|
|
27 |
puts "Started SVNCheck with:\n " + url + "\n " + rooturl + "\n " + user
|
|
28 |
|
|
29 |
status = 0
|
|
30 |
currentpath = ""
|
|
31 |
@user = user.dup
|
|
32 |
@authzpath = rooturl.dup #"/opt/svn/systies_cp/conf/authz"
|
|
33 |
if @authzpath.start_with?("file://")
|
|
34 |
@authzpath.slice!("file://") #remove beginning (we need absolut path)
|
|
35 |
end
|
|
36 |
@authzpath = @authzpath.chomp("/") + "/conf/authz"
|
|
37 |
|
|
38 |
usergroups = [] #all groups the user is in, groups have to be specified before beeing used
|
|
39 |
@ap = [] #paths with allowed access
|
|
40 |
@dp = [] #paths with denied access
|
|
41 |
|
|
42 |
#set prefix path
|
|
43 |
@urlprefix = "" #if repository is a svn subdirectory, we need a prefix to match paths from authz file
|
|
44 |
|
|
45 |
#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!!!!
|
|
46 |
if ((rooturl.size == 0) && !(user.eql?("haegdl") || user.eql?("admin")))
|
|
47 |
puts "ERROR during SVNCheck, rooturl is nil, please access the repo from admin/haegdl account to activate it!"
|
|
48 |
else
|
|
49 |
#set prefix if needed
|
|
50 |
if url.length > rooturl.length
|
|
51 |
@urlprefix = url.sub(rooturl, "")
|
|
52 |
end
|
|
53 |
|
|
54 |
File.open(@authzpath, "r") do |f|
|
|
55 |
f.each_line do |line|
|
|
56 |
line.gsub!(/\s+/, "") #.downcase! removes whitespace; lowercasing disabled!
|
|
57 |
if !(line.empty? || (line[0,1] == "#") || (line[0,9] == "[aliases]")) #comments or empty lines shall not be processed
|
|
58 |
nchanged = true #not changed; used to ignore section lines
|
|
59 |
|
|
60 |
#section check
|
|
61 |
if line[0,8] == '[groups]' then
|
|
62 |
status = 1 #1=> group declaration
|
|
63 |
nchanged = false
|
|
64 |
elsif line[0,2] == '[/' then
|
|
65 |
status = 2 # path check mode
|
|
66 |
nchanged = false
|
|
67 |
end
|
|
68 |
|
|
69 |
#PLACE FOR STRING OPERATION
|
|
70 |
case status
|
|
71 |
when 1 #check for groups
|
|
72 |
parts = line.split("=")
|
|
73 |
if ( !(parts.empty?) && (parts.size == 2) && nchanged)
|
|
74 |
if parts[1].split(",").include? user
|
|
75 |
usergroups << "@" + parts[0]
|
|
76 |
else
|
|
77 |
#allow groups in group creation
|
|
78 |
parts[1].split(",") do |x|
|
|
79 |
if usergroups.include? x
|
|
80 |
usergroups << parts[0]
|
|
81 |
break
|
|
82 |
end
|
|
83 |
end
|
|
84 |
end
|
|
85 |
end
|
|
86 |
when 2
|
|
87 |
if !(nchanged)
|
|
88 |
#this line specifies path
|
|
89 |
currentpath = line.gsub!(/[\[\]]/, "")
|
|
90 |
else
|
|
91 |
#3 checks: first: groups_allowed?; second: userallowed
|
|
92 |
parts = line.split("=")
|
|
93 |
if (!(parts.empty?) && (parts.size == 2))
|
|
94 |
#check groups
|
|
95 |
if (usergroups.include? parts[0]) #name matches a group name the user is in
|
|
96 |
case parts[1]
|
|
97 |
when "rw", "wr", "r"
|
|
98 |
@ap << currentpath
|
|
99 |
when "w"
|
|
100 |
puts "NOTE: just write-access, doing nothing!"
|
|
101 |
else
|
|
102 |
puts "ERROR, 2nd part could not be interpreted" + parts[1]
|
|
103 |
end
|
|
104 |
end
|
|
105 |
#check explicit mentioned user
|
|
106 |
if (parts[0].eql? user) #if username is mentiend
|
|
107 |
case parts[1]
|
|
108 |
when "rw", "wr", "r"
|
|
109 |
@ap << currentpath
|
|
110 |
when "w"
|
|
111 |
puts "NOTE: just write-access, not implemented yet, doing nothing!"
|
|
112 |
else
|
|
113 |
puts "ERROR, 2nd Part could not be interpreited" + "###" + parts[1] + "###"
|
|
114 |
end
|
|
115 |
end
|
|
116 |
#*= all user
|
|
117 |
if (parts[0].eql? "*")
|
|
118 |
case parts[1]
|
|
119 |
when "rw", "wr", "r"
|
|
120 |
@ap << currentpath
|
|
121 |
when "w"
|
|
122 |
puts "ERROR, not implemented yet!"
|
|
123 |
else
|
|
124 |
puts "ERROR, second part could not be interpreted"
|
|
125 |
end
|
|
126 |
end
|
|
127 |
end
|
|
128 |
#interpret denied access
|
|
129 |
if (parts.size == 1)
|
|
130 |
if ((parts[0].eql? user)|| (usergroups.include? parts[0]) || (parts[0].eql? "*") )
|
|
131 |
@dp << currentpath
|
|
132 |
end
|
|
133 |
end
|
|
134 |
end
|
|
135 |
else
|
|
136 |
puts "ERROR!"
|
|
137 |
end
|
|
138 |
end
|
|
139 |
end
|
|
140 |
end
|
|
141 |
end
|
|
142 |
end
|
|
143 |
|
|
144 |
# Erklaerung:
|
|
145 |
#
|
|
146 |
# Suche alle Pfade für die Richtlinien existieren, welche ein Teil des zu ueberpruefenden Pfades
|
|
147 |
# sind und gleichzeitig fuer den Benutzer gelten (andere werden ignoriert!)
|
|
148 |
# Bestimme den laengsten uebereinstimmenden Pfad fuer den es eine Regel gibt und wende sie an.
|
|
149 |
|
|
150 |
def chk(chkurl)
|
|
151 |
chkp = chkurl#.sub("/","") # @urlprefix + ...
|
|
152 |
puts "PATH TO CHECK: " + chkp + " PREFIX IS: " + @urlprefix
|
|
153 |
|
|
154 |
ap = @ap.dup
|
|
155 |
dp = @dp.dup
|
|
156 |
#remove tailing '/'
|
|
157 |
(ap + dp).each do |s|
|
|
158 |
if s.length > 1
|
|
159 |
s.chomp!("/")
|
|
160 |
end
|
|
161 |
end
|
|
162 |
if chkp.length > 1
|
|
163 |
chkp.chomp!("/")
|
|
164 |
end
|
|
165 |
#just keep paths matching to the path we have to check
|
|
166 |
dp.delete_if { |x| !dirorsubdir(chkp,x) }
|
|
167 |
ap.delete_if { |x| !dirorsubdir(chkp,x) }
|
|
168 |
puts "Authz rules found for following paths:"
|
|
169 |
puts (ap + dp)
|
|
170 |
path = (ap + dp).sort_by(&:length).reverse.first
|
|
171 |
puts "Winner Path = " + path unless path.nil?
|
|
172 |
|
|
173 |
if (path.nil? || path.eql?(""))
|
|
174 |
puts "ACCESS DENIED! Checked path: " + chkp + " There is no rule for this person in the conf file!"
|
|
175 |
return false
|
|
176 |
else
|
|
177 |
if ap.include?(path)
|
|
178 |
#access granted!
|
|
179 |
puts "ACCESS GRANTED! Checked path: " + chkp + " Allowed by read-rule in: "+ path
|
|
180 |
return true
|
|
181 |
end
|
|
182 |
if dp.include?(path)
|
|
183 |
#access denied!
|
|
184 |
puts "ACCESS DENIED! Checked path: " + chkp + " Denied by rule in: " + path
|
|
185 |
return false
|
|
186 |
end
|
|
187 |
end
|
|
188 |
end
|
|
189 |
|
|
190 |
def dirorsubdir(chkp, x)
|
|
191 |
if chkp.start_with?(x)
|
|
192 |
if ((chkp.length > x.length) && (chkp[x.length,1] == "/" ))
|
|
193 |
#chkp is in directory x or one of its subdirs (recursiv)
|
|
194 |
return true
|
|
195 |
elsif chkp == x
|
|
196 |
# path that was asked for
|
|
197 |
return true
|
|
198 |
elsif x == "/"
|
|
199 |
#is always valid (check if other paths match better is solved by sorting in chk
|
|
200 |
return true
|
|
201 |
else
|
|
202 |
#deny all other
|
|
203 |
return false
|
|
204 |
end
|
|
205 |
else
|
|
206 |
return false
|
|
207 |
end
|
|
208 |
end
|
|
209 |
|
|
210 |
#resolve revisions to paths
|
|
211 |
def chkrev(csetid)
|
|
212 |
puts 'SVN-Request for Revision: ' + Changeset.find(csetid).revision
|
|
213 |
changes = Change.where(changeset_id: csetid)
|
|
214 |
changes.each do |x|
|
|
215 |
if !(chk(x.path))
|
|
216 |
puts 'SVN-Request DENIED for ' + User.current.login
|
|
217 |
return false
|
|
218 |
end
|
|
219 |
end
|
|
220 |
#didnt returned false before -> no request denied
|
|
221 |
puts 'SVN-Request ALLOWED for ' + User.current.login
|
|
222 |
return true
|
|
223 |
end
|
|
224 |
|
|
225 |
def chkurl(url)
|
|
226 |
#add prefix if svn-subdir (not needed @revisions)
|
|
227 |
return chk(@urlprefix.chomp("/") + url)
|
|
228 |
end
|
|
229 |
|
|
230 |
end
|