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