gitauth 0.0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|