gitauth 0.0.5.0
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/LICENSE +661 -0
- data/README.rdoc +141 -0
- data/USAGE +70 -0
- data/bin/gitauth +261 -0
- data/bin/gitauth-shell +30 -0
- data/config.ru +22 -0
- data/lib/gitauth.rb +100 -0
- data/lib/gitauth/apache_authentication.rb +64 -0
- data/lib/gitauth/auth_setup_middleware.rb +44 -0
- data/lib/gitauth/client.rb +95 -0
- data/lib/gitauth/command.rb +101 -0
- data/lib/gitauth/group.rb +87 -0
- data/lib/gitauth/message.rb +69 -0
- data/lib/gitauth/repo.rb +155 -0
- data/lib/gitauth/saveable_class.rb +54 -0
- data/lib/gitauth/user.rb +135 -0
- data/lib/gitauth/web_app.rb +310 -0
- data/public/gitauth.css +316 -0
- data/public/gitauth.js +17 -0
- data/public/jquery.js +19 -0
- data/resources/messages.yml +9 -0
- data/views/auth_setup.erb +27 -0
- data/views/clone_repo.erb +24 -0
- data/views/group.erb +24 -0
- data/views/index.erb +88 -0
- data/views/layout.erb +27 -0
- data/views/repo.erb +57 -0
- data/views/user.erb +51 -0
- metadata +152 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 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
|
+
|
20
|
+
module GitAuth
|
21
|
+
class Group < SaveableClass(:groups)
|
22
|
+
include GitAuth::Loggable
|
23
|
+
|
24
|
+
attr_accessor :name, :members
|
25
|
+
|
26
|
+
def initialize(name)
|
27
|
+
@name = name
|
28
|
+
@members = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy!
|
32
|
+
GitAuth::Repo.all.each { |r| r.remove_permissions_for(self) }
|
33
|
+
self.class.all.each { |r| r.remove_member(self) }
|
34
|
+
self.class.all.reject! { |g| g == self }
|
35
|
+
GitAuth::Repo.save!
|
36
|
+
self.class.save!
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_member(member)
|
40
|
+
return if member == self
|
41
|
+
@members << member.to_s
|
42
|
+
@members.uniq!
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_member(member)
|
46
|
+
@members.reject! { |m| m == member.to_s }
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(group)
|
50
|
+
group.is_a?(Group) && group.name == self.name
|
51
|
+
end
|
52
|
+
|
53
|
+
def member?(user_or_group, recurse = false, level = 0)
|
54
|
+
member = @members.include?(user_or_group.to_s)
|
55
|
+
Thread.current[:checked_groups] = [] if level == 0
|
56
|
+
if !member
|
57
|
+
return false if level > 0 && Thread.current[:checked_groups].include?(self)
|
58
|
+
Thread.current[:checked_groups] << self
|
59
|
+
member = recurse && @members.map { |m| Group.get(m) }.compact.any? { |g| g.member?(user_or_group, true, level + 1) }
|
60
|
+
end
|
61
|
+
Thread.current[:checked_groups] = nil if level == 0
|
62
|
+
return member
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
"@#{name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.create(name)
|
70
|
+
name = name.to_s.strip.gsub(/^@/, "")
|
71
|
+
return false if name.empty? || name !~ /^([\w\_\-\.]+)$/
|
72
|
+
self.add_item self.new(name)
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.get(name)
|
77
|
+
logger.debug "Getting group named #{name.inspect}"
|
78
|
+
real_name = name.to_s.gsub(/^@/, "")
|
79
|
+
self.all.detect { |g| g.name == real_name }
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.group?(name)
|
83
|
+
name.to_s =~ /^@/ && !get(name).nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 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 'yaml'
|
20
|
+
|
21
|
+
module GitAuth
|
22
|
+
class Message
|
23
|
+
|
24
|
+
TEMPLATES = YAML.load_file(BASE_DIR.join("resources", "messages.yml"))
|
25
|
+
|
26
|
+
attr_accessor :type, :name, :message, :variables
|
27
|
+
|
28
|
+
def initialize(type, name, variables = {})
|
29
|
+
@type = type
|
30
|
+
@name = name
|
31
|
+
@variables = {}
|
32
|
+
variables.each_pair { |k,v| @variables[k.to_s] = v }
|
33
|
+
auto_set_message!
|
34
|
+
end
|
35
|
+
|
36
|
+
def success?
|
37
|
+
@type.to_sym == :notice
|
38
|
+
end
|
39
|
+
|
40
|
+
def error?
|
41
|
+
@type.to_sym == :error
|
42
|
+
end
|
43
|
+
|
44
|
+
class << self
|
45
|
+
# Handy accessor / generate methods
|
46
|
+
# for a given error code.
|
47
|
+
|
48
|
+
def error(name = :unknown)
|
49
|
+
new(:error, name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def notice(name = :unknown)
|
53
|
+
new(:notice, name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def warning(name = :unknown)
|
57
|
+
new(:warning, name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def auto_set_message!
|
64
|
+
raw_message = (TEMPLATES[@type.to_s] || {})[@name.to_s] || ""
|
65
|
+
@message = raw_message.gsub(/\:(\w+)/i) { |v| @variables[$1] || "" }
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/gitauth/repo.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 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 'fileutils'
|
20
|
+
module GitAuth
|
21
|
+
class Repo < SaveableClass(:repositories)
|
22
|
+
include GitAuth::Loggable
|
23
|
+
|
24
|
+
NAME_RE = /^([\w\_\-\.\+]+(\.git)?)$/i
|
25
|
+
|
26
|
+
def self.get(name)
|
27
|
+
logger.debug "Getting Repo w/ name: '#{name}'"
|
28
|
+
(all || []).detect { |r| r.name == name }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create(name, path = name)
|
32
|
+
return false if name.nil? || path.nil?
|
33
|
+
return false if self.get(name) || self.all.any? { |r| r.path == path } || name !~ NAME_RE || path !~ NAME_RE
|
34
|
+
repository = new(name, path)
|
35
|
+
return false unless repository.create_repo!
|
36
|
+
add_item(repository)
|
37
|
+
repository
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :name, :path, :permissions
|
41
|
+
|
42
|
+
def initialize(name, path, auto_create = false)
|
43
|
+
@name, @path = name, path
|
44
|
+
@permissions = {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def ==(other)
|
48
|
+
other.is_a?(Repo) && other.name == name && other.path == path
|
49
|
+
end
|
50
|
+
|
51
|
+
def writeable_by(whom)
|
52
|
+
add_permissions :write, whom
|
53
|
+
end
|
54
|
+
|
55
|
+
def readable_by(whom)
|
56
|
+
add_permissions :read, whom
|
57
|
+
end
|
58
|
+
|
59
|
+
def update_permissions!(user, permissions = [])
|
60
|
+
remove_permissions_for(user)
|
61
|
+
writeable_by(user) if permissions.include?("write")
|
62
|
+
readable_by(user) if permissions.include?("read")
|
63
|
+
self.class.save!
|
64
|
+
end
|
65
|
+
|
66
|
+
def writeable_by?(user_or_group)
|
67
|
+
has_permissions_for :write, user_or_group
|
68
|
+
end
|
69
|
+
|
70
|
+
def readable_by?(user_or_group)
|
71
|
+
has_permissions_for :read, user_or_group
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_permissions_for(user_or_group)
|
75
|
+
@permissions.each_value do |val|
|
76
|
+
val.reject! { |m| m == user_or_group.to_s }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def real_path
|
81
|
+
File.join(GitAuth::Settings.base_path, @path)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_repo!
|
85
|
+
return false if !GitAuth.has_git?
|
86
|
+
unless File.directory?(real_path)
|
87
|
+
FileUtils.mkdir_p(real_path)
|
88
|
+
output = ""
|
89
|
+
Dir.chdir(real_path) { IO.popen("git --bare init") { |f| output << f.read } }
|
90
|
+
!!(output =~ /Initialized empty Git repository/)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def destroy!
|
95
|
+
FileUtils.rm_rf(real_path) if File.exist?(real_path)
|
96
|
+
self.class.all.reject! { |r| r == self }
|
97
|
+
self.class.save!
|
98
|
+
end
|
99
|
+
|
100
|
+
def make_empty!
|
101
|
+
tmp_path = "/tmp/gitauth-#{rand(100000)}-#{Time.now.to_i}"
|
102
|
+
logger.info "Creating temporary dir at #{tmp_path}"
|
103
|
+
FileUtils.mkdir_p("#{tmp_path}/current-repo")
|
104
|
+
logger.info "Changing to new directory"
|
105
|
+
Dir.chdir("#{tmp_path}/current-repo") do
|
106
|
+
logger.info "Marking as git repo"
|
107
|
+
GitAuth.run "git init"
|
108
|
+
logger.info "Touching .gitignore"
|
109
|
+
GitAuth.run "touch .gitignore"
|
110
|
+
# Configure it
|
111
|
+
GitAuth.run "git config push.default current"
|
112
|
+
logger.info "Commiting"
|
113
|
+
GitAuth.run "git add ."
|
114
|
+
GitAuth.run "git commit -am 'Initialize Empty Repository'"
|
115
|
+
# Push the changes to the actual repository
|
116
|
+
logger.info "Adding origin #{self.real_path}"
|
117
|
+
GitAuth.run "git remote add origin '#{self.real_path}'"
|
118
|
+
logger.info "Pushing..."
|
119
|
+
GitAuth.run "git push origin master"
|
120
|
+
end
|
121
|
+
ensure
|
122
|
+
logger.info "Cleaning up old tmp file"
|
123
|
+
FileUtils.rm_rf(tmp_path) if File.directory?(tmp_path)
|
124
|
+
end
|
125
|
+
|
126
|
+
def execute_post_create_hook!
|
127
|
+
script = File.expand_path("~/.gitauth/post-create")
|
128
|
+
if File.executable?(script)
|
129
|
+
system(script, @name, @path)
|
130
|
+
return $?.success?
|
131
|
+
else
|
132
|
+
# If there isn't a file, run it ourselves.
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
|
139
|
+
def add_permissions(type, whom)
|
140
|
+
@permissions[type] ||= []
|
141
|
+
@permissions[type] << whom.to_s
|
142
|
+
@permissions[type].uniq!
|
143
|
+
end
|
144
|
+
|
145
|
+
def has_permissions_for(type, whom)
|
146
|
+
whom = GitAuth.get_user_or_group(whom) if whom.is_a?(String)
|
147
|
+
logger.info "Checking if #{whom.to_s} can #{type} #{self.name}"
|
148
|
+
!(@permissions[type] || []).detect do |reader|
|
149
|
+
reader = GitAuth.get_user_or_group(reader)
|
150
|
+
reader == whom || (reader.is_a?(Group) && reader.member?(whom, true))
|
151
|
+
end.nil?
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 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
|
+
module GitAuth
|
20
|
+
|
21
|
+
class BasicSaveable
|
22
|
+
|
23
|
+
class_inheritable_accessor :all, :store_path
|
24
|
+
self.all = nil
|
25
|
+
|
26
|
+
class << self
|
27
|
+
|
28
|
+
def load!
|
29
|
+
self.all = YAML.load(File.read(store_path)) rescue nil if File.file?(store_path)
|
30
|
+
self.all = [] unless all.is_a?(Array)
|
31
|
+
end
|
32
|
+
|
33
|
+
def save!
|
34
|
+
load! if all.nil?
|
35
|
+
File.open(store_path, "w+") { |f| f.write all.to_yaml }
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_item(item)
|
39
|
+
load! if all.nil?
|
40
|
+
all << item
|
41
|
+
save!
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.SaveableClass(kind)
|
48
|
+
klass = Class.new(BasicSaveable)
|
49
|
+
klass.store_path = GitAuth::GITAUTH_DIR.join("#{kind}.yml").to_s
|
50
|
+
klass.all = nil
|
51
|
+
return klass
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/lib/gitauth/user.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2009 Brown Beagle Software
|
3
|
+
# Copyright (C) 2009 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
|
+
|
20
|
+
module GitAuth
|
21
|
+
class User < SaveableClass(:users)
|
22
|
+
include GitAuth::Loggable
|
23
|
+
|
24
|
+
def self.get(name)
|
25
|
+
logger.debug "Getting user for the name '#{name}'"
|
26
|
+
(all || []).detect { |r| r.name == name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create(name, admin, key)
|
30
|
+
# Basic sanity checking
|
31
|
+
return false if name.nil? || admin.nil? || key.nil?
|
32
|
+
# Require that the name is valid and admin is a boolean.
|
33
|
+
return false unless name =~ /^([\w\_\-\.]+)$/ && !!admin == admin
|
34
|
+
# Check there isn't an existing user
|
35
|
+
return false unless get(name).blank?
|
36
|
+
if (user = new(name, admin)).write_ssh_key!(key)
|
37
|
+
add_item(user)
|
38
|
+
return true
|
39
|
+
else
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :name, :admin
|
45
|
+
|
46
|
+
def initialize(name, admin = false)
|
47
|
+
@name = name
|
48
|
+
@admin = admin
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
@name.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_ssh_key!(key)
|
56
|
+
cleaned_key = self.class.clean_ssh_key(key)
|
57
|
+
if cleaned_key.nil?
|
58
|
+
return false
|
59
|
+
else
|
60
|
+
output = "#{command_prefix} #{cleaned_key}"
|
61
|
+
File.open(GitAuth::Settings.authorized_keys_file, "a+") do |file|
|
62
|
+
file.puts output
|
63
|
+
end
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def command_prefix
|
69
|
+
options = ["command=\"#{GitAuth::Settings.shell_executable} #{@name}\"",
|
70
|
+
"no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding"]
|
71
|
+
options << "no-pty" if !shell_accessible?
|
72
|
+
options.join(",")
|
73
|
+
end
|
74
|
+
|
75
|
+
def destroy!
|
76
|
+
GitAuth::Repo.all.each { |r| r.remove_permissions_for(self) }
|
77
|
+
GitAuth::Group.all.each { |g| g.remove_member(self) }
|
78
|
+
# Remove the public key from the authorized_keys file.
|
79
|
+
auth_keys_path = GitAuth::Settings.authorized_keys_file
|
80
|
+
if File.exist?(auth_keys_path)
|
81
|
+
contents = File.read(auth_keys_path)
|
82
|
+
contents.gsub!(/#{command_prefix} ssh-\w+ [a-zA-Z0-9\/\+]+==\r?\n?/m, "")
|
83
|
+
File.open(auth_keys_path, "w+") { |f| f.write contents }
|
84
|
+
end
|
85
|
+
self.class.all.reject! { |u| u == self }
|
86
|
+
# Finally, save everything
|
87
|
+
self.class.save!
|
88
|
+
GitAuth::Repo.save!
|
89
|
+
GitAuth::Group.save!
|
90
|
+
end
|
91
|
+
|
92
|
+
def groups
|
93
|
+
(Group.all || []).select { |g| g.member?(self) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def admin?
|
97
|
+
!!@admin
|
98
|
+
end
|
99
|
+
|
100
|
+
alias shell_accessible? admin?
|
101
|
+
|
102
|
+
def pushable?(repo)
|
103
|
+
admin? || repo.writeable_by?(self)
|
104
|
+
end
|
105
|
+
|
106
|
+
def pullable?(repo)
|
107
|
+
admin? || repo.readable_by?(self)
|
108
|
+
end
|
109
|
+
|
110
|
+
def can_execute?(command, repo)
|
111
|
+
return if command.bad?
|
112
|
+
if command.write?
|
113
|
+
logger.debug "Checking if #{self.name} can push to #{repo.name}"
|
114
|
+
pushable?(repo)
|
115
|
+
else
|
116
|
+
logger.debug "Checking if #{self.name} can pull from #{repo.name}"
|
117
|
+
pullable?(repo)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.clean_ssh_key(key)
|
122
|
+
if key =~ /^(ssh-\w+ [a-zA-Z0-9\/\+]+==?).*$/
|
123
|
+
return $1
|
124
|
+
else
|
125
|
+
return nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.valid_key?(key)
|
130
|
+
clean_ssh_key(key).present?
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
Users = User # For Backwards Compat.
|
135
|
+
end
|