brownbeagle-gitauth 0.0.1 → 0.0.2

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.
@@ -18,41 +18,20 @@
18
18
 
19
19
 
20
20
  module GitAuth
21
- class Users
22
-
23
- USERS_PATH = File.join(GitAuth::GITAUTH_DIR, "users.yml")
24
-
25
- def self.all
26
- @@all_users ||= nil
27
- end
28
-
29
- def self.load!
30
- self.all = YAML.load_file(USERS_PATH) rescue nil if File.exist?(USERS_PATH)
31
- self.all = [] unless self.all.is_a?(Array)
32
- end
33
-
34
- def self.save!
35
- load! if self.all.nil?
36
- File.open(USERS_PATH, "w+") do |f|
37
- f.write self.all.to_yaml
38
- end
39
- end
40
-
41
- def self.all=(value)
42
- @@all_users = value
43
- end
44
-
21
+ class User < SaveableClass(:users)
22
+
45
23
  def self.get(name)
46
24
  GitAuth.logger.debug "Getting user for the name '#{name}'"
47
25
  self.all.detect { |r| r.name == name }
48
26
  end
49
27
 
50
28
  def self.create(name, admin, key)
29
+ # Basic sanity checking.
30
+ return false if name.nil? || admin.nil? || key.nil?
31
+ return false unless name =~ /^([\w\_\-\.]+)$/ && !!admin == admin
51
32
  user = self.new(name, admin)
52
33
  if user.write_ssh_key!(key)
53
- self.load!
54
- self.all << user
55
- self.save!
34
+ self.add_item(user)
56
35
  return true
57
36
  else
58
37
  return false
@@ -66,13 +45,16 @@ module GitAuth
66
45
  @admin = admin
67
46
  end
68
47
 
48
+ def to_s
49
+ @name.to_s
50
+ end
51
+
69
52
  def write_ssh_key!(key)
70
- cleaned_key = clean_ssh_key(key)
53
+ cleaned_key = self.class.clean_ssh_key(key)
71
54
  if cleaned_key.nil?
72
55
  return false
73
56
  else
74
- gitauth_path = GitAuth.settings.shell_executable
75
- output = "command=\"#{gitauth_path} #{@name}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding#{shell_accessible? ? "" : ",no-pty"} #{cleaned_key}"
57
+ output = "#{command_prefix} #{cleaned_key}"
76
58
  File.open(GitAuth.settings.authorized_keys_file, "a+") do |file|
77
59
  file.puts output
78
60
  end
@@ -80,6 +62,27 @@ module GitAuth
80
62
  end
81
63
  end
82
64
 
65
+ def command_prefix
66
+ "command=\"#{GitAuth.settings.shell_executable} #{@name}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding#{shell_accessible? ? "" : ",no-pty"}"
67
+ end
68
+
69
+ def destroy!
70
+ GitAuth::Repo.all.each { |r| r.remove_permissions_for(self) }
71
+ GitAuth::Group.all.each { |g| g.remove_member(self) }
72
+ # Remove the public key from the authorized_keys file.
73
+ auth_keys_path = GitAuth.settings.authorized_keys_file
74
+ if File.exist?(auth_keys_path)
75
+ contents = File.read(auth_keys_path)
76
+ contents.gsub!(/#{command_prefix} ssh-\w+ [a-zA-Z0-9\/\+]+==\r?\n?/m, "")
77
+ File.open(auth_keys_path, "w+") { |f| f.write contents }
78
+ end
79
+ self.class.all.reject! { |u| u == self }
80
+ # Finally, save everything
81
+ self.class.save!
82
+ GitAuth::Repo.save!
83
+ GitAuth::Group.save!
84
+ end
85
+
83
86
  def admin?
84
87
  !!@admin
85
88
  end
@@ -107,7 +110,7 @@ module GitAuth
107
110
  end
108
111
  end
109
112
 
110
- def clean_ssh_key(key)
113
+ def self.clean_ssh_key(key)
111
114
  if key =~ /^(ssh-\w+ [a-zA-Z0-9\/\+]+==) .*$/
112
115
  return $1
113
116
  else
@@ -116,4 +119,5 @@ module GitAuth
116
119
  end
117
120
 
118
121
  end
122
+ Users = User # For Backwards Compat.
119
123
  end
@@ -0,0 +1,230 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #++
18
+
19
+ require 'sinatra'
20
+ require 'digest/sha2'
21
+ module GitAuth
22
+ class WebApp < Sinatra::Base
23
+
24
+ use Rack::Auth::Basic do |username, password|
25
+ [username, Digest::SHA256.hexdigest(password)] == [GitAuth.settings.web_username, GitAuth.settings.web_password_hash]
26
+ end
27
+
28
+ configure do
29
+ set :port, 8998
30
+ set :views, File.join(GitAuth::BASE_DIR, "views")
31
+ set :public, File.join(GitAuth::BASE_DIR, "public")
32
+ set :static, true
33
+ set :methodoverride, true
34
+ end
35
+
36
+ before do
37
+ GitAuth.force_setup!
38
+ end
39
+
40
+ helpers do
41
+ include Rack::Utils
42
+ alias_method :h, :escape_html
43
+
44
+ def link_to(text, link)
45
+ "<a href='#{link}'>#{text}</a>"
46
+ end
47
+
48
+ def delete_link(text, url)
49
+ id = "deleteable-#{Digest::SHA256.hexdigest(url.to_s)[0, 6]}"
50
+ html = "<div class='deletable-container' style='display: none; margin: 0; padding: 0;'>"
51
+ html << "<form method='post' action='#{url}' id='#{id}'>"
52
+ html << "<input name='_method' type='hidden' value='delete' />"
53
+ html << "</form></div>"
54
+ html << "<a href='#' onclick='if(confirm(\"Are you sure you want to do that? Deletion can not be reversed.\")) $(\"##{id}\").submit(); return false;'>#{text}</a>"
55
+ return html
56
+ end
57
+
58
+ def auto_link(member)
59
+ member = member.to_s
60
+ url = (member[0] == ?@ ? "/groups/#{URI.encode(member[1..-1])}" : "/users/#{URI.encode(member)}")
61
+ return link_to(member, url)
62
+ end
63
+
64
+ end
65
+
66
+ get '/' do
67
+ @repos = GitAuth::Repo.all
68
+ @users = GitAuth::User.all
69
+ @groups = GitAuth::Group.all
70
+ erb :index
71
+ end
72
+
73
+
74
+ # Listing / Index Page
75
+
76
+ get '/repos/:name' do
77
+ @repo = GitAuth::Repo.get(params[:name])
78
+ if @repo.nil?
79
+ redirect root_with_message("The given repository couldn't be found.")
80
+ else
81
+ read_perms, write_perms = (@repo.permissions[:read]||[]), (@repo.permissions[:write]||[])
82
+ @all_access = read_perms & write_perms
83
+ @read_only = read_perms - @all_access
84
+ @write_only = write_perms - @all_access
85
+ erb :repo
86
+ end
87
+ end
88
+
89
+ get '/users/:name' do
90
+ @user = GitAuth::User.get(params[:name])
91
+ if @user.nil?
92
+ redirect root_with_message("The given user couldn't be found.")
93
+ else
94
+ repos = GitAuth::Repo.all
95
+ read_perms = repos.select { |r| r.readable_by?(@user) }
96
+ write_perms = repos.select { |r| r.writeable_by?(@user) }
97
+ @all_access = read_perms & write_perms
98
+ @read_only = read_perms - @all_access
99
+ @write_only = write_perms - @all_access
100
+ @groups = GitAuth::Group.all.select { |g| g.member?(@user) }
101
+ erb :user
102
+ end
103
+ end
104
+
105
+ get '/groups/:name' do
106
+ @group = GitAuth::Group.get(params[:name])
107
+ if @group.nil?
108
+ redirect root_with_message("The given group could not be found.")
109
+ else
110
+ erb :group
111
+ end
112
+ end
113
+
114
+ # Create and update repos
115
+
116
+ post '/repos' do
117
+ name = params[:repo][:name]
118
+ path = params[:repo][:path]
119
+ path = name if path.to_s.strip.empty?
120
+ if GitAuth::Repo.create(name, path)
121
+ redirect root_with_message("Repository successfully added")
122
+ else
123
+ redirect root_with_message("There was an error adding the repository.")
124
+ end
125
+ end
126
+
127
+ post '/repos/:name' do
128
+ repo = GitAuth::Repo.get(params[:name])
129
+ if repo.nil?
130
+ redirect root_with_message("The given repository couldn't be found.")
131
+ else
132
+ new_permissions = Hash.new([])
133
+ [:all, :read, :write].each do |k|
134
+ if params[:repo][k]
135
+ perm_lines = params[:repo][k].to_s.split("\n")
136
+ new_permissions[k] = perm_lines.map do |l|
137
+ i = GitAuth.get_user_or_group(l.strip)
138
+ i.nil? ? nil : i.to_s
139
+ end.compact
140
+ end
141
+ end
142
+ all = new_permissions.delete(:all)
143
+ new_permissions[:read] |= all
144
+ new_permissions[:write] |= all
145
+ new_permissions.each_value { |v| v.uniq! }
146
+ repo.permissions = new_permissions
147
+ GitAuth::Repo.save!
148
+ redirect "/repos/#{URI.encode(repo.name)}"
149
+ end
150
+ end
151
+
152
+ delete '/repos/:name' do
153
+ repo = GitAuth::Repo.get(params[:name])
154
+ if repo.nil?
155
+ redirect root_with_message("The given repository couldn't be found.")
156
+ else
157
+ repo.destroy!
158
+ redirect root_with_message("Repository removed.")
159
+ end
160
+ end
161
+
162
+ # Create, delete and update users
163
+
164
+ post '/users' do
165
+ name = params[:user][:name]
166
+ admin = params[:user][:admin].to_s == "1"
167
+ key = params[:user][:key]
168
+ if GitAuth::User.create(name, admin, key)
169
+ redirect root_with_message("User Added")
170
+ else
171
+ redirect root_with_message("There was an error adding the requested user.")
172
+ end
173
+ end
174
+
175
+ delete '/users/:name' do
176
+ user = GitAuth::User.get(params[:name])
177
+ if user.nil?
178
+ redirect root_with_message("The specified user couldn't be found.")
179
+ else
180
+ user.destroy!
181
+ redirect root_with_message("User removed.")
182
+ end
183
+ end
184
+
185
+ # Create and Update Groups
186
+
187
+ post '/groups' do
188
+ if GitAuth::Group.create(params[:group][:name])
189
+ redirect root_with_message("Group added")
190
+ else
191
+ redirect root_with_message("There was an error adding the requested group.")
192
+ end
193
+ end
194
+
195
+ post '/groups/:name' do
196
+ group = GitAuth::Group.get(params[:name])
197
+ if group.nil?
198
+ redirect root_with_message("The specified group couldn't be found.")
199
+ else
200
+ if params[:group][:members]
201
+ member_lines = params[:group][:members].to_s.split("\n")
202
+ group.members = member_lines.map do |l|
203
+ i = GitAuth.get_user_or_group(l.strip)
204
+ i.nil? ? nil : i.to_s
205
+ end.compact - [group.to_s]
206
+ GitAuth::Group.save!
207
+ end
208
+ redirect "/groups/#{URI.encode(group.name)}"
209
+ end
210
+ end
211
+
212
+ delete '/groups/:name' do
213
+ group = GitAuth::Group.get(params[:name])
214
+ if group.nil?
215
+ redirect root_with_message("The specified group couldn't be found.")
216
+ else
217
+ group.destroy!
218
+ redirect root_with_message("Group removed.")
219
+ end
220
+ end
221
+
222
+
223
+ # Misc Helpers
224
+
225
+ def root_with_message(message)
226
+ "/?message=#{URI.encode(message)}"
227
+ end
228
+
229
+ end
230
+ end
data/lib/gitauth.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (C) 2009 BrownBeagle
2
+ # Copyright (C) 2009 Brown Beagle Software
3
3
  # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
@@ -23,8 +23,8 @@ require 'ostruct'
23
23
 
24
24
  module GitAuth
25
25
 
26
- BASE_DIR = File.expand_path(File.join(File.dirname(__FILE__), ".."))
27
- GITAUTH_DIR = File.expand_path("~/.gitauth/")
26
+ BASE_DIR = File.expand_path(File.join(File.dirname(__FILE__), ".."))
27
+ GITAUTH_DIR = File.expand_path("~/.gitauth/")
28
28
 
29
29
  def self.logger
30
30
  @logger ||= ::Logger.new(File.join(GITAUTH_DIR, "gitauth.log"))
@@ -34,18 +34,42 @@ module GitAuth
34
34
  @settings ||= OpenStruct.new(YAML.load_file(File.join(GITAUTH_DIR, "settings.yml")))
35
35
  end
36
36
 
37
+ def self.reload_settings!
38
+ @settings = nil
39
+ end
40
+
41
+ def self.get_user_or_group(name)
42
+ return nil if name.to_s.strip.empty?
43
+ return (name =~ /^@/ ? Group : User).get(name)
44
+ end
45
+
37
46
  def self.setup!
38
47
  unless File.exist?(GITAUTH_DIR) && File.directory?(GITAUTH_DIR)
39
48
  $stderr.puts "GitAuth not been setup, please run: gitauth install"
40
49
  exit! 1
41
50
  end
42
51
  dir = File.expand_path(File.join(File.dirname(__FILE__), "gitauth"))
43
- %w(repo users command client).each do |file|
52
+ %w(saveable_class repo user command client group).each do |file|
44
53
  require File.join(dir, file)
45
54
  end
46
55
  # Load the users and repositories from a YAML File.
47
56
  GitAuth::Repo.load!
48
- GitAuth::Users.load!
57
+ GitAuth::User.load!
58
+ GitAuth::Group.load!
59
+ end
60
+
61
+ def self.serve_web!
62
+ self.setup!
63
+ require File.join(File.expand_path(File.join(File.dirname(__FILE__), "gitauth")), "web_app")
64
+ GitAuth::WebApp.run!
65
+ end
66
+
67
+ def self.force_setup!
68
+ @settings = nil
69
+ GitAuth::Repo.all = nil
70
+ GitAuth::User.all = nil
71
+ GitAuth::Group.all = nil
72
+ self.setup!
49
73
  end
50
74
 
51
75
  end
@@ -0,0 +1,264 @@
1
+ /* Reset */
2
+ html, body, div, span, applet, object, iframe,
3
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
4
+ a, abbr, acronym, address, big, cite, code,
5
+ del, dfn, em, font, img, ins, kbd, q, s, samp,
6
+ small, strike, strong, sub, sup, tt, var,
7
+ dl, dt, dd, ol, ul, li,
8
+ fieldset, form, label, legend,
9
+ table, caption, tbody, tfoot, thead, tr, th, td {
10
+ margin: 0;
11
+ padding: 0;
12
+ border: 0;
13
+ outline: 0;
14
+ font-weight: inherit;
15
+ font-style: inherit;
16
+ font-size: 100%;
17
+ font-family: inherit;
18
+ vertical-align: baseline;
19
+ }
20
+ /* remember to define focus styles! */
21
+ :focus {
22
+ outline: 0;
23
+ }
24
+ body {
25
+ line-height: 1;
26
+ color: black;
27
+ background: white;
28
+ }
29
+ ol, ul {
30
+ list-style: none;
31
+ }
32
+ /* tables still need 'cellspacing="0"' in the markup */
33
+ table {
34
+ border-collapse: separate;
35
+ border-spacing: 0;
36
+ }
37
+ caption, th, td {
38
+ text-align: left;
39
+ font-weight: normal;
40
+ }
41
+ blockquote:before, blockquote:after,
42
+ q:before, q:after {
43
+ content: "";
44
+ }
45
+ blockquote, q {
46
+ quotes: "" "";
47
+ }
48
+
49
+ /* Other Stuff */
50
+
51
+ body {
52
+ background: #222;
53
+ }
54
+
55
+ #container {
56
+ width: 640px;
57
+ margin: 2em auto;
58
+ padding: 1em;
59
+ background: #FFF;
60
+ -moz-border-radius: 0.5em;
61
+ -webkit-border-radius: 0.5em;
62
+ border-radius: 0.5em;
63
+ }
64
+
65
+ #header {
66
+ text-align: center;
67
+ margin: 0.5em 0 1em;
68
+ }
69
+
70
+ #header h1 {
71
+ font-size: 2em;
72
+ font-weight: bold;
73
+ }
74
+
75
+ #header h1 a {
76
+ text-decoration: none;
77
+ color: #000;
78
+ }
79
+
80
+ #footer {
81
+ color: #777;
82
+ text-align: center;
83
+ margin: 1em 0 0;
84
+ }
85
+
86
+ .section {
87
+ margin: 0.5em 0 1.5em;
88
+ }
89
+
90
+ .section h2 {
91
+ padding: 0.5em;
92
+ text-transform: uppercase;
93
+ background: #EEE;
94
+ border-top: 0.1em solid #999;
95
+ border-bottom: 0.1em solid #999;
96
+ font-weight: bold;
97
+ font-size: 1.25em;
98
+ color: #333;
99
+ }
100
+
101
+ .section h2 a {
102
+ text-decoration: none;
103
+ color: #233967;
104
+ margin-left: 0.15em;
105
+ }
106
+
107
+ form {
108
+ background: #FDFFE9;
109
+ border: 0.5em solid #F0EBD4;
110
+ padding: 0.5em;
111
+ margin: 1em;
112
+ }
113
+
114
+ .hidden {
115
+ display: none;
116
+ }
117
+
118
+ form .row {
119
+ margin: 0 0.5em 0.5em;
120
+ line-height: 1.8em;
121
+ display: inline-block;
122
+ display: block;
123
+ }
124
+
125
+ form .row:after {
126
+ clear: both;
127
+ content: '.';
128
+ display: block;
129
+ visibility: hidden;
130
+ height: 0;
131
+ }
132
+
133
+ form .row label {
134
+ display: block;
135
+ float: left;
136
+ zoom: 1;
137
+ width: 30%;
138
+ font-weight: bold;
139
+ font-size: 1.1em;
140
+ padding-top: 0.15em;
141
+ }
142
+
143
+ form .row input, form .row textarea {
144
+ font-size: 1.1em;
145
+ padding: 0.25em;
146
+ border: 0.15em solid #AAA;
147
+ }
148
+
149
+ form .row input {
150
+ width: 60%;
151
+ }
152
+
153
+ form .row input:focus, form .row textarea:focus {
154
+ border-color: #3E5299;
155
+ }
156
+
157
+ form .row p.hint {
158
+ color: #24813D;
159
+ padding-left: 30%;
160
+ }
161
+
162
+ form .buttons {
163
+ text-align: center;
164
+ padding: 0.75em;
165
+ }
166
+
167
+ form .buttons a {
168
+ text-decoration: none;
169
+ color: #204B99;
170
+ }
171
+
172
+ div.message {
173
+ font-size: 1.2em;
174
+ text-align: center;
175
+ padding: 0.5em 1em 1.5em;
176
+ color: #DE701E;
177
+ }
178
+
179
+ /* Listings */
180
+
181
+ .section ul {
182
+ line-height: 1.8em;
183
+ margin: 0.5em 1em;
184
+ }
185
+
186
+ .section ul li {
187
+ padding: 0.5em 0;
188
+ color: #333;
189
+ }
190
+
191
+ .section ul li span.tag {
192
+ display: inline-block;
193
+ width: 4em;
194
+ color: white;
195
+ background: #666;
196
+ font-size: 0.8em;
197
+ text-transform: uppercase;
198
+ font-weight: bold;
199
+ text-align: center;
200
+ padding: 0 0.75em;
201
+ margin-right: 0.5em;
202
+ }
203
+
204
+ #users .tag.admin {
205
+ background: #163F10;
206
+ }
207
+
208
+ #users .tag.user {
209
+ background: #141A3F;
210
+ }
211
+
212
+ #repositories .tag.repo {
213
+ background: #720E12;
214
+ }
215
+
216
+ #groups .tag.group {
217
+ background: #17768A;
218
+ }
219
+
220
+ .section ul li a {
221
+ text-decoration: none;
222
+ color: #395F99;
223
+ }
224
+
225
+ /* View Stuff */
226
+
227
+ .view h2 {
228
+ margin: 0 0 1em;
229
+ text-align: center;
230
+ font-weight: bold;
231
+ font-size: 1.3em;
232
+ color: #333;
233
+ border-top: 0.1em solid #DDD;
234
+ border-bottom: 0.1em solid #DDD;
235
+ padding: 0.5em;
236
+ }
237
+
238
+ .view h3 {
239
+ margin: 0.5em 1em;
240
+ font-weight: bold;
241
+ color: #555;
242
+ font-size: 1.2em;
243
+ }
244
+
245
+ .view ul {
246
+ margin: 0.5em 2em;
247
+ line-height: 1.8em;
248
+ font-size: 1.1em;
249
+ list-style: disc inside;
250
+ }
251
+
252
+ .view ul li {
253
+ }
254
+
255
+ .view ul li.empty {
256
+ color: #4A5258;
257
+ }
258
+
259
+ br.clear { clear: both; }
260
+
261
+ .view ul li a {
262
+ text-decoration: none;
263
+ color: #416999;
264
+ }
data/public/gitauth.js ADDED
@@ -0,0 +1,17 @@
1
+ $(function() {
2
+ setTimeout(function() { $(".message").slideUp("slow"); }, 7500);
3
+ });
4
+
5
+ function showForm(name)
6
+ {
7
+ $("#add-" + name + "-link").hide();
8
+ $("#add-" + name).slideDown("slow");
9
+ return false;
10
+ }
11
+
12
+ function hideForm(name)
13
+ {
14
+ $("#add-" + name + "-link").show();
15
+ $("#add-" + name).slideUp("slow");
16
+ return false;
17
+ }