diff --git a/collab.rb b/collab.rb index 3e0d6c0..48bbd9a 100644 --- a/collab.rb +++ b/collab.rb @@ -1,62 +1,189 @@ require 'fileutils' POSIX_NAME_PREFIX = "emacs" +USER_LOCATION = "./users/" +$projects = [] $users = [] -$id_map = {} +$project_id_map = {} +$user_id_map = {} + ##################### # User manipulation # ##################### class User - attr_accessor :id, :path, :posixname + attr_accessor :id, :keypath + attr_reader :keys + + def to_s + "#{@id}" + end + + def initialize(id) + @id = id + @keypath = "#{USER_LOCATION}#{id}" + refresh_keys + end + + def add_key_from_string(keystring) + unless valid_pubkey? keystring + raise InvalidSSHPubKey, "Public key not valid" + end + ks = keystring.split(" "); + key = SSHKey.new(ks[0][4..6], ks[1], ks[2]) + + unless @keys.include? key + @keys << key + else + raise DuplicateSSHPubKey, "Public key is already added to user" + end + + flush + refresh_keys + flush_projects_with_user(self) + end + + def remove_key(key) + @keys.delete(key) + flush + refresh_keys + flush_projects_with_user(self) + end + + def refresh_keys + File.open(@keypath,"r") do |f| + @keys = f.read.split("\n").select{ |line| line[0..2] == "ssh" }.map{ |ks| k = ks.split(" "); SSHKey.new(k[0][4..6], k[1], k[2]) } + end + end + + def flush + File.open(@keypath,"w+") do |key_file| + @keys.each do |key| + key_file << "#{key.to_s}\n" + end + end + end +end + +def add_user(id) + user = User.new(id) + $users << user + $user_id_map[id] = user +end + +def get_user_by_id(id) + $user_id_map[id] +end + +def validate_password + +end + +######################## +# Project manipulation # +######################## + +class Project + attr_accessor :id, :path, :posixname, :users + attr_reader :keys + + def initialize + @users = [] + end def to_s "#{@id}, #{@path}" end -end + + def add_key(key) + @keys << key + end -def get_user(id) - $id_map[id] -end + def add_user(user) + @users << user + user.keys.each do |key| + p key + add_key(key) + end + flush + refresh + end -def add_user(user) - $users << user - $id_map[user.id] = user -end + def remove_user(user) + @user.delete(user) + flush + refresh + end -def create_user(id) - if not system("useradd -m #{POSIX_NAME_PREFIX}#{id}") then - raise "User creation failed" + def refresh + @users = File.open("#{@path}/.ssh/users","r") do |f| + f.read.split("\n").map{|id| p id; puts $user_id_map; get_user_by_id(id)} + end + p @users + @keys = extract_ssh_pubkeys("#{@path}/.ssh/authorized_keys") + end + + def flush + File.open("#{@path}/.ssh/users","w+") do |user_file| + @users.each do |u| + user_file << "#{u.id}\n" + end + end + + get_ssh_keyfile(self,"w+") do |key_file| + @users.each do |user| + user.keys.each do |key| + key_file << "#{key.to_s}\n" + end + end + end end - user = User.new - user.id = id - user.path = "/home/#{POSIX_NAME_PREFIX}#{id}" - user.posixname = "#{POSIX_NAME_PREFIX}#{id}" - $users << user - $id_map[id] = user +end - FileUtils.mkdir "#{user.path}/.ssh" - FileUtils.touch "#{user.path}/.ssh/authorized_keys" - FileUtils.chown_R user.id, user.id, "#{user.path}/.ssh" +def get_project(id) + $project_id_map[id] +end + +def add_project(project) + $projects << project + $project_id_map[project.id] = project +end + +def create_project(id) + if not system("useradd -m #{POSIX_NAME_PREFIX}#{id}") then + raise "Project creation failed" + end - user + project = Project.new + project.id = id + project.path = "/home/#{POSIX_NAME_PREFIX}#{id}" + project.posixname = "#{POSIX_NAME_PREFIX}#{id}" + $projects << project + $project_id_map[id] = project + + FileUtils.mkdir "#{project.path}/.ssh" + FileUtils.touch "#{project.path}/.ssh/authorized_keys" + FileUtils.touch "#{project.path}/.ssh/users" + FileUtils.chown_R project.id, project.id, "#{project.path}/.ssh" + + project end -def remove_user(user) - system("userdel -r #{user.posixname}") - $users.remove(user) - $id_map[id] = nil +def remove_project(project) + system("userdel -r #{project.posixname}") + $projects.remove(project) + $project_id_map[id] = nil end -def user_by_posix_name(posix_name) - user = User.new - user.id = posix_name[POSIX_NAME_PREFIX.length..-1] - user.path = "/home/#{posix_name}" - user.posixname = posix_name - user +def project_by_posix_name(posix_name) + project = Project.new + project.id = posix_name[POSIX_NAME_PREFIX.length..-1] + project.path = "/home/#{posix_name}" + project.posixname = posix_name + project end #################### @@ -89,35 +216,44 @@ class SSHKey end end -def get_ssh_keyfile(user,mode,&block) - file = File.open("#{user.path}/.ssh/authorized_keys",mode) +class InvalidSSHPubKey < StandardError +end + +class DuplicateSSHPubKey < StandardError +end + +def get_ssh_keyfile(project,mode,&block) + file = File.open("#{project.path}/.ssh/authorized_keys",mode) return file unless block_given? yield(file) ensure file.close end -def get_ssh_pubkeys(user) - extract_ssh_pubkeys("#{user.path}/.ssh/authorized_keys") +def get_ssh_pubkeys(project) + extract_ssh_pubkeys("#{project.path}/.ssh/authorized_keys") end -def add_ssh_pubkey(user,key) - if not valid_pubkey? key then raise "Public key not valid" end - get_ssh_keyfile(user,"a") do |key_file| +# Deprecate +def add_ssh_pubkey(project,key) + unless valid_pubkey? key + raise InvalidSSHPubKey, "Public key not valid" + end + get_ssh_keyfile(project,"a") do |key_file| key_file << "#{key.to_s}\n" end end -def reset_pubkeys(user) - FileUtils.rm "#{user.path}/.ssh/authorized_keys" - FileUtils.touch "#{user.path}/.ssh/authorized_keys" - FileUtils.chown_R user.id, user.id, "#{user.path}/.ssh" +def reset_pubkeys(project) + FileUtils.rm "#{project.path}/.ssh/authorized_keys" + FileUtils.touch "#{project.path}/.ssh/authorized_keys" + FileUtils.chown_R project.id, project.id, "#{project.path}/.ssh" end -def remove_ssh_pubkey(user,key) - keys = get_ssh_pubkeys(user) - reset_pubkeys(user) - keys.each{ |k| unless k == key then add_ssh_pubkey(user,k) end} +def remove_ssh_pubkey(project,key) + keys = get_ssh_pubkeys(project) + reset_pubkeys(project) + keys.each{ |k| unless k == key then add_ssh_pubkey(project,k) end} end def remove_ssh_pubkey_by_key @@ -149,23 +285,40 @@ end ########### -# User DB # +# Project DB # ########### -def reload_users_from_passwd - $users = [] +def reload_projects_from_passwd + $projects = [] `cut -d: -f1 /etc/passwd`.split("\n"). select{|u| u.start_with? POSIX_NAME_PREFIX}. - map{|u| user_by_posix_name(u)}. - each{|u| add_user(u)} + map{|u| project_by_posix_name(u)}. + each{|u| add_project(u)} + $projects.each{|p| p.refresh} end +def reload_users_from_dir + `ls -1 #{USER_LOCATION}`.split("\n"). + map{|u| User.new(u)}. + each{|u| add_user(u.id)} +end + +def flush_all_to_disk + $users.each {|u| u.flush} + $projects.each {|p| p.flush} +end + +def flush_projects_with_user(user) + $projects.select{|p| p.users.include? user}.each do |p| + p.flush + end +end ################################################ -reload_users_from_passwd +reload_users_from_dir +reload_projects_from_passwd - -#remove_user 5 -#create_user 3 -#add_ssh_pubkey(get_user(3), "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7ZKN2fKcUQUaQqDjNCQQBSdqBVGX7lprNCJmceDBfybhbnuZJ8KvzJJIJIIbzqheW5BVCfkWJY6OgkpAumLWSRCS5n2+AnDHwQgpKDS93OeV+9/kattVtsVUBZaghymyJ2UfA0r918dkxcT9SZbNSl9raiDUUmj3JY8UM219BQP7BRqoZ6e/YZz9lO7ORy6yQT6fIMaVOaZcoDPr6oyNJfadm9POvS/Wl63onoRI9dzpHQG9RuHCcUhHJhkGtzY7GeRWc85WqA9Q4vYo0SK5Je9BG1cvAAVTfV+eYEJEiSDMwWj60roH0C3/ipmzxD/kWqg6YBJWL+XAyQkDnmbuD christoffermadsen@strawberry.thedevcave.net") +#remove_project 5 +#create_project 3 +#add_ssh_pubkey(get_project(3), "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7ZKN2fKcUQUaQqDjNCQQBSdqBVGX7lprNCJmceDBfybhbnuZJ8KvzJJIJIIbzqheW5BVCfkWJY6OgkpAumLWSRCS5n2+AnDHwQgpKDS93OeV+9/kattVtsVUBZaghymyJ2UfA0r918dkxcT9SZbNSl9raiDUUmj3JY8UM219BQP7BRqoZ6e/YZz9lO7ORy6yQT6fIMaVOaZcoDPr6oyNJfadm9POvS/Wl63onoRI9dzpHQG9RuHCcUhHJhkGtzY7GeRWc85WqA9Q4vYo0SK5Je9BG1cvAAVTfV+eYEJEiSDMwWj60roH0C3/ipmzxD/kWqg6YBJWL+XAyQkDnmbuD christoffermadsen@strawberry.thedevcave.net") diff --git a/prompt.rb b/prompt.rb index 2e79269..e1e191c 100644 --- a/prompt.rb +++ b/prompt.rb @@ -1,51 +1,92 @@ require_relative 'collab' require 'tty-prompt' +require 'colorize' $prompt = TTY::Prompt.new - +$login = "root" def init - choices = %w(Keys Users Quit) + puts "Logged in as " + $login.colorize(:cyan) + "\n" + choices = %w(Users Projects Quit) case $prompt.select("What do you want to do?", choices) + when "Projects" + projects when "Users" users - when "Keys" - keys when "Quit" exit end end -def keys +def users choices = $users user = $prompt.select("Pick a user", choices) keys_user(user) end def keys_user(user) - choices = {'List keys' => :list ,'Add key' => :add, 'Remove key' => :remove, 'Reset authorized_keys' => :reset} - case $prompt.select("What do you want to do for user #{user}?", choices) + choices = {'List keys' => :list ,'Add key' => :add, 'Remove key' => :remove} + case $prompt.select("What do you want to do?", choices) when :list - get_ssh_pubkeys(user).each{|k| puts k.pretty} + user.keys.each{|k| puts k.pretty} keys_user user when :add key = $prompt.ask("Please enter a valid public SSH key", echo: false) - add_ssh_pubkey(user,key) - when :remove - keys = get_ssh_pubkeys(user)#.map{|k| puts k.pretty} - key = $prompt.select("Select the key to delete", keys) - unless $prompt.no?("Are you sure you want to delete this key?") - remove_ssh_pubkey(user,key) + begin + user.add_key_from_string(key) + rescue InvalidSSHPubKey => e + puts e.message.colorize(:red) + keys_user(user) + rescue DuplicateSSHPubKey => e + puts e.message.colorize(:red) + keys_user(user) end - when :reset - unless $prompt.no?("Are you sure you want to reset the keys for this user?") - unless $prompt.no?("Really sure?") - reset_pubkeys(user) - end + when :remove + choices = user.keys.map { |k| [k.pretty, k] }.to_h + key = $prompt.select("Select the key to remove", choices) + unless $prompt.no?("Are you sure you want to remove this key?") + user.remove_key(key) end end end +def projects + choices = $projects + project = $prompt.select("Pick a project", choices) + keys_project(project) +end + +def keys_project(project) + choices = {'List users' => :list, + 'Add user' => :add, + 'Select users with access' => :select} + case $prompt.select("What do you want to do?", choices) + when :list + project.users.each {|u| puts u.id} + when :add + choices = $users + user = $prompt.select("Pick a user", choices) + project.add_user(user) + puts "Added user #{user} to #{project}".colorize(:green) + when :select + choices = $users + # Mark already added users as "default" + counter = 1 + defaults = [] + choices.each do |u| + if project.users.include? u then + defaults << counter + end + counter += 1 + end + users = $prompt.multi_select("Select users", choices, default: defaults) + project.users = users + project.flush + project.refresh + end + +end + while true init end