[api] fix permission checking on raising "sourceaccess" permissions
[opensuse:build-service.git] / src / api / app / controllers / source_controller.rb
1 require "rexml/document"
2
3 include ProductHelper
4
5 class SourceController < ApplicationController
6   validate_action :index => :directory, :packagelist => :directory, :filelist => :directory
7   validate_action :project_meta => :project, :package_meta => :package, :pattern_meta => :pattern
8  
9   skip_before_filter :extract_user, :only => [:file, :project_meta] 
10
11   def index
12     # ACL(index): projects with flag 'access' are not listed
13     projectlist
14   end
15
16   def projectlist
17     if request.post?
18       dispatch_command
19     elsif request.get?
20       if params.has_key? :deleted
21         if @http_user.is_admin?
22           pass_to_backend 
23         else
24           render_error :status => 403, :errorcode => 'no_permission_for_deleted', 
25                        :message => "only admins can see deleted projects"
26         end
27       else
28         dir = Project.find :all
29         # ACL(projectlist): projects with flag 'access' are not listed
30         accessprjs = DbProject.find( :all, :joins => "LEFT OUTER JOIN flags f ON f.db_project_id = db_projects.id", :conditions => [ "f.flag = 'access'", "ISNULL(f.repo)", "ISNULL(f.architecture_id)"] )
31         accessprjs.each do |prj|
32           dir.delete_element("//entry[@name='#{prj.name}']") if prj.disabled_for?('access', nil, nil) and not @http_user.can_access?(prj)
33         end
34         render :text => dir.dump_xml, :content_type => "text/xml"
35       end
36     end
37   end
38
39   def index_project
40     project_name = params[:project]
41     pro = DbProject.find_by_name project_name
42     # ACL(index_project): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
43     if pro and pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
44       render_error :status => 404, :errorcode => 'unknown_project',
45       :message => "Unknown project '#{project_name}'"
46       return
47     end
48     if pro.nil?
49       unless params[:cmd] == "undelete" or request.get?
50         render_error :status => 404, :errorcode => 'unknown_project',
51           :message => "Unknown project '#{project_name}'"
52         return
53       end
54     elsif params[:cmd] == "undelete"
55       render_error :status => 403, :errorcode => 'create_project_no_permission',
56           :message => "Can not undelete, project exists already '#{project_name}'"
57       return
58     end
59     
60     if request.get?
61       if params.has_key? :deleted
62         pass_to_backend
63       else
64         # ACL(index_project): private projects appear as empty
65         if pro and pro.enabled_for?('privacy', nil, nil) and not @http_user.can_private_view?(pro)
66           render :text => '<directory count="0"></directory>', :content_type => "text/xml"
67         else
68           if pro
69             @dir = Package.find :all, :project => project_name
70             render :text => @dir.dump_xml, :content_type => "text/xml"
71           else
72             pass_to_backend
73           end
74         end
75       end
76       return
77     elsif request.delete?
78       unless @http_user.can_modify_project?(pro)
79         logger.debug "No permission to delete project #{project_name}"
80         render_error :status => 403, :errorcode => 'delete_project_no_permission',
81           :message => "Permission denied (delete project #{project_name})"
82         return
83       end
84
85       #deny deleting if other packages use this as develproject
86       unless pro.develpackages.empty?
87         msg = "Unable to delete project #{pro.name}; following packages use this project as develproject: "
88         msg += pro.develpackages.map {|pkg| pkg.db_project.name+"/"+pkg.name}.join(", ")
89         render_error :status => 400, :errorcode => 'develproject_dependency',
90           :message => msg
91         return
92       end
93       #check all packages, if any get refered as develpackage
94       pro.db_packages.each do |pkg|
95         msg = ""
96         pkg.develpackages do |dpkg|
97           if pro != dpkg.db_project
98             msg += dpkg.db_project.name + "/" + dkg.name + ", "
99           end
100         end
101         unless msg == ""
102           render_error :status => 400, :errorcode => 'develpackage_dependency',
103             :message => "Unable to delete package #{pkg.name}; following packages use this package as devel package: #{msg}"
104           return
105         end
106       end
107
108       #find linking repos
109       lreps = Array.new
110       pro.repositories.each do |repo|
111         repo.linking_repositories.each do |lrep|
112           lreps << lrep
113         end
114       end
115
116       if lreps.length > 0
117         if params[:force] and not params[:force].empty?
118           #replace links to this projects with links to the "deleted" project
119           del_repo = DbProject.find_by_name("deleted").repositories[0]
120           lreps.each do |link_rep|
121             link_rep.path_elements.find(:all).each { |pe| pe.destroy }
122             link_rep.path_elements.create(:link => del_repo, :position => 1)
123             link_rep.save
124             #update backend
125             link_rep.db_project.store
126           end
127         else
128           lrepstr = lreps.map{|l| l.db_project.name+'/'+l.name}.join "\n"
129           render_error :status => 403, :errorcode => "repo_dependency",
130             :message => "Unable to delete project #{project_name}; following repositories depend on this project:\n#{lrepstr}\n"
131           return
132         end
133       end
134
135       #destroy all packages
136       pro.db_packages.each do |pack|
137         DbPackage.transaction do
138           logger.info "destroying package #{pack.name}"
139           pack.destroy
140         end
141       end
142
143       logger.info "destroying project #{pro.name}"
144       pro.destroy
145
146       logger.debug "delete request to backend: /source/#{pro.name}"
147       Suse::Backend.delete "/source/#{pro.name}"
148
149       render_ok
150       return
151     elsif request.post?
152       cmd = params[:cmd]
153
154       if 'undelete' == cmd
155         unless @http_user.can_create_project?(project_name)
156           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
157             :message => "no permission to execute command '#{cmd}'"
158           return
159         end
160         dispatch_command
161
162         # read meta data from backend to restore database object
163         path = request.path + "/_meta"
164         Project.new(backend_get(path)).save
165
166         # restore all package meta data objects in DB
167         backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}'"
168         backend_pkgs.each_package do |package|
169           path = request.path + "/" + package.name + "/_meta"
170           Package.new(backend_get(path), :project => params[:project]).save
171         end
172         return
173       end
174
175       if @http_user.can_modify_project?(pro) or cmd == "showlinked"
176         dispatch_command
177       else
178         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
179           :message => "no permission to execute command '#{cmd}'"
180         return
181       end
182     else
183       render_error :status => 400, :errorcode => "illegal_request",
184         :message => "illegal POST request to #{request.request_uri}"
185     end
186   end
187
188   # FIXME: for OBS 3, api of branch and copy calls have target and source in the opossite place
189   def index_package
190     valid_http_methods :get, :delete, :post
191     required_parameters :project, :package
192     cmd = params[:cmd]
193     deleted = params.has_key? :deleted
194
195     # list of commands which are allowed even when the project has the package only via a project link
196     read_commands = ['diff', 'branch', 'linkdiff', 'showlinked']
197
198     # find out about source and target dependening on command
199     if cmd == 'branch'
200       origin_project_name = params[:project]
201       target_package_name = origin_package_name = params[:package]
202       target_project_name = params[:target_project] if params[:target_project]
203       target_package_name = params[:target_package] if params[:target_package]
204     else
205       origin_project_name = target_project_name = params[:project]
206       origin_package_name = target_package_name = params[:package]
207       origin_project_name = params[:oproject] if params[:oproject]
208       origin_package_name = params[:opackage] if params[:opackage]
209     end
210     if origin_package_name and not origin_project_name
211         render_error :status => 404, :errorcode => "missing_argument",
212         :message => "origin package name is specified, but no origin project"
213         return
214     end
215     # FIXME: not found error messages needs to be defined in one place to avoid the risk that a typo can
216     #        be used to find out about existens
217     unknownTargetPackageError = "Unknown package '#{target_package_name}' in project '#{target_project_name}'"
218     unknownTargetProjectError = "Unknown project '#{target_project_name}'"
219
220     # test read access from origin package if specified, ignore it when it does not exist, assuming a remote package
221     if (origin_package_name)
222       sprj = DbProject.find_by_name(origin_project_name)
223       spkg = sprj.find_package(origin_package_name) if sprj
224    
225       # ACL(index_package): access behaves like package / project not existing
226       if spkg and spkg.disabled_for?('access', nil, nil) and not @http_user.can_access?(spkg)
227         render_error :status => 404, :errorcode => 'unknown_package',
228         :message => "Unknown package '#{origin_package_name}' in project '#{origin_project_name}'"
229         return
230       end
231
232       # ACL(index_package): source access gives permisson denied
233       if spkg and spkg.disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(spkg)
234         render_error :status => 403, :errorcode => "source_access_no_permission",
235         :message => "no read access to package #{origin_package_name} in project #{origin_project_name}"
236         return
237       end
238
239     end
240
241     prj = DbProject.find_by_name(target_project_name)
242     if prj
243       # FIXME3.0: disallow the "rebuild" via /source/
244       if request.get? or ( request.post? and read_commands.include?(cmd) ) or ( request.post? and cmd == "rebuild" )
245         # include project links on get or for diff and branch command
246         pkg = prj.find_package(target_package_name)
247       else
248         # allow operations only for local packages
249         pkg = prj.db_packages.find_by_name(target_package_name)
250       end
251     else
252       if target_project_name and not (request.post? and [ "branch", "showlinked" ].include?(cmd) )
253         # Check if this is a package via project link to a remote OBS instance
254         answer = Suse::Backend.get("/source/#{CGI.escape(target_project_name)}")
255         if answer
256           # exist remote
257           if request.get?
258               path = request.path
259               path << build_query_from_hash(params, [:rev, :linkrev, :emptylink, :expand, :view, :extension, :lastworking, :withlinked, :meta, :deleted])
260               pass_to_backend path
261               return
262           end
263         end
264         render_error :status => 404, :errorcode => "unknown_project",
265           :message => unknownTargetProjectError
266         return
267       end
268     end
269
270     # ACL(index_package): in case of access, package is really hidden and shown as non existing to users without access
271     if pkg and pkg.disabled_for?('access', nil, nil) and not @http_user.can_access?(pkg)
272       render_error :status => 404, :errorcode => 'unknown_package',
273         :message => unknownTargetPackageError
274       return
275     end
276
277     # ACL(index_package): if private view is on behave like pkg without any src files
278     if pkg and pkg.enabled_for?('privacy', nil, nil) and not @http_user.can_private_view?(pkg)
279       render_ok
280       return
281     end
282
283     # ACL(index_package): source access gives permisson denied
284     if pkg and pkg.disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(pkg)
285       render_error :status => 403, :errorcode => "source_access_no_permission",
286       :message => "no read access to package #{pkg.name} in project #{pkg.db_project.name}"
287       return
288     end
289
290     if request.post? and cmd == "showlinked"
291       dispatch_command
292       return
293     end
294
295     # package may not exist currently here, but can we work anyway with it ?
296     dpkg = nil
297     if deleted and request.get? and prj and not prj.disabled_for?('sourceaccess', nil, nil) and not pkg
298       # load last package meta file and just check if sourceaccess flag was used at all, no per user checking atm
299       begin
300         r = Suse::Backend.get("/source/#{CGI.escape(target_project_name)}/#{target_package_name}/_history?deleted=1&meta=1")
301       rescue
302         r = nil
303       end
304       if r
305         data = ActiveXML::XMLNode.new(r.body.to_s)
306         lastrev = nil
307         data.each_revision {|rev| lastrev = rev}
308         srcmd5 = lastrev.value("srcmd5")
309         metapath = "/source/#{CGI.escape(target_project_name)}/#{target_package_name}/_meta?rev=#{srcmd5}"
310         r = Suse::Backend.get(metapath)
311         if r
312           dpkg = Package.new(r.body)
313           if dpkg and dpkg.disabled_for? 'sourceaccess'
314              dpkg = nil
315           end
316         end
317       end
318     end
319     # validate if package exists in db, except when working on deleted package sources
320     unless deleted.blank? and not request.delete? and not dpkg
321       unless target_package_name == "_project" or pkg or DbProject.find_remote_project(target_project_name)
322         render_error :status => 404, :errorcode => "unknown_package",
323           :message => unknownTargetPackageError
324         return
325       end
326     end
327
328     if request.get?
329       path = request.path
330       path << build_query_from_hash(params, [:rev, :linkrev, :emptylink, :expand, :view, :extension, :lastworking, :withlinked, :meta, :deleted])
331       pass_to_backend path
332       return
333     elsif request.delete?
334       if target_package_name == "_project"
335         render_error :status => 403, :errorcode => "delete_package_no_permission",
336           :message => "_project package can not be deleted."
337         return
338       end
339
340       # ACL: check if user is allowed to delete package
341       if not @http_user.can_modify_package?(pkg)
342         render_error :status => 403, :errorcode => "delete_package_no_permission",
343           :message => "no permission to delete package #{target_package_name}"
344         return
345       end
346       
347       #deny deleting if other packages use this as develpackage
348       # Shall we offer a --force option here as well ?
349       # Shall we ask the other package owner accepting to be a devel package ?
350       unless pkg.develpackages.empty?
351         msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
352         msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
353         render_error :status => 400, :errorcode => 'develpackage_dependency',
354           :message => msg
355         return
356       end
357
358       DbPackage.transaction do
359         pkg.destroy
360         Suse::Backend.delete "/source/#{target_project_name}/#{target_package_name}"
361         if target_package_name == "_product"
362           update_product_autopackages params[:project]
363         end
364       end
365       render_ok
366     elsif request.post?
367       if ['undelete'].include?(cmd) 
368         # ACL: check if user is allowed to undelete package
369         unless @http_user.can_modify_project?(prj)
370           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
371             :message => "no permission to execute command '#{cmd}'"
372           return
373         end
374         dispatch_command
375
376         # read meta data from backend to restore database object
377         path = request.path + "/_meta"
378         Package.new(backend_get(path), :project => params[:project]).save
379         return
380       end
381
382       package_creating_commands = [ "branch", "copy" ]
383       if pkg.nil? and not package_creating_commands.include?(cmd)
384         render_error :status => 404, :errorcode => 'unknown_package',
385           :message => unknownTargetPackageError
386         return
387       end
388
389       # ACL: check if user is allowed to modify project, if package is not existing yet
390       if prj and pkg.nil? and package_creating_commands.include?(cmd)
391         unless @http_user.can_create_package_in?(prj)
392           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
393             :message => "no permission to execute command '#{cmd}' creating not existing package"
394           return
395         end
396       elsif pkg and not read_commands.include?(cmd) and not @http_user.can_modify_package?(pkg)
397         # ACL: check if user is allowed to modify existing package if command is changing source
398         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
399           :message => "no permission to execute command '#{cmd}'"
400         return
401       end
402       
403       dispatch_command
404     end
405   end
406
407   # /source/:project/_attribute/:attribute
408   # /source/:project/:package/_attribute/:attribute
409   # /source/:project/:package/:binary/_attribute/:attribute
410   def attribute_meta
411     valid_http_methods :get, :post, :delete
412     params[:user] = @http_user.login if @http_user
413
414     binary=nil
415     binary=params[:binary] if params[:binary]
416
417     if params[:package]
418       @attribute_container = DbPackage.find_by_project_and_name(params[:project], params[:package])
419       unless @attribute_container
420         render_error :message => "Unknown package '#{params[:project]}/#{params[:package]}'",
421           :status => 404, :errorcode => "unknown_package"
422         return
423       end
424     else
425       @attribute_container = DbProject.find_by_name(params[:project])
426       unless @attribute_container
427         render_error :message => "Unknown project '#{params[:project]}'",
428           :status => 404, :errorcode => "unknown_project"
429         return
430       end
431     end
432
433     # ACL(attribute_meta): in case of access, project or package is really hidden
434     if @attribute_container and (@attribute_container.disabled_for?('access', nil, nil) and not @http_user.can_access?(@attribute_container))
435       if params[:package]
436         render_error :message => "Unknown package '#{params[:project]}/#{params[:package]}'",
437         :status => 404, :errorcode => "unknown_package"
438         return
439       else
440         render_error :message => "Unknown project '#{params[:project]}'",
441         :status => 404, :errorcode => "unknown_project"
442         return
443       end
444     end
445
446     # is the attribute type defined at all ?
447     if params[:attribute]
448       at = AttribType.find_by_name(params[:attribute])
449       unless at
450         render_error :status => 403, :errorcode => "not_existing_attribute", 
451           :message => "Attribute is not defined in system"
452         return
453       end
454     end
455
456     if request.get?
457       params[:binary]=binary if binary
458       render :text => @attribute_container.render_attribute_axml(params), :content_type => 'text/xml'
459       return
460     end
461
462     if request.post?
463       begin
464         req = BsRequest.new(request.body.read)
465         req.data # trigger XML parsing
466       rescue ActiveXML::ParseError => e
467         render_error :message => "Invalid XML",
468           :status => 400, :errorcode => "invalid_xml"
469         return
470       end
471     end
472
473     # permission checking
474     if params[:attribute]
475       aname = params[:attribute]
476       name_parts = aname.split(/:/)
477       if name_parts.length != 2
478         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
479       end
480       unless @http_user.can_create_attribute_in? @attribute_container, :namespace => name_parts[0], :name => name_parts[1]
481         render_error :status => 403, :errorcode => "change_attribute_no_permission", 
482           :message => "user #{user.login} has no permission to change attribute"
483         return
484       end
485     else
486       if request.post?
487         req.each_attribute do |attr|
488           begin
489             can_create = @http_user.can_create_attribute_in? @attribute_container, :namespace => attr.namespace, :name => attr.name
490           rescue ActiveRecord::RecordNotFound => e
491             render_error :status => 404, :errorcode => "not_found",
492               :message => e.message
493             return
494           rescue ArgumentError => e
495             render_error :status => 400, :errorcode => "change_attribute_attribute_error",
496               :message => e.message
497             return
498           end
499           unless can_create
500             render_error :status => 403, :errorcode => "change_attribute_no_permission", 
501               :message => "user #{user.login} has no permission to change attribute"
502             return
503           end
504         end
505       else
506         render_error :status => 403, :errorcode => "internal_error", 
507           :message => "INTERNAL ERROR: unhandled request"
508         return
509       end
510     end
511
512     # execute action
513     if request.post?
514       req.each_attribute do |attr|
515         begin
516           @attribute_container.store_attribute_axml(attr, binary)
517         rescue DbProject::SaveError => e
518           render_error :status => 403, :errorcode => "save_error", :message => e.message
519           return
520         rescue DbPackage::SaveError => e
521           render_error :status => 403, :errorcode => "save_error", :message => e.message
522           return
523         end
524       end
525       @attribute_container.store
526       render_ok
527     elsif request.delete?
528       ac = @attribute_container.find_attribute(name_parts[0], name_parts[1],binary)
529       unless ac
530           render_error :status => 404, :errorcode => "not_found",
531             :message => "Attribute #{aname} does not exist" and return
532       end
533       ac.destroy
534       @attribute_container.store
535       render_ok
536     else
537       render_error :message => "INTERNAL ERROR: Unhandled operation",
538         :status => 404, :errorcode => "unknown_operation"
539     end
540   end
541
542   # /source/:project/_pattern/:pattern
543   def pattern_meta
544     valid_http_methods :get, :put, :delete
545
546     params[:user] = @http_user.login if @http_user
547     
548     @project = DbProject.find_by_name params[:project]
549     unless @project
550       render_error :message => "Unknown project '#{params[:project]}'",
551         :status => 404, :errorcode => "unknown_project"
552       return
553     end
554
555     # ACL(pattern_meta): in case of access, project or package is really hidden
556     if  @project.disabled_for?('access', nil, nil) and not @http_user.can_access?(@project)
557       render_error :message => "Unknown project '#{params[:project]}'",
558       :status => 404, :errorcode => "unknown_project"
559       return
560     end
561
562     if request.get?
563       pass_to_backend
564     else
565       # PUT and DELETE
566       permerrormsg = nil
567       if request.put?
568         permerrormsg = "no permission to store pattern"
569       elsif request.delete?
570         permerrormsg = "no permission to delete pattern"
571       end
572
573       unless @http_user.can_modify_project? @project
574         logger.debug "user #{user.login} has no permission to modify project #{@project}"
575         render_error :status => 403, :errorcode => "change_project_no_permission", 
576           :message => permerrormsg
577         return
578       end
579       
580       path = request.path + build_query_from_hash(params, [:rev, :user, :comment])
581       pass_to_backend path
582     end
583   end
584
585   # GET /source/:project/_pattern
586   def index_pattern
587     valid_http_methods :get
588
589     @project = DbProject.find_by_name(params[:project])
590     unless @project
591       render_error :message => "Unknown project '#{params[:project]}'",
592         :status => 404, :errorcode => "unknown_project"
593       return
594     end
595     
596     # ACL(index_pattern): in case of access, project or package is really hidden
597     if  @project.disabled_for?('access', nil, nil) and not @http_user.can_access?(@project)
598       render_error :message => "Unknown project '#{params[:project]}'",
599       :status => 404, :errorcode => "unknown_project"
600       return
601     end
602
603     pass_to_backend
604   end
605
606   def project_meta
607     valid_http_methods :get, :put
608     required_parameters :project
609
610     project_name = params[:project]
611
612     return unless extract_user
613     params[:user] = @http_user.login
614
615     unless project_name and valid_project_name? project_name
616       render_error :status => 400, :errorcode => "invalid_project_name",
617         :message => "invalid project name '#{project_name}'"
618       return
619     end
620
621     # ACL(project_meta): if access is set, this behaves like project non existing
622     flag_access = false
623     flag_sourceaccess = false
624     @project = DbProject.find_by_name( project_name )
625     if @project
626       flag_access       = @project.disabled_for?('access', nil, nil)
627       flag_sourceaccess = @project.disabled_for?('sourceaccess', nil, nil)
628       if flag_access and not @http_user.can_access?(@project)
629         render_error :message => "Unknown project '#{project_name}'",
630         :status => 404, :errorcode => "unknown_project"
631         return
632       end
633     end
634
635     if request.get?
636       if @project
637         render :text => @project.to_axml(params[:view]), :content_type => 'text/xml'
638       elsif DbProject.find_remote_project(project_name)
639         # project from remote buildservice, get metadata from backend
640         pass_to_backend
641       else
642         render_error :message => "Unknown project '#{project_name}'",
643         :status => 404, :errorcode => "unknown_project"
644       end
645       return
646     end
647
648     #assemble path for backend
649     path = request.path
650     path += build_query_from_hash(params, [:user, :comment, :rev])
651
652     if request.put?
653
654       # Need permission
655       logger.debug "Checking permission for the put"
656       allowed = false
657       request_data = request.raw_post
658
659       @project = DbProject.find_by_name( project_name )
660       if @project
661         #project exists, change it
662         unless @http_user.can_modify_project? @project
663           logger.debug "user #{user.login} has no permission to modify project #{@project.name}"
664           render_error :status => 403, :errorcode => "change_project_no_permission", 
665             :message => "no permission to change project"
666           return
667         end
668       else
669         #project is new
670         unless @http_user.can_create_project? project_name
671           logger.debug "Not allowed to create new project"
672           render_error :status => 403, :errorcode => 'create_project_no_permission',
673             :message => "not allowed to create new project '#{project_name}'"
674           return
675         end
676       end
677
678       # ACL(project_meta): the following code checks if the target project of a linked project exists or is ACL protected, skip remote projects
679       rdata = REXML::Document.new(request.raw_post.to_s)
680       rdata.elements.each("project/link") do |e|
681          # ACL(project_meta) TODO: check if project linking check cannot be circumvented
682         tproject_name = e.attributes["project"]
683         tprj = DbProject.find_by_name(tproject_name)
684
685         if tprj.nil?
686           if not DbProject.find_remote_project(tproject_name)
687             render_error :status => 404, :errorcode => 'not_found',
688             :message => "The link target project #{tproject_name} does not exist"
689             return
690           end
691         else
692           # ACL(project_meta): project link to project with access behaves like target project not existing
693           if tprj.disabled_for?('access', nil, nil) and not @http_user.can_access?(tprj)
694             render_error :status => 404, :errorcode => 'not_found',
695             :message => "The link target project #{tproject_name} does not exist"
696             return
697           end
698         end
699
700         logger.debug "project #{project_name} link checked against #{tproject_name} projects permission"
701       end
702
703       # ACL(project_meta): the following code checks if a repository path is to protected project, skip remote projects
704       rdata.elements.each("project/repository/path") do |e|
705          # ACL(project_meta) TODO: check if repository check cannot be circumvented
706         tproject_name = e.attributes["project"]
707         tprj = DbProject.find_by_name(tproject_name)
708         if tprj.nil?
709           if not DbProject.find_remote_project(tproject_name)
710             render_error :status => 404, :errorcode => 'not_found',
711             :message => "The link target project #{tproject_name} does not exist"
712             return
713           end
714         else
715           # ACL(project_meta): project link to project with access behaves like target project not existing
716           if tprj.disabled_for?('access', nil, nil) and not @http_user.can_access?(tprj)
717             render_error :status => 404, :errorcode => 'not_found',
718             :message => "The project #{tproject_name} does not exist"
719             return
720           end
721
722           # FIXME2.1: or to be discussed. This is currently a regression. It was wanted so far that it is still
723           #           possible to build against a path, where binary download was not possible.
724           # ACL(project_meta): project link to project with binarydownload gives permisson denied
725 #          if tprj.disabled_for?('binarydownload', nil, nil) and not @http_user.can_download_binaries?(tprj)
726 #            render_error :status => 403, :errorcode => "binary_download_no_permission",
727 #            :message => "No permission for a repository path to project #{tproject_name}"
728 #            return
729 #          end
730
731           # ACL(project_meta): check that user does not link an unprotected project to a protected project
732           if @project
733             if (tprj.disabled_for?('access', nil, nil) and @project.enabled_for?('access', nil, nil)) or
734                 (tprj.disabled_for?('binarydownload', nil, nil) and @project.enabled_for?('access', nil, nil) and
735                  @project.enabled_for?('binarydownload', nil, nil))
736               render_error :status => 403, :errorcode => "binary_download_no_permission" ,
737               :message => "repository path from a insufficiently protected project #{project_name} to a protected project #{tproject_name}"
738               return
739             end
740           elsif @project.nil? and (tprj.disabled_for?('access', nil, nil) or tprj.disabled_for?('binarydownload', nil, nil))
741             render_error :status => 403, :errorcode => "binary_download_no_permission" ,
742             :message => "cannot check permission of project #{project_name} which must preexist"
743             return
744           end
745         end
746
747         logger.debug "project #{project_name} repository path checked against #{tproject_name} projects permission"
748       end
749
750       p = Project.new(request_data, :name => project_name)
751       if @project and not @project.disabled_for?('sourceaccess', nil, nil) and not @http_user.is_admin?
752         if p.disabled_for? :sourceaccess
753            render_error :status => 403, :errorcode => "change_project_protection_level",
754              :message => "admin rights are required to raise the source protection level of a project"
755            return
756         end
757       end
758
759       if @project and not @project.disabled_for?('access', nil, nil) and not @http_user.is_admin?
760         if p.disabled_for? :access
761            render_error :status => 403, :errorcode => "change_project_protection_level",
762              :message => "admin rights are required to raise the protection level of a project"
763            return
764         end
765       end
766
767       if p.name != project_name
768         render_error :status => 400, :errorcode => 'project_name_mismatch',
769           :message => "project name in xml data does not match resource path component"
770         return
771       end
772
773       if (p.has_element? :remoteurl or p.has_element? :remoteproject) and not @http_user.is_admin?
774         render_error :status => 403, :errorcode => "change_project_no_permission",
775           :message => "admin rights are required to change remoteurl or remoteproject"
776         return
777       end
778
779       p.add_person(:userid => @http_user.login) unless @project
780       p.save
781
782       render_ok
783     else
784       render_error :status => 400, :errorcode => 'illegal_request',
785         :message => "Illegal request: POST #{request.path}"
786     end
787   end
788
789   def project_config
790     valid_http_methods :get, :put
791
792     #check if project exists
793     unless (prj = DbProject.find_by_name(params[:project]))
794       render_error :status => 404, :errorcode => 'project_not_found',
795         :message => "Unknown project #{params[:project]}"
796       return
797     end
798
799     #assemble path for backend
800     params[:user] = @http_user.login
801
802     # ACL(project_config): in case of access, project is really hidden, accessing says project is not existing
803     if prj.disabled_for?('access', nil, nil) and not @http_user.can_access?(prj)
804       render_error :status => 404, :errorcode => 'project_not_found',
805         :message => "Unknown project #{params[:project]}"
806       return
807     end
808
809     if request.get?
810       path = request.path
811       path += build_query_from_hash(params, [:rev])
812       pass_to_backend path
813       return
814     end
815
816     #assemble path for backend
817     path = request.path
818     path += build_query_from_hash(params, [:user, :comment])
819
820     if request.put?
821       unless @http_user.can_modify_project?(prj)
822         render_error :status => 403, :errorcode => 'put_project_config_no_permission',
823           :message => "No permission to write build configuration for project '#{params[:project]}'"
824         return
825       end
826
827       pass_to_backend path
828       return
829     end
830     render_error :status => 400, :errorcode => 'illegal_request',
831         :message => "Illegal request: #{request.path}"
832   end
833
834   def project_pubkey
835     valid_http_methods :get, :delete
836
837     #assemble path for backend
838     params[:user] = @http_user.login if request.delete?
839     path = request.path
840     path += build_query_from_hash(params, [:user, :comment, :rev])
841
842     #check if project exists
843     unless prj = DbProject.find_by_name(params[:project])
844       prj, pro_name = DbProject.find_remote_project(params[:project])
845       unless request.get? and prj
846         render_error :status => 404, :errorcode => "project_not_found",
847           :message => "Unknown project '#{params[:project]}'"
848         return
849       end
850     end
851
852     # ACL(project_pubkey): in case of access, project or package is really hidden
853     if prj and prj.disabled_for?('access', nil, nil) and not @http_user.can_access?(prj)
854       render_error :message => "Unknown project '#{params[:project]}'",
855       :status => 404, :errorcode => "project_not_found"
856       return
857     end
858
859     if request.get?
860       pass_to_backend path
861     elsif request.delete?
862       #check for permissions
863       unless @http_user.can_modify_project?(prj)
864         render_error :status => 403, :errorcode => 'delete_project_pubkey_no_permission',
865           :message => "No permission to delete public key for project '#{params[:project]}'"
866         return
867       end
868
869       pass_to_backend path
870       return
871     end
872   end
873
874   def package_meta
875     valid_http_methods :put, :get
876     required_parameters :project, :package
877    
878     project_name = params[:project]
879     package_name = params[:package]
880
881     unless pro = DbProject.find_by_name(project_name)
882       pro, pro_name = DbProject.find_remote_project(project_name)
883       unless request.get? and pro
884         render_error :status => 404, :errorcode => "unknown_project",
885           :message => "Unknown project '#{project_name}'"
886         return
887       end
888     end
889
890     unless valid_package_name? package_name
891       render_error :status => 400, :errorcode => "invalid_package_name",
892         :message => "invalid package name '#{package_name}'"
893       return
894     end
895
896     pack = pro.find_package( package_name )
897
898     # ACL(package_meta): in case of access, project is really hidden, accessing says project is not existing
899     if pack and (pack.disabled_for?('access', nil, nil) and not @http_user.can_access?(pack))
900       render_error :message => "Unknown package '#{project_name}/#{package_name}'",
901       :status => 404, :errorcode => "unknown_package"
902       return
903     end
904
905     if request.get?
906       if pack.nil? or params.has_key?(:rev)
907         # check if this comes from a remote project, also true for _project package
908         # or if rev it specified we need to fetch the meta from the backend
909         answer = Suse::Backend.get(request.path)
910         if answer
911           render :text => answer.body.to_s, :content_type => 'text/xml'
912         else
913           render_error :status => 404, :errorcode => "unknown_package",
914             :message => "Unknown package '#{package_name}'"
915         end
916         return
917       end
918
919       render :text => pack.to_axml(params[:view]), :content_type => 'text/xml'
920     else
921       update_package_meta(project_name, package_name, request.raw_post, @http_user.login, params[:comment])
922     end
923   end
924
925   def file
926     valid_http_methods :get, :delete, :put
927     project_name = params[:project]
928     package_name = params[:package]
929     file = params[:file]
930     path = "/source/#{CGI.escape(project_name)}/#{CGI.escape(package_name)}/#{CGI.escape(file)}"
931
932     #authenticate
933     return unless extract_user
934     params[:user] = @http_user.login
935
936     prj = DbProject.find_by_name(project_name)
937     pack = DbPackage.find_by_project_and_name(project_name, package_name)
938     if package_name == "_project"
939       if prj.nil?
940         render_error :status => 403, :errorcode => 'not_found',
941           :message => "The given project #{project_name} does not exist"
942         return
943       end
944       allowed = permissions.project_change? prj
945     else
946       if prj and pack.nil? and request.get?
947         # find package instance of linked local project
948         pack = prj.find_package(package_name)
949       end
950       if pack.nil? and request.get?
951         # Check if this is a package on a remote OBS instance
952         answer = Suse::Backend.get(request.path)
953         if answer
954           pass_to_backend
955           return
956         end
957       end
958       if pack.nil? and package_name != "_project"
959         render_error :status => 403, :errorcode => 'not_found',
960           :message => "The given package #{package_name} does not exist in project #{project_name}"
961         return
962       end
963       allowed = permissions.package_change? pack
964
965       # ACL(file): access behaves like project not existing
966       if pack.disabled_for?('access', nil, nil) and not @http_user.can_access?(pack)
967         render_error :status => 404, :errorcode => 'not_found',
968         :message => "The given package #{package_name} does not exist in project #{project_name}"
969         return
970       end
971
972       # ACL(file): source access gives permisson denied
973       if pack.disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(pack)
974         render_error :status => 403, :errorcode => "source_access_no_permission",
975         :message => "no read access to package #{package_name}, project #{project_name}"
976         return
977       end
978     end
979
980     if request.get?
981       path += build_query_from_hash(params, [:rev, :meta])
982       pass_to_backend path
983       return
984     end
985
986     # ACL(file): lookup via :rev / :linkrev is prevented now by backend if $nosharedtrees is set
987     if request.put?
988       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink, :meta])
989       
990       if  allowed
991         # file validation where possible
992         if params[:file] == "_link"
993            validator = Suse::Validator.new( "link" )
994            validator.validate(request)
995         elsif params[:file] == "_aggregate"
996            validator = Suse::Validator.new( "aggregate" )
997            validator.validate(request)
998         end
999
1000         # ACL(file): the following code checks if link or aggregate
1001         if params[:file] == "_aggregate"
1002           data = REXML::Document.new(request.raw_post.to_s)
1003           data.elements.each("aggregatelist/aggregate") do |e|
1004             # ACL(file) TODO: check if the _aggregate check cannot be circumvented somehow
1005             tproject_name = e.attributes["project"]
1006             tprj = DbProject.find_by_name(tproject_name)
1007             if tprj.nil?
1008               if not DbProject.find_remote_project(tproject_name)
1009                 render_error :status => 404, :errorcode => 'not_found',
1010                 :message => "The given #{tproject_name} does not exist"
1011                 return
1012               end
1013             else
1014               # ACL(file): _aggregate access behaves like project not existing
1015               if tprj.disabled_for?('access', nil, nil) and not @http_user.can_access?(tprj)
1016                 render_error :status => 404, :errorcode => 'not_found',
1017                 :message => "The project #{tproject_name} does not exist"
1018                 return
1019               end
1020
1021               # ACL(file): _aggregate binarydownload denies access to repositories
1022               if tprj.disabled_for?('binarydownload', nil, nil) and not @http_user.can_download_binaries?(tprj)
1023                 render_error :status => 403, :errorcode => "download_binary_no_permission",
1024                 :message => "No permission to _aggregate binaries from project #{params[:project]}"
1025                 return
1026               end
1027
1028               # ACL(file): check that user does not aggregate an unprotected project to a protected project
1029               if prj
1030                 if (tprj.disabled_for?('access', nil, nil) and prj.enabled_for?('access', nil, nil)) or
1031                     (tprj.disabled_for?('binarydownload', nil, nil) and prj.enabled_for?('access', nil, nil) and
1032                      prj.enabled_for?('binarydownload', nil, nil))
1033                   render_error :status => 403, :errorcode => "binary_download_no_permission" ,
1034                   :message => "aggregate with an unprotected project  #{project_name} to a protected project #{tproject_name}"
1035                   return
1036                 end
1037               end
1038             end
1039
1040             logger.debug "_aggregate checked for #{tproject_name} project permission"
1041           end
1042         elsif params[:file] == "_link"
1043           data = REXML::Document.new(request.raw_post.to_s)
1044           data.elements.each("link") do |e|
1045             tproject_name = e.attributes["project"]
1046             tpackage_name = e.attributes["package"]
1047             tproject_name = project_name if tproject_name.blank?
1048             tpackage_name = package_name if tpackage_name.blank?
1049             tprj = DbProject.find_by_name(tproject_name)
1050             if tprj.nil?
1051               # link to remote project ?
1052               unless tprj = DbProject.find_remote_project(tproject_name)
1053                 render_error :status => 404, :errorcode => 'not_found',
1054                 :message => "The given project #{tproject_name} does not exist"
1055                 return
1056               end
1057             else
1058               tpkg = tprj.find_package(tpackage_name)
1059               if tpkg.nil?
1060                 # check if this is a package on a remote OBS instance
1061                 begin
1062                   answer = Suse::Backend.get("/source/#{URI.escape tproject_name}/#{URI.escape tpackage_name}/_meta")
1063                 rescue
1064                   render_error :status => 404, :errorcode => 'not_found',
1065                   :message => "The given package #{tpackage_name} does not exist in project #{tproject_name}"
1066                   return
1067                 end
1068               end
1069               
1070               # ACL(file): _link access behaves like project not existing
1071               if tpkg and tpkg.disabled_for?('access', nil, nil) and not @http_user.can_access?(tpkg)
1072                 render_error :status => 404, :errorcode => 'not_found',
1073                 :message => "The given package #{tpackage_name} does not exist in project #{tproject_name}"
1074                 return
1075               end
1076
1077               # ACL(file): _link sourceaccess gives permisson denied
1078               if tpkg and tpkg.disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(tpkg)
1079                 render_error :status => 403, :errorcode => "source_access_no_permission",
1080                 :message => "No permission to _link to package #{tpackage_name} at project #{tproject_name}"
1081                 return
1082               end
1083
1084               # ACL(file): check that user does not link an unprotected package to a protected package
1085               if pack and tpkg
1086                 if (tpkg.disabled_for?('access', nil, nil) and pack.enabled_for?('access', nil, nil))
1087                   render_error :status => 403, :errorcode => "access_no_permission" ,
1088                   :message => "linking an unprotected package #{package_name}/#{project_name} to a protected package #{tpackage_name}/#{tproject_name}"
1089                   return
1090                 end
1091                 if (tpkg.disabled_for?('sourceaccess', nil, nil) and pack.enabled_for?('sourceaccess', nil, nil))
1092                   render_error :status => 403, :errorcode => "source_access_no_permission" ,
1093                   :message => "linking an unprotected package #{package_name}/#{project_name} to a protected package #{tpackage_name}/#{tproject_name}"
1094                   return
1095                 end
1096               end
1097
1098               logger.debug "_link checked against #{tpackage_name} in  #{tproject_name} package permission"
1099             end
1100           end
1101         end
1102
1103         pass_to_backend path
1104         pack.update_timestamp
1105         if package_name == "_product"
1106           update_product_autopackages params[:project]
1107         end
1108       else
1109         render_error :status => 403, :errorcode => 'put_file_no_permission',
1110           :message => "Insufficient permissions to store file in package #{package_name}, project #{project_name}"
1111       end
1112     elsif request.delete?
1113       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink])
1114       
1115       if  allowed
1116         Suse::Backend.delete path
1117         pack = DbPackage.find_by_project_and_name(project_name, package_name)
1118         pack.update_timestamp
1119         if package_name == "_product"
1120           update_product_autopackages params[:project]
1121         end
1122         render_ok
1123       else
1124         render_error :status => 403, :errorcode => 'delete_file_no_permission',
1125           :message => "Insufficient permissions to delete file"
1126       end
1127     end
1128   end
1129
1130   private
1131
1132   def update_package_meta(project_name, package_name, request_data, user=nil, comment=nil)
1133     pkg = DbPackage.find_by_project_and_name(project_name, package_name)
1134
1135     if pkg
1136       # Being here means that the package already exists
1137       unless permissions.package_change? pkg
1138         logger.debug "user #{user} has no permission to change package #{package_name}"
1139         render_error :status => 403, :errorcode => "change_package_no_permission",
1140           :message => "no permission to change package"
1141         return
1142       end
1143     else
1144       # Ok, the package is new
1145       unless permissions.package_create?( project_name )
1146         # User is not allowed by global permission.
1147         logger.debug "Not allowed to create new packages"
1148         render_error :status => 403, :errorcode => "create_package_no_permission",
1149           :message => "no permission to create package for project #{project_name}"
1150         return
1151       end
1152     end
1153
1154     @package = Package.new( request_data, :project => project_name, :name => package_name )
1155
1156     if pkg and not pkg.disabled_for?('sourceaccess', nil, nil)
1157       if @package.disabled_for? :sourceaccess
1158          render_error :status => 403, :errorcode => "change_package_protection_level",
1159            :message => "admin rights are required to raise the protection level of a package"
1160          return
1161       end
1162     end
1163
1164     if pkg and not pkg.disabled_for?('access', nil, nil)
1165       if @package.disabled_for? :access
1166          render_error :status => 403, :errorcode => "change_package_protection_level",
1167            :message => "admin rights are required to raise the protection level of a package"
1168          return
1169       end
1170     end
1171
1172     if( @package.name != package_name )
1173       render_error :status => 400, :errorcode => 'package_name_mismatch',
1174         :message => "package name in xml data does not match resource path component"
1175       return
1176     end
1177
1178     begin
1179       @package.save
1180     rescue DbPackage::CycleError => e
1181       render_error :status => 400, :errorcode => 'devel_cycle', :message => e.message
1182       return
1183     end
1184
1185     render_ok
1186   end
1187
1188   # POST /source?cmd=branch
1189   def index_branch
1190     # set defaults
1191     unless params[:attribute]
1192       params[:attribute] = "OBS:Maintained"
1193     end
1194     unless params[:update_project_attribute]
1195       params[:update_project_attribute] = "OBS:UpdateProject"
1196     end
1197     unless params[:target_project]
1198       if params[:request]
1199         params[:target_project] = "home:#{@http_user.login}:branches:REQUEST_#{params[:request]}"
1200       else
1201         params[:target_project] = "home:#{@http_user.login}:branches:#{params[:attribute].gsub(':', '_')}"
1202         params[:target_project] += ":#{params[:package]}" if params[:package]
1203       end
1204     end
1205
1206     # find packages to be branched
1207     @packages = []
1208     if params[:request]
1209       # find packages from request
1210       data = Suse::Backend.get("/request/#{params[:request]}").body
1211       req = BsRequest.new(data)
1212
1213       req.each_action do |action|
1214         prj=nil
1215         pkg=nil
1216         if action.has_element? 'source'
1217           if action.source.has_attribute? 'project'
1218             prj = DbProject.find_by_name action.source.project
1219             unless prj
1220               render_error :status => 404, :errorcode => 'unknown_project',
1221               :message => "Unknown source project #{action.source.project} in request #{params[:request]}"
1222               return
1223             end
1224           end
1225           if action.source.has_attribute? 'package'
1226             pkg = prj.db_packages.find_by_name action.source.package
1227             unless pkg
1228               render_error :status => 404, :errorcode => 'unknown_package',
1229                 :message => "Unknown source package #{action.source.package} in project #{action.source.project} in request #{params[:request]}"
1230               return
1231             end
1232
1233           end
1234         end
1235
1236         @packages.push({ :target_project => pkg.db_project, :package => pkg })
1237       end
1238     else
1239       # find packages via attributes
1240       at = AttribType.find_by_name(params[:attribute])
1241       if not at
1242         render_error :status => 403, :errorcode => 'not_found',
1243           :message => "The given attribute #{params[:attribute]} does not exist"
1244         return
1245       end
1246       if params[:value]
1247         DbPackage.find_by_attribute_type_and_value( at, params[:value], params[:package] ) do |pkg|
1248           @packages.push({ :target_project => pkg.db_project, :package => pkg })
1249         end
1250         # FIXME: how to handle linked projects here ? shall we do at all or has the tagger (who creates the attribute) to create the package instance ?
1251       else
1252         # Find all direct instances of a package
1253         DbPackage.find_by_attribute_type( at, params[:package] ).each do |pkg|
1254           @packages.push({ :target_project => pkg.db_project, :package => pkg })
1255         end
1256         # Find all indirect instance via project links, a new package will get created on submit accept
1257         if params[:package]
1258           projects = DbProject.find_by_attribute_type( at )
1259           projects.each do |prj|
1260             prj.linkedprojects.each do |lprj|
1261               if lprj.linked_db_project
1262                 if pkg = lprj.linked_db_project.db_packages.find_by_name( params[:package] )
1263                   @packages.push({ :target_project => prj, :package => pkg })
1264                 else
1265                   # FIXME: add support for branching from remote projects
1266                 end
1267               end
1268             end
1269           end
1270         end
1271       end
1272     end
1273
1274     #ACL permission check of source packages
1275     @packages.each do |p|
1276       # ACL(index_branch): source access gives permisson denied
1277       if p[:package].disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(p[:package])
1278         render_error :status => 403, :errorcode => "source_access_no_permission",
1279           :message => "user #{@http_user.login} has no read access to package #{action.source.package}, project #{action.source.project}"
1280         return
1281       end
1282
1283       # ACL(index_branch): skip access protected packages
1284       @packages.delete(p) if p[:package].disabled_for?('access', nil, nil) and not @http_user.can_access?(p[:package])
1285     end
1286
1287     unless @packages.length > 0
1288       render_error :status => 403, :errorcode => "not_found",
1289         :message => "no packages found by search criteria"
1290       return
1291     end
1292
1293     #create branch project
1294     tprj = DbProject.find_by_name params[:target_project]
1295     if tprj.nil?
1296        # permission check
1297        unless @http_user.can_create_project?(params[:target_project])
1298          render_error :status => 403, :errorcode => "create_project_no_permission",
1299            :message => "no permission to create project '#{params[:target_project]}' while executing branch command"
1300          return
1301        end
1302
1303       title = "Branch project for package #{params[:package]}"
1304       description = "This project was created for package #{params[:package]} via attribute #{params[:attribute]}"
1305       if params[:request]
1306         title = "Branch project based on request #{params[:request]}"
1307         description = "This project was created as a clone of request #{params[:request]}"
1308       end
1309       DbProject.transaction do
1310         tprj = DbProject.new :name => params[:target_project], :title => title, :description => description
1311         tprj.add_user @http_user, "maintainer"
1312         tprj.flags.create( :position => 1, :flag => 'build', :status => "disable" )
1313         tprj.store
1314       end
1315       if params[:request]
1316         ans = AttribNamespace.find_by_name "OBS"
1317         at = AttribType.find( :first, :joins => ans, :conditions=>{:name=>"RequestCloned"} )
1318
1319         a = Attrib.new(:db_project => tprj, :attrib_type => at)
1320         a.values << AttribValue.new(:value => params[:request], :position => 1)
1321         a.save
1322       end
1323     else
1324       # ACL(index_branch): in case of access, project is really hidden and shown as non existing to users without access
1325       if not @http_user.can_modify_project?(tprj) or (tprj.disabled_for?('access', nil, nil) and not @http_user.can_access?(tprj))
1326         render_error :status => 403, :errorcode => "modify_project_no_permission",
1327           :message => "no permission to modify project '#{params[:target_project]}' while executing branch project command"
1328         return
1329       end
1330     end
1331
1332     # create package branches
1333     # collect also the needed repositories here
1334     @packages.each do |p|
1335       # is a update project defined and a package there ?
1336       pac = p[:package]
1337       aname = params[:update_project_attribute]
1338       name_parts = aname.split(/:/)
1339       if name_parts.length != 2
1340         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
1341       end
1342
1343       # find origin package to be branched
1344       branch_target_project = p[:target_project].name
1345       branch_target_package = pac.name
1346       proj_name = branch_target_project.gsub(':', '_')
1347       pack_name = branch_target_package.gsub(':', '_') + "." + proj_name
1348
1349       # check for update project
1350       if not params[:request] and a = p[:target_project].find_attribute(name_parts[0], name_parts[1]) and a.values[0]
1351         if pa = DbPackage.find_by_project_and_name( a.values[0].value, p[:package].name )
1352           branch_target_project = pa.db_project.name
1353           branch_target_package = pa.name
1354         else
1355           # package exists not yet in update project, but it may have a project link ?
1356           uprj = DbProject.find_by_name(a.values[0].value)
1357           if uprj and uprj.find_package( pac.name )
1358             branch_target_project = a.values[0].value
1359           end
1360         end
1361       end
1362
1363       # create branch package
1364       # no find_package call here to check really this project only
1365       if tpkg = tprj.db_packages.find_by_name(pack_name)
1366         render_error :status => 400, :errorcode => "double_branch_package",
1367           :message => "branch target package already exists: #{tprj.name}/#{tpkg.name}"
1368         return
1369       else
1370         tpkg = tprj.db_packages.new(:name => pack_name, :title => pac.title, :description => pac.description)
1371         tprj.db_packages << tpkg
1372       end
1373
1374       # create repositories, if missing
1375       pac.db_project.repositories.each do |repo|
1376         repoName = proj_name+"_"+repo.name
1377         unless tprj.repositories.find_by_name(repoName)
1378           trepo = tprj.repositories.create :name => repoName
1379           trepo.architectures = repo.architectures
1380           trepo.path_elements.create(:link => repo, :position => 1)
1381         end
1382         tpkg.flags.create( :position => 1, :flag => 'build', :status => "enable", :repo => repoName )
1383       end
1384       tpkg.store
1385
1386       # branch sources in backend
1387       Suse::Backend.post "/source/#{tpkg.db_project.name}/#{tpkg.name}?cmd=branch&oproject=#{CGI.escape(branch_target_project)}&opackage=#{CGI.escape(branch_target_package)}", nil
1388     end
1389
1390     # store project data in DB and XML
1391     tprj.store
1392
1393     # all that worked ? :)
1394     render_ok :data => {:targetproject => params[:target_project]}
1395   end
1396
1397   # create a id collection of all projects doing a project link to this one
1398   # POST /source/<project>?cmd=showlinked
1399   def index_project_showlinked
1400     valid_http_methods :post
1401     project_name = params[:project]
1402
1403     # ACL(index_project_showlinked): check project itself for access, project is really hidden and shown as non existing to users without access
1404     tprj = DbProject.find_by_name(params[:project])
1405     if tprj and tprj.disabled_for?('access', nil, nil) and not @http_user.can_access?(tprj)
1406       render_error :status => 404, :errorcode => 'unknown_project',
1407       :message => "Unknown project #{params[:project]}"
1408       return
1409     end
1410
1411     builder = FasterBuilder::XmlMarkup.new( :indent => 2 )
1412     pro = DbProject.find_by_name project_name
1413     xml = builder.collection() do |c|
1414       pro.find_linking_projects.each do |l|
1415         # ACL(index_project_showlinked): sort out access proteced projects as hidden
1416         unless pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
1417           p={}
1418           p[:name] = l.name
1419           c.project(p)
1420         end
1421       end
1422     end
1423     render :text => xml.target!, :content_type => "text/xml"
1424   end
1425
1426   # POST /source/<project>?cmd=extendkey
1427   def index_project_extendkey
1428     valid_http_methods :post
1429     params[:user] = @http_user.login
1430     project_name = params[:project]
1431
1432     pro = DbProject.find_by_name project_name
1433     # ACL(index_project_extendkey): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
1434     if pro and pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
1435       render_error :status => 404, :errorcode => 'unknown_project',
1436       :message => "Unknown project '#{project_name}'"
1437       return
1438     end
1439
1440     path = request.path
1441     path << build_query_from_hash(params, [:cmd, :user, :comment])
1442     pass_to_backend path
1443   end
1444
1445   # POST /source/<project>?cmd=createkey
1446   def index_project_createkey
1447     valid_http_methods :post
1448     params[:user] = @http_user.login
1449     project_name = params[:project]
1450
1451     pro = DbProject.find_by_name project_name
1452     # ACL(index_project_createkey): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
1453     # adrian: this should be checked anyway in index_project already
1454     if pro and pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
1455       render_error :status => 404, :errorcode => 'unknown_project',
1456       :message => "Unknown project '#{project_name}'"
1457       return
1458     end
1459
1460     path = request.path
1461     path << build_query_from_hash(params, [:cmd, :user, :comment])
1462     pass_to_backend path
1463   end
1464
1465   # POST /source/<project>?cmd=undelete
1466   def index_project_undelete
1467     valid_http_methods :post
1468     params[:user] = @http_user.login
1469     project_name = params[:project]
1470
1471     pro = DbProject.find_by_name project_name
1472     # ACL(index_project_undelete): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
1473     # ACL Adrian: this should have be checked anyway in index_project already
1474     if pro and pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
1475       render_error :status => 404, :errorcode => 'unknown_project',
1476       :message => "Unknown project '#{project_name}'"
1477       return
1478     end
1479
1480     path = request.path
1481     path << build_query_from_hash(params, [:cmd, :user, :comment])
1482     pass_to_backend path
1483   end
1484
1485   # POST /source/<project>?cmd=createpatchinfo
1486   def index_project_createpatchinfo
1487     project_name = params[:project]
1488
1489     pro = DbProject.find_by_name project_name
1490     # ACL(index_project_createpatchinfo): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
1491     # adrian: this should be checked anyway in index_project already
1492     if pro and pro.disabled_for?('access', nil, nil) and not @http_user.can_access?(pro)
1493       render_error :status => 404, :errorcode => 'unknown_project',
1494       :message => "Unknown project '#{project_name}'"
1495       return
1496     end
1497
1498     name=""
1499     if params[:name]
1500       name=params[:name] if params[:name]
1501     end
1502     pkg_name = "_patchinfo:#{name.gsub(/\W/, '_')}"
1503     patchinfo_path = "#{request.path}/#{pkg_name}"
1504
1505     # request binaries in project from backend
1506     binaries = list_all_binaries_in_path("/build/#{params[:project]}")
1507
1508     if binaries.length < 1 and not params[:force]
1509       render_error :status => 400, :errorcode => "no_matched_binaries",
1510         :message => "No binary packages were found in project repositories"
1511       return
1512     end
1513
1514     # FIXME: check for still building packages
1515
1516     # create patchinfo package
1517     if not DbPackage.find_by_project_and_name( params[:project], pkg_name )
1518       prj = DbProject.find_by_name( params[:project] )
1519       pkg = DbPackage.new(:name => pkg_name, :title => "Patchinfo", :description => "Collected packages for update")
1520       prj.db_packages << pkg
1521       Package.find(pkg_name, :project => params[:project]).save
1522       if name==""
1523         name=pkg_name
1524       end
1525     else
1526       # shall we do a force check here ?
1527     end
1528
1529     # create patchinfo XML file
1530     node = Builder::XmlMarkup.new(:indent=>2)
1531     xml = node.patchinfo(:name => name) do |n|
1532       binaries.each do |binary|
1533         node.binary(binary)
1534       end
1535       node.packager    @http_user.login
1536       node.bugzilla    ""
1537       node.swampid     ""
1538       node.category    ""
1539       node.rating      ""
1540       node.summary     ""
1541       node.description ""
1542       # FIXME add all bugnumbers from attributes
1543     end
1544     backend_put( patchinfo_path+"/_patchinfo?user="+@http_user.login+"&comment=generated%20file%20by%20frontend", xml )
1545
1546     render_ok
1547   end
1548
1549   def list_all_binaries_in_path path
1550     # ACL(list_all_binaries_in_path) TODO: check if this needs to be instrumented
1551     d = backend_get(path)
1552     data = REXML::Document.new(d)
1553     binaries = []
1554
1555     data.elements.each("directory/entry") do |e|
1556       name = e.attributes["name"]
1557       list_all_binaries_in_path("#{path}/#{name}").each do |l|
1558         binaries.push( l )
1559       end
1560     end
1561     data.elements.each("binarylist/binary") do |b|
1562       name = b.attributes["filename"]
1563       # strip main name from binary
1564       # patchinfos are only designed for rpms so far
1565       binaries.push( name.sub(/-[^-]*-[^-]*.rpm$/, '' ) )
1566     end
1567
1568     binaries.uniq!
1569     return binaries
1570   end
1571
1572   # create a id collection of all packages doing a package source link to this one
1573   # POST /source/<project>/<package>?cmd=showlinked
1574   def index_package_showlinked
1575     valid_http_methods :post
1576     project_name = params[:project]
1577     package_name = params[:package]
1578
1579     pack = DbPackage.find_by_project_and_name( project_name, package_name )
1580
1581     unless pack
1582       # package comes from remote instance
1583
1584       # FIXME: return an empty list for now
1585       # we could request the links on remote instance via that: but we would need to search also localy and merge ...
1586
1587 #      path = "/search/package/id?match=(@linkinfo/package=\"#{CGI.escape(package_name)}\"+and+@linkinfo/project=\"#{CGI.escape(project_name)}\")"
1588 #      answer = Suse::Backend.post path, nil
1589 #      render :text => answer.body, :content_type => 'text/xml'
1590       render :text => "<collection/>", :content_type => 'text/xml'
1591       return
1592     end
1593
1594     builder = FasterBuilder::XmlMarkup.new( :indent => 2 )
1595     xml = builder.collection() do |c|
1596       pack.find_linking_packages.each do |l|
1597         unless pack.disabled_for?('access', nil, nil) and not @http_user.can_access?(pack)
1598           p={}
1599           p[:project] = l.db_project.name
1600           p[:name] = l.name
1601           c.package(p)
1602         end
1603       end
1604     end
1605     render :text => xml.target!, :content_type => "text/xml"
1606   end
1607
1608   # POST /source/<project>/<package>?cmd=undelete
1609   def index_package_undelete
1610     valid_http_methods :post
1611     params[:user] = @http_user.login
1612     project_name = params[:project]
1613     package_name = params[:package]
1614
1615     path = request.path
1616     path << build_query_from_hash(params, [:cmd, :user, :comment])
1617     pass_to_backend path
1618   end
1619
1620   # FIXME: obsolete this for 3.0
1621   # POST /source/<project>/<package>?cmd=createSpecFileTemplate
1622   def index_package_createSpecFileTemplate
1623     # ACL(index_package_createSpecFileTemplate) TODO: check if this needs access check
1624     specfile_path = "#{request.path}/#{params[:package]}.spec"
1625     begin
1626       backend_get( specfile_path )
1627       render_error :status => 400, :errorcode => "spec_file_exists",
1628         :message => "SPEC file already exists."
1629       return
1630     rescue ActiveXML::Transport::NotFoundError
1631       specfile = File.read "#{RAILS_ROOT}/files/specfiletemplate"
1632       backend_put( specfile_path, specfile )
1633     end
1634     render_ok
1635   end
1636
1637   # OBS 3.0: this should be obsoleted, we have /build/ controller for this
1638   # POST /source/<project>/<package>?cmd=rebuild
1639   def index_package_rebuild
1640     project_name = params[:project]
1641     package_name = params[:package]
1642     repo_name = params[:repo]
1643     arch_name = params[:arch]
1644
1645     # check for sources in this or linked project
1646     pkg = DbPackage.find_by_project_and_name(project_name, package_name)
1647     unless pkg
1648       # check if this is a package on a remote OBS instance
1649       answer = Suse::Backend.get(request.path)
1650       unless answer
1651         render_error :status => 400, :errorcode => 'unknown_package',
1652           :message => "Unknown package '#{package_name}'"
1653         return
1654       end
1655     end
1656
1657     path = "/build/#{project_name}?cmd=rebuild&package=#{package_name}"
1658     if repo_name
1659       if p.repositories.find_by_name(repo_name).nil?
1660         render_error :status => 400, :errorcode => 'unknown_repository',
1661           :message=> "Unknown repository '#{repo_name}'"
1662         return
1663       end
1664       path += "&repository=#{repo_name}"
1665     end
1666     if arch_name
1667       path += "&arch=#{arch_name}"
1668     end
1669
1670     backend.direct_http( URI(path), :method => "POST", :data => "" )
1671
1672     render_ok
1673   end
1674
1675   # POST /source/<project>/<package>?cmd=commit
1676   def index_package_commit
1677     valid_http_methods :post
1678     params[:user] = @http_user.login
1679
1680     path = request.path
1681     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1682     pass_to_backend path
1683
1684     if params[:package] == "_product"
1685       update_product_autopackages params[:project]
1686     end
1687   end
1688
1689   # POST /source/<project>/<package>?cmd=commitfilelist
1690   def index_package_commitfilelist
1691     valid_http_methods :post
1692     params[:user] = @http_user.login
1693     project_name = params[:project]
1694     package_name = params[:package]
1695
1696     prj = DbProject.find_by_name(project_name)
1697     pkg = prj.find_package(package_name)
1698     # ACL(index_package_commitfilelist): in case of access, package is really hidden and shown as non existing to users without access
1699     if pkg and pkg.disabled_for?('access', nil, nil) and not @http_user.can_access?(pkg)
1700       render_error :status => 404, :errorcode => 'unknown_package',
1701       :message => "Unknown package #{params[:package]} in project #{params[:project]}"
1702       return
1703     end
1704
1705     # ACL(index_package_commitfilelist): sourceaccess gives permisson denied
1706     if pkg and pkg.disabled_for?('sourceaccess', nil, nil) and not @http_user.can_source_access?(pkg)
1707       render_error :status => 403, :errorcode => "source_access_no_permission",
1708       :message => "user #{params[:user]} has no read access to package #{params[:package]}, project #{params[:project]}"
1709       return
1710     end
1711
1712     # ACL(index_package_commitfilelist): lookup via :rev / :linkrev is prevented now by backend if $nosharedtrees is set
1713     path = request.path
1714     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1715     pass_to_backend path
1716     
1717     if params[:package] == "_product"
1718       update_product_autopackages params[:project]
1719     end
1720   end
1721
1722   # POST /source/<project>/<package>?cmd=diff
1723   def index_package_diff
1724     valid_http_methods :post
1725     project_name = params[:project]
1726     package_name = params[:package]
1727     oproject_name = params[:oproject]
1728     opackage_name = params[:opackage]
1729  
1730     path = request.path
1731     path << build_query_from_hash(params, [:cmd, :rev, :oproject, :opackage, :orev, :expand, :unified, :linkrev, :olinkrev, :missingok])
1732     pass_to_backend path
1733   end
1734
1735   # POST /source/<project>/<package>?cmd=linkdiff
1736   def index_package_linkdiff
1737     valid_http_methods :post
1738     project_name = params[:project]
1739     package_name = params[:package]
1740
1741     path = request.path
1742     path << build_query_from_hash(params, [:rev, :unified, :linkrev])
1743     pass_to_backend path
1744   end
1745
1746   # POST /source/<project>/<package>?cmd=copy
1747   def index_package_copy
1748     valid_http_methods :post
1749     params[:user] = @http_user.login
1750
1751     sproject = params[:project]
1752     sproject = params[:oproject] if params[:oproject]
1753     spackage = params[:package]
1754     spackage = params[:opackage] if params[:opackage]
1755
1756     # create target package, if it does not exist
1757     tpkg = DbPackage.find_by_project_and_name(params[:project], params[:package])
1758     if tpkg.nil?
1759       answer = Suse::Backend.get("/source/#{CGI.escape(sproject)}/#{CGI.escape(spackage)}/_meta")
1760       if answer
1761         p = Package.new(answer.body, :project => params[:project])
1762         p.name = params[:package]
1763         p.save
1764         tpkg = DbPackage.find_by_project_and_name(params[:project], params[:package])
1765       else
1766         render_error :status => 404, :errorcode => 'unknown_package',
1767           :message => "Unknown package #{spackage} in project #{sproject}"
1768         return
1769       end
1770     end
1771
1772     # ACL(index_package_copy): lookup via :rev / :linkrev is prevented now by backend if $nosharedtrees is set
1773     # We need to use the project name of package object, since it might come via a project linked project
1774     path = "/source/#{CGI.escape(tpkg.db_project.name)}/#{CGI.escape(tpkg.name)}"
1775     path << build_query_from_hash(params, [:cmd, :rev, :user, :comment, :oproject, :opackage, :orev, :expand, :keeplink, :repairlink, :linkrev, :olinkrev, :requestid, :dontupdatesource])
1776     
1777     pass_to_backend path
1778   end
1779
1780   # POST /source/<project>/<package>?cmd=runservice
1781   def index_package_runservice
1782     valid_http_methods :post
1783     params[:user] = @http_user.login
1784     project_name = params[:project]
1785     package_name = params[:package]
1786
1787     path = request.path
1788     path << build_query_from_hash(params, [:cmd, :comment])
1789     pass_to_backend path
1790   end
1791
1792   # POST /source/<project>/<package>?cmd=deleteuploadrev
1793   def index_package_deleteuploadrev
1794     valid_http_methods :post
1795     params[:user] = @http_user.login
1796     project_name = params[:project]
1797     package_name = params[:package]
1798
1799     path = request.path
1800     path << build_query_from_hash(params, [:cmd])
1801     pass_to_backend path
1802   end
1803
1804   # POST /source/<project>/<package>?cmd=linktobranch
1805   def index_package_linktobranch
1806     valid_http_methods :post
1807     params[:user] = @http_user.login
1808     prj_name = params[:project]
1809     pkg_name = params[:package]
1810     pkg_rev = params[:rev]
1811     pkg_linkrev = params[:linkrev]
1812
1813     prj = DbProject.find_by_name prj_name
1814     pkg = prj.db_packages.find_by_name(pkg_name)
1815     if pkg.nil?
1816       render_error :status => 404, :errorcode => 'unknown_package',
1817         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1818       return
1819     end
1820
1821     #convert link to branch
1822     rev = ""
1823     if not pkg_rev.nil? and not pkg_rev.empty?
1824       rev = "&orev=#{pkg_rev}"
1825     end
1826     linkrev = ""
1827     if not pkg_linkrev.nil? and not pkg_linkrev.empty?
1828       linkrev = "&linkrev=#{pkg_linkrev}"
1829     end
1830     Suse::Backend.post "/source/#{prj_name}/#{pkg_name}?cmd=linktobranch&user=#{CGI.escape(params[:user])}#{rev}#{linkrev}", nil
1831
1832     render_ok
1833   end
1834
1835   # POST /source/<project>/<package>?cmd=branch&target_project="optional_project"&target_package="optional_package"&update_project_attribute="alternative_attribute"&comment="message"
1836   def index_package_branch
1837     valid_http_methods :post
1838     params[:user] = @http_user.login
1839     prj_name = params[:project]
1840     pkg_name = params[:package]
1841     pkg_rev = params[:rev]
1842     target_project = params[:target_project]
1843     target_package = params[:target_package]
1844     if not params[:update_project_attribute]
1845       params[:update_project_attribute] = "OBS:UpdateProject"
1846     end
1847     logger.debug "branch call of #{prj_name} #{pkg_name}"
1848
1849     prj = DbProject.find_by_name prj_name
1850     if prj.nil? and DbProject.find_remote_project(prj_name).nil?
1851       render_error :status => 404, :errorcode => 'unknown_project',
1852         :message => "Unknown project #{prj_name}"
1853       return
1854     end
1855     if prj 
1856       pkg = prj.find_package( pkg_name )
1857       if pkg.nil?
1858         # Check if this is a package via project link to a remote OBS instance
1859         answer = Suse::Backend.get("/source/#{CGI.escape(prj.name)}/#{CGI.escape(pkg_name)}")
1860         unless answer
1861           render_error :status => 404, :errorcode => 'unknown_package',
1862             :message => "Unknown package #{pkg_name} in project #{prj.name}"
1863           return
1864         end
1865       end
1866     end
1867
1868     # is a update project defined and a package there ?
1869     aname = params[:update_project_attribute]
1870     name_parts = aname.split(/:/)
1871     if name_parts.length != 2
1872       raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
1873     end
1874
1875     if prj and a = prj.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
1876       if pa = DbPackage.find_by_project_and_name( a.values[0].value, pkg.name )
1877         # We have a package in the update project already, take that
1878         pkg = pa
1879         prj = pkg.db_project
1880         logger.debug "branch call found package in update project #{prj.name}"
1881       else
1882         update_prj = DbProject.find_by_name( a.values[0].value )
1883         update_pkg = update_prj.find_package( pkg.name )
1884         if update_pkg
1885           # We have no package in the update project yet, but sources are reachable via project link
1886           pkg = update_pkg
1887           prj = update_prj
1888         end
1889       end
1890     end
1891
1892     # validate and resolve devel package or devel project definitions
1893     if not params[:ignoredevel] and pkg and ( pkg.develproject or pkg.develpackage )
1894       pkg = pkg.resolve_devel_package
1895       prj = pkg.db_project
1896       logger.debug "devel project is #{prj.name} #{pkg.name}"
1897     end
1898
1899     # link against srcmd5 instead of plain revision
1900     unless pkg_rev.nil?
1901       begin
1902         dir = Directory.find({ :project => params[:project], :package => params[:package], :rev => params[:rev]})
1903       rescue
1904         render_error :status => 400, :errorcode => 'invalid_filelist',
1905           :message => "no such revision"
1906         return
1907       end
1908       if dir.has_attribute? 'srcmd5'
1909         pkg_rev = dir.srcmd5
1910       else
1911         render_error :status => 400, :errorcode => 'invalid_filelist',
1912           :message => "no srcmd5 revision found"
1913         return
1914       end
1915     end
1916  
1917     oprj_name = "home:#{@http_user.login}:branches:#{prj_name}"
1918     oprj_name = "home:#{@http_user.login}:branches:#{prj.name}" if prj
1919     opkg_name = pkg_name
1920     opkg_name = pkg.name if pkg
1921     oprj_name = target_project unless target_project.nil?
1922     opkg_name = target_package unless target_package.nil?
1923
1924     #create branch container
1925     oprj = DbProject.find_by_name oprj_name
1926     raise IllegalRequestError.new "invalid_project_name" unless valid_project_name?(oprj_name)
1927     if oprj.nil?
1928       unless @http_user.can_create_project?(oprj_name)
1929         render_error :status => 403, :errorcode => "create_project_no_permission",
1930           :message => "no permission to create project '#{oprj_name}' while executing branch command"
1931         return
1932       end
1933
1934       DbProject.transaction do
1935         oprj = DbProject.new :name => oprj_name, :title => "Branch of #{prj_name}"
1936         oprj.add_user @http_user, "maintainer"
1937         oprj.flags.create( :status => "disable", :flag => 'publish')
1938         # ACL(index_package_branch): inherit all access flags from branched project
1939         if prj 
1940           if prj.disabled_for?('access', nil, nil)
1941             oprj.flags.create( :status => "disable", :flag => 'access')
1942           end
1943           if prj.disabled_for?('sourceaccess', nil, nil)
1944             oprj.flags.create( :status => "disable", :flag => 'sourceaccess')
1945           end
1946           if prj.disabled_for?('binarydownload', nil, nil)
1947             oprj.flags.create( :status => "disable", :flag => 'binarydownload')
1948           end
1949           if prj.enabled_for?('privacy', nil, nil)
1950             oprj.flags.create( :status => "enable", :flag => 'privacy')
1951           end
1952         end
1953         if prj
1954           # FIXME: support this also for remote projects
1955           prj.repositories.each do |repo|
1956             orepo = oprj.repositories.create :name => repo.name
1957             orepo.architectures = repo.architectures
1958             orepo.path_elements << PathElement.new(:link => repo, :position => 1)
1959           end
1960         end
1961         oprj.store
1962       end
1963     end
1964
1965     #create branch package
1966     unless valid_package_name? opkg_name
1967       render_error :status => 400, :errorcode => "invalid_package_name",
1968         :message => "invalid package name '#{opkg_name}'"
1969     end
1970     if opkg = oprj.db_packages.find_by_name(opkg_name)
1971       if params[:force]
1972         # shall we clean all files here ?
1973       else
1974         render_error :status => 400, :errorcode => "double_branch_package",
1975           :message => "branch target package already exists: #{oprj_name}/#{opkg_name}"
1976         return
1977       end
1978
1979       unless @http_user.can_modify_package?(opkg)
1980         render_error :status => 403, :errorcode => "create_package_no_permission",
1981           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1982         return
1983       end
1984     else
1985       unless @http_user.can_create_package_in?(oprj)
1986         render_error :status => 403, :errorcode => "create_package_no_permission",
1987           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1988         return
1989       end
1990
1991       if pkg
1992         opkg = oprj.db_packages.create(:name => opkg_name, :title => pkg.title, :description => params.has_key?(:comment) ? params[:comment] : pkg.description)
1993       else
1994         opkg = oprj.db_packages.create(:name => opkg_name, :description => params.has_key?(:comment) ? params[:comment] : "" )
1995       end
1996       opkg.add_user @http_user, "maintainer"
1997       opkg.store
1998     end
1999
2000     #create branch of sources in backend
2001     rev = ""
2002     if not pkg_rev.nil? and not pkg_rev.empty?
2003       rev = "&orev=#{pkg_rev}"
2004     end
2005     comment = params.has_key?(:comment) ? "&comment=#{CGI.escape(params[:comment])}" : ""
2006     if pkg
2007       Suse::Backend.post "/source/#{oprj_name}/#{opkg_name}?cmd=branch&oproject=#{CGI.escape(prj.name)}&opackage=#{CGI.escape(pkg.name)}#{rev}&user=#{CGI.escape(@http_user.login)}#{comment}", nil
2008       render_ok :data => {:targetproject => oprj_name, :targetpackage => opkg_name, :sourceproject => prj.name, :sourcepackage => pkg.name}
2009     else
2010       Suse::Backend.post "/source/#{oprj_name}/#{opkg_name}?cmd=branch&oproject=#{CGI.escape(prj_name)}&opackage=#{CGI.escape(pkg_name)}#{rev}&user=#{CGI.escape(@http_user.login)}#{comment}", nil
2011       render_ok :data => {:targetproject => oprj_name, :targetpackage => opkg_name, :sourceproject => prj_name, :sourcepackage => pkg_name}
2012     end
2013   end
2014
2015   # POST /source/<project>/<package>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
2016   def index_package_set_flag
2017     valid_http_methods :post
2018
2019     required_parameters :project, :package, :flag, :status
2020
2021     prj_name = params[:project]
2022     pkg_name = params[:package]
2023
2024     # we can savely assume it exists - this function is called through dispatch_command
2025     prj = DbProject.find_by_name prj_name
2026     pkg = prj.find_package( pkg_name )
2027     if pkg.nil?
2028       render_error :status => 404, :errorcode => "unknown_package",
2029         :message => "Unknown package '#{pkg_name}' in project '#{prj_name}'"
2030       return
2031     end
2032
2033     # first remove former flags of the same class
2034     begin
2035       pkg.remove_flag(params[:flag], params[:repository], params[:arch])
2036       pkg.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
2037     rescue ArgumentError => e
2038       render_error :status => 400, :errorcode => 'invalid_flag', :message => e.message
2039       return
2040     end
2041     pkg.store
2042     render_ok
2043   end
2044
2045   # POST /source/<project>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
2046   def index_project_set_flag
2047     valid_http_methods :post
2048
2049     required_parameters :project, :flag, :status
2050
2051     prj_name = params[:project]
2052
2053     prj = DbProject.find_by_name prj_name
2054     if prj.nil?
2055       render_error :status => 404, :errorcode => "unknown_project",
2056         :message => "Unknown project '#{prj_name}'"
2057       return
2058     end
2059
2060     begin
2061       # first remove former flags of the same class
2062       prj.remove_flag(params[:flag], params[:repository], params[:arch])
2063       prj.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
2064     rescue ArgumentError => e
2065       render_error :status => 400, :errorcode => 'invalid_flag', :message => e.message
2066       return
2067     end
2068       
2069     # ACL(index_project_set_flag): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
2070     if prj and prj.disabled_for?('access', params[:repository], params[:arch]) and not @http_user.can_access?(prj)
2071       render_error :status => 404, :errorcode => 'unknown_project',
2072       :message => "Unknown project '#{prj_name}'"
2073       return
2074     end
2075     # ACL(index_project_set_flag): you are not allowed to protect an unprotected project with access
2076     if params[:flag] == "access" and params[:status] == "disable" and prj.enabled_for?('access', params[:repository], params[:arch]) and not
2077         @http_user.is_admin?
2078       render_error :status => 403, :errorcode => "change_project_protection_level",
2079       :message => "admin rights are required to raise the protection level of a project"
2080       return
2081     end
2082     # ACL(index_project_set_flag): you are not allowed to protect an unprotected project with sourceaccess
2083     if params[:flag] == "sourceaccess" and params[:status] == "disable" and prj.enabled_for?('sourceaccess', params[:repository], params[:arch]) and not
2084         @http_user.is_admin?
2085       render_error :status => 403, :errorcode => "change_project_protection_level",
2086       :message => "admin rights are required to raise the protection level of a project"
2087       return
2088     end
2089
2090     prj.store
2091     render_ok
2092   end
2093
2094   # POST /source/<project>/<package>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
2095   def index_package_remove_flag
2096     valid_http_methods :post
2097
2098     required_parameters :project, :package, :flag
2099     
2100     pkg = DbPackage.find_by_project_and_name( params[:project], params[:package] )
2101     pkg.remove_flag(params[:flag], params[:repository], params[:arch])
2102     pkg.store
2103     render_ok
2104   end
2105
2106   # POST /source/<project>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
2107   def index_project_remove_flag
2108     valid_http_methods :post
2109     required_parameters :project, :flag
2110
2111     prj_name = params[:project]
2112
2113     prj = DbProject.find_by_name prj_name
2114     if prj.nil?
2115       render_error :status => 404, :errorcode => "unknown_project",
2116         :message => "Unknown project '#{prj_name}'"
2117       return
2118     end
2119
2120     # ACL(index_project_remove_flag): in case of access, project is really hidden, e.g. does not get listed, accessing says project is not existing
2121     if prj and prj.disabled_for?('access', params[:repository], params[:arch]) and not @http_user.can_access?(prj)
2122       render_error :status => 404, :errorcode => 'unknown_project',
2123       :message => "Unknown project '#{prj_name}'"
2124       return
2125     end
2126
2127     prj.remove_flag(params[:flag], params[:repository], params[:arch])
2128     prj.store
2129     render_ok
2130   end
2131
2132   def valid_project_name? name
2133     name =~ /^\w[-_+\w\.:]*$/
2134   end
2135
2136   def valid_package_name? name
2137     return true if name == "_pattern"
2138     return true if name == "_project"
2139     return true if name == "_product"
2140     return true if name =~ /^_product:[-_+\w\.:]*$/
2141     name =~ /^\w[-_+\w\.:]*$/
2142   end
2143 end