mbbx6spp-gitauth 0.0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2009 Darcy Laycock <sutto@sutto.net>
4
+ # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
5
+ # Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
6
+ # Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
7
+ #
8
+ # This program is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #++
21
+
22
+ module GitAuth
23
+ class Client
24
+ include GitAuth::Loggable
25
+
26
+ attr_accessor :user, :command
27
+
28
+ def initialize(user_name, command)
29
+ logger.debug "Initializing client with command: #{command.inspect} and user name #{user_name.inspect}"
30
+ @callbacks = Hash.new { |h,k| h[k] = [] }
31
+ @user = GitAuth::User.get(user_name.to_s.strip)
32
+ @command = command
33
+ end
34
+
35
+ def exit_with_error(error)
36
+ logger.warn "Exiting with error: #{error}"
37
+ $stderr.puts error
38
+ exit! 1
39
+ end
40
+
41
+ def run!
42
+ if @user.nil?
43
+ exit_with_error "An invalid user / key was used. Please ensure it is setup with GitAuth"
44
+ elsif @command.to_s.strip.empty?
45
+ if user.shell_accessible?
46
+ exec(ENV["SHELL"])
47
+ else
48
+ exit_with_error "SSH_ORIGINAL_COMMAND is needed, mmmkay?"
49
+ end
50
+ else
51
+ command = Command.parse(@command)
52
+ repo = command.bad? ? nil : Repo.get(extract_repo_name(command))
53
+ if command.bad?
54
+ if user.shell_accessible?
55
+ exec(@command)
56
+ else
57
+ exit_with_error "Invalid ssh command - Access Denied"
58
+ end
59
+ elsif repo.nil?
60
+ exit_with_error "Unable to push to a non-existant repository"
61
+ elsif user.can_execute?(command, repo)
62
+ git_shell_argument = "#{command.verb} '#{repo.real_path}'"
63
+ logger.info "Running command: #{git_shell_argument} for user: #{@user.name}"
64
+ exec("git-shell", "-c", git_shell_argument)
65
+ else
66
+ exit_with_error "Unable to execute command on this repository"
67
+ end
68
+ end
69
+ rescue Exception => e
70
+ logger.fatal "Exception: #{e.class.name}: #{e.message}"
71
+ e.backtrace.each do |l|
72
+ logger.fatal " => #{l}"
73
+ end
74
+ exit_with_error "Exception raised - Please check your gitauth log / contact an administrator"
75
+ end
76
+
77
+ def self.start!(user, command)
78
+ # Gitorious does it so I should too!
79
+ File.umask(0022)
80
+ # Setup models etc
81
+ GitAuth.prepare
82
+ # Finally, create and initialize
83
+ client = self.new(user, command)
84
+ yield client if block_given?
85
+ client.run!
86
+ end
87
+
88
+ protected
89
+
90
+ def extract_repo_name(command)
91
+ command.path
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,101 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2009 Darcy Laycock <sutto@sutto.net>
4
+ # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
5
+ # Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
6
+ # Copyright (C) 2008 Tim Dysinger <tim@dysinger.net>
7
+ # Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
8
+ #
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU Affero General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU Affero General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU Affero General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+ # This along with the associated gitauth-shell command are inspired by
24
+ # (and essentially, derived from) gitosis (http://eagain.net/gitweb/?p=gitosis.git)
25
+ # and gitorius (http://gitorius.org)
26
+ # Gitosis is of this writing licensed under the GPLv2 and is copyright (c) Tommi Virtanen
27
+ # and can be found at http://eagain.net/gitweb/?p=gitosis.git
28
+ # GitAuth::Command is licensed under the same license
29
+
30
+ module GitAuth
31
+ class Command
32
+
33
+ # Standard Commands
34
+ READ_COMMANDS = ["git-upload-pack", "git upload-pack"]
35
+ WRITE_COMMANDS = ["git-receive-pack", "git receive-pack"]
36
+ PATH_REGEXP = /^'([\w\_\-\~\.\+\/]+\/)?([\w\_\-\.\+]+(\.git)?)'$/i.freeze
37
+
38
+ attr_reader :path, :verb, :command
39
+
40
+ def initialize(command)
41
+ @command = command
42
+ @verb = nil
43
+ @argument = nil
44
+ @path = nil
45
+ @bad_command = true
46
+ end
47
+
48
+ def bad?
49
+ @bad_command
50
+ end
51
+
52
+ def write?
53
+ !bad? && @verb_type == :write
54
+ end
55
+
56
+ def read?
57
+ !bad? && !write?
58
+ end
59
+
60
+ def process
61
+ return if @command.include?("\n") || @command !~ /^git[ \-]/i
62
+ @verb, @argument = split_command
63
+ return if @argument.nil? || @argument.is_a?(Array)
64
+ # Check if it's read / write
65
+ if READ_COMMANDS.include?(@verb)
66
+ @verb_type = :read
67
+ elsif WRITE_COMMANDS.include?(@verb)
68
+ @verb_type = :write
69
+ else
70
+ return
71
+ end
72
+ if PATH_REGEXP =~ @argument
73
+ @path = $2
74
+ return unless @path
75
+ else
76
+ return
77
+ end
78
+ @bad_command = false
79
+ end
80
+
81
+ def self.parse(command)
82
+ command = self.new(command)
83
+ command.process
84
+ command
85
+ end
86
+
87
+ protected
88
+
89
+ def split_command
90
+ parts = @command.split(" ")
91
+ if parts.size == 3
92
+ ["#{parts[0]} #{parts[1]}", parts[2]]
93
+ elsif parts.size == 2
94
+ parts
95
+ else
96
+ raise BadCommandError
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -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