From 1ddb9eda1b2da1e48af29664c7af3bc5e41b244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20M=C3=BCller=20Madsen?= Date: Sun, 4 Jun 2017 20:42:43 +0200 Subject: [PATCH] restructured + sqllite-integration for users --- collab.rb | 304 ----------------------------- lib/emacscollab.rb | 2 + lib/emacscollab/collab.rb | 182 +++++++++++++++++ lib/emacscollab/database/sqlite.rb | 51 +++++ lib/emacscollab/project.rb | 61 ++++++ lib/emacscollab/sshkey.rb | 35 ++++ lib/emacscollab/user.rb | 59 ++++++ prompt.rb | 2 +- sqlite.rb | 33 ---- 9 files changed, 391 insertions(+), 338 deletions(-) delete mode 100644 collab.rb create mode 100644 lib/emacscollab.rb create mode 100644 lib/emacscollab/collab.rb create mode 100644 lib/emacscollab/database/sqlite.rb create mode 100644 lib/emacscollab/project.rb create mode 100644 lib/emacscollab/sshkey.rb create mode 100644 lib/emacscollab/user.rb delete mode 100644 sqlite.rb diff --git a/collab.rb b/collab.rb deleted file mode 100644 index 0be9a54..0000000 --- a/collab.rb +++ /dev/null @@ -1,304 +0,0 @@ -require 'fileutils' - -POSIX_NAME_PREFIX = "emacs" -USER_LOCATION = "./users/" - -$projects = [] -$users = [] -$project_id_map = {} -$user_id_map = {} - - -##################### -# User manipulation # -##################### - -class User - 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) - db.add_user(user) - $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 - - def add_key(key) - @keys << key - end - - def add_user(user) - @users << user - user.keys.each do |key| - add_key(key) - end - flush - refresh - end - - def remove_user(user) - @user.delete(user) - flush - refresh - end - - def refresh - @users = File.open("#{@path}/.ssh/users","r") do |f| - f.read.split("\n").map{|id| get_user_by_id(id)} - end - @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 - -end - -class DuplicateProjectError < StandardError -end - -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) - $projects.each do |p| - if p == id then - raise DuplicateProjectError, "Project with id #{id} already exists" - end - end - - if not system("useradd -m #{POSIX_NAME_PREFIX}#{id} -s /usr/bin/eshell") then - raise "Project creation failed" - end - - 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.posixname, project.posixname, "#{project.path}/.ssh" - - project -end - -def remove_project(project) - system("userdel -r #{project.posixname}") - $projects.delete(project) - $project_id_map[id] = nil -end - -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 - -#################### -# Key manipulation # -#################### - -class SSHKey - attr_accessor :cipher, :pubkey, :comment - - def initialize(cipher,pubkey,comment="") - @cipher = cipher - @pubkey = pubkey - @comment = comment - end - - def ==(o) - o.class == self.class && o.attrs == attrs - end - - def pretty - "#{@cipher} #{@comment}" - end - - def to_s - "ssh-#{@cipher} #{@pubkey} #{@comment}" - end - - def attrs - [@cipher, @pubkey, @comment] - end -end - -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(project) - extract_ssh_pubkeys("#{project.path}/.ssh/authorized_keys") -end - -def valid_pubkey?(key) - IO.popen("ssh-keygen -qlf -","r+") do |io| - io.write key.to_s - io.close_write - io.read - end - if $?.to_i == 0 - true - else - false - end -end - -def extract_ssh_pubkeys(file) - File.open(file,"r") do |f| - 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 - -################ - - -########### -# Project DB # -########### - -def reload_projects_from_passwd - $projects = [] - `cut -d: -f1 /etc/passwd`.split("\n"). - select{|u| u.start_with? POSIX_NAME_PREFIX}. - 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_dir -#reload_projects_from_passwd - -#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/lib/emacscollab.rb b/lib/emacscollab.rb new file mode 100644 index 0000000..b0f480d --- /dev/null +++ b/lib/emacscollab.rb @@ -0,0 +1,2 @@ +module EmacsCollab +end diff --git a/lib/emacscollab/collab.rb b/lib/emacscollab/collab.rb new file mode 100644 index 0000000..0a0968e --- /dev/null +++ b/lib/emacscollab/collab.rb @@ -0,0 +1,182 @@ +require 'fileutils' +require 'scrypt' +require_relative 'user' +require_relative 'project' +require_relative 'sshkey' + +POSIX_NAME_PREFIX = "emacs" +USER_LOCATION = "./users/" +DATA_PATH = "/home/christoffermadsen/git/emacs-collab/data" + + +require_relative 'database/sqlite' +$db = EmacsCollab::Database::SQLite.new "test" + +module EmacsCollab + + # TODO: Fix this weird definition + + ##################### + # User manipulation # + ##################### + + def self.create_user(id) + user = User.new(id) + add_user(user) + end + + def self.add_user(user) + $db.add_user(user) + end + + def self.get_user_by_id(id) + $db.get_user(id) + end + + def self.change_user_password(id,password) + user = get_user_by_id(id) + + hash, salt = salthash_password(password) + $db.change_user_password(user, hash, salt) + end + + def self.check_password(id, password) + user = get_user_by_id(id) + + hash = salthash_password(password, user.salt)[0] + + user.pw_hash == hash + end + + def self.salthash_password(password, salt = SCrypt::Engine.generate_salt) + hash = SCrypt::Engine.hash_secret(password,salt) + return hash, salt + end + + ######################## + # Project manipulation # + ######################## + + def self.get_project(id) + $db.get_project(id) + end + + def self.add_project(project) + $db.add_project(project) + end + + def self.create_project(id) + if get_project(id) then + raise DuplicateProjectError, "Project with id #{id} already exists" + end + + if not system("useradd -m #{POSIX_NAME_PREFIX}#{id} -s /usr/bin/eshell") then + raise "Project creation failed" + end + + project = Project.new + project.id = id + project.path = "/home/#{POSIX_NAME_PREFIX}#{id}" + project.posixname = "#{POSIX_NAME_PREFIX}#{id}" + + FileUtils.mkdir "#{project.path}/.ssh" + FileUtils.touch "#{project.path}/.ssh/authorized_keys" + FileUtils.touch "#{project.path}/.ssh/users" + FileUtils.chown_R project.posixname, project.posixname, "#{project.path}/.ssh" + + add_project(project) + + project + end + + def self.remove_project(project) + system("userdel -r #{project.posixname}") + $db.remove_project(project) + end + + def self.load_project_from_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 + + #################### + # Key manipulation # + #################### + + def self.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 self.get_ssh_pubkeys(project) + extract_ssh_pubkeys("#{project.path}/.ssh/authorized_keys") + end + + def self.valid_pubkey?(key) + IO.popen("ssh-keygen -qlf -","r+") do |io| + io.write key.to_s + io.close_write + io.read + end + if $?.to_i == 0 + true + else + false + end + end + + def self.extract_ssh_pubkeys(file) + File.open(file,"r") do |f| + 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 + + ################ + + + ########### + # Project $DB # + ########### + + def self.reload_projects_from_passwd + $projects = [] + `cut -d: -f1 /etc/passwd`.split("\n"). + select{|u| u.start_with? POSIX_NAME_PREFIX}. + map{|u| load_project_from_posix_name(u)}. + each{|u| add_project(u)} + $projects.each{|p| p.refresh} + end + + def self.reload_users_from_dir + `ls -1 #{USER_LOCATION}`.split("\n"). + map{|u| User.new(u)}. + each{|u| add_user(u.id)} + end + + def self.flush_all_to_disk + $users.each {|u| u.flush} + $projects.each {|p| p.flush} + end + + def self.flush_projects_with_user(user) + $projects.select{|p| p.users.include? user}.each do |p| + p.flush + end + end + + ################################################ + +end + + #reload_users_from_dir + #reload_projects_from_passwd + + #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/lib/emacscollab/database/sqlite.rb b/lib/emacscollab/database/sqlite.rb new file mode 100644 index 0000000..b814d6d --- /dev/null +++ b/lib/emacscollab/database/sqlite.rb @@ -0,0 +1,51 @@ +require_relative '../user' +require_relative '../project' +require_relative '../sshkey' + +require 'sqlite3' +require 'json' + +module EmacsCollab + module Database + + class SQLite + def initialize(db_name) + @db = SQLite3::Database.new "#{DATA_PATH}/#{db_name}.db" + end + + def get_user(id) + # TODO + row = @db.execute("SELECT * FROM Users WHERE name = ?", [id])[0] + unless row + raise UserNotFoundError, "User with id #{id} could not be found in database" + end + + user = User.new(row[0]) + user.pw_hash = row[1] + user.salt = row[2] + user.keys = row[3] == nil ? nil : JSON.parse(row[3]) + + user + end + + def add_user(user) + @db.execute("INSERT INTO Users (name) VALUES (?)", + [user.id]) + end + + def update_user(user) + @db.execute("UPDATE Users SET keys = ? WHERE name = ?", + [JSON.generate(user.keys),user.id]) + end + + def change_user_password(user,hash,salt) + @db.execute("UPDATE Users SET passwd = ?, salt = ? WHERE name = ?", + [hash,salt,user.id]) + end + end + end +end + +#user1 = User.new("user1") +#add_user(user1) +#change_user_password(user1,"testtest") diff --git a/lib/emacscollab/project.rb b/lib/emacscollab/project.rb new file mode 100644 index 0000000..9e4c7ae --- /dev/null +++ b/lib/emacscollab/project.rb @@ -0,0 +1,61 @@ +module EmacsCollab + class Project + attr_accessor :id, :path, :posixname, :users + attr_reader :keys + + def initialize + @users = [] + end + + def to_s + "#{@id}, #{@path}" + end + + def add_key(key) + @keys << key + end + + def add_user(user) + @users << user + user.keys.each do |key| + add_key(key) + end + flush + refresh + end + + def remove_user(user) + @user.delete(user) + flush + refresh + end + + def refresh + @users = File.open("#{@path}/.ssh/users","r") do |f| + f.read.split("\n").map{|id| get_user_by_id(id)} + end + @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 + + end + + class DuplicateProjectError < StandardError + end + +end diff --git a/lib/emacscollab/sshkey.rb b/lib/emacscollab/sshkey.rb new file mode 100644 index 0000000..83ab9c4 --- /dev/null +++ b/lib/emacscollab/sshkey.rb @@ -0,0 +1,35 @@ +module EmacsCollab + + class SSHKey + attr_accessor :cipher, :pubkey, :comment + + def initialize(cipher,pubkey,comment="") + @cipher = cipher + @pubkey = pubkey + @comment = comment + end + + def ==(o) + o.class == self.class && o.attrs == attrs + end + + def pretty + "#{@cipher} #{@comment}" + end + + def to_s + "ssh-#{@cipher} #{@pubkey} #{@comment}" + end + + def attrs + [@cipher, @pubkey, @comment] + end + end + + class InvalidSSHPubKey < StandardError + end + + class DuplicateSSHPubKey < StandardError + end + +end diff --git a/lib/emacscollab/user.rb b/lib/emacscollab/user.rb new file mode 100644 index 0000000..32d60f2 --- /dev/null +++ b/lib/emacscollab/user.rb @@ -0,0 +1,59 @@ +module EmacsCollab + + class User + attr_accessor :id, :keypath, :pw_hash, :salt, :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 + + class UserNotFoundError < StandardError + end + +end diff --git a/prompt.rb b/prompt.rb index 0421e6e..745609c 100644 --- a/prompt.rb +++ b/prompt.rb @@ -1,4 +1,4 @@ -require_relative 'collab' +require_relative 'lib/emacscollab' require 'tty-prompt' require 'colorize' diff --git a/sqlite.rb b/sqlite.rb deleted file mode 100644 index 575427f..0000000 --- a/sqlite.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'sqlite3' -require 'json' -require 'securerandom' -require 'scrypt' -require_relative 'collab.rb' - - -$db = SQLite3::Database.new "data/test.db" - -def get_user(id) - puts $db.execute("SELECT * FROM Users WHERE name = ?", [id]) -end - -def add_user(user) - $db.execute("INSERT INTO Users (name) VALUES (?)", - [user.id]) -end - -def update_user(user) - $db.execute("UPDATE Users SET keys = ? WHERE name = ?", - [JSON.generate(user.keys),user.id]) -end - -def change_user_password(user,password) - salt = SecureRandom.base64(32) - hash = SCrypt::Password.create(salt + password) - $db.execute("UPDATE Users SET passwd = ?, salt = ? WHERE name = ?", - [hash,salt,user.id]) -end - -#user1 = User.new("user1") -#add_user(user1) -#change_user_password(user1,"testtest")