From 2a267ea7a2341197bb4222a7fb46030705927654 Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Sat, 10 Jun 2023 17:26:12 +0200 Subject: [PATCH] Authd: users are now logged and have an 'admin' attribute. --- src/authd/client.cr | 13 +++--- src/authd/user.cr | 8 ++-- src/better-parser.cr | 8 ++-- src/client.cr | 5 ++- src/network.cr | 2 +- src/requests/admin.cr | 17 +++---- src/requests/contact.cr | 4 +- src/requests/delete.cr | 67 ++++++++-------------------- src/requests/list.cr | 2 +- src/requests/{token.cr => login.cr} | 8 ++-- src/requests/password.cr | 6 +-- src/requests/permissions.cr | 4 +- src/requests/profile.cr | 4 +- src/requests/register.cr | 2 +- src/requests/search.cr | 2 +- src/requests/users.cr | 6 +-- src/responses/{token.cr => login.cr} | 4 +- src/server.cr | 25 ++++++++++- 18 files changed, 91 insertions(+), 96 deletions(-) rename src/requests/{token.cr => login.cr} (79%) rename src/responses/{token.cr => login.cr} (67%) diff --git a/src/authd/client.cr b/src/authd/client.cr index d75ee23..65badb1 100644 --- a/src/authd/client.cr +++ b/src/authd/client.cr @@ -23,11 +23,11 @@ module AuthD end def get_token?(login : String, password : String) : String? - send_now Request::GetToken.new login, password + send_now Request::Login.new login, password response = AuthD.responses.parse_ipc_json read - if response.is_a?(Response::Token) + if response.is_a?(Response::Login) response.token else nil @@ -78,11 +78,12 @@ module AuthD # FIXME: Extra options may be useful to implement here. def add_user(login : String, password : String, + admin : Bool, email : String?, phone : String?, profile : Hash(String, ::JSON::Any)?) : ::AuthD::User::Public | Exception - send_now Request::AddUser.new @key, login, password, email, phone, profile + send_now Request::AddUser.new login, password, admin, email, phone, profile response = AuthD.responses.parse_ipc_json read @@ -158,7 +159,7 @@ module AuthD end def mod_user(uid_or_login : Int32 | String, password : String? = nil, email : String? = nil, phone : String? = nil, avatar : String? = nil) : Bool | Exception - request = Request::ModUser.new @key, uid_or_login + request = Request::ModUser.new uid_or_login request.password = password if password request.email = email if email @@ -180,7 +181,7 @@ module AuthD end def check_permission(user : Int32, service_name : String, resource_name : String) : User::PermissionLevel - request = Request::CheckPermission.new @key, user, service_name, resource_name + request = Request::CheckPermission.new user, service_name, resource_name send_now request @@ -197,7 +198,7 @@ module AuthD end def set_permission(uid : Int32, service : String, resource : String, permission : User::PermissionLevel) - request = Request::SetPermission.new @key, uid, service, resource, permission + request = Request::SetPermission.new uid, service, resource, permission send_now request diff --git a/src/authd/user.cr b/src/authd/user.cr index 8bfd4cf..3802efb 100644 --- a/src/authd/user.cr +++ b/src/authd/user.cr @@ -31,6 +31,7 @@ class AuthD::User # Public. property login : String property uid : Int32 + property admin : Bool = false property profile : Hash(String, JSON::Any)? # Private. @@ -47,7 +48,7 @@ class AuthD::User Token.new @login, @uid end - def initialize(@uid, @login, @password_hash) + def initialize(@uid, @login, @password_hash, @admin = false) @contact = Contact.new @permissions = Hash(String, Hash(String, PermissionLevel)).new @configuration = Hash(String, Hash(String, JSON::Any)).new @@ -58,16 +59,17 @@ class AuthD::User property login : String property uid : Int32 + property admin : Bool = false property profile : Hash(String, JSON::Any)? property date_registration : Time? - def initialize(@uid, @login, @profile, @date_registration) + def initialize(@uid, @login, @admin, @profile, @date_registration) end end def to_public : Public - Public.new @uid, @login, @profile, @date_registration + Public.new @uid, @login, @admin, @profile, @date_registration end end diff --git a/src/better-parser.cr b/src/better-parser.cr index c266e8b..f5e1771 100644 --- a/src/better-parser.cr +++ b/src/better-parser.cr @@ -49,8 +49,8 @@ opt_email = -> (parser : OptionParser) { # Unrecognized parameters are used to create commands with multiple arguments. -# Example: user add _login email phone_ -# Here, login, email and phone are unrecognized arguments. +# Example: user add _login email +# Here, login and email 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. @@ -87,7 +87,7 @@ parser = OptionParser.new do |parser| parser.banner = "Usage: user [add | mod | delete | validate | search | get | recover | register ]" parser.on "add", "Adding a user to the DB." do - parser.banner = "usage: user add login email phone [-P profile] [opt]" + parser.banner = "usage: user add login email [-P profile] [opt]" Baguette::Log.info "Adding a user to the DB." Context.command = "user-add" opt_authd_admin.call parser @@ -96,7 +96,7 @@ parser = OptionParser.new do |parser| opt_phone.call parser opt_help.call parser # login email phone - unrecognized_args_to_context_args.call parser, 3 + unrecognized_args_to_context_args.call parser, 2 end parser.on "mod", "Modify a user account." do diff --git a/src/client.cr b/src/client.cr index 4511ec0..b3c9eb2 100644 --- a/src/client.cr +++ b/src/client.cr @@ -74,13 +74,14 @@ class Actions def user_add puts "User add!!!" args = Context.args.not_nil! - login, email, phone = args[0..2] + login, email = args[0..1] profile = Context.user_profile password = Actions.ask_password exit 1 unless password - pp! authd.add_user login, password.not_nil!, email, phone, profile: profile + # By default: no phone, not admin. + pp! authd.add_user login, password.not_nil!, false, email, nil, profile: profile rescue e : AuthD::Exception puts "error: #{e.message}" end diff --git a/src/network.cr b/src/network.cr index 56edeb7..6322bf1 100644 --- a/src/network.cr +++ b/src/network.cr @@ -2,7 +2,7 @@ require "ipc" require "ipc/json" class IPC::JSON - def handle(service : AuthD::Service) + def handle(service : AuthD::Service, fd : Int32) raise "unimplemented" end end diff --git a/src/requests/admin.cr b/src/requests/admin.cr index e32f331..f40140c 100644 --- a/src/requests/admin.cr +++ b/src/requests/admin.cr @@ -1,24 +1,19 @@ class AuthD::Request IPC::JSON.message AddUser, 1 do - # Only clients that have the right shared key will be allowed - # to create users. - property shared_key : String - property login : String property password : String + property admin : Bool = false property email : String? = nil property phone : String? = nil property profile : Hash(String, JSON::Any)? = nil - def initialize(@shared_key, @login, @password, @email, @phone, @profile) + def initialize(@login, @password, @admin, @email, @phone, @profile) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) # No verification of the users' informations when an admin adds it. # No mail address verification. - if @shared_key != authd.configuration.shared_key - return Response::Error.new "invalid authentication key" - end + # TODO: ADMIN OPERATION, verify the logged user is an admin. if authd.users_per_login.get? @login return Response::Error.new "login already used" @@ -35,6 +30,7 @@ class AuthD::Request user = User.new uid, @login, password_hash user.contact.email = @email unless @email.nil? user.contact.phone = @phone unless @phone.nil? + user.admin = @admin @profile.try do |profile| user.profile = profile @@ -55,6 +51,7 @@ class AuthD::Request property shared_key : String property user : Int32 | String + property admin : Bool = false property password : String? = nil property email : String? = nil property phone : String? = nil @@ -63,7 +60,7 @@ class AuthD::Request def initialize(@shared_key, @user) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) if @shared_key != authd.configuration.shared_key return Response::Error.new "invalid authentication key" end diff --git a/src/requests/contact.cr b/src/requests/contact.cr index 4ab83fb..8b40de9 100644 --- a/src/requests/contact.cr +++ b/src/requests/contact.cr @@ -8,7 +8,7 @@ class AuthD::Request def initialize(@token) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.get_user_from_token @token return Response::Error.new "invalid user" unless user @@ -32,7 +32,7 @@ class AuthD::Request def initialize(@token) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.get_user_from_token @token return Response::Error.new "invalid user" unless user diff --git a/src/requests/delete.cr b/src/requests/delete.cr index a1e58c6..e2ab993 100644 --- a/src/requests/delete.cr +++ b/src/requests/delete.cr @@ -1,58 +1,24 @@ class AuthD::Request IPC::JSON.message Delete, 17 do - # Deletion can be triggered by either an admin or the user. - property shared_key : String? = nil - - property login : String? = nil - property password : String? = nil - + # Deletion can be triggered by either an admin or the related user. property user : String | Int32 - def initialize(@user, @login, @password) - end - def initialize(@user, @shared_key) + def initialize(@user) end - def handle(authd : AuthD::Service) - uid_or_login = @user - user_to_delete = if uid_or_login.is_a? Int32 - authd.users_per_uid.get? uid_or_login.to_s - else - authd.users_per_login.get? uid_or_login + def handle(authd : AuthD::Service, fd : Int32) + user_to_delete = authd.user? @user + return Response::Error.new "invalid user" if user_to_delete.nil? + + # Get currently logged user. + logged_user = authd.get_logged_user? fd + if logged_user.nil? + return Response::Error.new "you must be logged" end - if user_to_delete.nil? - return Response::Error.new "invalid user" - end - - # Either the request comes from an admin or the user. - # Shared key == admin, check the key. - if key = @shared_key - return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key - else - login = @login - pass = @password - if login.nil? || pass.nil? - return Response::Error.new "authentication failed (no shared key, no login)" - end - - # authenticate the user - begin - user = authd.users_per_login.get login - rescue e : DODB::MissingEntry - return Response::Error.new "invalid credentials" - end - - if user.nil? - return Response::Error.new "invalid credentials" - end - - if user.password_hash != authd.hash_password pass - return Response::Error.new "invalid credentials" - end - - # Is the user to delete the requesting user? - if user.uid != user_to_delete.uid + unless logged_user.admin + # Is the logged user the target? + if logged_user.uid != user_to_delete.uid return Response::Error.new "invalid credentials" end end @@ -60,10 +26,15 @@ class AuthD::Request # User or admin is now verified: let's proceed with the user deletion. authd.users_per_login.delete user_to_delete.login + # TODO: if the current user is deleted, unlog! + if logged_user.uid == user_to_delete.uid + authd.close fd + authd.logged_users.delete fd + end + # TODO: better response Response::User.new user_to_delete.to_public end end AuthD.requests << Delete - end diff --git a/src/requests/list.cr b/src/requests/list.cr index 6f52d92..bb7ab7b 100644 --- a/src/requests/list.cr +++ b/src/requests/list.cr @@ -6,7 +6,7 @@ class AuthD::Request def initialize(@token, @key) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) # FIXME: Lines too long, repeatedly (>80c with 4c tabs). @token.try do |token| user = authd.get_user_from_token token diff --git a/src/requests/token.cr b/src/requests/login.cr similarity index 79% rename from src/requests/token.cr rename to src/requests/login.cr index 94f8462..705a1a6 100644 --- a/src/requests/token.cr +++ b/src/requests/login.cr @@ -1,12 +1,12 @@ class AuthD::Request - IPC::JSON.message GetToken, 0 do + IPC::JSON.message Login, 0 do property login : String property password : String def initialize(@login, @password) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) begin user = authd.users_per_login.get @login rescue e : DODB::MissingEntry @@ -27,8 +27,8 @@ class AuthD::Request # change the date of the last connection authd.users_per_uid.update user.uid.to_s, user - Response::Token.new (token.to_s authd.configuration.shared_key), user.uid + Response::Login.new (token.to_s authd.configuration.shared_key), user.uid end end - AuthD.requests << GetToken + AuthD.requests << Login end diff --git a/src/requests/password.cr b/src/requests/password.cr index 19602b8..2034e8d 100644 --- a/src/requests/password.cr +++ b/src/requests/password.cr @@ -7,7 +7,7 @@ class AuthD::Request def initialize(@login, @old_password, @new_password) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.users_per_login.get? @login unless user @@ -35,7 +35,7 @@ class AuthD::Request def initialize(@user, @password_renew_key, @new_password) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) uid_or_login = @user user = if uid_or_login.is_a? Int32 authd.users_per_uid.get? uid_or_login.to_s @@ -69,7 +69,7 @@ class AuthD::Request def initialize(@user, @email) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) uid_or_login = @user user = if uid_or_login.is_a? Int32 authd.users_per_uid.get? uid_or_login.to_s diff --git a/src/requests/permissions.cr b/src/requests/permissions.cr index 48cf7b5..4900632 100644 --- a/src/requests/permissions.cr +++ b/src/requests/permissions.cr @@ -10,7 +10,7 @@ class AuthD::Request def initialize(@shared_key, @user, @service, @resource) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) authorized = false if key = @shared_key @@ -79,7 +79,7 @@ class AuthD::Request def initialize(@shared_key, @user, @service, @resource, @permission) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) unless @shared_key == authd.configuration.shared_key return Response::Error.new "unauthorized" end diff --git a/src/requests/profile.cr b/src/requests/profile.cr index 306987a..05a9612 100644 --- a/src/requests/profile.cr +++ b/src/requests/profile.cr @@ -6,7 +6,7 @@ class AuthD::Request def initialize(@token, @new_profile) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.get_user_from_token @token return Response::Error.new "invalid user" unless user @@ -45,7 +45,7 @@ class AuthD::Request def initialize(@token, @new_profile) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = if token = @token u = authd.get_user_from_token token raise UserNotFound.new unless u diff --git a/src/requests/register.cr b/src/requests/register.cr index 2855e30..10ae01d 100644 --- a/src/requests/register.cr +++ b/src/requests/register.cr @@ -9,7 +9,7 @@ class AuthD::Request def initialize(@login, @password, @email, @phone, @profile) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) if ! authd.configuration.registrations return Response::Error.new "registrations not allowed" end diff --git a/src/requests/search.cr b/src/requests/search.cr index 239ab47..e834069 100644 --- a/src/requests/search.cr +++ b/src/requests/search.cr @@ -5,7 +5,7 @@ class AuthD::Request def initialize(@user) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) pattern = Regex.new @user, Regex::Options::IGNORE_CASE matching_users = Array(AuthD::User::Public).new diff --git a/src/requests/users.cr b/src/requests/users.cr index edf6b91..940a174 100644 --- a/src/requests/users.cr +++ b/src/requests/users.cr @@ -6,7 +6,7 @@ class AuthD::Request def initialize(@login, @activation_key) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.users_per_login.get? @login if user.nil? @@ -37,7 +37,7 @@ class AuthD::Request def initialize(@user) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) uid_or_login = @user user = if uid_or_login.is_a? Int32 authd.users_per_uid.get? uid_or_login.to_s @@ -61,7 +61,7 @@ class AuthD::Request def initialize(@login, @password) end - def handle(authd : AuthD::Service) + def handle(authd : AuthD::Service, fd : Int32) user = authd.users_per_login.get? @login unless user diff --git a/src/responses/token.cr b/src/responses/login.cr similarity index 67% rename from src/responses/token.cr rename to src/responses/login.cr index b3ebb3f..c73c6af 100644 --- a/src/responses/token.cr +++ b/src/responses/login.cr @@ -1,9 +1,9 @@ class AuthD::Response - IPC::JSON.message Token, 1 do + IPC::JSON.message Login, 1 do property uid : Int32 property token : String def initialize(@token, @uid) end end - AuthD.responses << Token + AuthD.responses << Login end diff --git a/src/server.cr b/src/server.cr index ffb327b..3e8561a 100644 --- a/src/server.cr +++ b/src/server.cr @@ -26,6 +26,8 @@ class AuthD::Service < IPC property users_per_uid : DODB::Index(User) property users_per_login : DODB::Index(User) + property logged_users : Hash(Int32, AuthD::User::Public) + # #{@configuration.storage}/last_used_uid property last_uid_file : String @@ -38,6 +40,8 @@ class AuthD::Service < IPC @last_uid_file = "#{@configuration.storage}/last_used_uid" + @logged_users = Hash(Int32, AuthD::User::Public).new + if @configuration.recreate_indexes @users.reindex_everything! end @@ -75,6 +79,18 @@ class AuthD::Service < IPC File.write @last_uid_file, uid.to_s end + def get_logged_user?(fd : Int32) + @logged_users[fd]? + end + + def user?(uid_or_login : Int32 | String) + if uid_or_login.is_a? Int32 + @users_per_uid.get? uid_or_login.to_s + else + @users_per_login.get? uid_or_login + end + end + def handle_request(event : IPC::Event) request_start = Time.utc @@ -91,7 +107,7 @@ class AuthD::Service < IPC Baguette::Log.debug "<< #{request_name}" response = begin - request.handle self + request.handle self, event.fd rescue e : UserNotFound Baguette::Log.error "#{request_name} user not found" AuthD::Response::Error.new "authorization error" @@ -156,8 +172,15 @@ class AuthD::Service < IPC Baguette::Log.debug "Connection from #{event.fd}" if @configuration.print_ipc_connection when LibIPC::EventType::Disconnection Baguette::Log.debug "Disconnection from #{event.fd}" if @configuration.print_ipc_disconnection + @logged_users.delete event.fd else Baguette::Log.error "Not implemented behavior for event: #{event}" + if event.responds_to?(:fd) + fd = event.fd + Baguette::Log.warning "closing #{fd}" + close fd + @logged_users.delete fd + end end end