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