gitauth 0.0.5.0

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