git_wit 0.0.3 → 0.0.4.pre

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 (195) hide show
  1. data/README.md +105 -157
  2. data/app/controllers/git_wit/git_controller.rb +37 -23
  3. data/bin/git_wit +4 -0
  4. data/config/routes.rb +2 -2
  5. data/lib/generators/git_wit/install/install_generator.rb +42 -11
  6. data/lib/generators/git_wit/{templates → install/templates}/README +0 -0
  7. data/lib/generators/git_wit/{templates → install/templates}/git_wit.rb +21 -11
  8. data/lib/generators/git_wit/ssh_user/USAGE +11 -0
  9. data/lib/generators/git_wit/ssh_user/ssh_user_generator.rb +53 -0
  10. data/lib/generators/git_wit/ssh_user/templates/bashrc.tt +7 -0
  11. data/lib/generators/git_wit/ssh_user/templates/sudoers.tt +12 -0
  12. data/lib/git_wit.rb +19 -4
  13. data/lib/git_wit/actions.rb +17 -0
  14. data/lib/git_wit/actions/dscl.rb +15 -0
  15. data/lib/git_wit/actions/dscl/base.rb +75 -0
  16. data/lib/git_wit/actions/dscl/group.rb +20 -0
  17. data/lib/git_wit/actions/dscl/group_membership.rb +30 -0
  18. data/lib/git_wit/actions/dscl/user.rb +39 -0
  19. data/lib/git_wit/actions/ssh.rb +11 -0
  20. data/lib/git_wit/actions/ssh/home.rb +55 -0
  21. data/lib/git_wit/actions/ssh/sudoers.rb +94 -0
  22. data/lib/git_wit/auth.rb +2 -2
  23. data/lib/git_wit/authorized_keys.rb +45 -88
  24. data/lib/git_wit/authorized_keys/file.rb +61 -0
  25. data/lib/git_wit/authorized_keys/key.rb +15 -0
  26. data/lib/git_wit/cli.rb +19 -0
  27. data/lib/git_wit/commands/debug.rb +21 -0
  28. data/lib/git_wit/commands/git_shell.rb +48 -0
  29. data/lib/git_wit/commands/util.rb +37 -0
  30. data/lib/git_wit/errors.rb +1 -0
  31. data/lib/git_wit/version.rb +2 -1
  32. data/lib/tasks/git_wit.rake +46 -0
  33. data/test/dummy/bin/coderay +16 -0
  34. data/test/dummy/bin/erubis +16 -0
  35. data/test/dummy/bin/git_wit +16 -0
  36. data/test/dummy/bin/htmldiff +16 -0
  37. data/test/dummy/bin/ldiff +16 -0
  38. data/test/dummy/bin/posix-spawn-benchmark +16 -0
  39. data/test/dummy/bin/pry +16 -0
  40. data/test/dummy/bin/rackup +16 -0
  41. data/test/dummy/bin/rails +16 -0
  42. data/test/dummy/bin/rake +16 -0
  43. data/test/dummy/bin/rake2thor +16 -0
  44. data/test/dummy/bin/rdoc +16 -0
  45. data/test/dummy/bin/ri +16 -0
  46. data/test/dummy/bin/sprockets +16 -0
  47. data/test/dummy/bin/thor +16 -0
  48. data/test/dummy/bin/tilt +16 -0
  49. data/test/dummy/bin/tt +16 -0
  50. data/test/dummy/bin/tunnels +16 -0
  51. data/test/dummy/config/initializers/git_wit.rb +33 -24
  52. data/test/dummy/db/development.sqlite3 +0 -0
  53. data/test/dummy/db/test.sqlite3 +0 -0
  54. data/test/dummy/log/development.log +1639 -5391
  55. data/test/dummy/log/test.log +108 -2
  56. data/test/dummy/tmp/pids/server.pid +1 -0
  57. data/test/unit/auth_test.rb +10 -6
  58. data/test/unit/authorized_keys_test.rb +5 -5
  59. data/test/unit/config_test.rb +15 -11
  60. data/test/unit/shell_test.rb +5 -5
  61. metadata +84 -278
  62. data/bin/gw-shell +0 -4
  63. data/lib/tasks/git_wit_shell.rake +0 -8
  64. data/test/dummy/tmp/cache/assets/C7E/BC0/sprockets%2Fb7118f368364962573a44054bcfb80d0 +0 -0
  65. data/test/dummy/tmp/cache/assets/C80/840/sprockets%2F562c2d168da585f80579347d10790a0a +0 -0
  66. data/test/dummy/tmp/cache/assets/C8C/B80/sprockets%2F371bf96e99717688ed7313a0c53f4212 +0 -0
  67. data/test/dummy/tmp/cache/assets/C9C/700/sprockets%2Fc7b1373dbf219a8722efc21160641340 +0 -0
  68. data/test/dummy/tmp/cache/assets/C9E/5F0/sprockets%2F2bca2b107bb6c26b720d135270688918 +0 -0
  69. data/test/dummy/tmp/cache/assets/CA9/9C0/sprockets%2F0c1b7ebd087418498ea6037225d33d25 +0 -0
  70. data/test/dummy/tmp/cache/assets/CC8/B00/sprockets%2F9815364bfd49ed907870e270d75a995a +0 -0
  71. data/test/dummy/tmp/cache/assets/CD1/800/sprockets%2Fc044b140dcef533c52712c7b51e21996 +0 -0
  72. data/test/dummy/tmp/cache/assets/CD5/2C0/sprockets%2F166c056119ebdfb8b7104c97b424b423 +0 -0
  73. data/test/dummy/tmp/cache/assets/CD7/380/sprockets%2F4079ce1dbbcf4a599527303670006b6b +0 -0
  74. data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  75. data/test/dummy/tmp/cache/assets/CE0/CC0/sprockets%2F2b38c3fb549036de5c4666637a0c80c6 +0 -0
  76. data/test/dummy/tmp/cache/assets/CEC/B70/sprockets%2F7f98753ca8c35e4249363a04389b3caf +0 -0
  77. data/test/dummy/tmp/cache/assets/CF0/1D0/sprockets%2F6fc757c2c8329244ca95d6909865bbc2 +0 -0
  78. data/test/dummy/tmp/cache/assets/CF9/980/sprockets%2Fbd55042e1acd32eb611041444d794d4d +0 -0
  79. data/test/dummy/tmp/cache/assets/CFD/560/sprockets%2Fe4e7fe4ee089382325686f806b939d3e +0 -0
  80. data/test/dummy/tmp/cache/assets/D00/B90/sprockets%2F07c00c80f1ea62d95a01f11f9c728b72 +0 -0
  81. data/test/dummy/tmp/cache/assets/D0D/9A0/sprockets%2F1fce44192cdb30f44b7545a37c9891b2 +0 -0
  82. data/test/dummy/tmp/cache/assets/D0F/390/sprockets%2F9df081609c3449ab40c93b1cf07de357 +0 -0
  83. data/test/dummy/tmp/cache/assets/D25/A60/sprockets%2F0e036061ad22b2e6dce1639b234cf15c +0 -0
  84. data/test/dummy/tmp/cache/assets/D27/DB0/sprockets%2Fa3a0a778855bce9fa47913d389ea9884 +0 -0
  85. data/test/dummy/tmp/cache/assets/D29/5A0/sprockets%2Fdad9e81b43ca12671246ee52a1900d0c +0 -0
  86. data/test/dummy/tmp/cache/assets/D2E/FF0/sprockets%2Fc06112642c994b6b7c2ba6632fad1fd0 +0 -0
  87. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  88. data/test/dummy/tmp/cache/assets/D36/120/sprockets%2Feac54bd3c540af6b964d025e345227d6 +0 -0
  89. data/test/dummy/tmp/cache/assets/D3E/240/sprockets%2F84b96d6b2d2653cb4127b06d8acb990d +0 -0
  90. data/test/dummy/tmp/cache/assets/D3F/830/sprockets%2F18578d4ef3abd6e12d836841dd6b8a00 +0 -0
  91. data/test/dummy/tmp/cache/assets/D4B/E70/sprockets%2F0909bc70e589d40a6cd90dfe6f18257e +0 -0
  92. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  93. data/test/dummy/tmp/cache/assets/D57/3D0/sprockets%2F7bbccc5129a5013b70831ec9ad62ab24 +0 -0
  94. data/test/dummy/tmp/cache/assets/D5A/000/sprockets%2F7d4f67f146b6d7904dfc4edd9135f588 +0 -0
  95. data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  96. data/test/dummy/tmp/cache/assets/D5B/BB0/sprockets%2Fba769276c4de14151bc4202cba7f3ad3 +0 -0
  97. data/test/dummy/tmp/cache/assets/D5E/BC0/sprockets%2F2d96fa667066778db858d7b7cb0fce69 +0 -0
  98. data/test/dummy/tmp/cache/assets/D6E/BA0/sprockets%2F5178d3788fe35a52acb5f3bd22ea078a +0 -0
  99. data/test/dummy/tmp/cache/assets/D6F/C20/sprockets%2F22e783a8f5f9224f01e8e62fab6afb40 +0 -0
  100. data/test/dummy/tmp/cache/assets/D76/5C0/sprockets%2Fd8a5669df31f129f355283e6dab4c5ad +0 -0
  101. data/test/dummy/tmp/cache/assets/D79/DE0/sprockets%2F7cfd335e68d881b03f6b7f1bd91f270f +0 -0
  102. data/test/dummy/tmp/cache/assets/D8A/CA0/sprockets%2F656af8b87ad378e8e4f2ec94b6b5c719 +0 -0
  103. data/test/dummy/tmp/cache/assets/D8C/620/sprockets%2Ff37f8e5b8cccd9880276a9f5157d4c7e +0 -0
  104. data/test/dummy/tmp/cache/assets/D92/200/sprockets%2Fb816d858281027bdd3fe2bfac43b4ca1 +0 -0
  105. data/test/dummy/tmp/cache/assets/D92/CE0/sprockets%2Ffca6a13676a2be09234905f9acae22cd +0 -0
  106. data/test/dummy/tmp/cache/assets/D96/9E0/sprockets%2F6fabecd33f7a5a087f4fb6a2d6312443 +0 -0
  107. data/test/dummy/tmp/cache/assets/DA2/D20/sprockets%2Ff5faf079fb660bde5bc9502bde442088 +0 -0
  108. data/test/dummy/tmp/cache/assets/DA5/570/sprockets%2F3517de599b6fd005bc5d5d69ba5ff1e2 +0 -0
  109. data/test/dummy/tmp/cache/assets/DA7/070/sprockets%2F69eadf8c3a94b04fe0b4992ee4a8c821 +0 -0
  110. data/test/dummy/tmp/cache/assets/DBA/BF0/sprockets%2Fe63ea1d7bfb0ee50380debe42360a3b5 +0 -0
  111. data/test/dummy/tmp/cache/assets/DBB/3B0/sprockets%2F6a0aaa6c5b0d10b936e237a7ecb4e4c9 +0 -0
  112. data/test/dummy/tmp/cache/assets/DBC/8E0/sprockets%2F908976cfbcdf6ad4c59737bf3c78e1e8 +0 -0
  113. data/test/dummy/tmp/cache/assets/DD3/FD0/sprockets%2Febf97c76a9ba2a889dd01be2caa75806 +0 -0
  114. data/test/dummy/tmp/cache/assets/DD8/410/sprockets%2Fc02eeb7ea977fd713cc19ca93d838af4 +0 -0
  115. data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  116. data/test/dummy/tmp/cache/assets/DEA/E40/sprockets%2F4166d7d00d1e72fed2004debed2bea3e +0 -0
  117. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  118. data/test/dummy/tmp/cache/assets/E05/C70/sprockets%2Fccd814ec859d582ada46e271edfe7ae1 +0 -0
  119. data/test/dummy/tmp/cache/assets/E0C/2A0/sprockets%2F37c59fadd9a21cab7d05d78a7dfe7e85 +0 -0
  120. data/test/dummy/tmp/cache/assets/E28/130/sprockets%2F6f332ca43b7ed658d90b8ba5daaa5ded +0 -0
  121. data/test/dummy/tmp/cache/assets/E3B/080/sprockets%2F09e2a090befacdae0db10cafb1893a0a +0 -0
  122. data/test/dummy/tmp/cache/assets/E65/CD0/sprockets%2F93cdf3fec0e3aa6deefa955c6828fbd0 +0 -0
  123. data/test/dummy/tmp/cache/assets/E9F/450/sprockets%2Fbbfdc5edaaf25dfdb5ee8f9db7895435 +0 -0
  124. data/test/dummy/tmp/git/fuck.git/HEAD +0 -1
  125. data/test/dummy/tmp/git/fuck.git/config +0 -6
  126. data/test/dummy/tmp/git/fuck.git/description +0 -1
  127. data/test/dummy/tmp/git/fuck.git/hooks/applypatch-msg.sample +0 -15
  128. data/test/dummy/tmp/git/fuck.git/hooks/commit-msg.sample +0 -24
  129. data/test/dummy/tmp/git/fuck.git/hooks/post-update.sample +0 -8
  130. data/test/dummy/tmp/git/fuck.git/hooks/pre-applypatch.sample +0 -14
  131. data/test/dummy/tmp/git/fuck.git/hooks/pre-commit.sample +0 -50
  132. data/test/dummy/tmp/git/fuck.git/hooks/pre-rebase.sample +0 -169
  133. data/test/dummy/tmp/git/fuck.git/hooks/prepare-commit-msg.sample +0 -36
  134. data/test/dummy/tmp/git/fuck.git/hooks/update.sample +0 -128
  135. data/test/dummy/tmp/git/fuck.git/info/exclude +0 -6
  136. data/test/dummy/tmp/git/lkasdjf.git/HEAD +0 -1
  137. data/test/dummy/tmp/git/lkasdjf.git/config +0 -6
  138. data/test/dummy/tmp/git/lkasdjf.git/description +0 -1
  139. data/test/dummy/tmp/git/lkasdjf.git/hooks/applypatch-msg.sample +0 -15
  140. data/test/dummy/tmp/git/lkasdjf.git/hooks/commit-msg.sample +0 -24
  141. data/test/dummy/tmp/git/lkasdjf.git/hooks/post-update.sample +0 -8
  142. data/test/dummy/tmp/git/lkasdjf.git/hooks/pre-applypatch.sample +0 -14
  143. data/test/dummy/tmp/git/lkasdjf.git/hooks/pre-commit.sample +0 -50
  144. data/test/dummy/tmp/git/lkasdjf.git/hooks/pre-rebase.sample +0 -169
  145. data/test/dummy/tmp/git/lkasdjf.git/hooks/prepare-commit-msg.sample +0 -36
  146. data/test/dummy/tmp/git/lkasdjf.git/hooks/update.sample +0 -128
  147. data/test/dummy/tmp/git/lkasdjf.git/info/exclude +0 -6
  148. data/test/dummy/tmp/git/new/test.git/HEAD +0 -1
  149. data/test/dummy/tmp/git/new/test.git/config +0 -6
  150. data/test/dummy/tmp/git/new/test.git/description +0 -1
  151. data/test/dummy/tmp/git/new/test.git/hooks/applypatch-msg.sample +0 -15
  152. data/test/dummy/tmp/git/new/test.git/hooks/commit-msg.sample +0 -24
  153. data/test/dummy/tmp/git/new/test.git/hooks/post-update.sample +0 -8
  154. data/test/dummy/tmp/git/new/test.git/hooks/pre-applypatch.sample +0 -14
  155. data/test/dummy/tmp/git/new/test.git/hooks/pre-commit.sample +0 -50
  156. data/test/dummy/tmp/git/new/test.git/hooks/pre-rebase.sample +0 -169
  157. data/test/dummy/tmp/git/new/test.git/hooks/prepare-commit-msg.sample +0 -36
  158. data/test/dummy/tmp/git/new/test.git/hooks/update.sample +0 -128
  159. data/test/dummy/tmp/git/new/test.git/info/exclude +0 -6
  160. data/test/dummy/tmp/git/testing.git/HEAD +0 -1
  161. data/test/dummy/tmp/git/testing.git/config +0 -6
  162. data/test/dummy/tmp/git/testing.git/description +0 -1
  163. data/test/dummy/tmp/git/testing.git/hooks/applypatch-msg.sample +0 -15
  164. data/test/dummy/tmp/git/testing.git/hooks/commit-msg.sample +0 -24
  165. data/test/dummy/tmp/git/testing.git/hooks/post-update.sample +0 -8
  166. data/test/dummy/tmp/git/testing.git/hooks/pre-applypatch.sample +0 -14
  167. data/test/dummy/tmp/git/testing.git/hooks/pre-commit.sample +0 -50
  168. data/test/dummy/tmp/git/testing.git/hooks/pre-rebase.sample +0 -169
  169. data/test/dummy/tmp/git/testing.git/hooks/prepare-commit-msg.sample +0 -36
  170. data/test/dummy/tmp/git/testing.git/hooks/update.sample +0 -128
  171. data/test/dummy/tmp/git/testing.git/info/exclude +0 -6
  172. data/test/dummy/tmp/git/under_scored/sub.git/HEAD +0 -1
  173. data/test/dummy/tmp/git/under_scored/sub.git/config +0 -6
  174. data/test/dummy/tmp/git/under_scored/sub.git/description +0 -1
  175. data/test/dummy/tmp/git/under_scored/sub.git/hooks/applypatch-msg.sample +0 -15
  176. data/test/dummy/tmp/git/under_scored/sub.git/hooks/commit-msg.sample +0 -24
  177. data/test/dummy/tmp/git/under_scored/sub.git/hooks/post-update.sample +0 -8
  178. data/test/dummy/tmp/git/under_scored/sub.git/hooks/pre-applypatch.sample +0 -14
  179. data/test/dummy/tmp/git/under_scored/sub.git/hooks/pre-commit.sample +0 -50
  180. data/test/dummy/tmp/git/under_scored/sub.git/hooks/pre-rebase.sample +0 -169
  181. data/test/dummy/tmp/git/under_scored/sub.git/hooks/prepare-commit-msg.sample +0 -36
  182. data/test/dummy/tmp/git/under_scored/sub.git/hooks/update.sample +0 -128
  183. data/test/dummy/tmp/git/under_scored/sub.git/info/exclude +0 -6
  184. data/test/dummy/tmp/git/work/pls/thnx.git/HEAD +0 -1
  185. data/test/dummy/tmp/git/work/pls/thnx.git/config +0 -6
  186. data/test/dummy/tmp/git/work/pls/thnx.git/description +0 -1
  187. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/applypatch-msg.sample +0 -15
  188. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/commit-msg.sample +0 -24
  189. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/post-update.sample +0 -8
  190. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/pre-applypatch.sample +0 -14
  191. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/pre-commit.sample +0 -50
  192. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/pre-rebase.sample +0 -169
  193. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/prepare-commit-msg.sample +0 -36
  194. data/test/dummy/tmp/git/work/pls/thnx.git/hooks/update.sample +0 -128
  195. data/test/dummy/tmp/git/work/pls/thnx.git/info/exclude +0 -6
@@ -0,0 +1,94 @@
1
+ module GitWit::Actions::Ssh
2
+ class Sudoers < Thor::Actions::EmptyDirectory
3
+
4
+ attr_reader :base, :name
5
+
6
+ def initialize(base, name, config = {})
7
+ @base, @name = base, name
8
+ @config = {verbose: true}.merge config
9
+ end
10
+
11
+ def invoke!
12
+ invoke_with_conflict_check do
13
+ create
14
+ end
15
+ end
16
+
17
+ def revoke!
18
+ say_status :remove, :red
19
+ destroy if !pretend? && exists?
20
+ end
21
+
22
+ def exists?
23
+ `sudo grep '#{sentinel}' /etc/sudoers &>/dev/null`
24
+ $?.success?
25
+ end
26
+
27
+ protected
28
+ def sentinel
29
+ "# git_wit sudoers #{name}"
30
+ end
31
+
32
+ def real_path
33
+ "/etc/sudoers"
34
+ end
35
+
36
+ def lock_path
37
+ "#{real_path}.tmp"
38
+ end
39
+
40
+ def append(str)
41
+ `echo '#{str}' | sudo tee -a '#{lock_path}' >/dev/null`
42
+ raise Thor::Error, "Could not append to #{lock_path}" unless $?.success?
43
+ end
44
+
45
+ def with_lock(&block)
46
+ `sudo cp -p '#{real_path}' '#{lock_path}'`
47
+ unless $?.success?
48
+ raise Thor::Error, "Could not copy sudoers from #{real_path} to #{lock_path}"
49
+ end
50
+
51
+ old_destination = base.destination_root
52
+ Dir.mktmpdir do |dir|
53
+ base.destination_root = dir
54
+ yield
55
+ end
56
+ base.destination_root = old_destination
57
+
58
+ `sudo visudo -cqf '#{lock_path}'`
59
+ unless $?.success?
60
+ raise Thor::Error, "Invalid sudoers lock at #{lock_path}"
61
+ end
62
+
63
+ out = `sudo cat '#{lock_path}'`
64
+ raise Thor::Error, "Refusing to install empty sudoers" unless out.present?
65
+
66
+ `sudo mv '#{lock_path}' '#{real_path}'`
67
+ raise Thor::Error, "Could install sudoers from #{lock_path} to #{real_path}" unless $?.success?
68
+ ensure
69
+ `sudo rm -rf '#{lock_path}'`
70
+ end
71
+
72
+ def create
73
+ with_lock do
74
+ base.template "sudoers.tt"
75
+ append sentinel
76
+ `cat '#{base.destination_root}/sudoers' | sudo tee -a '#{lock_path}' >/dev/null`
77
+ raise Thor::Error, "Could not modify #{lock_path}" unless $?.success?
78
+ append sentinel
79
+ end
80
+ end
81
+
82
+ def destroy
83
+ with_lock do
84
+ `sudo cat '#{lock_path}' | sed -e '/^#{sentinel}/,/^#{sentinel}/d' > '#{base.destination_root}/sudoers'`
85
+ `cat '#{base.destination_root}/sudoers' | sudo tee '#{lock_path}' >/dev/null`
86
+ raise Thor::Error, "Could not modify #{lock_path}" unless $?.success?
87
+ end
88
+ end
89
+
90
+ def relative_destination
91
+ "sudoers #{name}"
92
+ end
93
+ end
94
+ end
data/lib/git_wit/auth.rb CHANGED
@@ -10,7 +10,7 @@ module GitWit
10
10
  if config.authenticate.respond_to?(:call)
11
11
  return config.authenticate.call(user, password)
12
12
  end
13
- false
13
+ config.authenticate
14
14
  end
15
15
 
16
16
  def self.authorize_write(user, repository)
@@ -23,6 +23,6 @@ module GitWit
23
23
 
24
24
  def self.authorize(operation, user, repository)
25
25
  cfg = config.send "authorize_#{operation}".to_sym
26
- cfg.respond_to?(:call) ? cfg.call(user, repository) : false
26
+ cfg.respond_to?(:call) ? cfg.call(user, repository) : cfg
27
27
  end
28
28
  end
@@ -1,103 +1,60 @@
1
- require "tempfile"
2
- require "authorized_keys"
3
-
4
1
  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|
2
+ # Public: Determine the path to the authorized_keys file based on the
3
+ # configuration. If not explicitly configured, the ssh_user's home directory
4
+ # is used to construct the path by adding ".ssh/authorized_keys".
5
+ #
6
+ # Returns the path as a String or nothing if it cannot be determined.
7
+ def self.authorized_keys_path
8
+ return config.authorized_keys_path if config.authorized_keys_path.present?
9
+ if ssh_user.present?
10
+ File.expand_path(File.join("~#{ssh_user}", ".ssh", "authorized_keys"))
11
+ end
12
+ end
13
+
14
+ # Public: Get an authorized_keys file instance.
15
+ #
16
+ # Returns an AuthorizedKeys::File instance.
17
+ # Raises ConfigurationError if the path cannot be determined.
18
+ def self.authorized_keys_file
19
+ path = authorized_keys_path
20
+ return AuthorizedKeys::File.new path if path.present?
21
+ raise ConfigurationError, "Could not determine path to authorized_keys file"
22
+ end
23
+
24
+ # Public: Clear out all existing public keys in the authorized_keys file and
25
+ # add each public key for each user in the key map to the new file.
26
+ #
27
+ # keys_map - The Hash of String public key contents, keyed by String username.
28
+ #
29
+ # Returns nothing.
30
+ def self.regenerate_authorized_keys(keys_map)
31
+ keys_file = authorized_keys_file
32
+ keys_file.clear do |file|
33
+ keys_map.each do |username, keys|
9
34
  keys.each do |key|
10
- key_file.add AuthorizedKeys::Key.shell_key_for_username(username, key)
35
+ keys_file.add AuthorizedKeys::Key.shell_key_for_username(username, key)
11
36
  end
12
37
  end
13
38
  end
39
+ nil
14
40
  end
15
41
 
42
+ # Public: Add a public key for a given username to the authorized_keys file.
43
+ #
44
+ # username - The String username for the public key owner.
45
+ # key - The String public key contents.
46
+ #
47
+ # Returns nothing.
16
48
  def self.add_authorized_key(username, key)
17
49
  authorized_keys_file.add AuthorizedKeys::Key.shell_key_for_username(username, key)
18
50
  end
19
51
 
52
+ # Public: Remove a public key from the authorized_keys file.
53
+ #
54
+ # key - The String public key contents.
55
+ #
56
+ # Returns nothing.
20
57
  def self.remove_authorized_key(key)
21
58
  authorized_keys_file.remove key
22
59
  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, debug = false)
96
- key = self.new key if key.is_a? String
97
- debug = debug ? "--debug " : ""
98
- key.options = [%(command="gw-shell #{debug}#{username}"), *SHELL_OPTIONS]
99
- key
100
- end
101
- end
102
- end
103
60
  end
@@ -0,0 +1,61 @@
1
+ module GitWit
2
+ module AuthorizedKeys
3
+ class File < ::AuthorizedKeys::File
4
+ attr_accessor :original_location
5
+
6
+ def keys
7
+ list = []
8
+ modify "r" do |file|
9
+ file.each do |line|
10
+ list << Key.new(line.chomp)
11
+ end
12
+ end
13
+ list
14
+ end
15
+
16
+ def remove(key)
17
+ key = Key.new(key) if key.is_a?(String)
18
+ cached_keys = keys
19
+ modify 'w' do |file|
20
+ cached_keys.each do |k|
21
+ file.puts k unless key == k
22
+ end
23
+ end
24
+ end
25
+
26
+ def owned?
27
+ owner == Process.uid
28
+ end
29
+
30
+ def owner(file = nil)
31
+ file ||= location
32
+ ::File.stat(file).uid
33
+ rescue Errno::EACCES, Errno::ENOENT
34
+ parent = ::File.dirname file
35
+ owner parent unless file == parent
36
+ end
37
+
38
+ def modify(mode, &block)
39
+ return super if owned? || self.original_location
40
+ contents = %x(sudo -u "##{owner}" cat "#{location}") unless mode.include? "w"
41
+ original_owner = owner
42
+ self.original_location = location
43
+ tmp = Tempfile.new "git_wit_authorized_keys"
44
+ self.location = tmp.path
45
+ tmp.write contents unless mode.include? "w"
46
+ tmp.close
47
+ super
48
+ self.location = original_location
49
+ if mode != "r"
50
+ %x(cat "#{tmp.path}" | sudo -u "##{owner}" tee "#{location}" >/dev/null)
51
+ end
52
+ tmp.unlink
53
+ self.original_location = nil
54
+ end
55
+
56
+ def clear(&block)
57
+ modify "w", &block
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,15 @@
1
+ module GitWit
2
+ module AuthorizedKeys
3
+ class Key < ::AuthorizedKeys::Key
4
+ SHELL_OPTIONS = %w(no-port-forwarding no-X11-forwarding
5
+ no-agent-forwarding no-pty)
6
+
7
+ def self.shell_key_for_username(username, key, debug = false)
8
+ key = self.new key if key.is_a? String
9
+ cmd = debug ? "debug" : "git-shell #{username}"
10
+ key.options = [%(command="git_wit #{cmd}"), *SHELL_OPTIONS]
11
+ key
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ require "thor"
2
+ require "git_wit/commands/util"
3
+ require "git_wit/commands/git_shell"
4
+ require "git_wit/commands/debug"
5
+
6
+ module GitWit
7
+ class Cli < Thor
8
+ include Thor::Actions
9
+ include Commands::Util
10
+ include Commands::GitShell
11
+ include Commands::Debug
12
+
13
+ desc "debug", "debug the SSH configuration"
14
+ def debug(*args); super; end
15
+
16
+ desc "git-shell USER CMD REPO", "run git-shell CMD as USER in REPO"
17
+ def git_shell(*args); super; end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module GitWit
2
+ module Commands
3
+ module Debug
4
+ def debug
5
+ boot_app
6
+ debug_banner "Start"
7
+ pp "ENVIRONMENT:", ENV
8
+ GitWit.configure { |c| pp "GitWit Config:", c }
9
+ debug_banner "End"
10
+ $stdout.flush
11
+ exec_with_sudo!
12
+ end
13
+
14
+ protected
15
+ def debug_banner(msg = nil)
16
+ msg = "#{msg} " if msg.present?
17
+ puts "\n" * 2 + "*" * 5 + " GitWit DEBUG #{msg}" + "*" * 5 + "\n" * 2
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ module GitWit
2
+ module Commands
3
+ module GitShell
4
+ GIT_SHELL_COMMAND_RE = /^(git-[^\s]+)\s+'([^']+\.git)'/
5
+ GIT_SHELL_COMMANDS = %w(git-upload-pack git-receive-pack git-upload-archive)
6
+
7
+ def git_shell(user, cmd = nil, repo = nil)
8
+ @command, @repository, @user = cmd, repo, nil
9
+ exec_with_sudo!
10
+ boot_app
11
+ parse_ssh_original_command if ENV["SSH_ORIGINAL_COMMAND"].present?
12
+ validate_git_shell_command
13
+ authenticate user
14
+ authorize
15
+ run_git_shell
16
+ end
17
+
18
+ protected
19
+ def parse_ssh_original_command
20
+ m = GIT_SHELL_COMMAND_RE.match ENV["SSH_ORIGINAL_COMMAND"]
21
+ nothing, @command, @repository = m.to_a if m.present?
22
+ end
23
+
24
+ def validate_git_shell_command
25
+ unless GIT_SHELL_COMMANDS.include? @command
26
+ abort "Unknown git shell command: #{@command}"
27
+ end
28
+ end
29
+
30
+ def authenticate(user)
31
+ @user = GitWit.user_for_authentication user
32
+ abort "Anonymous access denied via SSH" unless @user.present?
33
+ end
34
+
35
+ def authorize
36
+ op = @command == "git-receive-pack" ? :write : :read
37
+ abort "Unauthorized" unless GitWit.authorize op, @user, @repository
38
+ end
39
+
40
+ def run_git_shell
41
+ repo_path = File.expand_path File.join(GitWit.repositories_path, @repository)
42
+ cmd = [GitWit.git_path, "shell", "-c", "#{@command} '#{repo_path}'"]
43
+ Rails.logger.info "GitWit SSH cmd: #{cmd.join " "}"
44
+ exec *cmd
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ module GitWit
2
+ module Commands
3
+ module Util
4
+ def exec_with_sudo!(user = app_user)
5
+ return if running_as?(user)
6
+ Dir.chdir rails_root
7
+ ENV["TERM"] = "dumb"
8
+ cmd = ["sudo", "-u", "##{user}", $PROGRAM_NAME, *ARGV]
9
+ exec *cmd
10
+ end
11
+
12
+ def running_as?(user)
13
+ Process.uid == user
14
+ end
15
+
16
+ def app_user
17
+ File.stat(rails_root).uid
18
+ end
19
+
20
+ def rails_root
21
+ return File.expand_path(ENV["RAILS_ROOT"]) if ENV["RAILS_ROOT"]
22
+ return Dir.pwd if File.exist? File.join(Dir.pwd, "config/environment.rb")
23
+ return File.expand_path("..", ENV["BUNDLE_GEMFILE"]) if ENV["BUNDLE_GEMFILE"]
24
+ Dir.pwd
25
+ end
26
+
27
+ def boot_app
28
+ require File.expand_path File.join(rails_root, "config/environment") unless booted?
29
+ require "git_wit"
30
+ end
31
+
32
+ def booted?
33
+ defined?(Rails)
34
+ end
35
+ end
36
+ end
37
+ end