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.
@@ -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