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.
- data/MIT-LICENSE +20 -0
- data/README.md +82 -0
- data/Rakefile +40 -0
- data/app/controllers/git_wit/application_controller.rb +6 -0
- data/app/controllers/git_wit/git_controller.rb +116 -0
- data/bin/gw-shell +4 -0
- data/config/initializers/git_wit.rb +2 -0
- data/config/routes.rb +5 -0
- data/lib/generators/git_wit/install/USAGE +8 -0
- data/lib/generators/git_wit/install/install_generator.rb +15 -0
- data/lib/generators/git_wit/templates/README +11 -0
- data/lib/generators/git_wit/templates/git_wit.rb +78 -0
- data/lib/git_wit/auth.rb +28 -0
- data/lib/git_wit/authorized_keys.rb +102 -0
- data/lib/git_wit/engine.rb +12 -0
- data/lib/git_wit/errors.rb +6 -0
- data/lib/git_wit/shell.rb +72 -0
- data/lib/git_wit/version.rb +3 -0
- data/lib/git_wit.rb +38 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +16 -0
- data/test/dummy/app/assets/javascripts/bootstrap.js +5 -0
- data/test/dummy/app/assets/javascripts/public_keys.js +2 -0
- data/test/dummy/app/assets/javascripts/repositories.js +2 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/assets/stylesheets/bootstrap_and_overrides.css +12 -0
- data/test/dummy/app/assets/stylesheets/public_keys.css +4 -0
- data/test/dummy/app/assets/stylesheets/repositories.css +4 -0
- data/test/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/public_keys_controller.rb +61 -0
- data/test/dummy/app/controllers/repositories_controller.rb +76 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/public_keys_helper.rb +2 -0
- data/test/dummy/app/helpers/repositories_helper.rb +2 -0
- data/test/dummy/app/models/ability.rb +16 -0
- data/test/dummy/app/models/public_key.rb +46 -0
- data/test/dummy/app/models/repository.rb +27 -0
- data/test/dummy/app/models/role.rb +6 -0
- data/test/dummy/app/models/user.rb +14 -0
- data/test/dummy/app/views/devise/confirmations/new.html.erb +22 -0
- data/test/dummy/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/test/dummy/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/test/dummy/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/test/dummy/app/views/devise/passwords/edit.html.erb +30 -0
- data/test/dummy/app/views/devise/passwords/new.html.erb +22 -0
- data/test/dummy/app/views/devise/registrations/edit.html.erb +50 -0
- data/test/dummy/app/views/devise/registrations/new.html.erb +43 -0
- data/test/dummy/app/views/devise/sessions/new.html.erb +37 -0
- data/test/dummy/app/views/devise/shared/_links.erb +29 -0
- data/test/dummy/app/views/devise/unlocks/new.html.erb +12 -0
- data/test/dummy/app/views/layouts/application.html.erb +106 -0
- data/test/dummy/app/views/public_keys/_form.html.erb +16 -0
- data/test/dummy/app/views/public_keys/index.html.erb +36 -0
- data/test/dummy/app/views/public_keys/new.html.erb +5 -0
- data/test/dummy/app/views/public_keys/show.html.erb +25 -0
- data/test/dummy/app/views/repositories/_form.html.erb +29 -0
- data/test/dummy/app/views/repositories/edit.html.erb +5 -0
- data/test/dummy/app/views/repositories/index.html.erb +52 -0
- data/test/dummy/app/views/repositories/new.html.erb +5 -0
- data/test/dummy/app/views/repositories/show.html.erb +31 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/devise.rb +242 -0
- data/test/dummy/config/initializers/git_wit.rb +73 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/rolify.rb +8 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/devise.en.yml +59 -0
- data/test/dummy/config/locales/en.bootstrap.yml +17 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +12 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20130217110616_devise_create_users.rb +46 -0
- data/test/dummy/db/migrate/20130217111055_create_repositories.rb +14 -0
- data/test/dummy/db/migrate/20130217114405_rolify_create_roles.rb +19 -0
- data/test/dummy/db/migrate/20130217191808_add_username_to_users.rb +5 -0
- data/test/dummy/db/migrate/20130217221157_create_public_keys.rb +13 -0
- data/test/dummy/db/schema.rb +76 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/test/fixtures/public_keys.yml +14 -0
- data/test/dummy/test/fixtures/repositories.yml +17 -0
- data/test/dummy/test/fixtures/users.yml +9 -0
- data/test/dummy/test/functional/public_keys_controller_test.rb +49 -0
- data/test/dummy/test/functional/repositories_controller_test.rb +49 -0
- data/test/dummy/test/unit/helpers/public_keys_helper_test.rb +4 -0
- data/test/dummy/test/unit/helpers/repositories_helper_test.rb +4 -0
- data/test/dummy/test/unit/public_key_test.rb +7 -0
- data/test/dummy/test/unit/repository_test.rb +7 -0
- data/test/dummy/test/unit/user_test.rb +7 -0
- data/test/dummy/tmp/cache/assets/C7E/BC0/sprockets%2Fb7118f368364962573a44054bcfb80d0 +0 -0
- data/test/dummy/tmp/cache/assets/C80/840/sprockets%2F562c2d168da585f80579347d10790a0a +0 -0
- data/test/dummy/tmp/cache/assets/C8C/B80/sprockets%2F371bf96e99717688ed7313a0c53f4212 +0 -0
- data/test/dummy/tmp/cache/assets/C9C/700/sprockets%2Fc7b1373dbf219a8722efc21160641340 +0 -0
- data/test/dummy/tmp/cache/assets/C9E/5F0/sprockets%2F2bca2b107bb6c26b720d135270688918 +0 -0
- data/test/dummy/tmp/cache/assets/CA9/9C0/sprockets%2F0c1b7ebd087418498ea6037225d33d25 +0 -0
- data/test/dummy/tmp/cache/assets/CC8/B00/sprockets%2F9815364bfd49ed907870e270d75a995a +0 -0
- data/test/dummy/tmp/cache/assets/CD1/800/sprockets%2Fc044b140dcef533c52712c7b51e21996 +0 -0
- data/test/dummy/tmp/cache/assets/CD5/2C0/sprockets%2F166c056119ebdfb8b7104c97b424b423 +0 -0
- data/test/dummy/tmp/cache/assets/CD7/380/sprockets%2F4079ce1dbbcf4a599527303670006b6b +0 -0
- data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/CE0/CC0/sprockets%2F2b38c3fb549036de5c4666637a0c80c6 +0 -0
- data/test/dummy/tmp/cache/assets/CEC/B70/sprockets%2F7f98753ca8c35e4249363a04389b3caf +0 -0
- data/test/dummy/tmp/cache/assets/CF0/1D0/sprockets%2F6fc757c2c8329244ca95d6909865bbc2 +0 -0
- data/test/dummy/tmp/cache/assets/CF9/980/sprockets%2Fbd55042e1acd32eb611041444d794d4d +0 -0
- data/test/dummy/tmp/cache/assets/CFD/560/sprockets%2Fe4e7fe4ee089382325686f806b939d3e +0 -0
- data/test/dummy/tmp/cache/assets/D00/B90/sprockets%2F07c00c80f1ea62d95a01f11f9c728b72 +0 -0
- data/test/dummy/tmp/cache/assets/D0D/9A0/sprockets%2F1fce44192cdb30f44b7545a37c9891b2 +0 -0
- data/test/dummy/tmp/cache/assets/D0F/390/sprockets%2F9df081609c3449ab40c93b1cf07de357 +0 -0
- data/test/dummy/tmp/cache/assets/D25/A60/sprockets%2F0e036061ad22b2e6dce1639b234cf15c +0 -0
- data/test/dummy/tmp/cache/assets/D27/DB0/sprockets%2Fa3a0a778855bce9fa47913d389ea9884 +0 -0
- data/test/dummy/tmp/cache/assets/D29/5A0/sprockets%2Fdad9e81b43ca12671246ee52a1900d0c +0 -0
- data/test/dummy/tmp/cache/assets/D2E/FF0/sprockets%2Fc06112642c994b6b7c2ba6632fad1fd0 +0 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D36/120/sprockets%2Feac54bd3c540af6b964d025e345227d6 +0 -0
- data/test/dummy/tmp/cache/assets/D3E/240/sprockets%2F84b96d6b2d2653cb4127b06d8acb990d +0 -0
- data/test/dummy/tmp/cache/assets/D3F/830/sprockets%2F18578d4ef3abd6e12d836841dd6b8a00 +0 -0
- data/test/dummy/tmp/cache/assets/D4B/E70/sprockets%2F0909bc70e589d40a6cd90dfe6f18257e +0 -0
- data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/D57/3D0/sprockets%2F7bbccc5129a5013b70831ec9ad62ab24 +0 -0
- data/test/dummy/tmp/cache/assets/D5A/000/sprockets%2F7d4f67f146b6d7904dfc4edd9135f588 +0 -0
- data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/D5B/BB0/sprockets%2Fba769276c4de14151bc4202cba7f3ad3 +0 -0
- data/test/dummy/tmp/cache/assets/D5E/BC0/sprockets%2F2d96fa667066778db858d7b7cb0fce69 +0 -0
- data/test/dummy/tmp/cache/assets/D6E/BA0/sprockets%2F5178d3788fe35a52acb5f3bd22ea078a +0 -0
- data/test/dummy/tmp/cache/assets/D6F/C20/sprockets%2F22e783a8f5f9224f01e8e62fab6afb40 +0 -0
- data/test/dummy/tmp/cache/assets/D76/5C0/sprockets%2Fd8a5669df31f129f355283e6dab4c5ad +0 -0
- data/test/dummy/tmp/cache/assets/D79/DE0/sprockets%2F7cfd335e68d881b03f6b7f1bd91f270f +0 -0
- data/test/dummy/tmp/cache/assets/D8A/CA0/sprockets%2F656af8b87ad378e8e4f2ec94b6b5c719 +0 -0
- data/test/dummy/tmp/cache/assets/D8C/620/sprockets%2Ff37f8e5b8cccd9880276a9f5157d4c7e +0 -0
- data/test/dummy/tmp/cache/assets/D92/200/sprockets%2Fb816d858281027bdd3fe2bfac43b4ca1 +0 -0
- data/test/dummy/tmp/cache/assets/D92/CE0/sprockets%2Ffca6a13676a2be09234905f9acae22cd +0 -0
- data/test/dummy/tmp/cache/assets/D96/9E0/sprockets%2F6fabecd33f7a5a087f4fb6a2d6312443 +0 -0
- data/test/dummy/tmp/cache/assets/DA2/D20/sprockets%2Ff5faf079fb660bde5bc9502bde442088 +0 -0
- data/test/dummy/tmp/cache/assets/DA5/570/sprockets%2F3517de599b6fd005bc5d5d69ba5ff1e2 +0 -0
- data/test/dummy/tmp/cache/assets/DA7/070/sprockets%2F69eadf8c3a94b04fe0b4992ee4a8c821 +0 -0
- data/test/dummy/tmp/cache/assets/DBA/BF0/sprockets%2Fe63ea1d7bfb0ee50380debe42360a3b5 +0 -0
- data/test/dummy/tmp/cache/assets/DBB/3B0/sprockets%2F6a0aaa6c5b0d10b936e237a7ecb4e4c9 +0 -0
- data/test/dummy/tmp/cache/assets/DBC/8E0/sprockets%2F908976cfbcdf6ad4c59737bf3c78e1e8 +0 -0
- data/test/dummy/tmp/cache/assets/DD3/FD0/sprockets%2Febf97c76a9ba2a889dd01be2caa75806 +0 -0
- data/test/dummy/tmp/cache/assets/DD8/410/sprockets%2Fc02eeb7ea977fd713cc19ca93d838af4 +0 -0
- data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/DEA/E40/sprockets%2F4166d7d00d1e72fed2004debed2bea3e +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/E05/C70/sprockets%2Fccd814ec859d582ada46e271edfe7ae1 +0 -0
- data/test/dummy/tmp/cache/assets/E0C/2A0/sprockets%2F37c59fadd9a21cab7d05d78a7dfe7e85 +0 -0
- data/test/dummy/tmp/cache/assets/E28/130/sprockets%2F6f332ca43b7ed658d90b8ba5daaa5ded +0 -0
- data/test/dummy/tmp/cache/assets/E3B/080/sprockets%2F09e2a090befacdae0db10cafb1893a0a +0 -0
- data/test/dummy/tmp/cache/assets/E65/CD0/sprockets%2F93cdf3fec0e3aa6deefa955c6828fbd0 +0 -0
- data/test/dummy/tmp/cache/assets/E9F/450/sprockets%2Fbbfdc5edaaf25dfdb5ee8f9db7895435 +0 -0
- data/test/dummy/tmp/restart.txt +0 -0
- data/test/functional/git_wit/git_controller_test.rb +10 -0
- data/test/git_wit_test.rb +7 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/test_helper.rb +18 -0
- data/test/unit/auth_test.rb +56 -0
- data/test/unit/authorized_keys_test.rb +57 -0
- data/test/unit/config_test.rb +42 -0
- data/test/unit/shell_test.rb +89 -0
- 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
|
+
[](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,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
data/config/routes.rb
ADDED
@@ -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
|
data/lib/git_wit/auth.rb
ADDED
@@ -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,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
|
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
|