Support deployments of Gitorious outside of the root path
[gitorious:mainline.git] / lib / gitorious / ssh / client.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2012 Gitorious AS
4 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
5 #   Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
6 #   Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
7 #
8 #   This program is free software: you can redistribute it and/or modify
9 #   it under the terms of the GNU Affero General Public License as published by
10 #   the Free Software Foundation, either version 3 of the License, or
11 #   (at your option) any later version.
12 #
13 #   This program is distributed in the hope that it will be useful,
14 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #   GNU Affero General Public License for more details.
17 #
18 #   You should have received a copy of the GNU Affero General Public License
19 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #++
21
22 require "net/http"
23 require "uri"
24 require "gitorious/configuration_loader"
25 require File.expand_path(File.join(File.dirname(__FILE__), "../../../app/models/repository_root.rb"))
26
27 module Gitorious
28   module SSH
29     class AccessDeniedError < StandardError; end
30
31     class Client
32       def initialize(strainer, username)
33         @strainer = strainer
34         @project_name, @repository_name = strainer.path.split("/", 2)
35         @repository_name.gsub!(/\.git$/, "")
36         @user_name = username
37         @root_path = Gitorious.client.rootpath
38         @configuration = {}
39         load_config
40       end
41       attr_accessor :project_name, :repository_name, :user_name
42
43       def writable_by_user?
44         query_for_permission_and_path
45         @writable == "true"
46       end
47
48       def assure_user_can_write!
49         writable_by_user? || raise(AccessDeniedError)
50       end
51
52       # The full URL
53       def writable_by_query_url
54         writable_by_query_uri.to_s
55       end
56
57       # The path only
58       def writable_by_query_path
59         writable_by_query_uri.request_uri
60       end
61
62       def real_path
63         if !configuration["real_path"] || configuration["real_path"] == "nil"
64           raise AccessDeniedError
65         end
66         full_real_path = File.join(RepositoryRoot.default_base_path,
67                                    configuration["real_path"])
68         raise AccessDeniedError unless File.exist?(full_real_path)
69         full_real_path
70       end
71
72       def force_pushing_denied?
73         configuration["force_pushing_denied"] == "true"
74       end
75
76       def to_git_shell_argument
77         "#{@strainer.verb} '#{real_path}'"
78       end
79
80       def pre_receive_hook_exists?
81         filename = File.join(real_path, "hooks", "pre-receive")
82         File.executable?(filename)
83       end
84
85       def configuration
86         if @configuration.empty?
87           query_url = "#{@root_path}/#{@project_name}/#{@repository_name}/config?username=#@user_name"
88           # $stderr.puts "Querying #{query_url}" if $DEBUG
89           resp = connection.get(query_url)
90           # $stderr.puts resp
91           parse_configuration(resp.body)
92         end
93         @configuration
94       end
95
96       protected
97       def parse_configuration(raw_config)
98         raw_config.split("\n").each do |line|
99           key, val = line.split(":")
100           if key && val
101             @configuration[key] = val
102           end
103         end
104       end
105
106       def connection
107         host = Gitorious.client.host
108         port = Gitorious.client.port
109         @connection ||= Net::HTTP.start(host, port)
110       end
111
112       def load_config
113         return if Gitorious.respond_to?(:client)
114         Gitorious::ConfigurationLoader.new.configure_application!(RAILS_ENV)
115       end
116
117       # Returns an actual URI object
118       def writable_by_query_uri
119         path = "#{@root_path}/#{@project_name}/#{@repository_name}/writable_by"
120         query = "username=#{@user_name}"
121         host = Gitorious.client.host
122         _port = Gitorious.client.port
123         # Ruby 1.9 expects a number, while 1.8 expects a string. Oh well
124         port = RUBY_VERSION > "1.9" ? _port : _port.to_s
125
126         URI::HTTP.build(:host => host, :port => port, :path => path, :query => query)
127       end
128     end
129   end
130 end