git_wit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +82 -0
  3. data/Rakefile +40 -0
  4. data/app/controllers/git_wit/application_controller.rb +6 -0
  5. data/app/controllers/git_wit/git_controller.rb +116 -0
  6. data/bin/gw-shell +4 -0
  7. data/config/initializers/git_wit.rb +2 -0
  8. data/config/routes.rb +5 -0
  9. data/lib/generators/git_wit/install/USAGE +8 -0
  10. data/lib/generators/git_wit/install/install_generator.rb +15 -0
  11. data/lib/generators/git_wit/templates/README +11 -0
  12. data/lib/generators/git_wit/templates/git_wit.rb +78 -0
  13. data/lib/git_wit/auth.rb +28 -0
  14. data/lib/git_wit/authorized_keys.rb +102 -0
  15. data/lib/git_wit/engine.rb +12 -0
  16. data/lib/git_wit/errors.rb +6 -0
  17. data/lib/git_wit/shell.rb +72 -0
  18. data/lib/git_wit/version.rb +3 -0
  19. data/lib/git_wit.rb +38 -0
  20. data/test/dummy/README.rdoc +261 -0
  21. data/test/dummy/Rakefile +7 -0
  22. data/test/dummy/app/assets/javascripts/application.js +16 -0
  23. data/test/dummy/app/assets/javascripts/bootstrap.js +5 -0
  24. data/test/dummy/app/assets/javascripts/public_keys.js +2 -0
  25. data/test/dummy/app/assets/javascripts/repositories.js +2 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  27. data/test/dummy/app/assets/stylesheets/bootstrap_and_overrides.css +12 -0
  28. data/test/dummy/app/assets/stylesheets/public_keys.css +4 -0
  29. data/test/dummy/app/assets/stylesheets/repositories.css +4 -0
  30. data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
  31. data/test/dummy/app/controllers/application_controller.rb +3 -0
  32. data/test/dummy/app/controllers/public_keys_controller.rb +61 -0
  33. data/test/dummy/app/controllers/repositories_controller.rb +76 -0
  34. data/test/dummy/app/helpers/application_helper.rb +2 -0
  35. data/test/dummy/app/helpers/public_keys_helper.rb +2 -0
  36. data/test/dummy/app/helpers/repositories_helper.rb +2 -0
  37. data/test/dummy/app/models/ability.rb +16 -0
  38. data/test/dummy/app/models/public_key.rb +46 -0
  39. data/test/dummy/app/models/repository.rb +27 -0
  40. data/test/dummy/app/models/role.rb +6 -0
  41. data/test/dummy/app/models/user.rb +14 -0
  42. data/test/dummy/app/views/devise/confirmations/new.html.erb +22 -0
  43. data/test/dummy/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  44. data/test/dummy/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  45. data/test/dummy/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  46. data/test/dummy/app/views/devise/passwords/edit.html.erb +30 -0
  47. data/test/dummy/app/views/devise/passwords/new.html.erb +22 -0
  48. data/test/dummy/app/views/devise/registrations/edit.html.erb +50 -0
  49. data/test/dummy/app/views/devise/registrations/new.html.erb +43 -0
  50. data/test/dummy/app/views/devise/sessions/new.html.erb +37 -0
  51. data/test/dummy/app/views/devise/shared/_links.erb +29 -0
  52. data/test/dummy/app/views/devise/unlocks/new.html.erb +12 -0
  53. data/test/dummy/app/views/layouts/application.html.erb +106 -0
  54. data/test/dummy/app/views/public_keys/_form.html.erb +16 -0
  55. data/test/dummy/app/views/public_keys/index.html.erb +36 -0
  56. data/test/dummy/app/views/public_keys/new.html.erb +5 -0
  57. data/test/dummy/app/views/public_keys/show.html.erb +25 -0
  58. data/test/dummy/app/views/repositories/_form.html.erb +29 -0
  59. data/test/dummy/app/views/repositories/edit.html.erb +5 -0
  60. data/test/dummy/app/views/repositories/index.html.erb +52 -0
  61. data/test/dummy/app/views/repositories/new.html.erb +5 -0
  62. data/test/dummy/app/views/repositories/show.html.erb +31 -0
  63. data/test/dummy/config/application.rb +59 -0
  64. data/test/dummy/config/boot.rb +10 -0
  65. data/test/dummy/config/database.yml +25 -0
  66. data/test/dummy/config/environment.rb +5 -0
  67. data/test/dummy/config/environments/development.rb +37 -0
  68. data/test/dummy/config/environments/production.rb +67 -0
  69. data/test/dummy/config/environments/test.rb +37 -0
  70. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  71. data/test/dummy/config/initializers/devise.rb +242 -0
  72. data/test/dummy/config/initializers/git_wit.rb +73 -0
  73. data/test/dummy/config/initializers/inflections.rb +15 -0
  74. data/test/dummy/config/initializers/mime_types.rb +5 -0
  75. data/test/dummy/config/initializers/rolify.rb +8 -0
  76. data/test/dummy/config/initializers/secret_token.rb +7 -0
  77. data/test/dummy/config/initializers/session_store.rb +8 -0
  78. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  79. data/test/dummy/config/locales/devise.en.yml +59 -0
  80. data/test/dummy/config/locales/en.bootstrap.yml +17 -0
  81. data/test/dummy/config/locales/en.yml +5 -0
  82. data/test/dummy/config/routes.rb +12 -0
  83. data/test/dummy/config.ru +4 -0
  84. data/test/dummy/db/development.sqlite3 +0 -0
  85. data/test/dummy/db/migrate/20130217110616_devise_create_users.rb +46 -0
  86. data/test/dummy/db/migrate/20130217111055_create_repositories.rb +14 -0
  87. data/test/dummy/db/migrate/20130217114405_rolify_create_roles.rb +19 -0
  88. data/test/dummy/db/migrate/20130217191808_add_username_to_users.rb +5 -0
  89. data/test/dummy/db/migrate/20130217221157_create_public_keys.rb +13 -0
  90. data/test/dummy/db/schema.rb +76 -0
  91. data/test/dummy/db/test.sqlite3 +0 -0
  92. data/test/dummy/public/404.html +26 -0
  93. data/test/dummy/public/422.html +26 -0
  94. data/test/dummy/public/500.html +25 -0
  95. data/test/dummy/public/favicon.ico +0 -0
  96. data/test/dummy/script/rails +6 -0
  97. data/test/dummy/test/fixtures/public_keys.yml +14 -0
  98. data/test/dummy/test/fixtures/repositories.yml +17 -0
  99. data/test/dummy/test/fixtures/users.yml +9 -0
  100. data/test/dummy/test/functional/public_keys_controller_test.rb +49 -0
  101. data/test/dummy/test/functional/repositories_controller_test.rb +49 -0
  102. data/test/dummy/test/unit/helpers/public_keys_helper_test.rb +4 -0
  103. data/test/dummy/test/unit/helpers/repositories_helper_test.rb +4 -0
  104. data/test/dummy/test/unit/public_key_test.rb +7 -0
  105. data/test/dummy/test/unit/repository_test.rb +7 -0
  106. data/test/dummy/test/unit/user_test.rb +7 -0
  107. data/test/dummy/tmp/cache/assets/C7E/BC0/sprockets%2Fb7118f368364962573a44054bcfb80d0 +0 -0
  108. data/test/dummy/tmp/cache/assets/C80/840/sprockets%2F562c2d168da585f80579347d10790a0a +0 -0
  109. data/test/dummy/tmp/cache/assets/C8C/B80/sprockets%2F371bf96e99717688ed7313a0c53f4212 +0 -0
  110. data/test/dummy/tmp/cache/assets/C9C/700/sprockets%2Fc7b1373dbf219a8722efc21160641340 +0 -0
  111. data/test/dummy/tmp/cache/assets/C9E/5F0/sprockets%2F2bca2b107bb6c26b720d135270688918 +0 -0
  112. data/test/dummy/tmp/cache/assets/CA9/9C0/sprockets%2F0c1b7ebd087418498ea6037225d33d25 +0 -0
  113. data/test/dummy/tmp/cache/assets/CC8/B00/sprockets%2F9815364bfd49ed907870e270d75a995a +0 -0
  114. data/test/dummy/tmp/cache/assets/CD1/800/sprockets%2Fc044b140dcef533c52712c7b51e21996 +0 -0
  115. data/test/dummy/tmp/cache/assets/CD5/2C0/sprockets%2F166c056119ebdfb8b7104c97b424b423 +0 -0
  116. data/test/dummy/tmp/cache/assets/CD7/380/sprockets%2F4079ce1dbbcf4a599527303670006b6b +0 -0
  117. data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  118. data/test/dummy/tmp/cache/assets/CE0/CC0/sprockets%2F2b38c3fb549036de5c4666637a0c80c6 +0 -0
  119. data/test/dummy/tmp/cache/assets/CEC/B70/sprockets%2F7f98753ca8c35e4249363a04389b3caf +0 -0
  120. data/test/dummy/tmp/cache/assets/CF0/1D0/sprockets%2F6fc757c2c8329244ca95d6909865bbc2 +0 -0
  121. data/test/dummy/tmp/cache/assets/CF9/980/sprockets%2Fbd55042e1acd32eb611041444d794d4d +0 -0
  122. data/test/dummy/tmp/cache/assets/CFD/560/sprockets%2Fe4e7fe4ee089382325686f806b939d3e +0 -0
  123. data/test/dummy/tmp/cache/assets/D00/B90/sprockets%2F07c00c80f1ea62d95a01f11f9c728b72 +0 -0
  124. data/test/dummy/tmp/cache/assets/D0D/9A0/sprockets%2F1fce44192cdb30f44b7545a37c9891b2 +0 -0
  125. data/test/dummy/tmp/cache/assets/D0F/390/sprockets%2F9df081609c3449ab40c93b1cf07de357 +0 -0
  126. data/test/dummy/tmp/cache/assets/D25/A60/sprockets%2F0e036061ad22b2e6dce1639b234cf15c +0 -0
  127. data/test/dummy/tmp/cache/assets/D27/DB0/sprockets%2Fa3a0a778855bce9fa47913d389ea9884 +0 -0
  128. data/test/dummy/tmp/cache/assets/D29/5A0/sprockets%2Fdad9e81b43ca12671246ee52a1900d0c +0 -0
  129. data/test/dummy/tmp/cache/assets/D2E/FF0/sprockets%2Fc06112642c994b6b7c2ba6632fad1fd0 +0 -0
  130. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  131. data/test/dummy/tmp/cache/assets/D36/120/sprockets%2Feac54bd3c540af6b964d025e345227d6 +0 -0
  132. data/test/dummy/tmp/cache/assets/D3E/240/sprockets%2F84b96d6b2d2653cb4127b06d8acb990d +0 -0
  133. data/test/dummy/tmp/cache/assets/D3F/830/sprockets%2F18578d4ef3abd6e12d836841dd6b8a00 +0 -0
  134. data/test/dummy/tmp/cache/assets/D4B/E70/sprockets%2F0909bc70e589d40a6cd90dfe6f18257e +0 -0
  135. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  136. data/test/dummy/tmp/cache/assets/D57/3D0/sprockets%2F7bbccc5129a5013b70831ec9ad62ab24 +0 -0
  137. data/test/dummy/tmp/cache/assets/D5A/000/sprockets%2F7d4f67f146b6d7904dfc4edd9135f588 +0 -0
  138. data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  139. data/test/dummy/tmp/cache/assets/D5B/BB0/sprockets%2Fba769276c4de14151bc4202cba7f3ad3 +0 -0
  140. data/test/dummy/tmp/cache/assets/D5E/BC0/sprockets%2F2d96fa667066778db858d7b7cb0fce69 +0 -0
  141. data/test/dummy/tmp/cache/assets/D6E/BA0/sprockets%2F5178d3788fe35a52acb5f3bd22ea078a +0 -0
  142. data/test/dummy/tmp/cache/assets/D6F/C20/sprockets%2F22e783a8f5f9224f01e8e62fab6afb40 +0 -0
  143. data/test/dummy/tmp/cache/assets/D76/5C0/sprockets%2Fd8a5669df31f129f355283e6dab4c5ad +0 -0
  144. data/test/dummy/tmp/cache/assets/D79/DE0/sprockets%2F7cfd335e68d881b03f6b7f1bd91f270f +0 -0
  145. data/test/dummy/tmp/cache/assets/D8A/CA0/sprockets%2F656af8b87ad378e8e4f2ec94b6b5c719 +0 -0
  146. data/test/dummy/tmp/cache/assets/D8C/620/sprockets%2Ff37f8e5b8cccd9880276a9f5157d4c7e +0 -0
  147. data/test/dummy/tmp/cache/assets/D92/200/sprockets%2Fb816d858281027bdd3fe2bfac43b4ca1 +0 -0
  148. data/test/dummy/tmp/cache/assets/D92/CE0/sprockets%2Ffca6a13676a2be09234905f9acae22cd +0 -0
  149. data/test/dummy/tmp/cache/assets/D96/9E0/sprockets%2F6fabecd33f7a5a087f4fb6a2d6312443 +0 -0
  150. data/test/dummy/tmp/cache/assets/DA2/D20/sprockets%2Ff5faf079fb660bde5bc9502bde442088 +0 -0
  151. data/test/dummy/tmp/cache/assets/DA5/570/sprockets%2F3517de599b6fd005bc5d5d69ba5ff1e2 +0 -0
  152. data/test/dummy/tmp/cache/assets/DA7/070/sprockets%2F69eadf8c3a94b04fe0b4992ee4a8c821 +0 -0
  153. data/test/dummy/tmp/cache/assets/DBA/BF0/sprockets%2Fe63ea1d7bfb0ee50380debe42360a3b5 +0 -0
  154. data/test/dummy/tmp/cache/assets/DBB/3B0/sprockets%2F6a0aaa6c5b0d10b936e237a7ecb4e4c9 +0 -0
  155. data/test/dummy/tmp/cache/assets/DBC/8E0/sprockets%2F908976cfbcdf6ad4c59737bf3c78e1e8 +0 -0
  156. data/test/dummy/tmp/cache/assets/DD3/FD0/sprockets%2Febf97c76a9ba2a889dd01be2caa75806 +0 -0
  157. data/test/dummy/tmp/cache/assets/DD8/410/sprockets%2Fc02eeb7ea977fd713cc19ca93d838af4 +0 -0
  158. data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  159. data/test/dummy/tmp/cache/assets/DEA/E40/sprockets%2F4166d7d00d1e72fed2004debed2bea3e +0 -0
  160. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  161. data/test/dummy/tmp/cache/assets/E05/C70/sprockets%2Fccd814ec859d582ada46e271edfe7ae1 +0 -0
  162. data/test/dummy/tmp/cache/assets/E0C/2A0/sprockets%2F37c59fadd9a21cab7d05d78a7dfe7e85 +0 -0
  163. data/test/dummy/tmp/cache/assets/E28/130/sprockets%2F6f332ca43b7ed658d90b8ba5daaa5ded +0 -0
  164. data/test/dummy/tmp/cache/assets/E3B/080/sprockets%2F09e2a090befacdae0db10cafb1893a0a +0 -0
  165. data/test/dummy/tmp/cache/assets/E65/CD0/sprockets%2F93cdf3fec0e3aa6deefa955c6828fbd0 +0 -0
  166. data/test/dummy/tmp/cache/assets/E9F/450/sprockets%2Fbbfdc5edaaf25dfdb5ee8f9db7895435 +0 -0
  167. data/test/dummy/tmp/restart.txt +0 -0
  168. data/test/functional/git_wit/git_controller_test.rb +10 -0
  169. data/test/git_wit_test.rb +7 -0
  170. data/test/integration/navigation_test.rb +10 -0
  171. data/test/test_helper.rb +18 -0
  172. data/test/unit/auth_test.rb +56 -0
  173. data/test/unit/authorized_keys_test.rb +57 -0
  174. data/test/unit/config_test.rb +42 -0
  175. data/test/unit/shell_test.rb +89 -0
  176. metadata +409 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # GitWit
2
+
3
+ [![Build Status](https://travis-ci.org/xdissent/git_wit.png?branch=master)](https://travis-ci.org/xdissent/git_wit)
4
+
5
+ Dead simple Git hosting for Rails apps.
6
+
7
+ ## Quickstart
8
+
9
+ Run `rails g git_wit:install` and checkout `config/initializers/git_wit.rb`.
10
+ You'll want to first change `config.repositories_path` to a folder where you'd
11
+ like to store your repositories. Let's use "tmp/repositories" in our app root
12
+ for fun:
13
+
14
+ ```ruby
15
+ config.repositories_path = Rails.root.join("tmp", "repositories").to_s
16
+ ```
17
+
18
+ Normally you wouldn't want to allow users to send their authentication
19
+ credentials over an insecure protocol like HTTP, because they'll be sent in
20
+ plain text over the wire. And since anonymous write access is always disallowed,
21
+ that means you can't safely push over HTTP without SSL. To disable these
22
+ protections, something you'd **never** do in a production environment, change
23
+ the following config values in the initializer:
24
+
25
+ ```
26
+ config.insecure_auth = true
27
+ config.insecure_write = true
28
+ ```
29
+
30
+ Now let's set up some simple (fake) authentication and authorization:
31
+
32
+ ```ruby
33
+ config.authenticate = ->(user, password) do
34
+ %w(reader writer).include?(user) && user == password
35
+ end
36
+
37
+ config.authorize_read = ->(user, repository) do
38
+ %w(reader writer).include?(user)
39
+ end
40
+
41
+ config.authorize_write = ->(user, repository) do
42
+ user == "writer"
43
+ end
44
+ ```
45
+
46
+ What we've done is effectively create two users: `reader` and `writer`. Both can
47
+ read all repositories, but only `writer` may write (and can write to any repo.)
48
+ Both users are considered authenticated if the password matches the username.
49
+
50
+ Now your app is ready to start serving git repos over HTTP. Just create the
51
+ repositories folder, initialize a repo and start the server:
52
+
53
+ ```console
54
+ $ mkdir -p tmp/repositories
55
+ $ git init --bare tmp/repositories/example.git
56
+ $ rails s
57
+ ```
58
+
59
+ Clone your repo, make some changes, and push:
60
+
61
+ ```console
62
+ $ git clone http://localhost:3000/example.git
63
+ $ cd example
64
+ $ touch README
65
+ $ git add README
66
+ $ git commit -m "First"
67
+ $ git push origin master
68
+ ```
69
+
70
+ Your server will ask you for a username and password when you push - use
71
+ `writer` for both and it should accept your changes.
72
+
73
+ ## SSH support
74
+
75
+ See the dummy app in `test/dummy` for a working example of `authorized_keys`
76
+ management for the `ssh_user`.
77
+
78
+ **NOTE** To manage SSH keys, the `ssh_user` *must* be allowed to `sudo` as the
79
+ Rails application user, **and** vice versa. More documentation is forthcoming.
80
+
81
+
82
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'GitWit'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,6 @@
1
+ module GitWit
2
+ class ApplicationController < ActionController::Metal
3
+ include AbstractController::Callbacks
4
+ include ActionController::HttpAuthentication::Basic::ControllerMethods
5
+ end
6
+ end
@@ -0,0 +1,116 @@
1
+ require "open3"
2
+ require_dependency "git_wit/application_controller"
3
+
4
+ module GitWit
5
+ class GitController < ApplicationController
6
+
7
+ ENV_KEEPERS = %w(REQUEST_METHOD QUERY_STRING REMOTE_ADDR SERVER_ADDR
8
+ SERVER_NAME SERVER_PORT CONTENT_TYPE)
9
+
10
+ before_filter :authenticate, :authorize, :find_repository
11
+
12
+ def service
13
+ # Shell out to git-http-backend.
14
+ out, err, status = Open3.capture3 shell_env, GitWit.git_http_backend_path,
15
+ stdin_data: request.raw_post, binmode: true
16
+
17
+ # Bail if the backend failed.
18
+ raise GitError, err unless status.success?
19
+
20
+ # Split headers and body from response.
21
+ headers, body = out.split("\r\n\r\n", 2)
22
+
23
+ # Convert CGI headers to HTTP headers.
24
+ headers = Hash[headers.split("\r\n").map { |l| l.split(/\s*\:\s*/, 2) }]
25
+
26
+ # Set status from header if given, otherwise it's a 200.
27
+ self.status = headers.delete("Status").to_i if headers.key? "Status"
28
+
29
+ # Set response body if given, otherwise empty string.
30
+ self.response_body = body.presence || ""
31
+ end
32
+
33
+ private
34
+ def shell_env
35
+ request.headers.dup.extract!(*ENV_KEEPERS).merge(http_env).merge(git_env)
36
+ end
37
+
38
+ def git_env
39
+ {
40
+ GIT_HTTP_EXPORT_ALL: "uknoit",
41
+ GIT_PROJECT_ROOT: GitWit.repositories_path,
42
+ PATH_INFO: "/#{params[:repository]}/#{params[:refs] || params[:service]}",
43
+ REMOTE_USER: (user_attr(:username) || @username),
44
+ GIT_COMMITTER_NAME: user_attr(:committer_name),
45
+ GIT_COMMITTER_EMAIL: user_attr(:committer_email)
46
+ }.reject { |_, v| v.nil? }.stringify_keys
47
+ end
48
+
49
+ def http_env
50
+ request.headers.select { |k, _| k.start_with? "HTTP_" }
51
+ end
52
+
53
+ def authenticate
54
+ # Disallow authentication over insecure protocol per configuration.
55
+ raise ForbiddenError if !GitWit.insecure_auth \
56
+ && request.authorization.present? && !request.ssl?
57
+
58
+ # Authenticate user *ONLY IF CREDENTIALS ARE PROVIDED*
59
+ @user = authenticate_with_http_basic do |username, password|
60
+ @username = username
61
+ user = GitWit.user_for_authentication username
62
+ user if GitWit.authenticate user, password
63
+ end
64
+
65
+ # Request credentials again if provided and no user was authenticated.
66
+ if @user.nil? && request.authorization.present?
67
+ request_http_basic_authentication_if_allowed
68
+ end
69
+ end
70
+
71
+ def request_http_basic_authentication_if_allowed
72
+ raise ForbiddenError if !GitWit.insecure_auth && !request.ssl?
73
+ request_http_basic_authentication GitWit.realm
74
+ end
75
+
76
+ def authorize
77
+ write_op? ? authorize_write : authorize_read
78
+ end
79
+
80
+ def authorize_write
81
+ # Never allow anonymous write operations.
82
+ return request_http_basic_authentication_if_allowed if @user.nil?
83
+
84
+ # Disallow write operations over insecure protocol per configuration.
85
+ raise ForbiddenError if !GitWit.insecure_write && !request.ssl?
86
+
87
+ # Authorize for write operations.
88
+ raise UnauthorizedError unless GitWit.authorize_write @user, params[:repository]
89
+ end
90
+
91
+ def authorize_read
92
+ return if GitWit.authorize_read(@user, params[:repository])
93
+ return request_http_basic_authentication_if_allowed if @user.nil?
94
+ raise UnauthorizedError
95
+ end
96
+
97
+ # TODO: Sure about this?
98
+ def write_op?
99
+ params[:service] == "git-receive-pack"
100
+ end
101
+
102
+ def find_repository
103
+ repo_path = File.join GitWit.repositories_path, params[:repository]
104
+ raise NotFoundError unless File.exist? repo_path
105
+ end
106
+
107
+ def user_attr(sym)
108
+ try_user GitWit.config.send("#{sym}_attribute")
109
+ end
110
+
111
+ def try_user(sym_or_proc)
112
+ return @user.try(sym_or_proc) if sym_or_proc.is_a? Symbol
113
+ sym_or_proc.call(@user) if sym_or_proc.respond_to? :call
114
+ end
115
+ end
116
+ end
data/bin/gw-shell ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "git_wit/shell"
4
+ GitWit::Shell.run
@@ -0,0 +1,2 @@
1
+ # Configures GitWit defaults (before app initializer)
2
+ GitWit.default_config!
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ GitWit::Engine.routes.draw do
2
+ get ":repository/*refs" => "git#service", repository: /[\-\/\w\.]+\.git/
3
+ post ":repository/:service" => "git#service", repository: /[\-\/\w\.]+\.git/,
4
+ service: /git-[\w\-]+/
5
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Creates a GitWit initializer for your application.
3
+
4
+ Example:
5
+ rails generate git_wit:install
6
+
7
+ This will create:
8
+ config/intializers/git_wit.rb
@@ -0,0 +1,15 @@
1
+ class GitWit::InstallGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('../../templates', __FILE__)
3
+
4
+ def copy_initializer
5
+ template "git_wit.rb", "config/initializers/git_wit.rb"
6
+ end
7
+
8
+ def show_readme
9
+ readme "README" if behavior == :invoke
10
+ end
11
+
12
+ def mount_route
13
+ route 'mount GitWit::Engine => "/"'
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ _________________________________________________________________
2
+ |__ ____ ___ __ ____ __ ___ __|
3
+ | ____| |_ _| |__ __| | | | | |_ _| |__ __|
4
+ / / __ | | | | | | | | | | | |
5
+ | | | \ | | | | | | | | | | | |
6
+ \ \__| | _| |_ | | \ \/\/ / _| |_ | |
7
+ __| |__| |____| |______\ /___| |____| |____
8
+ |_________________________________________________________________|
9
+
10
+ Thank you for installing GitWit. You'll need to configure the initializer
11
+ at config/initializers/git_wit.rb to reflect your settings.
@@ -0,0 +1,78 @@
1
+ # Configure GitWit for your application using this initializer.
2
+ GitWit.configure do |config|
3
+
4
+ # Configure the path to the repositories. This folder should be readable
5
+ # and writable by your application. Use an absolute path for best results.
6
+ # No trailing slash necessary.
7
+ # config.repositories_path = "/var/git"
8
+
9
+ # Configure the user for which to manage SSH keys (if enabled.) This user
10
+ # must be allowed to run gw-ssh (or bundle exec or rvm or whatever) via sudo
11
+ # as the application user.
12
+ # config.ssh_user = "git"
13
+
14
+ # Configure the absolute path to the authorized_keys file for ssh_user. By
15
+ # default, this will be calculated as "~ssh_user/.ssh/authorized_keys".
16
+ # config.authorized_keys_path = "/var/git/.ssh/authorized_keys"
17
+
18
+ # Configure the path to the git-http-backend binary.
19
+ # config.git_http_backend_path = "/usr/libexec/git-core/git-http-backend"
20
+
21
+ # Configure the HTTP Basic Auth Realm. Go nuts.
22
+ # config.realm = "GitWit"
23
+
24
+ # Allow or disable write operations (push) via non-secure (http) protocols.
25
+ # config.insecure_write = false
26
+
27
+ # Allow or disable authentication via non-secure (http) protocols. GitWit uses
28
+ # HTTP Basic authentication, which sends your password in cleartext. This is
29
+ # bad behaviour so the default is to completely disallow authentication
30
+ # without SSL. Note that this will effectively disable insecure write
31
+ # operations as well when set to false, since writes require authentication.
32
+ # config.insecure_auth = false
33
+
34
+ # Configure git user attributes. GitWit will "try" these attributes when
35
+ # discerning the user information to pass to git. These may be callables that
36
+ # accept the user model (if authenticated) and should return a string value.
37
+ # If nil (or return nil), reasonable defaults will be used.
38
+ #
39
+ # config.username_attribute = :login # REMOTE_USER
40
+ # config.committer_email_attribute = :email # GIT_COMMITTER_NAME
41
+ # config.committer_name_attribute = :name # GIT_COMMITTER_EMAIL
42
+
43
+ # Customize how the user is derived from the username. Below is an example for
44
+ # devise. Your callable should accept a username return a user model. A string
45
+ # is OK if you don't want to use real models. In fact, the default just
46
+ # returns the username as the user model. You'll get the user as an argument
47
+ # to the config.authenticate method later for actual authentication. Returning
48
+ # nil means "authentication failed."
49
+ #
50
+ # config.user_for_authentication = ->(username) do
51
+ # user = User.find_for_authentication username: username
52
+ # user if user.active_for_authentication?
53
+ # end
54
+
55
+ # Customize the authentication handler. Below is an example for devise. Your
56
+ # callable should accept a user (from config.user_for_authentication) and
57
+ # a password. Return a boolean indicating whether the user is autenticated
58
+ # against the given password.
59
+ #
60
+ # config.authenticate = ->(user, password) do
61
+ # user.try :valid_password, password
62
+ # end
63
+
64
+ # Customize the authorization handlers. There are two - one for read and one
65
+ # for write operations. They will receive the user model (if authenticated)
66
+ # and the repository path as a string (without config.repositories_path
67
+ # prefixed.) A boolean should be returned. Below are some examples.
68
+ #
69
+ # config.authorize_read = ->(user, repository) do
70
+ # repo = Repository.find_by_path repository
71
+ # repo.public? || repo.user_id = user.id
72
+ # end
73
+ #
74
+ # config.authorize_write = ->(user, repository) do
75
+ # repo = Repository.find_by_path repository
76
+ # repo.user_id = user.id || user.admin?
77
+ # end
78
+ end
@@ -0,0 +1,28 @@
1
+ module GitWit
2
+ def self.user_for_authentication(username)
3
+ if config.user_for_authentication.respond_to?(:call)
4
+ return config.user_for_authentication.call(username)
5
+ end
6
+ username
7
+ end
8
+
9
+ def self.authenticate(user, password)
10
+ if config.authenticate.respond_to?(:call)
11
+ return config.authenticate.call(user, password)
12
+ end
13
+ false
14
+ end
15
+
16
+ def self.authorize_write(user, repository)
17
+ authorize :write, user, repository
18
+ end
19
+
20
+ def self.authorize_read(user, repository)
21
+ authorize :read, user, repository
22
+ end
23
+
24
+ def self.authorize(operation, user, repository)
25
+ cfg = config.send "authorize_#{operation}".to_sym
26
+ cfg.respond_to?(:call) ? cfg.call(user, repository) : false
27
+ end
28
+ end
@@ -0,0 +1,102 @@
1
+ require "tempfile"
2
+ require "authorized_keys"
3
+
4
+ module GitWit
5
+ def self.regenerate_authorized_keys(keymap)
6
+ key_file = authorized_keys_file
7
+ key_file.clear do |file|
8
+ keymap.each do |username, keys|
9
+ keys.each do |key|
10
+ key_file.add AuthorizedKeys::Key.shell_key_for_username(username, key)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.add_authorized_key(username, key)
17
+ authorized_keys_file.add AuthorizedKeys::Key.shell_key_for_username(username, key)
18
+ end
19
+
20
+ def self.remove_authorized_key(key)
21
+ authorized_keys_file.remove key
22
+ end
23
+
24
+ def self.authorized_keys_file
25
+ AuthorizedKeys::File.new authorized_keys_path
26
+ end
27
+
28
+ def self.authorized_keys_path
29
+ config.authorized_keys_path || File.expand_path("~#{ssh_user}/.ssh/authorized_keys")
30
+ end
31
+
32
+ module AuthorizedKeys
33
+ class File < ::AuthorizedKeys::File
34
+ attr_accessor :original_location
35
+
36
+ def keys
37
+ list = []
38
+ modify "r" do |file|
39
+ file.each do |line|
40
+ list << Key.new(line.chomp)
41
+ end
42
+ end
43
+ list
44
+ end
45
+
46
+ def remove(key)
47
+ key = Key.new(key) if key.is_a?(String)
48
+ cached_keys = keys
49
+ modify 'w' do |file|
50
+ cached_keys.each do |k|
51
+ file.puts k unless key == k
52
+ end
53
+ end
54
+ end
55
+
56
+ def owned?
57
+ owner == Process.uid
58
+ end
59
+
60
+ def owner(file = nil)
61
+ file ||= location
62
+ ::File.stat(file).uid
63
+ rescue Errno::EACCES, Errno::ENOENT
64
+ parent = ::File.dirname file
65
+ owner parent unless file == parent
66
+ end
67
+
68
+ def modify(mode, &block)
69
+ return super if owned? || self.original_location
70
+ contents = %x(sudo -u "##{owner}" cat "#{location}") unless mode.include? "w"
71
+ original_owner = owner
72
+ self.original_location = location
73
+ tmp = Tempfile.new "git_wit_authorized_keys"
74
+ self.location = tmp.path
75
+ tmp.write contents unless mode.include? "w"
76
+ tmp.close
77
+ super
78
+ self.location = original_location
79
+ if mode != "r"
80
+ %x(cat "#{tmp.path}" | sudo -u "##{owner}" tee "#{location}" >/dev/null)
81
+ end
82
+ tmp.unlink
83
+ self.original_location = nil
84
+ end
85
+
86
+ def clear(&block)
87
+ modify "w", &block
88
+ end
89
+ end
90
+
91
+ class Key < ::AuthorizedKeys::Key
92
+ SHELL_OPTIONS = %w(no-port-forwarding no-X11-forwarding
93
+ no-agent-forwarding no-pty)
94
+
95
+ def self.shell_key_for_username(username, key)
96
+ key = self.new key if key.is_a? String
97
+ key.options = [%(command="gw-shell #{username}"), *SHELL_OPTIONS]
98
+ key
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,12 @@
1
+ module GitWit
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace GitWit
4
+
5
+ config.action_dispatch.rescue_responses.merge!(
6
+ "GitWit::NotFoundError" => :not_found,
7
+ "GitWit::ForbiddenError" => :forbidden,
8
+ "GitWit::UnauthorizedError" => :unauthorized,
9
+ "GitWit::GitError" => :internal_server_error
10
+ )
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ module GitWit
2
+ class NotFoundError < Exception; end
3
+ class ForbiddenError < Exception; end
4
+ class UnauthorizedError < Exception; end
5
+ class GitError < Exception; end
6
+ end
@@ -0,0 +1,72 @@
1
+ module GitWit
2
+ module Shell
3
+ SHELL_COMMANDS = %w(git-upload-pack git-receive-pack git-upload-archive)
4
+ SUDO_ENV_KEYS = %w(SSH_ORIGINAL_COMMAND GEM_HOME GEM_PATH PATH
5
+ BUNDLE_GEMFILE RAILS_ENV)
6
+
7
+ def self.exec_with_sudo!(user = app_user)
8
+ return if running_as?(user)
9
+ Dir.chdir rails_root
10
+ env = SUDO_ENV_KEYS.map { |k| "#{k}=#{ENV[k]}" if ENV[k] }.compact
11
+ env << "RAILS_ROOT=#{rails_root}" << "TERM=dumb"
12
+ cmd = ["sudo", "-u", "##{app_user}", *env, "-s", $PROGRAM_NAME, *ARGV]
13
+ exec *cmd
14
+ end
15
+
16
+ def self.running_as?(user)
17
+ Process.uid == user
18
+ end
19
+
20
+ def self.app_user
21
+ File.stat(rails_root).uid
22
+ end
23
+
24
+ def self.rails_root
25
+ return File.expand_path(ENV["RAILS_ROOT"]) if ENV["RAILS_ROOT"].present?
26
+ return File.expand_path("..", ENV["BUNDLE_GEMFILE"]) if ENV["BUNDLE_GEMFILE"].present?
27
+ Dir.pwd
28
+ end
29
+
30
+ def self.boot_app
31
+ require File.expand_path File.join(rails_root, "config/environment")
32
+ end
33
+
34
+ def self.parse_ssh_original_command
35
+ /^(?<cmd>git-[^\s]+)\s+'(?<repository>[^']+\.git)'/ =~ ENV["SSH_ORIGINAL_COMMAND"]
36
+ abort "Uknown command #{cmd}" unless SHELL_COMMANDS.include? cmd
37
+ [cmd, repository]
38
+ end
39
+
40
+ def self.authenticate(username)
41
+ GitWit.user_for_authentication username
42
+ end
43
+
44
+ def self.authenticate!(username)
45
+ user = authenticate username
46
+ abort "Anonymous access denied" unless user.present?
47
+ user
48
+ end
49
+
50
+ def self.authorize(command, user, repository)
51
+ op = command == "git-receive-pack" ? :write : :read
52
+ GitWit.authorize op, user, repository
53
+ end
54
+
55
+ def self.authorize!(command, user, repository)
56
+ abort "Unauthorized" unless authorize command, user, repository
57
+ end
58
+
59
+ def self.run
60
+ exec_with_sudo!
61
+ boot_app
62
+ command, repository = parse_ssh_original_command
63
+ user = authenticate! ARGV[0]
64
+ authorize! command, user, repository
65
+
66
+ repo_path = File.expand_path File.join(GitWit.repositories_path, repository)
67
+ cmd = ["git", "shell", "-c", "#{command} '#{repo_path}'"]
68
+ Rails.logger.info "GitWit SSH command: #{cmd.join " "}"
69
+ exec *cmd
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module GitWit
2
+ VERSION = "0.0.1"
3
+ end
data/lib/git_wit.rb ADDED
@@ -0,0 +1,38 @@
1
+ require "active_support/configurable"
2
+ require "git_wit/engine"
3
+ require "git_wit/errors"
4
+ require "git_wit/auth"
5
+ require "git_wit/shell"
6
+ require "git_wit/authorized_keys"
7
+
8
+ module GitWit
9
+ include ActiveSupport::Configurable
10
+
11
+ config_accessor :repositories_path, :ssh_user, :realm,
12
+ :git_http_backend_path, :insecure_write, :insecure_auth
13
+
14
+ def self.reset_config!
15
+ @_config = nil
16
+ end
17
+
18
+ def self.stash_config
19
+ @_stashed = @_config.dup
20
+ end
21
+
22
+ def self.restore_config
23
+ @_config = @_stashed
24
+ @_stashed = nil
25
+ end
26
+
27
+ def self.default_config!
28
+ reset_config!
29
+ configure do |config|
30
+ config.realm = "GitWit"
31
+ config.repositories_path = "/var/git"
32
+ config.ssh_user = "git"
33
+ config.git_http_backend_path = "/usr/libexec/git-core/git-http-backend"
34
+ config.insecure_write = false
35
+ config.insecure_auth = false
36
+ end
37
+ end
38
+ end