From 88f87ef6f24334c02212d9f57b0a9195d72a2158 Mon Sep 17 00:00:00 2001 From: Karchnu Date: Fri, 9 Oct 2020 18:13:58 +0200 Subject: [PATCH] authc: a start --- shard.yml | 4 + utils/authc.cr | 300 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 utils/authc.cr diff --git a/shard.yml b/shard.yml index 0d14f3b..5350cc9 100644 --- a/shard.yml +++ b/shard.yml @@ -11,6 +11,10 @@ description: | targets: authd: main: src/main.cr + authc: + main: utils/authc.cr + +# TO REMOVE auth-user-perms: main: utils/authd-user-perms.cr auth-user-add: diff --git a/utils/authc.cr b/utils/authc.cr new file mode 100644 index 0000000..4aa2360 --- /dev/null +++ b/utils/authc.cr @@ -0,0 +1,300 @@ +require "phreak" + +require "ipc" +require "yaml" + +require "baguette-crystal-base" + +require "../src/authd.cr" + +# require "./altideal-client.cr" +# require "./yaml_dates.cr" # from "3 days - 2 hours" to YAML-compliant dates +# require "./yaml_uuid.cr" # YAML UUID parser +# require "./authd_api.cr" # Authd interface functions + + +class Context + class_property simulation = false # do not perform the action + + class_property authd_login = "undef" # undef authd user + class_property authd_pass = "undef" # undef authd user password + class_property shared_key = "undef" # undef authd user password + + # # Properties to select what to display when printing a deal. + # class_property print_title = true + # class_property print_description = true + # class_property print_owner = true + # class_property print_nb_comments = true + + class_property user_profile : Hash(String,JSON::Any)? + class_property command = "not-implemented" + + # TODO: inner arguments. + # Will be parsed later, with a specific parser. + class_property args : Array(String)? = nil +end + +opt_help = -> (root : Phreak::RootParser) { + root.bind(short_flag: 'h', long_flag: "help", description: "Get some help.") do |sub| + puts root + exit 0 + end + root.bind(short_flag: 'v', long_flag: "verbosity", description: "Verbosity.") do |sub| + next_token = root.next_token + Baguette::Context.verbosity = next_token.to_i + Baguette::Log.info "Verbosity: #{Baguette::Context.verbosity}." + end +} + +# frequently used functions +opt_authd_login = -> (root : Phreak::RootParser) { + root.bind(short_flag: 'l', long_flag: "login", description: "Authd user login.") do |sub| + next_token = root.next_token + Context.authd_login = next_token + Baguette::Log.info "User login for authd: #{Context.authd_login}." + end + root.bind(short_flag: 'p', long_flag: "password", description: "Authd user password.") do |sub| + next_token = root.next_token + Context.authd_pass = next_token + Baguette::Log.info "User password for authd: #{Context.authd_pass}." + end + root.bind(short_flag: 'k', long_flag: "key-file", description: "Read the authd shared key from a file.") do |sub| + next_token = root.next_token + key_file = next_token + Context.shared_key = File.read(key_file).chomp + Baguette::Log.info "Key for admin operations: #{Context.shared_key}." + end +} + +# frequently used functions +opt_user_add_mod = -> (root : Phreak::RootParser) { + root.bind(short_flag: 'P', long_flag: "profile", description: "Read the user profile from a file.") do |sub| + next_token = root.next_token + file = next_token + Context.user_profile = JSON.parse(File.read file).as_h + Baguette::Log.info "Reading the user profile: #{Context.user_profile}." + end +} + +opt_simulation = -> (root : Phreak::RootParser) { + root.bind(short_flag: 's', long_flag: "simulation", description: "Don't do anything.") do |sub| + Baguette::Log.info "This is a simulation." + Context.simulation = true + end +} + +opt_args = -> (root : Phreak::RootParser) { + # With the right args, these will be interpreted as serialized data. + # See "deal-add" for example. + root.unrecognized_args do |arg| + Baguette::Log.debug "Unrecognized argument: #{arg} (adding to Context.args)" + if Context.args.nil? + Context.args = Array(String).new + end + Context.args.not_nil! << arg + end +} + +parser_get_deals = -> (root : Phreak::RootParser) { + root.missing_args do |apex| + Baguette::Log.info "Missing an argument after #{apex}" + end + +# root.unrecognized_args do |arg| +# Baguette::Log.info "Unrecognized argument: #{arg}" +# end + + opt_args.call root + + # root.banner = "Usage: [...] [other options]" + # opt_help.call root +} + + +# Parsing arguments is reading and understanding the intent, not doing anything. +Phreak.parse! do |root| + + # Admin section. + root.bind(word: "user-add", description: "user-add") do |sub| + Baguette::Log.info "user-add" + Context.command = "user-add" + parser_get_deals.call root + opt_user_add_mod.call root + end + root.bind(word: "user-mod", description: "user-mod") do |sub| + Baguette::Log.info "user-mod" + Context.command = "user-mod" + parser_get_deals.call root + opt_user_add_mod.call root + end + root.bind(word: "delete", description: "Remove user.") do |sub| + Baguette::Log.info "Remove user." + Context.command = "delete" + parser_get_deals.call root + end + root.bind(word: "set-permissions", description: "Set permissions.") do |sub| + Baguette::Log.info "Set permissions." + Context.command = "set-permissions" + parser_get_deals.call root + end + root.bind(word: "check-permissions", description: "Check permissions.") do |sub| + Baguette::Log.info "Check permissions." + Context.command = "check-permissions" + parser_get_deals.call root + end + + root.bind(word: "registration", description: "Register a user.") do |sub| + Baguette::Log.info "Register a user." + Context.command = "registration" + parser_get_deals.call root + opt_user_add_mod.call root + end + + + root.default do + Baguette::Log.info "No arguments provided" + end + + root.missing_args do |apex| + Baguette::Log.info "Missing an argument after #{apex}" + end + + opt_args.call root + + root.banner = "Usage: #{PROGRAM_NAME} [opts] command subcommand [other options]" + opt_help.call root + opt_simulation.call root + opt_authd_login.call root + + root.bind(word: "help", description: "Get some help.") do |sub| + Baguette::Log.info "Help" + Baguette::Log.info root + exit 0 + end +end + +class Actions + + def self.ask_password + STDOUT << "password: " + STDOUT << `stty -echo` + STDOUT.flush + password = STDIN.gets.try &.chomp + + STDOUT << '\n' + STDOUT << `stty echo` + + password + end + + property the_call = {} of String => Proc(Nil) + property authd : AuthD::Client + + def initialize(@authd) + # Admin section. + @the_call["user-add"] = ->user_add + @the_call["user-mod"] = ->user_mod + @the_call["set-permissions"] = ->set_permissions + @the_call["check-permissions"] = ->check_permissions + + # User. + @the_call["registration"] = ->user_registration + @the_call["delete"] = ->user_deletion + end + + def user_registration + end + def user_deletion + end + + def user_add + args = Context.args + if args.nil? || args.size < 3 + Baguette::Log.warning "subcommand usage: user email phone" + Baguette::Log.warning " example: 1002 test@example.com 0690290516" + Baguette::Log.warning "" + Baguette::Log.warning "User profile opt: -P | --profile" + return + end + + profile = Context.user_profile + + password = Actions.ask_password + exit 1 unless password + end + + def user_mod + end + + def check_permissions + args = Context.args + if args.nil? || args.size < 3 + Baguette::Log.warning "subcommand usage: user application resource" + Baguette::Log.warning " example: 1002 my-application chat " + Baguette::Log.warning "" + Baguette::Log.warning "permission list: none read edit admin" + return + end + + user, application, resource = args[0..2] + pp! user, application, resource + + pp! @authd.check_permission user.to_i, application, resource + end + + def set_permissions + args = Context.args + if args.nil? || args.size < 4 + Baguette::Log.warning "subcommand usage: user application resource permission" + Baguette::Log.warning " example: 1002 my-application chat read" + Baguette::Log.warning "" + Baguette::Log.warning "permission list: none read edit admin" + return + end + + user, application, resource, permission = args[0..3] + pp! user, application, resource, permission + + perm = AuthD::User::PermissionLevel.parse(permission) + pp! @authd.set_permission user.to_i, application, resource, perm + end +end + +def main + + # Authd connection. + authd = AuthD::Client.new + authd.key = Context.shared_key if Context.shared_key != "undef" + + # Authd token. + # FIXME: not sure about getting the token, it seems not used elsewhere. + # If login == pass == "undef": do not even try. + #unless Context.authd_login == Context.authd_pass && Context.authd_login == "undef" + # login = Context.authd_login + # pass = Context.authd_pass + # token = authd.get_token? login, pass + # raise "cannot get a token" if token.nil? + # # authd.login token + #end + + actions = Actions.new authd + + # Now we did read the intent, we should proceed doing what was asked. + begin + actions.the_call[Context.command].call + rescue e + Baguette::Log.info "The command is not recognized (or implemented)." + end + + # authd disconnection + authd.close +rescue e + Baguette::Log.info "Exception: #{e}" +end + + +# Command line: +# tool [options] command [options-for-command] + +main +