From c3d5aef951163e80691dadb2da9e3045732b16e8 Mon Sep 17 00:00:00 2001 From: Karchnu Date: Tue, 13 Oct 2020 02:35:33 +0200 Subject: [PATCH] Better CLI parameters parsing. --- utils/authc.cr | 99 ++++++++++++++----------- utils/better-parser.cr | 161 +++++++++++++++++++++++++++++++---------- utils/parse-me.cr | 12 --- 3 files changed, 181 insertions(+), 91 deletions(-) diff --git a/utils/authc.cr b/utils/authc.cr index 14e91ba..ab8234e 100644 --- a/utils/authc.cr +++ b/utils/authc.cr @@ -8,7 +8,6 @@ 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 @@ -26,9 +25,12 @@ class Context # class_property print_owner = true # class_property print_nb_comments = true - class_property user_profile : Hash(String,JSON::Any)? class_property command = "not-implemented" + class_property user_profile : Hash(String,JSON::Any)? + class_property phone : String? + class_property email : String? + # TODO: inner arguments. # Will be parsed later, with a specific parser. class_property args : Array(String)? = nil @@ -51,21 +53,32 @@ class Actions password end + def self.ask_something(str : String) : String? + STDOUT << "#{str} " + STDOUT.flush + answer = STDIN.gets.try &.chomp + answer + 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 + @the_call["user-registration"] = ->user_registration # Do not require admin priviledges. + @the_call["user-delete"] = ->user_deletion # Do not require admin priviledges. + + @the_call["permission-set"] = ->permission_set + @the_call["permission-check"] = ->permission_check - # User. - @the_call["registration"] = ->user_registration - @the_call["delete"] = ->user_deletion end + # + # For all functions: the number of arguments is already tested. + # + def user_registration # pp! authd.register login, password.not_nil!, email, phone, profile: profile rescue e : AuthD::Exception @@ -75,41 +88,43 @@ class Actions 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 - + args = Context.args.not_nil! + login, email, phone = args[0..2] profile = Context.user_profile + password = Actions.ask_password exit 1 unless password - # pp! authd.add_user login, password.not_nil!, email, phone, profile: profile + pp! authd.add_user login, password.not_nil!, email, phone, profile: profile rescue e : AuthD::Exception puts "error: #{e.message}" end + # TODO def user_mod - end + args = Context.args.not_nil! + userid = args[0] - 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 + password : String? = nil + + should_ask_password = Actions.ask_something "Should we change the password (Yn) ?" || "n" + case should_ask_password + when /y/i + Baguette::Log.debug "Ok let's change the password!" + password = Actions.ask_password + exit 1 unless password + else + Baguette::Log.debug "Ok no change in password." end - user, application, resource = args[0..2] - pp! user, application, resource + email = Context.email + phone = Context.phone - pp! @authd.check_permission user.to_i, application, resource + Baguette::Log.error "This function shouldn't be used for now." + Baguette::Log.error "It is way too cumbersome." + + # res = authd.add_user login, password, email, phone, profile: profile + # puts res end # TODO @@ -126,21 +141,23 @@ class Actions # pp! authd.ask_password_recovery login 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 + def permission_check + args = Context.args.not_nil! + user, application, resource = args[0..2] + # pp! user, application, resource + res = @authd.check_permission user.to_i, application, resource + puts res + end + + def permission_set + args = Context.args.not_nil! user, application, resource, permission = args[0..3] - pp! user, application, resource, permission + # pp! user, application, resource, permission perm = AuthD::User::PermissionLevel.parse(permission) - pp! @authd.set_permission user.to_i, application, resource, perm + res = @authd.set_permission user.to_i, application, resource, perm + puts res end end diff --git a/utils/better-parser.cr b/utils/better-parser.cr index abbf241..3896ef7 100644 --- a/utils/better-parser.cr +++ b/utils/better-parser.cr @@ -7,6 +7,67 @@ opt_authd_admin = -> (parser : OptionParser) { end } +# frequently used functions +opt_authd_login = -> (parser : OptionParser) { + parser.on "-l LOGIN", "--login LOGIN", "Authd user login." do |login| + Context.authd_login = login + Baguette::Log.info "User login for authd: #{Context.authd_login}." + end + parser.on "-p PASSWORD", "--password PASSWORD", "Authd user password." do |password| + Context.authd_pass = password + Baguette::Log.info "User password for authd: #{Context.authd_pass}." + end +} + +opt_help = -> (parser : OptionParser) { + parser.on "help", "Prints this help message." do + puts parser + exit 0 + end +} + +opt_profile = -> (parser : OptionParser) { + parser.on "-P file", "--profile file", "Read the user profile from a file." do |file| + Context.user_profile = JSON.parse(File.read file).as_h + Baguette::Log.info "Reading the user profile: #{Context.user_profile}." + end +} + +opt_phone = -> (parser : OptionParser) { + parser.on "-n phone", "--phone-number num", "Phone number." do |phone| + Context.phone = phone + Baguette::Log.info "Reading the user phone number: #{Context.phone}." + end +} + +opt_email = -> (parser : OptionParser) { + parser.on "-e email", "--email address", "Email address." do |email| + Context.email = email + Baguette::Log.info "Reading the user email address: #{Context.email}." + end +} + + +# Unrecognized parameters are used to create commands with multiple arguments. +# Example: user add _login email phone_ +# Here, login, email and phone are unrecognized arguments. +# Still, the "user add" command expect them. +unrecognized_args_to_context_args = -> (parser : OptionParser, n_expected_args : Int32) { + # With the right args, these will be interpreted as serialized data. + parser.unknown_args do |args| + if args.size != n_expected_args + Baguette::Log.error "#{parser}" + exit 1 + end + args.each 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 + end +} parser = OptionParser.new do |parser| parser.banner = "usage: #{PROGRAM_NAME} command help" @@ -20,80 +81,104 @@ parser = OptionParser.new do |parser| exit 0 end - parser.on "user", "user" do + parser.on "user", "Operations on users." do parser.banner = "Usage: user [add | mod | delete]" - parser.on "add", "add" do - Baguette::Log.info "user-add" - Context.command = "user-add" - # parser_user_add.call parser - # opt_profile.call parser - opt_authd_admin.call parser - parser.banner = "Usage: user-add user-id -e email -p phone [opt]" - parser.on "help", "Prints this help message." do - puts "usage: #{PROGRAM_NAME} user-add THINGS [options]" - puts parser - exit 0 - end + parser.on "add", "Adding a user to the DB." do + parser.banner = "usage: user add login email phone [-P profile] [opt]" + Baguette::Log.info "Adding a user to the DB." + Context.command = "user-add" + opt_authd_admin.call parser + opt_profile.call parser + opt_email.call parser + opt_phone.call parser + opt_help.call parser + # login email phone + unrecognized_args_to_context_args.call parser, 3 end - parser.on "mod", "mod" do - Baguette::Log.info "user-mod" + parser.on "mod", "Modify a user account." do + parser.banner = "Usage: user mod userid [-e email|-n phone|-P profile] [opt]" + Baguette::Log.info "Modify a user account." Context.command = "user-mod" - # opt_profile.call parser opt_authd_admin.call parser + opt_email.call parser + opt_phone.call parser + opt_profile.call parser + opt_help.call parser + # userid + unrecognized_args_to_context_args.call parser, 1 end parser.on "delete", "Remove user." do + parser.banner = "Usage: user delete -u userid [opt]" Baguette::Log.info "Remove user." Context.command = "delete" + # You can either be the owner of the account, or an admin. + opt_authd_login.call parser opt_authd_admin.call parser + opt_help.call parser + end + + # Do not require to be admin. + parser.on "register", "Register a user (requires activation)." do + parser.banner = "Usage: user register login email phone [-P profile] [opt]" + Baguette::Log.info "Register a user (requires activation)." + Context.command = "user-registration" + # These options shouldn't be used here, + # email and phone parameters are mandatory. + # opt_email.call parser + # opt_phone.call parser + opt_profile.call parser + opt_help.call parser + # login email phone + unrecognized_args_to_context_args.call parser, 3 end end - parser.on "permissions", "Permissions." do + parser.on "permission", "Permissions management." do parser.banner = "Usage: permissions [check | set]" parser.on "set", "Set permissions." do + parser.banner = <<-END + usage: permission set user application resource permission +example: permission set 1002 my-application chat read + +permission list: none read edit admin +END Baguette::Log.info "Set permissions." - Context.command = "set-permissions" + Context.command = "permission-set" opt_authd_admin.call parser + opt_help.call parser + # userid application resource permission + unrecognized_args_to_context_args.call parser, 4 end parser.on "check", "Check permissions." do + parser.banner = <<-END + usage: permission check user application resource +example: permission check 1002 my-application chat + +permission list: none read edit admin +END Baguette::Log.info "Check permissions." - Context.command = "check-permissions" + Context.command = "permission-check" opt_authd_admin.call parser + opt_help.call parser + # userid application resource + unrecognized_args_to_context_args.call parser, 3 end end - # Do not require to be admin. - parser.on "registration", "Register a user." do - Baguette::Log.info "Register a user." - Context.command = "registration" - # opt_profile.call parser - # opt_authd_login.call parser - end parser.unknown_args do |args| if args.size > 0 Baguette::Log.warning "Unknown args: #{args}" end end -# parser.on "-p file", "--profile file", "Read the user profile from a file." do |file| -# profile_file = file -# end -# # parser.on "-X user-password", "--user-password pass", "Read the new user password." do |pass| # password = pass # end -# -# parser.on "-K file", "--key-file file", "Read the authd shared key from a file." do |file| -# key_file = file -# end -# -# parser.on "-R", "--register", "Use a registration request instead of a add-user one." do -# register = true -# end + end diff --git a/utils/parse-me.cr b/utils/parse-me.cr index 4c43ede..0e9dad3 100644 --- a/utils/parse-me.cr +++ b/utils/parse-me.cr @@ -57,18 +57,6 @@ opt_authd_admin = -> (root : Phreak::Subparser) { end } -# frequently used functions -opt_profile = -> (root : Phreak::Subparser) { - root.bind(short_flag: 'P', long_flag: "profile", description: "Read the user profile from a file.") do |sub| - sub.grab do |sub, name| - Blah.next_token = name - end - file = Blah.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."