use correct project for branching in case of exiting update project definition (...
[opensuse:build-service.git] / src / api / app / controllers / source_controller.rb
1 require "rexml/document"
2
3 class SourceController < ApplicationController
4   validate_action :index => :directory, :packagelist => :directory, :filelist => :directory
5   validate_action :project_meta => :project, :package_meta => :package, :pattern_meta => :pattern
6  
7   skip_before_filter :extract_user, :only => [:file, :project_meta, :project_config] 
8
9   def index
10     projectlist
11   end
12
13   def projectlist
14     if request.post?
15       # a bit danguerous, never implment a command without proper permission check
16       dispatch_command
17     elsif request.get?
18       if params[:deleted]
19         pass_to_backend
20       else
21         @dir = Project.find :all
22         render :text => @dir.dump_xml, :content_type => "text/xml"
23       end
24     end
25   end
26
27   def index_project
28     project_name = params[:project]
29     pro = DbProject.find_by_name project_name
30     if pro.nil?
31       unless params[:cmd] == "undelete"
32         render_error :status => 404, :errorcode => 'unknown_project',
33           :message => "Unknown project #{project_name}"
34         return
35       end
36     elsif params[:cmd] == "undelete"
37       render_error :status => 403, :errorcode => 'create_project_no_permission',
38           :message => "Can not undelete, project exists already '#{project_name}'"
39       return
40     end
41     
42     if request.get?
43       if params[:deleted]
44         pass_to_backend
45       else
46         @dir = Package.find :all, :project => project_name
47         render :text => @dir.dump_xml, :content_type => "text/xml"
48       end
49       return
50     elsif request.delete?
51       unless @http_user.can_modify_project?(pro)
52         logger.debug "No permission to delete project #{project_name}"
53         render_error :status => 403, :errorcode => 'delete_project_no_permission',
54           :message => "Permission denied (delete project #{project_name})"
55         return
56       end
57
58       #deny deleting if other packages use this as develproject
59       unless pro.develpackages.empty?
60         msg = "Unable to delete project #{pro.name}; following packages use this project as develproject: "
61         msg += pro.develpackages.map {|pkg| pkg.db_project.name+"/"+pkg.name}.join(", ")
62         render_error :status => 400, :errorcode => 'develproject_dependency',
63           :message => msg
64         return
65       end
66       #check all packages, if any get refered as develpackage
67       pro.db_packages.each do |pkg|
68         unless pkg.develpackages.empty?
69           msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
70           msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
71           render_error :status => 400, :errorcode => 'develpackage_dependency',
72             :message => msg
73           return
74         end
75       end
76
77       #find linking repos
78       lreps = Array.new
79       pro.repositories.each do |repo|
80         repo.linking_repositories.each do |lrep|
81           lreps << lrep
82         end
83       end
84
85       if lreps.length > 0
86         if params[:force] and not params[:force].empty?
87           #replace links to this projects with links to the "deleted" project
88           del_repo = DbProject.find_by_name("deleted").repositories[0]
89           lreps.each do |link_rep|
90             link_rep.path_elements.find(:all).each { |pe| pe.destroy }
91             link_rep.path_elements.create(:link => del_repo, :position => 1)
92             link_rep.save
93             #update backend
94             link_rep.db_project.store
95           end
96         else
97           lrepstr = lreps.map{|l| l.db_project.name+'/'+l.name}.join "\n"
98           render_error :status => 403, :errorcode => "repo_dependency",
99             :message => "Unable to delete project #{project_name}; following repositories depend on this project:\n#{lrepstr}\n"
100           return
101         end
102       end
103
104       #destroy all packages
105       pro.db_packages.each do |pack|
106         DbPackage.transaction do
107           logger.info "destroying package #{pack.name}"
108           pack.destroy
109         end
110       end
111
112       DbProject.transaction do
113         logger.info "destroying project #{pro.name}"
114         pro.destroy
115         logger.debug "delete request to backend: /source/#{pro.name}"
116         Suse::Backend.delete "/source/#{pro.name}"
117       end
118
119       render_ok
120       return
121     elsif request.post?
122       cmd = params[:cmd]
123
124       if ['undelete'].include?(cmd) 
125         unless @http_user.can_create_project?(project_name)
126           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
127             :message => "no permission to execute command '#{cmd}'"
128           return
129         end
130         dispatch_command
131
132         # read meta data from backend to restore database object
133         path = request.path + "/_meta"
134         Project.new(backend_get(path)).save
135
136         # restore all package meta data objects in DB
137         backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}'"
138         backend_pkgs.each_package do |package|
139           path = request.path + "/" + package.name + "/_meta"
140           Package.new(backend_get(path), :project => params[:project]).save
141         end
142         return
143       end
144
145       if @http_user.can_modify_project?(pro)
146         dispatch_command
147       else
148         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
149           :message => "no permission to execute command '#{cmd}'"
150         return
151       end
152     else
153       render_error :status => 400, :errorcode => "illegal_request",
154         :message => "illegal POST request to #{request.request_uri}"
155     end
156   end
157
158   def index_package
159     valid_http_methods :get, :delete, :post
160     project_name = params[:project]
161     package_name = params[:package]
162     cmd = params[:cmd]
163     deleted = params[:deleted]
164
165     prj = DbProject.find_by_name(project_name)
166     unless prj
167       if request.get?
168         # Check if this is a package on a remote OBS instance
169         answer = Suse::Backend.get(request.path)
170         if answer
171           pass_to_backend
172           return
173         end
174       end
175       render_error :status => 404, :errorcode => "unknown_project",
176         :message => "unknown project '#{project_name}'"
177       return
178     end
179     # look also via linked projects, package source may come from another project
180     begin
181       pkg = prj.find_package(package_name)
182     rescue DbProject::CycleError => e
183       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
184       return
185     end
186     unless deleted.blank? and not request.delete?
187       unless package_name == "_project" or pkg or DbProject.find_remote_project(project_name)
188         render_error :status => 404, :errorcode => "unknown_package",
189           :message => "unknown package '#{package_name}' in project '#{project_name}'"
190         return
191       end
192     end
193
194     if request.get?
195       pass_to_backend
196       return
197     elsif request.delete?
198       if package_name == "_project"
199         render_error :status => 403, :errorcode => "delete_package_no_permission",
200           :message => "_project package can not be deleted."
201         return
202       end
203
204       if not @http_user.can_modify_package?(pkg)
205         render_error :status => 403, :errorcode => "delete_package_no_permission",
206           :message => "no permission to delete package #{package_name}"
207         return
208       end
209       
210       #deny deleting if other packages use this as develpackage
211       # Shall we offer a --force option here as well ?
212       # Shall we ask the other package owner accepting to be a devel package ?
213       unless pkg.develpackages.empty?
214         msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
215         msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
216         render_error :status => 400, :errorcode => 'develpackage_dependency',
217           :message => msg
218         return
219       end
220
221       DbPackage.transaction do
222         pkg.destroy
223         Suse::Backend.delete "/source/#{project_name}/#{package_name}"
224         if package_name == "_product"
225           update_product_autopackages
226         end
227       end
228       render_ok
229     elsif request.post?
230       if ['undelete'].include?(cmd) 
231         unless @http_user.can_modify_project?(prj)
232           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
233             :message => "no permission to execute command '#{cmd}'"
234           return
235         end
236         dispatch_command
237
238         # read meta data from backend to restore database object
239         path = request.path + "/_meta"
240         Package.new(backend_get(path), :project => params[:project]).save
241         return
242       end
243
244       if pkg.nil?
245         unless @http_user.can_modify_project?(prj)
246           render_error :status => 403, :errorcode => "cmd_execution_no_permission",
247             :message => "no permission to execute command '#{cmd}' for not existing package"
248           return
249         end
250       elsif not ['diff', 'branch'].include?(cmd) and not @http_user.can_modify_package?(pkg)
251         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
252           :message => "no permission to execute command '#{cmd}'"
253         return
254       end
255       
256       dispatch_command
257     end
258   end
259
260   # updates packages automatically generated in the backend after submitting a product file
261   def update_product_autopackages
262     backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}' and starts-with(@name,'_product:')"
263     b_pkg_index = backend_pkgs.each_package.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
264     frontend_pkgs = DbProject.find_by_name(params[:project]).db_packages.find(:all, :conditions => "name LIKE '_product:%'")
265     f_pkg_index = frontend_pkgs.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
266
267     all_pkgs = [b_pkg_index.keys, f_pkg_index.keys].flatten.uniq
268
269     wt_state = ActiveXML::Config.global_write_through
270     begin
271       ActiveXML::Config.global_write_through = false
272       all_pkgs.each do |pkg|
273         if b_pkg_index.has_key?(pkg) and not f_pkg_index.has_key?(pkg)
274           # new autopackage, import in database
275           Package.new(b_pkg_index[pkg].dump_xml, :project => params[:project]).save
276         elsif f_pkg_index.has_key?(pkg) and not b_pkg_index.has_key?(pkg)
277           # autopackage was removed, remove from database
278           f_pkg_index[pkg].destroy
279         end
280       end
281     ensure
282       ActiveXML::Config.global_write_through = wt_state
283     end
284   end
285
286   # /source/:project/_attribute/:attribute
287   # /source/:project/:package/:binary/_attribute/:attribute
288   def attribute_meta
289     valid_http_methods :get, :post, :delete
290     params[:user] = @http_user.login if @http_user
291
292     binary=nil
293     binary=params[:binary] if params[:binary]
294
295     if params[:package]
296       @attribute_container = DbPackage.find_by_project_and_name(params[:project], params[:package])
297       unless @attribute_container
298         render_error :message => "Unknown package '#{params[:project]}/#{params[:package]}'",
299           :status => 404, :errorcode => "unknown_package"
300         return
301       end
302     else
303       @attribute_container = DbProject.find_by_name(params[:project])
304       unless @attribute_container
305         render_error :message => "Unknown project '#{params[:project]}'",
306           :status => 404, :errorcode => "unknown_project"
307         return
308       end
309     end
310
311     if request.get?
312       params[:binary]=binary if binary
313       render :text => @attribute_container.render_attribute_axml(params), :content_type => 'text/xml'
314       return
315     end
316
317     if request.post?
318       begin
319         req = BsRequest.new(request.body.read)
320         req.data # trigger XML parsing
321       rescue ActiveXML::ParseError => e
322         render_error :message => "Invalid XML",
323           :status => 400, :errorcode => "invalid_xml"
324         return
325       end
326     end
327
328     # permission checking
329     if params[:attribute]
330       aname = params[:attribute]
331       name_parts = aname.split(/:/)
332       if name_parts.length != 2
333         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
334       end
335       if a=@attribute_container.find_attribute(name_parts[0],name_parts[1],binary)
336         unless @http_user.can_modify_attribute? a
337           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
338             :message => "user #{user.login} has no permission to modify attribute"
339           return
340         end
341       else
342         unless request.post?
343           render_error :status => 403, :errorcode => "not_existing_attribute", 
344             :message => "Attempt to modify not existing attribute"
345           return
346         end
347         unless @http_user.can_create_attribute_in? @attribute_container, :namespace => name_parts[0], :name => name_parts[1]
348           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
349             :message => "user #{user.login} has no permission to change attribute"
350           return
351         end
352       end
353     else
354       if request.post?
355         req.each_attribute do |attr|
356           begin
357             can_create = @http_user.can_create_attribute_in? @attribute_container, :namespace => attr.namespace, :name => attr.name
358           rescue ActiveRecord::RecordNotFound => e
359             render_error :status => 404, :errorcode => "not_found",
360               :message => e.message
361             return
362           rescue ArgumentError => e
363             render_error :status => 400, :errorcode => "change_attribute_attribute_error",
364               :message => e.message
365             return
366           end
367           unless can_create
368             render_error :status => 403, :errorcode => "change_attribute_no_permission", 
369               :message => "user #{user.login} has no permission to change attribute"
370             return
371           end
372         end
373       else
374         render_error :status => 403, :errorcode => "internal_error", 
375           :message => "INTERNAL ERROR: unhandled request"
376         return
377       end
378     end
379
380     # execute action
381     if request.post?
382       req.each_attribute do |attr|
383         begin
384           @attribute_container.store_attribute_axml(attr, binary)
385         rescue DbProject::SaveError => e
386           render_error :status => 403, :errorcode => "save_error", :message => e.message
387           return
388         rescue DbPackage::SaveError => e
389           render_error :status => 403, :errorcode => "save_error", :message => e.message
390           return
391         end
392       end
393       @attribute_container.store
394       render_ok
395     elsif request.delete?
396       @attribute_container.find_attribute(name_parts[0], name_parts[1],binary).destroy
397       @attribute_container.store
398       render_ok
399     else
400       render_error :message => "INTERNAL ERROR: Unhandled operation",
401         :status => 404, :errorcode => "unknown_operation"
402     end
403   end
404
405   # /source/:project/_pattern/:pattern
406   def pattern_meta
407     valid_http_methods :get, :put, :delete
408
409     params[:user] = @http_user.login if @http_user
410     
411     @project = DbProject.find_by_name params[:project]
412     unless @project
413       render_error :message => "Unknown project '#{params[:project]}'",
414         :status => 404, :errorcode => "unknown_project"
415       return
416     end
417
418     if request.get?
419       pass_to_backend
420     else
421       # PUT and DELETE
422       permerrormsg = nil
423       if request.put?
424         permerrormsg = "no permission to store pattern"
425       elsif request.delete?
426         permerrormsg = "no permission to delete pattern"
427       end
428
429       unless @http_user.can_modify_project? @project
430         logger.debug "user #{user.login} has no permission to modify project #{@project}"
431         render_error :status => 403, :errorcode => "change_project_no_permission", 
432           :message => permerrormsg
433         return
434       end
435       
436       path = request.path + build_query_from_hash(params, [:rev, :user, :comment])
437       pass_to_backend path
438     end
439   end
440
441   # GET /source/:project/_pattern
442   def index_pattern
443     valid_http_methods :get
444
445     unless DbProject.find_by_name(params[:project])
446       render_error :message => "Unknown project '#{params[:project]}'",
447         :status => 404, :errorcode => "unknown_project"
448       return
449     end
450     
451     pass_to_backend
452   end
453
454   def project_meta
455     valid_http_methods :get, :put
456
457     project_name = params[:project]
458     if project_name.nil?
459       render_error :status => 400, :errorcode => 'missing_parameter',
460         :message => "parameter 'project' is missing"
461       return
462     end
463
464     if request.get?
465       @project = DbProject.find_by_name( project_name )
466
467       if @project
468         render :text => @project.to_axml(params[:view]), :content_type => 'text/xml'
469       elsif DbProject.find_remote_project(project_name)
470         # project from remote buildservice, get metadata from backend
471         pass_to_backend
472       else
473         render_error :message => "Unknown project '#{project_name}'",
474           :status => 404, :errorcode => "unknown_project"
475       end
476       return
477     end
478
479     return unless extract_user
480
481     #assemble path for backend
482     params[:user] = @http_user.login
483     path = request.path
484     path += build_query_from_hash(params, [:user, :comment, :rev])
485
486     if request.put?
487       unless valid_project_name? project_name
488         render_error :status => 400, :errorcode => "invalid_project_name",
489           :message => "invalid project name '#{project_name}'"
490         return
491       end
492
493       # Need permission
494       logger.debug "Checking permission for the put"
495       allowed = false
496       request_data = request.raw_post
497
498       @project = DbProject.find_by_name( project_name )
499       if @project
500         #project exists, change it
501         unless @http_user.can_modify_project? @project
502           logger.debug "user #{user.login} has no permission to modify project #{@project}"
503           render_error :status => 403, :errorcode => "change_project_no_permission", 
504             :message => "no permission to change project"
505           return
506         end
507       else
508         #project is new
509         unless @http_user.can_create_project? project_name
510           logger.debug "Not allowed to create new project"
511           render_error :status => 403, :errorcode => 'create_project_no_permission',
512             :message => "not allowed to create new project '#{project_name}'"
513           return
514         end
515       end
516
517       p = Project.new(request_data, :name => project_name)
518
519       if p.name != project_name
520         render_error :status => 400, :errorcode => 'project_name_mismatch',
521           :message => "project name in xml data does not match resource path component"
522         return
523       end
524
525       if (p.has_element? :remoteurl or p.has_element? :remoteproject) and not @http_user.is_admin?
526         render_error :status => 403, :errorcode => "change_project_no_permission",
527           :message => "admin rights are required to change remoteurl or remoteproject"
528         return
529       end
530
531       p.add_person(:userid => @http_user.login) unless @project
532       p.save
533
534       render_ok
535     else
536       render_error :status => 400, :errorcode => 'illegal_request',
537         :message => "Illegal request: POST #{request.path}"
538     end
539   end
540
541   def project_config
542     valid_http_methods :get, :put
543
544     #check if project exists
545     unless (@project = DbProject.find_by_name(params[:project]))
546       render_error :status => 404, :errorcode => 'project_not_found',
547         :message => "Unknown project #{params[:project]}"
548       return
549     end
550
551     if request.get?
552       path = request.path
553       path += build_query_from_hash(params, [:rev])
554       pass_to_backend path
555       return
556     end
557
558     return unless extract_user
559
560     #assemble path for backend
561     params[:user] = @http_user.login
562     path = request.path
563     path += build_query_from_hash(params, [:user, :comment])
564
565     if request.put?
566       unless @http_user.can_modify_project?(@project)
567         render_error :status => 403, :errorcode => 'put_project_config_no_permission',
568           :message => "No permission to write build configuration for project '#{params[:project]}'"
569         return
570       end
571
572       pass_to_backend path
573       return
574     end
575     render_error :status => 400, :errorcode => 'illegal_request',
576         :message => "Illegal request: #{request.path}"
577   end
578
579   def project_pubkey
580     valid_http_methods :get, :delete
581
582     #assemble path for backend
583     params[:user] = @http_user.login if request.delete?
584     path = request.path
585     path += build_query_from_hash(params, [:user, :comment, :rev])
586
587     #check if project exists
588     unless (@project = DbProject.find_by_name(params[:project]))
589       render_error :status => 404, :errorcode => 'project_not_found',
590         :message => "Unknown project #{params[:project]}"
591       return
592     end
593
594     if request.get?
595       pass_to_backend path
596     elsif request.delete?
597       #check for permissions
598       unless @http_user.can_modify_project?(@project)
599         render_error :status => 403, :errorcode => 'delete_project_pubkey_no_permission',
600           :message => "No permission to delete public key for project '#{params[:project]}'"
601         return
602       end
603
604       pass_to_backend path
605       return
606     end
607   end
608
609   def update_package_meta(project_name, package_name, request_data, user=nil, comment=nil)
610     allowed = false
611     # Try to fetch the package to see if it already exists
612     @package = Package.find( package_name, :project => project_name )
613
614     if @package
615       # Being here means that the package already exists
616       allowed = permissions.package_change? @package
617       if allowed
618         @package = Package.new( request_data, :project => project_name, :name => package_name )
619       else
620         logger.debug "user #{user} has no permission to change package #{@package}"
621         render_error :status => 403, :errorcode => "change_package_no_permission",
622           :message => "no permission to change package"
623         return
624       end
625     else
626       # Ok, the package is new
627       allowed = permissions.package_create?( project_name )
628
629       if allowed
630         #FIXME: parameters that get substituted into the url must be specified here... should happen
631         #somehow automagically... no idea how this might work
632         @package = Package.new( request_data, :project => project_name, :name => package_name )
633       else
634         # User is not allowed by global permission.
635         logger.debug "Not allowed to create new packages"
636         render_error :status => 403, :errorcode => "create_package_no_permission",
637           :message => "no permission to create package for project #{project_name}"
638         return
639       end
640     end
641
642     if allowed
643       if( @package.name != package_name )
644         render_error :status => 400, :errorcode => 'package_name_mismatch',
645           :message => "package name in xml data does not match resource path component"
646         return
647       end
648
649       begin
650         @package.save
651       rescue DbPackage::CycleError => e
652         render_error :status => 400, :errorcode => 'devel_cycle', :message => e.message
653         return
654       end
655       render_ok
656     else
657       logger.debug "user #{user} has no permission to write package meta for package #{@package}"
658     end
659   end
660   private :update_package_meta
661
662   def package_meta
663     valid_http_methods :put, :get
664    
665     project_name = params[:project]
666     package_name = params[:package]
667
668     if project_name.nil?
669       render_error :status => 400, :errorcode => "parameter_missing",
670         :message => "parameter 'project' missing"
671       return
672     end
673
674     if package_name.nil?
675       render_error :status => 400, :errorcode => "parameter_missing",
676         :message => "parameter 'package' missing"
677       return
678     end
679
680     unless pro = DbProject.find_by_name(project_name)
681       pro, pro_name = DbProject.find_remote_project(project_name)
682       unless request.get? and pro
683         render_error :status => 404, :errorcode => "unknown_project",
684           :message => "Unknown project '#{project_name}'"
685         return
686       end
687     end
688
689     unless valid_package_name? package_name
690       render_error :status => 400, :errorcode => "invalid_package_name",
691         :message => "invalid package name '#{package_name}'"
692       return
693     end
694
695     if request.get?
696       begin
697         pack = pro.find_package( package_name )
698       rescue DbProject::CycleError => e
699         render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
700         return
701       end
702       unless pack
703         # check if this comes from a remote project, also true for _project package
704         answer = Suse::Backend.get(request.path)
705         if answer
706           render :text => answer.body.to_s, :content_type => 'text/xml'
707         else
708           render_error :status => 404, :errorcode => "unknown_package",
709             :message => "Unknown package '#{package_name}'"
710         end
711         return
712       end
713
714       render :text => pack.to_axml(params[:view]), :content_type => 'text/xml'
715     else
716       update_package_meta(project_name, package_name, request.raw_post, @http_user.login, params[:comment])
717     end
718   end
719
720   def file
721     valid_http_methods :get, :delete, :put
722     project_name = params[:project]
723     package_name = params[:package]
724     file = params[:file]
725
726     path = "/source/#{project_name}/#{package_name}/#{file}"
727
728     if request.get?
729       path += build_query_from_hash(params, [:rev, :meta])
730       pass_to_backend path
731       return
732     end
733
734     #authenticate
735     return unless extract_user
736
737     pack = DbPackage.find_by_project_and_name(project_name, package_name)
738     if package_name == "_project"
739       allowed = permissions.project_change? project_name
740     else
741       if pack.nil? and package_name != "_project"
742         render_error :status => 403, :errorcode => 'not_found',
743           :message => "The given package #{package_name} does not exist in project #{project_name}"
744         return
745       end
746       allowed = permissions.package_change? pack
747     end
748
749     params[:user] = @http_user.login
750     if request.put?
751       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink, :meta])
752       
753       if  allowed
754         # file validation where possible
755         if params[:file] == "_link"
756            validator = Suse::Validator.new( "link" )
757            validator.validate(request)
758         elsif params[:file] == "_aggregate"
759            validator = Suse::Validator.new( "aggregate" )
760            validator.validate(request)
761         end
762
763         pass_to_backend path
764         pack.update_timestamp
765         if package_name == "_product"
766           update_product_autopackages
767         end
768       else
769         render_error :status => 403, :errorcode => 'put_file_no_permission',
770           :message => "Insufficient permissions to store file in package #{package_name}, project #{project_name}"
771       end
772     elsif request.delete?
773       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink])
774       
775       if  allowed
776         Suse::Backend.delete path
777         pack = DbPackage.find_by_project_and_name(project_name, package_name)
778         pack.update_timestamp
779         if package_name == "_product"
780           update_product_autopackages
781         end
782         render_ok
783       else
784         render_error :status => 403, :errorcode => 'delete_file_no_permission',
785           :message => "Insufficient permissions to delete file"
786       end
787     end
788   end
789
790   private
791
792   # POST /source?cmd=branch
793   def index_branch
794     # set defaults
795     mparams=params
796     if not params[:target_project]
797       mparams[:target_project] = "home:#{@http_user.login}:branches:#{params[:attribute].gsub(':', '_')}"
798       mparams[:target_project] += ":#{params[:package]}" if params[:package]
799     end
800     if not params[:update_project_attribute]
801       params[:update_project_attribute] = "OBS:UpdateProject"
802     end
803     if not params[:attribute]
804       params[:attribute] = "OBS:Maintained"
805     end
806
807     # permission check
808     unless @http_user.can_create_project?(mparams[:target_project])
809       render_error :status => 403, :errorcode => "create_project_no_permission",
810         :message => "no permission to create project '#{mparams[:target_project]}' while executing branch command"
811       return
812     end
813
814     # find packages
815     at = AttribType.find_by_name(params[:attribute])
816     if not at
817       render_error :status => 403, :errorcode => 'not_found',
818         :message => "The given attribute #{params[:attribute]} does not exist"
819       return
820     end
821     if params[:value]
822       @packages = DbPackage.find_by_attribute_type_and_value( at, params[:value], params[:package] )
823     else
824       @packages = DbPackage.find_by_attribute_type( at, params[:package] )
825     end
826     unless @packages.length > 0
827       render_error :status => 403, :errorcode => "not_found",
828         :message => "no packages could get found"
829       return
830     end
831
832     #create branch project
833     oprj = DbProject.find_by_name mparams[:target_project]
834     if oprj.nil?
835       DbProject.transaction do
836         oprj = DbProject.new :name => mparams[:target_project], :title => "Branch Project _FIXME_", :description => "_FIXME_"
837         oprj.add_user @http_user, "maintainer"
838         oprj.build_flags.create( :position => 1, :status => "disable" )
839         oprj.publish_flags.create( :position => 1, :status => "disable" )
840         oprj.store
841       end
842     else
843       unless @http_user.can_modify_project?(oprj)
844         render_error :status => 403, :errorcode => "modify_project_no_permission",
845           :message => "no permission to modify project '#{mparams[:target_project]}' while executing branch by attribute command"
846         return
847       end
848     end
849
850     # create package branches
851     # collect also the needed repositories here
852     @packages.each do |p|
853     
854       # is a update project defined and a package there ?
855       pac = p
856       aname = params[:update_project_attribute]
857       name_parts = aname.split(/:/)
858       if name_parts.length != 2
859         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
860       end
861
862       # find origin package to be branched
863       branch_target_project = pac.db_project.name
864       branch_target_package = pac.name
865       if a = p.db_project.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
866         if pa = DbPackage.find_by_project_and_name( a.values[0].value, p.name )
867           pac = pa
868           branch_target_project = pac.db_project.name
869           branch_target_package = pac.name
870         else
871           # package exists not yet in update project, to be created
872           branch_target_project = a.name
873         end
874       end
875       proj_name = pac.db_project.name.gsub(':', '_')
876       pack_name = pac.name.gsub(':', '_')+"."+proj_name
877
878       # create branch package
879       # no find_package call here to check really this project only
880       if opkg = oprj.db_packages.find_by_name(pack_name)
881         render_error :status => 400, :errorcode => "double_branch_package",
882           :message => "branch target package already exists: #{oprj.name}/#{opkg.name}"
883         return
884       else
885         opkg = oprj.db_packages.new(:name => pack_name, :title => pac.title, :description => pac.description)
886         oprj.db_packages << opkg
887       end
888
889       # create repositories, if missing
890       pac.db_project.repositories.each do |repo|
891         orepo = oprj.repositories.create :name => proj_name+"_"+repo.name
892         orepo.architectures = repo.architectures
893         orepo.path_elements.create(:link => repo, :position => 1)
894       end
895
896       opkg.store
897
898       # branch sources in backend
899       Suse::Backend.post "/source/#{oprj.name}/#{opkg.name}?cmd=branch&oproject=#{CGI.escape(branch_target_project)}&opackage=#{CGI.escape(branch_target_package)}", nil
900     end
901
902     # store project data in DB and XML
903     oprj.store
904
905     # all that worked ? :)
906     render_ok :data => {:targetproject => mparams[:target_project]}
907   end
908
909   # POST /source/<project>?cmd=createkey
910   def index_project_createkey
911     valid_http_methods :post
912     params[:user] = @http_user.login
913
914     path = request.path
915     path << build_query_from_hash(params, [:cmd, :user, :comment])
916     pass_to_backend path
917   end
918
919   # POST /source/<project>?cmd=undelete
920   def index_project_undelete
921     valid_http_methods :post
922     params[:user] = @http_user.login
923
924     path = request.path
925     path << build_query_from_hash(params, [:cmd, :user, :comment])
926     pass_to_backend path
927   end
928
929   # POST /source/<project>?cmd=createpatchinfo
930   def index_project_createpatchinfo
931     name=""
932     if params[:name]
933       name=params[:name] if params[:name]
934     end
935     pkg_name = "_patchinfo:#{name.gsub(/\W/, '_')}"
936     patchinfo_path = "#{request.path}/#{pkg_name}"
937
938     # request binaries in project from backend
939     binaries = list_all_binaries_in_path("/build/#{params[:project]}")
940
941     if binaries.length < 1 and not params[:force]
942       render_error :status => 400, :errorcode => "no_matched_binaries",
943         :message => "No binary packages were found in project repositories"
944       return
945     end
946
947     # FIXME: check for still building packages
948
949     # create patchinfo package
950     if not DbPackage.find_by_project_and_name( params[:project], pkg_name )
951       prj = DbProject.find_by_name( params[:project] )
952       pkg = DbPackage.new(:name => pkg_name, :title => "Patchinfo", :description => "Collected packages for update")
953       prj.db_packages << pkg
954       Package.find(pkg_name, :project => params[:project]).save
955       if name==""
956         name=pkg_name
957       end
958     else
959       # shall we do a force check here ?
960     end
961
962     # create patchinfo XML file
963     node = Builder::XmlMarkup.new(:indent=>2)
964     xml = node.patchinfo(:name => name) do |n|
965       binaries.each do |binary|
966         node.binary(binary)
967       end
968       node.packager    @http_user.login
969       node.bugzilla    ""
970       node.swampid     ""
971       node.category    ""
972       node.rating      ""
973       node.summary     ""
974       node.description ""
975       # FIXME add all bugnumbers from attributes
976     end
977     backend_put( patchinfo_path+"/_patchinfo?user="+@http_user.login+"&comment=generated%20file%20by%20frontend", xml )
978
979     render_ok
980   end
981
982   def list_all_binaries_in_path path
983     d = backend_get(path)
984     data = REXML::Document.new(d)
985     binaries = []
986
987     data.elements.each("directory/entry") do |e|
988       name = e.attributes["name"]
989       list_all_binaries_in_path("#{path}/#{name}").each do |l|
990         binaries.push( l )
991       end
992     end
993     data.elements.each("binarylist/binary") do |b|
994       name = b.attributes["filename"]
995       # strip main name from binary
996       # patchinfos are only designed for rpms so far
997       binaries.push( name.sub(/-[^-]*-[^-]*.rpm$/, '' ) )
998     end
999
1000     binaries.uniq!
1001     return binaries
1002   end
1003
1004   # POST /source/<project>/<package>?cmd=undelete
1005   def index_package_undelete
1006     valid_http_methods :post
1007     params[:user] = @http_user.login
1008
1009     path = request.path
1010     path << build_query_from_hash(params, [:cmd, :user, :comment])
1011     pass_to_backend path
1012   end
1013
1014   # POST /source/<project>/<package>?cmd=createSpecFileTemplate
1015   def index_package_createSpecFileTemplate
1016     specfile_path = "#{request.path}/#{params[:package]}.spec"
1017     begin
1018       backend_get( specfile_path )
1019       render_error :status => 400, :errorcode => "spec_file_exists",
1020         :message => "SPEC file already exists."
1021       return
1022     rescue ActiveXML::Transport::NotFoundError
1023       specfile = File.read "#{RAILS_ROOT}/files/specfiletemplate"
1024       backend_put( specfile_path, specfile )
1025     end
1026     render_ok
1027   end
1028
1029   # POST /source/<project>/<package>?cmd=rebuild
1030   def index_package_rebuild
1031     project_name = params[:project]
1032     package_name = params[:package]
1033     repo_name = params[:repo]
1034     arch_name = params[:arch]
1035
1036     path = "/build/#{project_name}?cmd=rebuild&package=#{package_name}"
1037     
1038     p = DbProject.find_by_name project_name
1039     if p.nil?
1040       render_error :status => 400, :errorcode => 'unknown_project',
1041         :message => "Unknown project '#{project_name}'"
1042       return
1043     end
1044
1045     begin
1046       pkg = find_package( p, package_name )
1047     rescue DbProject::CycleError => e
1048       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1049       return
1050     end
1051     unless pkg
1052       render_error :status => 400, :errorcode => 'unknown_package',
1053         :message => "Unknown package '#{package_name}'"
1054       return
1055     end
1056
1057     if repo_name
1058       path += "&repository=#{repo_name}"
1059       if p.repositories.find_by_name(repo_name).nil?
1060         render_error :status => 400, :errorcode => 'unknown_repository',
1061           :message=> "Unknown repository '#{repo_name}'"
1062         return
1063       end
1064     end
1065
1066     if arch_name
1067       path += "&arch=#{arch_name}"
1068     end
1069
1070     backend.direct_http( URI(path), :method => "POST", :data => "" )
1071     render_ok
1072   end
1073
1074   # POST /source/<project>/<package>?cmd=commit
1075   def index_package_commit
1076     valid_http_methods :post
1077     params[:user] = @http_user.login
1078
1079     path = request.path
1080     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1081     pass_to_backend path
1082
1083     if params[:package] == "_product"
1084       update_product_autopackages
1085     end
1086   end
1087
1088   # POST /source/<project>/<package>?cmd=commitfilelist
1089   def index_package_commitfilelist
1090     valid_http_methods :post
1091     params[:user] = @http_user.login
1092
1093     path = request.path
1094     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
1095     pass_to_backend path
1096     
1097     if params[:package] == "_product"
1098       update_product_autopackages
1099     end
1100   end
1101
1102   # POST /source/<project>/<package>?cmd=diff
1103   def index_package_diff
1104     valid_http_methods :post
1105     path = request.path
1106     path << build_query_from_hash(params, [:cmd, :rev, :oproject, :opackage, :orev, :expand, :unified, :linkrev, :olinkrev, :missingok])
1107     pass_to_backend path
1108   end
1109
1110   # POST /source/<project>/<package>?cmd=linkdiff
1111   def index_package_linkdiff
1112     valid_http_methods :post
1113     path = request.path
1114     path << build_query_from_hash(params, [:rev, :unified, :linkrev])
1115     pass_to_backend path
1116   end
1117
1118   # POST /source/<project>/<package>?cmd=copy
1119   def index_package_copy
1120     valid_http_methods :post
1121     params[:user] = @http_user.login
1122
1123     pack = DbPackage.find_by_project_and_name(params[:project], params[:package])
1124     if pack.nil? 
1125       render_error :status => 404, :errorcode => 'unknown_package',
1126         :message => "Unknown package #{params[:package]} in project #{params[:project]}"
1127       return
1128     end
1129
1130     path = request.path
1131     path << build_query_from_hash(params, [:cmd, :rev, :user, :comment, :oproject, :opackage, :orev, :expand, :keeplink, :repairlink, :linkrev, :olinkrev, :requestid, :dontupdatesource])
1132     
1133     pass_to_backend path
1134   end
1135
1136   # POST /source/<project>/<package>?cmd=runservice
1137   def index_package_runservice
1138     valid_http_methods :post
1139     params[:user] = @http_user.login
1140
1141     path = request.path
1142     path << build_query_from_hash(params, [:cmd, :comment])
1143     pass_to_backend path
1144   end
1145
1146   # POST /source/<project>/<package>?cmd=deleteuploadrev
1147   def index_package_deleteuploadrev
1148     valid_http_methods :post
1149     params[:user] = @http_user.login
1150
1151     path = request.path
1152     path << build_query_from_hash(params, [:cmd])
1153     pass_to_backend path
1154   end
1155
1156   # POST /source/<project>/<package>?cmd=linktobranch
1157   def index_package_linktobranch
1158     valid_http_methods :post
1159     params[:user] = @http_user.login
1160     prj_name = params[:project]
1161     pkg_name = params[:package]
1162     pkg_rev = params[:rev]
1163     pkg_linkrev = params[:linkrev]
1164
1165     prj = DbProject.find_by_name prj_name
1166     pkg = prj.db_packages.find_by_name(pkg_name)
1167     if pkg.nil?
1168       render_error :status => 404, :errorcode => 'unknown_package',
1169         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1170       return
1171     end
1172
1173     #convert link to branch
1174     rev = ""
1175     if not pkg_rev.nil? and not pkg_rev.empty?
1176       rev = "&rev=#{pkg_rev}"
1177     end
1178     linkrev = ""
1179     if not pkg_linkrev.nil? and not pkg_linkrev.empty?
1180       linkrev = "&linkrev=#{pkg_linkrev}"
1181     end
1182     Suse::Backend.post "/source/#{prj_name}/#{pkg_name}?cmd=linktobranch&user=#{CGI.escape(params[:user])}#{rev}#{linkrev}", nil
1183
1184     render_ok
1185   end
1186
1187   # POST /source/<project>/<package>?cmd=branch&target_project="optional_project"&target_package="optional_package"&update_project_attribute="alternative_attribute"&comment="message"
1188   def index_package_branch
1189     valid_http_methods :post
1190     params[:user] = @http_user.login
1191     prj_name = params[:project]
1192     pkg_name = params[:package]
1193     pkg_rev = params[:rev]
1194     target_project = params[:target_project]
1195     target_package = params[:target_package]
1196     if not params[:update_project_attribute]
1197       params[:update_project_attribute] = "OBS:UpdateProject"
1198     end
1199     logger.debug "branch call of #{prj_name} #{pkg_name}"
1200
1201     prj = DbProject.find_by_name prj_name
1202     if prj.nil?
1203       render_error :status => 404, :errorcode => 'unknown_project',
1204         :message => "Unknown project #{prj_name}"
1205       return
1206     end
1207     begin
1208       pkg = prj.find_package( pkg_name )
1209     rescue DbProject::CycleError => e
1210       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1211       return
1212     end
1213     if pkg.nil?
1214       render_error :status => 404, :errorcode => 'unknown_package',
1215         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1216       return
1217     end
1218
1219     # is a update project defined and a package there ?
1220     aname = params[:update_project_attribute]
1221     name_parts = aname.split(/:/)
1222     if name_parts.length != 2
1223       raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
1224     end
1225     if a = prj.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
1226       if pa = DbPackage.find_by_project_and_name( a.values[0].value, pkg.name )
1227         pkg = pa
1228         pkg_name = pkg.name
1229         prj = pkg.db_project
1230         prj_name = prj.name
1231         logger.debug "branch call found package in update project #{prj_name}"
1232       end
1233     end
1234
1235     # validate and resolve devel package or devel project definitions
1236     if not params[:ignoredevel] and ( pkg.develproject or pkg.develpackage )
1237       pkg = pkg.resolve_devel_package
1238       pkg_name = pkg.name
1239       prj = pkg.db_project
1240       prj_name = prj.name
1241       logger.debug "devel project is #{prj_name} #{pkg_name}"
1242     end
1243
1244     # link against srcmd5 instead of plain revision
1245     unless pkg_rev.nil?
1246       path = "/source/#{params[:project]}/#{params[:package]}" + build_query_from_hash(params, [:rev])
1247       files = Suse::Backend.get(path)
1248       # get srcmd5 from the xml data
1249       match = files.body.match(/<directory['"=\w\s]+srcmd5=['"](\w{32})['"]['"=\w\s]*>/)
1250       if match
1251         pkg_rev = match[1]
1252       else
1253         # this should not happen
1254         render_error :status => 400, :errorcode => 'invalid_filelist',
1255           :message => "Unable parse filelist from backend"
1256         return
1257       end
1258     end
1259  
1260     oprj_name = "home:#{@http_user.login}:branches:#{prj_name}"
1261     opkg_name = pkg_name
1262     oprj_name = target_project unless target_project.nil?
1263     opkg_name = target_package unless target_package.nil?
1264
1265     #create branch container
1266     oprj = DbProject.find_by_name oprj_name
1267     if oprj.nil?
1268       unless @http_user.can_create_project?(oprj_name)
1269         render_error :status => 403, :errorcode => "create_project_no_permission",
1270           :message => "no permission to create project '#{oprj_name}' while executing branch command"
1271         return
1272       end
1273
1274       DbProject.transaction do
1275         oprj = DbProject.new :name => oprj_name, :title => "Branch of #{prj.title}", :description => prj.description
1276         oprj.add_user @http_user, "maintainer"
1277         oprj.publish_flags << PublishFlag.new( :status => "disable", :position => 1 )
1278         prj.repositories.each do |repo|
1279           orepo = oprj.repositories.create :name => repo.name
1280           orepo.architectures = repo.architectures
1281           orepo.path_elements << PathElement.new(:link => repo, :position => 1)
1282         end
1283         oprj.store
1284       end
1285     end
1286
1287     #create branch package
1288     if opkg = oprj.db_packages.find_by_name(opkg_name)
1289       if params[:force]
1290         # shall we clean all files here ?
1291       else
1292         render_error :status => 400, :errorcode => "double_branch_package",
1293           :message => "branch target package already exists: #{oprj_name}/#{opkg_name}"
1294         return
1295       end
1296
1297       unless @http_user.can_modify_package?(opkg)
1298         render_error :status => 403, :errorcode => "create_package_no_permission",
1299           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1300         return
1301       end
1302     else
1303       unless @http_user.can_create_package_in?(oprj)
1304         render_error :status => 403, :errorcode => "create_package_no_permission",
1305           :message => "no permission to create package '#{opkg_name}' for project '#{oprj_name}' while executing branch command"
1306         return
1307       end
1308
1309       opkg = oprj.db_packages.create(:name => opkg_name, :title => pkg.title, :description => params.has_key?(:comment) ? params[:comment] : pkg.description)
1310       opkg.add_user @http_user, "maintainer"
1311       opkg.store
1312     end
1313
1314     #create branch of sources in backend
1315     rev = ""
1316     if not pkg_rev.nil? and not pkg_rev.empty?
1317       rev = "&rev=#{pkg_rev}"
1318     end
1319     comment = params.has_key?(:comment) ? "&comment=#{CGI.escape(params[:comment])}" : ""
1320     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
1321
1322     render_ok :data => {:targetproject => oprj_name, :targetpackage => opkg_name, :sourceproject => prj_name, :sourcepackage => pkg_name}
1323   end
1324
1325   # POST /source/<project>/<package>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
1326   def index_package_set_flag
1327     valid_http_methods :post
1328
1329     prj_name = params[:project]
1330     pkg_name = params[:package]
1331
1332     prj = DbProject.find_by_name prj_name
1333     if prj.nil?
1334       render_error :status => 404, :errorcode => 'unknown_project',
1335         :message => "Unknown project #{prj_name}"
1336       return
1337     end
1338     begin
1339       pkg = prj.find_package( pkg_name )
1340     rescue DbProject::CycleError => e
1341       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1342       return
1343     end
1344     # first remove former flags of the same class
1345     pkg.remove_flag(params[:flag], params[:repository], params[:arch])
1346     pkg.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
1347     pkg.store
1348     render_ok
1349   end
1350
1351   # POST /source/<project>?cmd=set_flag&repository=:opt&arch=:opt&flag=flag&status=status
1352   def index_project_set_flag
1353     valid_http_methods :post
1354
1355     prj_name = params[:project]
1356
1357     prj = DbProject.find_by_name prj_name
1358     if prj.nil?
1359       render_error :status => 404, :errorcode => 'unknown_project',
1360         :message => "Unknown project #{prj_name}"
1361       return
1362     end
1363     # first remove former flags of the same class
1364     prj.remove_flag(params[:flag], params[:repository], params[:arch])
1365     prj.add_flag(params[:flag], params[:status], params[:repository], params[:arch])
1366     prj.store
1367     render_ok
1368   end
1369
1370   # POST /source/<project>/<package>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
1371   def index_package_remove_flag
1372     valid_http_methods :post
1373     
1374     prj_name = params[:project]
1375     pkg_name = params[:package]
1376
1377     prj = DbProject.find_by_name prj_name
1378     if prj.nil?
1379       render_error :status => 404, :errorcode => 'unknown_project',
1380         :message => "Unknown project #{prj_name}"
1381       return
1382     end
1383     begin
1384       pkg = prj.find_package( pkg_name )
1385     rescue DbProject::CycleError => e
1386       render_error :status => 400, :errorcode => 'project_cycle', :message => e.message
1387       return
1388     end
1389     if pkg.nil?
1390       render_error :status => 404, :errorcode => "unknown_package",
1391         :message => "unknown package '#{pkg_name}' in project '#{prj_name}'"
1392       return
1393     end
1394     pkg.remove_flag(params[:flag], params[:repository], params[:arch])
1395     pkg.store
1396     render_ok
1397   end
1398
1399   # POST /source/<project>?cmd=remove_flag&repository=:opt&arch=:opt&flag=flag
1400   def index_project_remove_flag
1401     valid_http_methods :post
1402
1403     prj_name = params[:project]
1404
1405     prj = DbProject.find_by_name prj_name
1406     if prj.nil?
1407       render_error :status => 404, :errorcode => 'unknown_project',
1408         :message => "Unknown project #{prj_name}"
1409       return
1410     end
1411     prj.remove_flag(params[:flag], params[:repository], params[:arch])
1412     prj.store
1413     render_ok
1414   end
1415
1416   def valid_project_name? name
1417     name =~ /^\w[-_+\w\.:]*$/
1418   end
1419
1420   def valid_package_name? name
1421     return true if name == "_pattern"
1422     return true if name == "_project"
1423     return true if name == "_product"
1424     return true if name =~ /^_product:[-_+\w\.:]*$/
1425     name =~ /^\w[-_+\w\.:]*$/
1426   end
1427
1428 end