- reject PUT on package directory
[opensuse:build-service.git] / src / api / app / controllers / source_controller.rb
1 require "rexml/document"
2
3 class SourceController < ApplicationController
4   validate_action :index => :directory, :packagelist => :directory, :filelist => :directory
5   validate_action :project_meta => :project, :package_meta => :package, :pattern_meta => :pattern
6  
7   skip_before_filter :extract_user, :only => [:file, :project_meta, :project_config] 
8
9   def index
10     projectlist
11   end
12
13   def projectlist
14     @dir = Project.find :all
15     render :text => @dir.dump_xml, :content_type => "text/xml"
16   end
17
18   def index_project
19     project_name = params[:project]
20     pro = DbProject.find_by_name project_name
21     if pro.nil?
22       render_error :status => 404, :errorcode => 'unknown_project',
23         :message => "Unknown project #{project_name}"
24       return
25     end
26     
27     if request.get?
28       @dir = Package.find :all, :project => project_name
29       render :text => @dir.dump_xml, :content_type => "text/xml"
30       return
31     elsif request.delete?
32       unless @http_user.can_modify_project?(pro)
33         logger.debug "No permission to delete project #{project_name}"
34         render_error :status => 403, :errorcode => 'delete_project_no_permission',
35           :message => "Permission denied (delete project #{project_name})"
36         return
37       end
38
39       #deny deleting if other packages use this as develproject
40       unless pro.develpackages.empty?
41         msg = "Unable to delete project #{pro.name}; following packages use this project as develproject: "
42         msg += pro.develpackages.map {|pkg| pkg.db_project.name+"/"+pkg.name}.join(", ")
43         render_error :status => 400, :errorcode => 'develproject_dependency',
44           :message => msg
45         return
46       end
47
48       #find linking repos
49       lreps = Array.new
50       pro.repositories.each do |repo|
51         repo.linking_repositories.each do |lrep|
52           lreps << lrep
53         end
54       end
55
56       if lreps.length > 0
57         if params[:force] and not params[:force].empty?
58           #replace links to this projects with links to the "deleted" project
59           del_repo = DbProject.find_by_name("deleted").repositories[0]
60           lreps.each do |link_rep|
61             pe = link_rep.path_elements.find(:first, :include => ["link"], :conditions => ["db_project_id = ?", pro.id])
62             pe.link = del_repo
63             pe.save
64             #update backend
65             link_prj = link_rep.db_project
66             logger.info "updating project '#{link_prj.name}'"
67             Suse::Backend.put_source "/source/#{link_prj.name}/_meta", link_prj.to_axml
68           end
69         else
70           lrepstr = lreps.map{|l| l.db_project.name+'/'+l.name}.join "\n"
71           render_error :status => 400, :errorcode => "repo_dependency",
72             :message => "Unable to delete project #{project_name}; following repositories depend on this project:\n#{lrepstr}\n"
73           return
74         end
75       end
76
77       #destroy all packages
78       pro.db_packages.each do |pack|
79         DbPackage.transaction do
80           logger.info "destroying package #{pack.name}"
81           pack.destroy
82           logger.debug "delete request to backend: /source/#{pro.name}/#{pack.name}"
83           Suse::Backend.delete "/source/#{pro.name}/#{pack.name}"
84         end
85       end
86
87       DbProject.transaction do
88         logger.info "destroying project #{pro.name}"
89         pro.destroy
90         logger.debug "delete request to backend: /source/#{pro.name}"
91         Suse::Backend.delete "/source/#{pro.name}"
92       end
93
94       render_ok
95       return
96     elsif request.post?
97       cmd = params[:cmd]
98       if @http_user.can_modify_project?(pro)
99         dispatch_command
100       else
101         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
102           :message => "no permission to execute command '#{cmd}'"
103         return
104       end
105     else
106       render_error :status => 400, :errorcode => "illegal_request",
107         :message => "illegal POST request to #{request.request_uri}"
108     end
109   end
110
111   def index_package
112     valid_http_methods :get, :delete, :post
113     project_name = params[:project]
114     package_name = params[:package]
115     cmd = params[:cmd]
116
117     pkg = DbPackage.find_by_project_and_name(project_name, package_name)
118     unless pkg or DbProject.find_remote_project(project_name)
119       render_error :status => 404, :errorcode => "unknown_package",
120         :message => "unknown package '#{package_name}' in project '#{project_name}'"
121       return
122     end
123
124     if request.get?
125       pass_to_source
126       return
127     elsif request.delete?
128       if not @http_user.can_modify_package?(pkg)
129         render_error :status => 403, :errorcode => "delete_package_no_permission",
130           :message => "no permission to delete package #{package_name}"
131         return
132       end
133       
134       DbPackage.transaction do
135         pkg.destroy
136         Suse::Backend.delete "/source/#{project_name}/#{package_name}"
137         if package_name == "_product"
138           update_product_autopackages
139         end
140       end
141       render_ok
142     elsif request.post?
143       if not ['diff', 'branch'].include?(cmd) and not @http_user.can_modify_package?(pkg)
144         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
145           :message => "no permission to execute command '#{cmd}'"
146         return
147       end
148       
149       dispatch_command
150     end
151   end
152
153   # updates packages automatically generated in the backend after submitting a product file
154   def update_product_autopackages
155     backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}' and starts-with(@name,'_product:')"
156     b_pkg_index = backend_pkgs.each_package.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
157     frontend_pkgs = DbProject.find_by_name(params[:project]).db_packages.find(:all, :conditions => "name LIKE '_product:%'")
158     f_pkg_index = frontend_pkgs.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
159
160     all_pkgs = [b_pkg_index.keys, f_pkg_index.keys].flatten.uniq
161
162     wt_state = ActiveXML::Config.global_write_through
163     begin
164       ActiveXML::Config.global_write_through = false
165       all_pkgs.each do |pkg|
166         if b_pkg_index.has_key?(pkg) and not f_pkg_index.has_key?(pkg)
167           # new autopackage, import in database
168           Package.new(b_pkg_index[pkg].dump_xml, :project => params[:project]).save
169         elsif f_pkg_index.has_key?(pkg) and not b_pkg_index.has_key?(pkg)
170           # autopackage was removed, remove from database
171           f_pkg_index[pkg].destroy
172         end
173       end
174     ensure
175       ActiveXML::Config.global_write_through = wt_state
176     end
177   end
178
179   #GET /source/:project/_pattern/:pattern
180   def pattern_meta
181     valid_http_methods :get, :put, :delete
182
183     params[:user] = @http_user.login if @http_user
184     
185     @project = DbProject.find_by_name params[:project]
186     unless @project
187       render_error :message => "Unknown project '#{params[:project]}'",
188         :status => 404, :errorcode => "unknown_project"
189       return
190     end
191
192     if request.get?
193       pass_to_source
194     else
195       # PUT and DELETE
196       permerrormsg = nil
197       if request.put?
198         permerrormsg = "no permission to store pattern"
199       elsif request.delete?
200         permerrormsg = "no permission to delete pattern"
201       end
202
203       unless @http_user.can_modify_project? @project
204         logger.debug "user #{user.login} has no permission to modify project #{@project}"
205         render_error :status => 403, :errorcode => "change_project_no_permission", 
206           :message => permerrormsg
207         return
208       end
209       
210       path = request.path + build_query_from_hash(params, [:rev, :user, :comment])
211       forward_data path, :method => request.method
212     end
213   end
214
215   # GET /source/:project/_pattern
216   def index_pattern
217     valid_http_methods :get
218
219     unless DbProject.find_by_name(params[:project])
220       render_error :message => "Unknown project '#{params[:project]}'",
221         :status => 404, :errorcode => "unknown_project"
222       return
223     end
224     
225     pass_to_source
226   end
227
228   def project_meta
229     project_name = params[:project]
230     if project_name.nil?
231       render_error :status => 400, :errorcode => 'missing_parameter',
232         :message => "parameter 'project' is missing"
233       return
234     end
235
236     unless valid_project_name? project_name
237       render_error :status => 400, :errorcode => "invalid_project_name",
238         :message => "invalid project name '#{project_name}'"
239       return
240     end
241
242     if request.get?
243       @project = DbProject.find_by_name( project_name )
244
245       if @project
246         render :text => @project.to_axml, :content_type => 'text/xml'
247       elsif DbProject.find_remote_project(project_name)
248         # project from remote buildservice, get metadata from backend
249         pass_to_backend
250       else
251         render_error :message => "Unknown project '#{project_name}'",
252           :status => 404, :errorcode => "unknown_project"
253       end
254       return
255     end
256
257     #authenticate
258     return unless extract_user
259
260     if request.put?
261       # Need permission
262       logger.debug "Checking permission for the put"
263       allowed = false
264       request_data = request.raw_post
265
266       @project = DbProject.find_by_name( project_name )
267       if @project
268         #project exists, change it
269         unless @http_user.can_modify_project? @project
270           logger.debug "user #{user.login} has no permission to modify project #{@project}"
271           render_error :status => 403, :errorcode => "change_project_no_permission", 
272             :message => "no permission to change project"
273           return
274         end
275       else
276         #project is new
277         unless @http_user.can_create_project? project_name
278           logger.debug "Not allowed to create new project"
279           render_error :status => 403, :errorcode => 'create_project_no_permission',
280             :message => "not allowed to create new project '#{project_name}'"
281           return
282         end
283       end
284       
285       p = Project.new(request_data, :name => project_name)
286
287       if p.name != project_name
288         render_error :status => 400, :errorcode => 'project_name_mismatch',
289           :message => "project name in xml data does not match resource path component"
290         return
291       end
292
293       if (p.has_element? :remoteurl or p.has_element? :remoteproject) and not @http_user.is_admin?
294         render_error :status => 403, :errorcode => "change_project_no_permission",
295           :message => "admin rights are required to change remoteurl or remoteproject"
296         return
297       end
298
299       p.add_person(:userid => @http_user.login) unless @project
300       p.save
301
302       render_ok
303     else
304       render_error :status => 400, :errorcode => 'illegal_request',
305         :message => "Illegal request: POST #{request.path}"
306     end
307   end
308
309   def project_config
310     valid_http_methods :get, :put
311
312     #check if project exists
313     unless (@project = DbProject.find_by_name(params[:project]))
314       render_error :status => 404, :errorcode => 'project_not_found',
315         :message => "Unknown project #{params[:project]}"
316       return
317     end
318
319     #assemble path for backend
320     path = request.path
321     unless request.query_string.empty?
322       path += "?" + request.query_string
323     end
324
325     if request.get?
326       forward_data path
327       return
328     end
329
330     #authenticate
331     return unless extract_user
332
333     if request.put?
334       unless @http_user.can_modify_project?(@project)
335         render_error :status => 403, :errorcode => 'put_project_config_no_permission',
336           :message => "No permission to write build configuration for project '#{params[:project]}'"
337         return
338       end
339
340       forward_data path, :method => :put
341       return
342     end
343   end
344
345   def project_pubkey
346     valid_http_methods :get, :delete
347
348     #check if project exists
349     unless (@project = DbProject.find_by_name(params[:project]))
350       render_error :status => 404, :errorcode => 'project_not_found',
351         :message => "Unknown project #{params[:project]}"
352       return
353     end
354
355     #assemble path for backend
356     path = request.path
357     unless request.query_string.empty?
358       path += "?" + request.query_string
359     end
360
361     if request.get?
362       forward_data path
363     elsif request.delete?
364       #check for permissions
365       unless @http_user.can_modify_project?(@project)
366         render_error :status => 403, :errorcode => 'delete_project_pubkey_no_permission',
367           :message => "No permission to delete public key for project '#{params[:project]}'"
368         return
369       end
370
371       forward_data path, :method => :delete
372       return
373     end
374   end
375
376   def package_meta
377     #TODO: needs cleanup/split to smaller methods
378     valid_http_methods :put, :get
379    
380     project_name = params[:project]
381     package_name = params[:package]
382
383     if project_name.nil?
384       render_error :status => 400, :errorcode => "parameter_missing",
385         :message => "parameter 'project' missing"
386       return
387     end
388
389     if package_name.nil?
390       render_error :status => 400, :errorcode => "parameter_missing",
391         :message => "parameter 'package' missing"
392       return
393     end
394
395     unless pro = DbProject.find_by_name(project_name)
396       if DbProject.find_remote_project(project_name)
397         pass_to_backend
398       else
399         render_error :status => 404, :errorcode => "unknown_project",
400           :message => "Unknown project '#{project_name}'"
401       end
402       return
403     end
404
405     if request.get?
406       unless pack = pro.db_packages.find_by_name(package_name)
407         render_error :status => 404, :errorcode => "unknown_package",
408           :message => "Unknown package '#{package_name}'"
409         return
410       end
411
412       render :text => pack.to_axml, :content_type => 'text/xml'
413     elsif request.put?
414       allowed = false
415       request_data = request.raw_post
416       begin
417         # Try to fetch the package to see if it already exists
418         @package = Package.find( package_name, :project => project_name )
419   
420         # Being here means that the project already exists
421         allowed = permissions.package_change? @package
422         if allowed
423           @package = Package.new( request_data, :project => project_name, :name => package_name )
424         else
425           logger.debug "user #{user.login} has no permission to change package #{@package}"
426           render_error :status => 403, :errorcode => "change_package_no_permission",
427             :message => "no permission to change package"
428           return
429         end
430       rescue ActiveXML::Transport::NotFoundError
431         # Ok, the project is new
432         allowed = permissions.package_create?( project_name )
433   
434         if allowed
435           #FIXME: parameters that get substituted into the url must be specified here... should happen
436           #somehow automagically... no idea how this might work
437           @package = Package.new( request_data, :project => project_name, :name => package_name )
438         
439           # add package creator as maintainer if he is not added already
440           if not @package.has_element?( "person[@userid='#{user.login}']" )
441             @package.add_person( :userid => user.login )
442           end
443         else
444           # User is not allowed by global permission.
445           logger.debug "Not allowed to create new packages"
446           render_error :status => 403, :errorcode => "create_package_no_permission",
447             :message => "no permission to create package for project #{project_name}"
448           return
449         end
450       end
451       
452       if allowed
453         if( @package.name != package_name )
454           render_error :status => 400, :errorcode => 'package_name_mismatch',
455             :message => "package name in xml data does not match resource path component"
456           return
457         end
458
459         @package.save
460         render_ok
461       else
462         logger.debug "user #{user.login} has no permission to write package meta for package #@package"
463       end
464     end
465   end
466
467   def file
468     project_name = params[:project]
469     package_name = params[:package]
470     file = params[:file]
471
472     path = "/source/#{project_name}/#{package_name}/#{file}"
473
474     if request.get?
475       #get file size
476       fpath = "/source/#{project_name}/#{package_name}" + build_query_from_hash(params, [:rev])
477       file_list = Suse::Backend.get(fpath)
478       regexp = file_list.body.match(/name=["']#{Regexp.quote file}["'].*size=["']([^"']*)["']/)
479       if regexp
480         fsize = regexp[1]
481         
482         path += build_query_from_hash(params, [:rev])
483         logger.info "streaming #{path}"
484        
485         headers.update(
486           'Content-Disposition' => %(attachment; filename="#{file}"),
487           'Content-Type' => 'application/octet-stream',
488           'Transfer-Encoding' => 'binary',
489           'Content-Length' => fsize
490         )
491         
492         render :status => 200, :text => Proc.new {|request,output|
493           backend_request = Net::HTTP::Get.new(path)
494           response = Net::HTTP.start(SOURCE_HOST,SOURCE_PORT) do |http|
495             http.request(backend_request) do |response|
496               response.read_body do |chunk|
497                 output.write(chunk)
498               end
499             end
500           end
501         }
502       else
503         forward_data path
504       end
505       return
506     end
507
508     #authenticate
509     return unless extract_user
510
511     params[:user] = @http_user.login
512     if request.put?
513       path += build_query_from_hash(params, [:user, :comment, :rev, :keeplink])
514       
515       allowed = permissions.package_change? package_name, project_name
516       if  allowed
517         Suse::Backend.put_source path, request.raw_post
518         package = Package.find( package_name, :project => project_name )
519         package.update_timestamp
520         logger.info "wrote #{request.raw_post.to_s.size} bytes to #{path}"
521         if package_name == "_product"
522           update_product_autopackages
523         end
524         render_ok
525       else
526         render_error :status => 403, :errorcode => 'put_file_no_permission',
527           :message => "Insufficient permissions to store file in package #{package_name}, project #{project_name}"
528       end
529     elsif request.delete?
530       path += build_query_from_hash(params, [:user, :comment, :rev, :keeplink])
531       
532       allowed = permissions.package_change? package_name, project_name
533       if  allowed
534         Suse::Backend.delete path
535         package = Package.find( package_name, :project => project_name )
536         package.update_timestamp
537         if package_name == "_product"
538           update_product_autopackages
539         end
540         render_ok
541       else
542         render_error :status => 403, :errorcode => 'delete_file_no_permission',
543           :message => "Insufficient permissions to delete file"
544       end
545     end
546   end
547
548   private
549
550   # POST /source/<project>?cmd=createkey
551   def index_project_createkey
552     path = request.path + "?" + request.query_string
553     forward_data path, :method => :post
554   end
555
556   # POST /source/<project>/<package>?cmd=createSpecFileTemplate
557   def index_package_createSpecFileTemplate
558     specfile_path = "#{request.path}/#{params[:package]}.spec"
559     begin
560       backend_get( specfile_path )
561       render_error :status => 400, :errorcode => "spec_file_exists",
562         :message => "SPEC file already exists."
563       return
564     rescue ActiveXML::Transport::NotFoundError
565       specfile = File.read "#{RAILS_ROOT}/files/specfiletemplate"
566       backend_put( specfile_path, specfile )
567     end
568     render_ok
569   end
570
571   # POST /source/<project>/<package>?cmd=rebuild
572   def index_package_rebuild
573     project_name = params[:project]
574     package_name = params[:package]
575     repo_name = params[:repo]
576     arch_name = params[:arch]
577
578     path = "/build/#{project_name}?cmd=rebuild&package=#{package_name}"
579     
580     p = DbProject.find_by_name project_name
581     if p.nil?
582       render_error :status => 400, :errorcode => 'unknown_project',
583         :message => "Unknown project '#{project_name}'"
584       return
585     end
586
587     if p.db_packages.find_by_name(package_name).nil?
588       render_error :status => 400, :errorcode => 'unknown_package',
589         :message => "Unknown package '#{package_name}'"
590       return
591     end
592
593     if repo_name
594       path += "&repository=#{repo_name}"
595       if p.repositories.find_by_name(repo_name).nil?
596         render_error :status => 400, :errorcode => 'unknown_repository',
597           :message=> "Unknown repository '#{repo_name}'"
598         return
599       end
600     end
601
602     if arch_name
603       path += "&arch=#{arch_name}"
604     end
605
606     backend.direct_http( URI(path), :method => "POST", :data => "" )
607     render_ok
608   end
609
610   # POST /source/<project>/<package>?cmd=commit
611   def index_package_commit
612     params[:user] = @http_user.login if @http_user
613
614     path = request.path
615     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :keeplink, :repairlink])
616     forward_data path, :method => :post
617
618     if params[:package] == "_product"
619       update_product_autopackages
620     end
621   end
622
623   # POST /source/<project>/<package>?cmd=commitfilelist
624   def index_package_commitfilelist
625     params[:user] = @http_user.login if @http_user
626
627     path = request.path
628     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :keeplink, :repairlink])
629     forward_data path, :method => :post
630     
631     if params[:package] == "_product"
632       update_product_autopackages
633     end
634   end
635
636   # POST /source/<project>/<package>?cmd=diff
637   def index_package_diff
638     path = request.path
639     path << build_query_from_hash(params, [:cmd, :rev, :oproject, :opackage, :orev, :expand])
640     forward_data path, :method => :post
641   end
642
643   # POST /source/<project>/<package>?cmd=copy
644   def index_package_copy
645     params[:user] = @http_user.login if @http_user
646
647     pack = DbPackage.find_by_project_and_name(params[:project], params[:package])
648     if pack.nil? 
649       render_error :status => 404, :errorcode => 'unknown_package',
650         :message => "Unknown package #{params[:package]} in project #{params[:project]}"
651       return
652     end
653
654     #permission check
655     if not @http_user.can_modify_package?(pack)
656       render_error :status => 403, :errorcode => "cmd_execution_no_permission",
657         :message => "no permission to execute command 'copy'"
658       return
659     end
660
661     path = request.path
662     path << build_query_from_hash(params, [:cmd, :rev, :user, :comment, :oproject, :opackage, :orev, :expand])
663     
664     forward_data path, :method => :post
665   end
666
667   # POST /source/<project>/<package>?cmd=deleteuploadrev
668   def index_package_deleteuploadrev
669     params[:user] = @http_user.login if @http_user
670
671     path = request.path
672     path << build_query_from_hash(params, [:cmd, :user, :comment])
673     forward_data path, :method => :post
674   end
675
676   # POST /source/<project>/<package>?cmd=branch&target_project="optional_project"&target_package="optional_package"
677   def index_package_branch
678     params[:user] = @http_user.login
679     prj_name = params[:project]
680     pkg_name = params[:package]
681     pkg_rev = params[:rev]
682     target_project = params[:target_project]
683     target_package = params[:target_package]
684
685     prj = DbProject.find_by_name prj_name
686     pkg = prj.db_packages.find_by_name(pkg_name)
687     if pkg.nil?
688       render_error :status => 404, :errorcode => 'unknown_package',
689         :message => "Unknown package #{pkg_name} in project #{prj_name}"
690       return
691     end
692
693     # reroute if devel project is set
694     if pkg.develproject and not params[:ignoredevel]
695       prj = pkg.develproject
696       prj_name = prj.name
697       pkg = prj.db_packages.find_by_name(pkg_name)
698
699       if pkg.nil?
700         render_error :status => 404, :errorcode => 'unknown_package',
701           :message => "Unknown package #{pkg_name} in project #{prj_name}"
702         return
703       end
704
705       # link against srcmd5 instead of plain revision
706       unless pkg_rev.nil?
707         path = "/source/#{params[:project]}/#{params[:package]}" + build_query_from_hash(params, [:rev])
708         files = Suse::Backend.get(path)
709         # get srcmd5 from the xml data
710         match = files.body.match(/<directory['"=\w\s]+srcmd5=['"](\w{32})['"]['"=\w\s]*>/)
711         if match
712           pkg_rev = match[1]
713         else
714           # this should not happen
715           render_error :status => 400, :errorcode => 'invalid_filelist',
716             :message => "Unable parse filelist from backend"
717           return
718         end
719       end
720     end
721  
722     oprj_name = "home:#{@http_user.login}:branches:#{prj_name}"
723     opkg_name = pkg_name
724     oprj_name = target_project unless target_project.nil?
725     opkg_name = target_package unless target_package.nil?
726
727     unless @http_user.can_create_project?(oprj_name)
728       render_error :status => 403, :errorcode => "create_project_no_permission",
729         :message => "no permission to create project '#{oprj_name}' while executing branch command"
730       return
731     end
732
733     #create branch container
734     oprj = DbProject.find_by_name oprj_name
735     if oprj.nil?
736       DbProject.transaction do
737         oprj = DbProject.new :name => oprj_name, :title => prj.title, :description => prj.description
738         oprj.add_user @http_user, "maintainer"
739         prj.repositories.each do |repo|
740           orepo = Repository.create :name => repo.name
741           orepo.architectures = repo.architectures
742           orepo.path_elements << PathElement.new(:link => repo)
743           oprj.repositories << orepo
744         end
745         oprj.save
746       end
747       Project.find(oprj_name).save
748     end
749
750     #create branch package
751     if opkg = oprj.db_packages.find_by_name(opkg_name)
752       render_error :status => 400, :errorcode => "double_branch_package",
753         :message => "branch target package already exists: #{oprj_name}/#{opkg_name}"
754       return
755     else
756       opkg = DbPackage.new(:name => opkg_name, :title => pkg.title, :description => pkg.description)
757       oprj.db_packages << opkg
758     
759       opkg.add_user @http_user, "maintainer"
760       Package.find(opkg_name, :project => oprj_name).save
761     end
762
763     #link sources
764     rev = pkg_rev.nil? ? "" : "rev='#{pkg_rev}'"
765     link_data = "<link project='#{prj_name}' package='#{pkg_name}' #{rev}/>"
766     logger.debug "link_data: #{link_data}"
767     Suse::Backend.put "/source/#{oprj_name}/#{opkg_name}/_link", link_data
768
769     render_ok :data => {:targetproject => oprj_name}
770   end
771
772   def valid_project_name? name
773     name =~ /^\w[-_+\w\.:]+$/
774   end
775
776   def valid_package_name? name
777     name =~ /^\w[-_+\w\.:]+$/
778   end
779
780 end