git_handler 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format=nested
3
+ --backtrace
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # GitHandler [![Build Status](https://secure.travis-ci.org/sosedoff/git-handler.png?branch=master)](http://travis-ci.org/sosedoff/git-handler)
2
+
3
+ A tool to simplify your git flow customizations. Its main purpose is to provide an
4
+ application-based control layer for Git request processing.
5
+
6
+ ## Installation
7
+
8
+ Install using rubygems:
9
+
10
+ ```
11
+ gem install git_handler
12
+ ```
13
+
14
+ Or using latest source code:
15
+
16
+ ```
17
+ git clone git://github.com/sosedoff/git-handler.git
18
+ cd git-handler
19
+ bundle install
20
+ rake install
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ If you already have an operation system configured, make sure you have ```git```
26
+ user in your system. In order to use git_handler you'll need to generate a customized SSH public key and
27
+ add it to ```~/.ssh/authorized_keys``` on server. Generation should be something
28
+ that needs to be implemented in your application or script, there is functionality already
29
+ built for that:
30
+
31
+ ```ruby
32
+ require 'git_handler/public_key'
33
+
34
+ # Load your current pub key
35
+ content = File.read(File.expand_path('~/.ssh/id_rsa.pub'))
36
+
37
+ # Create a key
38
+ key = GitHandler::PublicKey.new(content)
39
+ ```
40
+
41
+ Now, to convert loaded key into a system key just run:
42
+
43
+ ```ruby
44
+ key.to_system_key('/usr/bin/git_proxy')
45
+ # => command="/usr/bin/git_proxy",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNjN3ZUOoosWeuJ7KczE5FAOzwZ+Z51KSQvqTCb7ccBi4u+pPYcGEYr2t0cx/BUcx/ZGE8ih+zxN1qM8KmM0uluuy54itHsKFdAwoibkbG22fQc2DY0RmktXXB/w6LxmFuQrmz0fkcbkE39pm5k6Nw6mqks5HjM7aDXRdwM8fSrq0PjfUNiESIrIAeEMGhtZFaj+WZVMfXaIlgzxZsAUpUULhN4j069v8VgxWyyOUT+gwcQB8lVc0BVYhptlFaJBtwhfWvOAviSuK7Cpjh60NdkZ3R2QYeh6wb6fF+KGCkM4iED4PZ1Ep8fRzrbCHky4VHSOyOvg9qKcgP1h+e+diD
46
+ ```
47
+
48
+ SSH public key is now ready for usage on server side. Drop it into ```~/home/git/.ssh/authorized_keys``` file
49
+ if your user is ```git```. The whole purpose of key modifications is that we're
50
+ restricting SSH to a specific command or script on server, which gives us ability
51
+ to control permissions and other restrictions.
52
+
53
+ ### Control script
54
+
55
+ In the example above as you can see we specify ```/usr/bin/git_proxy``` to be
56
+ executed once SSH connection is being established. GitHandler provides a simple
57
+ api to verify and execute git request that comes from client.
58
+
59
+ Example of ```/usr/bin/git_proxy``` file:
60
+
61
+ ```ruby
62
+ #!/usr/bin/env ruby
63
+ require 'git_handler'
64
+
65
+ config = GitHandler::Configuration.new
66
+
67
+ # Configuration has a bunch of options:
68
+ # :user - Git user, default: git
69
+ # :home_path - Home path, default: /home/git
70
+ # :repos_path - Path to repositories, default: /home/git/repositories
71
+ # :log_path - Git requests logger, default: /var/log/git_handler.log
72
+
73
+ begin
74
+ session = GitHandler::Session.new(config)
75
+ session.execute(ARGV, ENV)
76
+ rescue Exception => ex
77
+ STDERR.puts "Error: #{ex.message}"
78
+ exit(1)
79
+ end
80
+ ```
81
+
82
+ **NOTE:** Script must have permissions for execution.
83
+
84
+ Session instance will check if incoming git request has a valid environment and
85
+ valid git command. After check is complete it will shell out to ```git-shell -c COMMAND```
86
+ to perform an original git command. Providing block to ```session.execute``` will
87
+ override default and allow you to control the logic:
88
+
89
+ ```ruby
90
+ session.execute(ARGV, ENV) do |request|
91
+
92
+ # Yields GitHandler::Request instance that
93
+ # contains all information about git request, env and repo
94
+
95
+ STDERR.puts "-----------------------------"
96
+ STDERR.puts "REMOTE IP: #{request.remote_ip}"
97
+ STDERR.puts "ARGS: #{request.args.inspect}"
98
+ STDERR.puts "ENV: #{request.env.inspect}"
99
+ STDERR.puts "REPO: #{request.repo}"
100
+ STDERR.puts "REPO PATH: #{request.repo_path}"
101
+ STDERR.puts "COMMAND: #{request.command}"
102
+ STDERR.puts "-----------------------------"
103
+ end
104
+ ```
105
+
106
+ By default, if request has invalid environment attributes or not a git request,
107
+ session raises ```GitHandler::SessionError```. If you dont want to handle exceptions,
108
+ just use ```session.execute_safe``` method:
109
+
110
+ ```ruby
111
+ session = GitHandler::Session.new(config)
112
+ session.execute_safe(ARGV, ENV)
113
+ ```
114
+
115
+ To test if all that works try this:
116
+
117
+ ```
118
+ ssh -vT git@YOUR_HOST.com
119
+ ```
120
+
121
+ In the debug output you'll something similar:
122
+
123
+ ```
124
+ debug1: Remote: Agent forwarding disabled.
125
+ debug1: Remote: Pty allocation disabled.
126
+ debug1: Remote: Forced command.
127
+ debug1: Remote: Port forwarding disabled.
128
+ debug1: Remote: X11 forwarding disabled.
129
+ debug1: Remote: Agent forwarding disabled.
130
+ debug1: Remote: Pty allocation disabled.
131
+ debug1: Sending environment.
132
+ debug1: Sending env LANG = en_US.UTF-8
133
+
134
+ >>> Error: Invalid git request <<<<
135
+
136
+ debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
137
+ debug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0
138
+ debug1: channel 0: free: client-session, nchannels 1
139
+ Transferred: sent 2384, received 2880 bytes, in 0.3 seconds
140
+ Bytes per second: sent 7308.1, received 8828.6
141
+ debug1: Exit status 1
142
+ ```
143
+
144
+ This means that everything works. Script does not provide any shell access and
145
+ only allows git requests. To test that, create an empty repository:
146
+
147
+ ```
148
+ mkdir /home/git/repositories
149
+ cd /home/git/repositories
150
+ git init --bare testrepo.git
151
+ ```
152
+
153
+ And clone it (on local machine):
154
+
155
+ ```
156
+ git clone git@YOUR_HOST.com:testrepo.git
157
+ ```
158
+
159
+ ### Server side configuration
160
+
161
+ In case you dont have a git user on your server, here is a quick manual
162
+ on how to get it rolling.
163
+
164
+ Create a git user:
165
+
166
+ ```bash
167
+ adduser --home /home/git --disabled-password git
168
+ ```
169
+
170
+ Restrict SSH authentication only via public keys. Open file ```/etc/ssh/sshd_config``` and
171
+ add this snippet to the end:
172
+
173
+ ```
174
+ Match User !root
175
+ PasswordAuthentication no
176
+ ```
177
+
178
+ This will disable password authentications for everyone except root, or other user
179
+ of your choice. You'll need to restart ssh daemon:
180
+
181
+ ```
182
+ /etc/init.d/ssh restart
183
+ ```
184
+
185
+ ### Authorized Keys
186
+
187
+ GitHandler provides a simple api to manage your ```authorized_keys``` file content.
188
+
189
+ Each write operation issues a lock ```File::LOCK_EX``` on file.
190
+
191
+ Example:
192
+
193
+ ```ruby
194
+ require 'git_handler/public_key'
195
+ require 'git_handler/authorized_keys'
196
+
197
+ # Read your local ssh public key content
198
+ content = File.read(File.expand_path('~/.ssh/id_rsa.pub'))
199
+
200
+ # Create a new key
201
+ key = GitHandler::PublicKey.new(content)
202
+
203
+ # Write formatted key to authorized_keys file
204
+ GitHandler::AuthorizedKeys.write_key('/path/to/file', key, 'my_command')
205
+ ```
206
+
207
+ You can also write multiple keys:
208
+
209
+ ```ruby
210
+ GitHandler::AuthorizedKeys.write_keys('/path/to/file', [k1, k2, k3], 'my_command')
211
+ ```
212
+
213
+ ## Testing
214
+
215
+ To run the test suite execute:
216
+
217
+ ```
218
+ rake test
219
+ ```
220
+
221
+ ## License
222
+
223
+ Copyright (c) 2012 Dan Sosedoff.
224
+
225
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
226
+
227
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
228
+
229
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../lib/git_handler/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "git_handler"
5
+ s.version = GitHandler::VERSION
6
+ s.summary = "Server-side git request handler"
7
+ s.description = "Set of tool to simplify custom git server setup"
8
+ s.homepage = "http://github.com/sosedoff/git_handler"
9
+ s.authors = ["Dan Sosedoff"]
10
+ s.email = ["dan.sosedoff@gmail.com"]
11
+
12
+ s.add_development_dependency 'rake', '~> 0.8'
13
+ s.add_development_dependency 'rspec', '~> 2.6'
14
+ s.add_development_dependency 'simplecov', '~> 0.4'
15
+
16
+ s.add_runtime_dependency 'sshkey', '~> 1.3'
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,10 @@
1
+ require 'git_handler/core_ext/array'
2
+ require 'git_handler/core_ext/hash'
3
+ require 'git_handler/version'
4
+ require 'git_handler/errors'
5
+ require 'git_handler/configuration'
6
+ require 'git_handler/request'
7
+ require 'git_handler/git_command'
8
+ require 'git_handler/session'
9
+
10
+ module GitHandler ; end
@@ -0,0 +1,40 @@
1
+ module GitHandler
2
+ module AuthorizedKeys
3
+ # Write contents to file with lock
4
+ #
5
+ # path - Path to output file
6
+ # content - String buffer
7
+ #
8
+ def self.write(path, content)
9
+ raise ArgumentError, "File \"#{path}\" does not exist." if !File.exists?(path)
10
+ raise ArgumentError, "File \"#{path}\" is not writable." if !File.writable?(path)
11
+
12
+ File.open(path, 'w') do |f|
13
+ f.flock(File::LOCK_EX)
14
+ f.write(content)
15
+ f.flock(File::LOCK_UN)
16
+ end
17
+ end
18
+
19
+ # Write formatted keys content to file
20
+ #
21
+ # path - Path to authorized_keys file
22
+ # keys - Array of GitHandler::PublicKey instances
23
+ # command - A custom command for the key
24
+ #
25
+ def self.write_keys(path, keys, command)
26
+ content = keys.map { |k| k.to_system_key(command) }.join("\n").strip
27
+ self.write(path, content)
28
+ end
29
+
30
+ # Write a single key formatted content to file
31
+ #
32
+ # path - Path to the output file
33
+ # key - GitHandler::PublicKey instance
34
+ # command - A custom command for the key
35
+ #
36
+ def self.write_key(path, key, command)
37
+ self.write_keys(path, [key], command)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module GitHandler
2
+ class Configuration
3
+ attr_reader :user
4
+ attr_reader :home_path
5
+ attr_reader :repos_path
6
+ attr_reader :log_path
7
+
8
+ # Initialize a new Configuration instance with options hash
9
+ #
10
+ # Valid options:
11
+ # :user - Git user
12
+ # :home_path - Git user home path
13
+ # :repos_path - Path to repositories
14
+ # :log_path - Git access log path
15
+ #
16
+ def initialize(options={})
17
+ @user = options[:user] || 'git'
18
+ @home_path = options[:home_path] || '/home/git'
19
+ @repos_path = options[:repos_path] || File.join(@home_path, 'repositories')
20
+ @log_path = options[:log_path] || File.join(@home_path, 'access.log')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def include_all?(values)
3
+ (values - self).empty?
4
+ end
5
+
6
+ def include_any?(values)
7
+ (self & values).any?
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def keys_all?(keys)
3
+ self.keys.include_all?(keys)
4
+ end
5
+
6
+ def keys_any?(keys)
7
+ self.keys.include_any?(keys)
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module GitHandler
2
+ class Error < StandardError ; end
3
+ class SessionError < Error ; end
4
+ class ParseError < Error ; end
5
+ class ConfigurationError < Error ; end
6
+ end
@@ -0,0 +1,46 @@
1
+ module GitHandler
2
+ module GitCommand
3
+ GIT_COMMAND = /^(git-upload-pack|git upload-pack|git-upload-archive|git upload-archive|git-receive-pack|git receive-pack) '(.*)'$/
4
+
5
+ COMMANDS_READONLY = [
6
+ 'git-upload-pack',
7
+ 'git upload-pack',
8
+ 'git-upload-archive',
9
+ 'git upload-archive'
10
+ ]
11
+
12
+ COMMANDS_WRITE = [
13
+ 'git-receive-pack',
14
+ 'git receive-pack'
15
+ ]
16
+
17
+ def parse_command(cmd)
18
+ unless valid_command?(cmd)
19
+ raise ParseError, "Invalid command: #{cmd}"
20
+ end
21
+
22
+ match = cmd.scan(GIT_COMMAND).flatten
23
+ action = match.first
24
+ repo = match.last
25
+
26
+ {
27
+ :action => action,
28
+ :repo => repo,
29
+ :read => read_command?(action),
30
+ :write => write_command?(action)
31
+ }
32
+ end
33
+
34
+ def valid_command?(cmd)
35
+ cmd =~ GIT_COMMAND ? true : false
36
+ end
37
+
38
+ def read_command?(cmd)
39
+ COMMANDS_READONLY.include?(cmd)
40
+ end
41
+
42
+ def write_command?(cmd)
43
+ COMMANDS_WRITE.include?(cmd)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ require 'digest'
2
+ require 'sshkey'
3
+
4
+ module GitHandler
5
+ class PublicKey
6
+ COMMAND_OPTIONS = [
7
+ 'no-port-forwarding',
8
+ 'no-X11-forwarding',
9
+ 'no-agent-forwarding',
10
+ 'no-pty'
11
+ ]
12
+
13
+ attr_reader :content
14
+
15
+ def initialize(content=nil)
16
+ @content = cleanup_content(content)
17
+ if @content.empty?
18
+ raise ArgumentError, 'Key content is empty!'
19
+ end
20
+ unless valid?
21
+ raise ArgumentError, "Is not a valid public key!"
22
+ end
23
+ end
24
+
25
+ def valid?
26
+ SSHKey.valid_ssh_public_key?(@content)
27
+ end
28
+
29
+ def md5
30
+ Digest::MD5.hexdigest(@content)
31
+ end
32
+
33
+ def sha1
34
+ Digest::SHA1.hexdigest(@content)
35
+ end
36
+
37
+ def to_system_key(command)
38
+ "command=\"#{command}\",#{COMMAND_OPTIONS.join(",")} #{@content}"
39
+ end
40
+
41
+ private
42
+
43
+ def cleanup_content(str)
44
+ str.to_s.strip.gsub(/(\r|\n)*/m, "")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ require 'ostruct'
2
+
3
+ module GitHandler
4
+ class Request < OpenStruct ; end
5
+ end
@@ -0,0 +1,129 @@
1
+ require 'logger'
2
+
3
+ module GitHandler
4
+ class Session
5
+ include GitHandler::GitCommand
6
+
7
+ attr_reader :args, :env, :config
8
+ attr_reader :log
9
+
10
+ # Initialize a new Session
11
+ #
12
+ # config - GitHandler::Configuration instance
13
+ #
14
+ def initialize(config=nil)
15
+ unless config.kind_of?(GitHandler::Configuration)
16
+ raise SessionError, 'Configuration required!'
17
+ end
18
+
19
+ unless File.exists?(config.home_path)
20
+ raise ConfigurationError, "Home path does not exist!"
21
+ end
22
+
23
+ unless File.exists?(config.repos_path)
24
+ raise ConfigurationError, "Repositories path does not exist!"
25
+ end
26
+
27
+ @config = config
28
+ @log = Logger.new(@config.log_path)
29
+ end
30
+
31
+ # Execute session
32
+ #
33
+ # args - Command arguments
34
+ # env - Environment parameters
35
+ # run_git - Execute git shell if no block provided#
36
+ #
37
+ def execute(args, env, run_git=true)
38
+ @args = args
39
+ @env = env
40
+
41
+ raise SessionError, "Invalid environment" unless valid_environment?
42
+ raise SessionError, "Invalid git request" unless valid_request?
43
+
44
+ command = parse_command(env['SSH_ORIGINAL_COMMAND'])
45
+ repo_path = File.join(config.repos_path, command[:repo])
46
+ request = GitHandler::Request.new(
47
+ :remote_ip => env['SSH_CLIENT'].split(' ').first,
48
+ :args => args,
49
+ :env => env,
50
+ :repo => command[:repo],
51
+ :repo_path => repo_path,
52
+ :command => [command[:action], "'#{repo_path}'"].join(' '),
53
+ :read => command[:read],
54
+ :write => command[:write]
55
+ )
56
+
57
+ log_request(request)
58
+
59
+ unless File.exist?(request.repo_path)
60
+ raise SessionError, "Repository #{request.repo} does not exist!"
61
+ end
62
+
63
+ if block_given?
64
+ # Pass all request information for custom processing
65
+ # if no block is defined it will execute git-shell
66
+ # with parameters provided
67
+ yield request
68
+ else
69
+ if run_git == true
70
+ exec("git-shell", "-c", request.command)
71
+ end
72
+ end
73
+
74
+ # Interesting part, inspired by github write-up
75
+ # if we need to pass this to another server
76
+ # the process should replace itself with another ssh call:
77
+ # exec("ssh", "git@TARGET", "#{args.join(' ')}")
78
+ end
79
+
80
+ # Execute session in safe manner, catch all exceptions
81
+ # and terminate session
82
+ #
83
+ def execute_safe(args, env, run_git=true)
84
+ begin
85
+ execute(args, env, run_git)
86
+ rescue GitHandler::SessionError => err
87
+ # TODO: Some additional logging here
88
+ terminate(err.message)
89
+ rescue Exception => err
90
+ # TODO: Needs some love here
91
+ terminate(err.message)
92
+ end
93
+ end
94
+
95
+ # Terminate session execution
96
+ #
97
+ # reason - Process termination reason message
98
+ # exit_status - Exit code (default: 1)
99
+ #
100
+ def terminate(reason='', exit_status=1)
101
+ logger.error("Session terminated. Reason: #{reason}")
102
+ $stderr.puts("Request failed: #{reason}")
103
+ exit(exit_status)
104
+ end
105
+
106
+ # Check if session environment is valid
107
+ #
108
+ def valid_environment?
109
+ env['USER'] == config.user && env['HOME'] == config.home_path
110
+ end
111
+
112
+ # Check if session request is valid
113
+ #
114
+ def valid_request?
115
+ if env.keys_all?(['SSH_CLIENT', 'SSH_CONNECTION', 'SSH_ORIGINAL_COMMAND'])
116
+ if valid_command?(env['SSH_ORIGINAL_COMMAND'])
117
+ return true
118
+ end
119
+ end
120
+ false
121
+ end
122
+
123
+ private
124
+
125
+ def log_request(req)
126
+ log.info("Request \"#{req.command}\" from #{req.remote_ip}. Args: #{req.args.join(' ')}")
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,3 @@
1
+ module GitHandler
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+ require 'git_handler/authorized_keys'
4
+
5
+ describe GitHandler::AuthorizedKeys do
6
+ before :each do
7
+ @path = '/tmp/authorized_keys'
8
+ File.delete(@path) if File.exists?(@path)
9
+ end
10
+
11
+ after :each do
12
+ File.delete(@path) if File.exists?(@path)
13
+ end
14
+
15
+ describe '.write' do
16
+ it 'raises error if output file does not exist' do
17
+ proc { GitHandler::AuthorizedKeys.write(@path, 'data') }.
18
+ should raise_error ArgumentError, "File \"#{@path}\" does not exist."
19
+ end
20
+
21
+ it 'raises error if output file is not writable' do
22
+ FileUtils.touch(@path)
23
+ FileUtils.chmod(0400, @path)
24
+
25
+ proc { GitHandler::AuthorizedKeys.write(@path, 'data') }.
26
+ should raise_error ArgumentError, "File \"#{@path}\" is not writable."
27
+ end
28
+
29
+ it 'writes data to the output file' do
30
+ FileUtils.touch(@path)
31
+ proc { GitHandler::AuthorizedKeys.write(@path, 'data') }.should_not raise_error
32
+ File.read(@path).should eq("data")
33
+ end
34
+ end
35
+
36
+ describe '.write_keys' do
37
+ it 'writes formatted keys content into the output file' do
38
+ FileUtils.touch(@path)
39
+ k = SSHKey.generate
40
+ key = GitHandler::PublicKey.new(k.ssh_public_key)
41
+ GitHandler::AuthorizedKeys.write_keys(@path, [key], 'custom_command')
42
+ File.read(@path).should eq('command="custom_command",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ' + k.ssh_public_key)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ class TestInstance
4
+ include GitHandler::GitCommand
5
+ end
6
+
7
+ describe GitHandler::GitCommand do
8
+ before do
9
+ @obj = TestInstance.new
10
+ end
11
+
12
+ it 'detects a valid git command' do
13
+ @obj.valid_command?("invalid command").should be_false
14
+ @obj.valid_command?("git-receive-pack").should be_false
15
+ @obj.valid_command?("git-receive-pack repo.git").should be_false
16
+ @obj.valid_command?("git-receive-pack 'repo'").should be_true
17
+ @obj.valid_command?("git-receive-pack 'repo.git'").should be_true
18
+ end
19
+
20
+ context '.parse_command' do
21
+ it 'raises error on invalid git command' do
22
+ proc { @obj.parse_command("invalid command") }.
23
+ should raise_error GitHandler::ParseError
24
+
25
+ proc { @obj.parse_command("git-receive-pack 'repo.git'") }.
26
+ should_not raise_error GitHandler::ParseError
27
+ end
28
+
29
+ it 'returns a proper action and repo' do
30
+ result = @obj.parse_command("git-receive-pack 'repo.git'")
31
+ result.should be_a Hash
32
+ result.should eql(
33
+ :action => 'git-receive-pack',
34
+ :repo => 'repo.git',
35
+ :read => false,
36
+ :write => true
37
+ )
38
+ end
39
+ end
40
+
41
+ it 'detects read command' do
42
+ @obj.read_command?('git-receive-pack').should be_false
43
+ @obj.read_command?('git-upload-pack').should be_true
44
+ @obj.read_command?('git upload-pack').should be_true
45
+ @obj.read_command?('git-upload-archive').should be_true
46
+ @obj.read_command?('git upload-archive').should be_true
47
+ end
48
+
49
+ it 'detects write command' do
50
+ @obj.write_command?("git-upload-pack").should be_false
51
+ @obj.write_command?("git-upload-archive").should be_false
52
+ @obj.write_command?("git receive-pack").should be_true
53
+ @obj.write_command?("git-receive-pack").should be_true
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe GitHandler::Configuration do
4
+ it 'has default settings' do
5
+ config = GitHandler::Configuration.new
6
+ config.user.should eq('git')
7
+ config.home_path.should eq('/home/git')
8
+ config.repos_path.should eq('/home/git/repositories')
9
+ config.log_path.should eq('/home/git/access.log')
10
+ end
11
+ end
File without changes
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'git_handler/public_key'
3
+
4
+ describe GitHandler::PublicKey do
5
+ it 'required content' do
6
+ proc { GitHandler::PublicKey.new }.
7
+ should raise_error ArgumentError, 'Key content is empty!'
8
+ end
9
+
10
+ it 'should be valid' do
11
+ proc { GitHandler::PublicKey.new('some data') }.
12
+ should raise_error ArgumentError, 'Is not a valid public key!'
13
+
14
+ k = SSHKey.generate
15
+
16
+ proc { GitHandler::PublicKey.new(k.ssh_public_key) }.
17
+ should_not raise_error ArgumentError, 'Is not a valid public key!'
18
+ end
19
+
20
+ context '.to_system_key' do
21
+ it 'returns a customized key content' do
22
+ k = SSHKey.generate
23
+ key = GitHandler::PublicKey.new(k.ssh_public_key)
24
+ custom = key.to_system_key('foobar')
25
+ custom.should eq('command="foobar",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ' + k.ssh_public_key)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe GitHandler::Session do
5
+ before do
6
+ FileUtils.mkdir_p('/tmp/valid-repo.git')
7
+ end
8
+
9
+ context '.new' do
10
+ it 'requires configuration' do
11
+ proc { GitHandler::Session.new }.
12
+ should raise_error GitHandler::SessionError, 'Configuration required!'
13
+ end
14
+
15
+ it 'raises error if home path does not exist' do
16
+ config = GitHandler::Configuration.new(:home_path => '/var/foo')
17
+ proc { GitHandler::Session.new(config) }.
18
+ should raise_error GitHandler::ConfigurationError, "Home path does not exist!"
19
+ end
20
+
21
+ it 'raises error if repos path does not exist' do
22
+ config = GitHandler::Configuration.new(:home_path => '/tmp', :repos_path => '/var/foo')
23
+ proc { GitHandler::Session.new(config) }.
24
+ should raise_error GitHandler::ConfigurationError, "Repositories path does not exist!"
25
+ end
26
+ end
27
+
28
+ context '.execute' do
29
+ before :each do
30
+ @config = GitHandler::Configuration.new(
31
+ :home_path => '/tmp',
32
+ :repos_path => '/tmp'
33
+ )
34
+ @session = GitHandler::Session.new(@config)
35
+ @env = {
36
+ 'USER' => 'git',
37
+ 'HOME' => '/tmp',
38
+ 'SSH_CLIENT' => '127.0.0.1',
39
+ 'SSH_CONNECTION' => '127.0.0.1 64039 127.0.0.2 22',
40
+ 'SSH_ORIGINAL_COMMAND' => "git-upload-pack 'valid-repo.git'"
41
+ }
42
+ end
43
+
44
+ subject do
45
+ GitHandler::Session.new(@config)
46
+ end
47
+
48
+ it 'validates environment' do
49
+ proc { subject.execute([], {}) }.
50
+ should raise_error GitHandler::SessionError, 'Invalid environment'
51
+
52
+ proc { subject.execute([], {'USER' => 'git', 'HOME' => '/invalid/path'}) }.
53
+ should raise_error GitHandler::SessionError, 'Invalid environment'
54
+
55
+ proc { subject.execute([], {'USER' => 'git', 'HOME' => '/tmp'}) }.
56
+ should_not raise_error GitHandler::SessionError, 'Invalid environment'
57
+ end
58
+
59
+ it 'validates git request' do
60
+ env = {'USER' => 'git', 'HOME' => '/tmp'}
61
+
62
+ proc { subject.execute([], env) }.
63
+ should raise_error GitHandler::SessionError, 'Invalid git request'
64
+
65
+ env.merge!(
66
+ 'SSH_CLIENT' => '127.0.0.1',
67
+ 'SSH_CONNECTION' => '127.0.0.1 64039 127.0.0.2 22',
68
+ 'SSH_ORIGINAL_COMMAND' => 'invalid command'
69
+ )
70
+
71
+ proc { subject.execute([], env, false) }.
72
+ should raise_error GitHandler::SessionError, 'Invalid git request'
73
+
74
+ env['SSH_ORIGINAL_COMMAND'] = "git-upload-pack 'foobar.git'"
75
+
76
+ proc { subject.execute([], env, false) }.
77
+ should_not raise_error GitHandler::SessionError, 'Invalid git request'
78
+ end
79
+
80
+ it 'validates repository existense' do
81
+ @env['SSH_ORIGINAL_COMMAND'] = "git-upload-pack 'invalid-repo.git'"
82
+ proc { subject.execute([], @env, false) }.
83
+ should raise_error GitHandler::SessionError, 'Repository invalid-repo.git does not exist!'
84
+
85
+ @env['SSH_ORIGINAL_COMMAND'] = "git-upload-pack 'valid-repo.git'"
86
+ proc { subject.execute([], @env, false) }.
87
+ should_not raise_error GitHandler::SessionError, 'Repository valid-repo.git does not exist!'
88
+ end
89
+
90
+ it 'yields request payload if block provided' do
91
+ payload = nil
92
+ subject.execute([], @env, false) { |req| payload = req }
93
+ payload.should_not be_nil
94
+ payload.should be_a GitHandler::Request
95
+ payload.env.should eq(@env)
96
+ payload.repo.should eq('valid-repo.git')
97
+ payload.repo_path.should eq('/tmp/valid-repo.git')
98
+ payload.command.should eq("git-upload-pack '/tmp/valid-repo.git'")
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,11 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'lib/git_handler'
4
+
5
+ def fixture_path
6
+ File.expand_path("../fixtures", __FILE__)
7
+ end
8
+
9
+ def fixture(file)
10
+ File.read(File.join(fixture_path, file))
11
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: git_handler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.2.0
6
+ platform: ruby
7
+ authors:
8
+ - Dan Sosedoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-05-23 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "0.8"
24
+ type: :development
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: "2.6"
35
+ type: :development
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: simplecov
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: "0.4"
46
+ type: :development
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: sshkey
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: "1.3"
57
+ type: :runtime
58
+ version_requirements: *id004
59
+ description: Set of tool to simplify custom git server setup
60
+ email:
61
+ - dan.sosedoff@gmail.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files: []
67
+
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - .travis.yml
72
+ - Gemfile
73
+ - README.md
74
+ - Rakefile
75
+ - git_handler.gemspec
76
+ - lib/git_handler.rb
77
+ - lib/git_handler/authorized_keys.rb
78
+ - lib/git_handler/configuration.rb
79
+ - lib/git_handler/core_ext/array.rb
80
+ - lib/git_handler/core_ext/hash.rb
81
+ - lib/git_handler/errors.rb
82
+ - lib/git_handler/git_command.rb
83
+ - lib/git_handler/public_key.rb
84
+ - lib/git_handler/request.rb
85
+ - lib/git_handler/session.rb
86
+ - lib/git_handler/version.rb
87
+ - spec/authorized_keys_spec.rb
88
+ - spec/command_spec.rb
89
+ - spec/configuration_spec.rb
90
+ - spec/fixtures/.gitkeep
91
+ - spec/public_key_spec.rb
92
+ - spec/session_spec.rb
93
+ - spec/spec_helper.rb
94
+ homepage: http://github.com/sosedoff/git_handler
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project:
117
+ rubygems_version: 1.8.24
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: Server-side git request handler
121
+ test_files:
122
+ - spec/authorized_keys_spec.rb
123
+ - spec/command_spec.rb
124
+ - spec/configuration_spec.rb
125
+ - spec/fixtures/.gitkeep
126
+ - spec/public_key_spec.rb
127
+ - spec/session_spec.rb
128
+ - spec/spec_helper.rb