rewrite
Karchnu 2020-11-22 13:27:48 +01:00
parent c757b19c7b
commit fcf165de73
19 changed files with 913 additions and 896 deletions

View File

@ -31,3 +31,4 @@ end
require "./requests/*"
require "./responses/*"

100
src/requests/admin.cr Normal file
View File

@ -0,0 +1,100 @@
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 email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@shared_key, @login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
# 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
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
password_hash = authd.hash_password @password
uid = authd.new_uid
user = User.new uid, @login, password_hash
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
# We consider adding the user as a registration
user.date_registration = Time.local
authd.users << user
Response::UserAdded.new user.to_public
end
end
AuthD.requests << AddUser
IPC::JSON.message ModUser, 5 do
property shared_key : String
property user : Int32 | String
property password : String? = nil
property email : String? = nil
property phone : String? = nil
property avatar : String? = nil
def initialize(@shared_key, @user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
if @shared_key != authd.configuration.shared_key
return Response::Error.new "invalid authentication key"
end
uid_or_login = @user
user = 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
end
unless user
return Response::Error.new "user not found"
end
@password.try do |s|
user.password_hash = authd.hash_password s
end
@email.try do |email|
user.contact.email = email
end
@phone.try do |phone|
user.contact.phone = phone
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << ModUser
end

46
src/requests/contact.cr Normal file
View File

@ -0,0 +1,46 @@
class AuthD::Request
IPC::JSON.message EditContacts, 16 do
property token : String
property email : String? = nil
property phone : String? = nil
def initialize(@token)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
if email = @email
# FIXME: This *should* require checking the new mail, with
# a new activation key and everything else.
user.contact.email = email
end
authd.users_per_uid.update user
Response::UserEdited.new user.uid
end
end
AuthD.requests << EditContacts
IPC::JSON.message GetContacts, 18 do
property token : String
def initialize(@token)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
_c = user.contact
Response::Contacts.new user.uid, _c.email, _c.phone
end
end
AuthD.requests << GetContacts
end

69
src/requests/delete.cr Normal file
View File

@ -0,0 +1,69 @@
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
property user : String | Int32
def initialize(@user, @login, @password)
end
def initialize(@user, @shared_key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
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
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
return Response::Error.new "invalid credentials"
end
end
# User or admin is now verified: let's proceed with the user deletion.
authd.users_per_login.delete user_to_delete.login
# TODO: better response
Response::User.new user_to_delete.to_public
end
end
AuthD.requests << Delete
end

29
src/requests/list.cr Normal file
View File

@ -0,0 +1,29 @@
class AuthD::Request
IPC::JSON.message ListUsers, 8 do
property token : String? = nil
property key : String? = nil
def initialize(@token, @key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
@token.try do |token|
user = authd.get_user_from_token token
return Response::Error.new "unauthorized (user not found from token)"
return Response::Error.new "unauthorized (user not in authd group)" unless user.permissions["authd"]?.try(&.["*"].>=(User::PermissionLevel::Read))
end
@key.try do |key|
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
end
return Response::Error.new "unauthorized (no key nor token)" unless @key || @token
Response::UsersList.new authd.users.to_h.map &.[1].to_public
end
end
AuthD.requests << ListUsers
end

125
src/requests/password.cr Normal file
View File

@ -0,0 +1,125 @@
class AuthD::Request
IPC::JSON.message UpdatePassword, 7 do
property login : String
property old_password : String
property new_password : String
def initialize(@login, @old_password, @new_password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@old_password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.password_hash = authd.hash_password @new_password
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << UpdatePassword
IPC::JSON.message PasswordRecovery, 11 do
property user : Int32 | String
property password_renew_key : String
property new_password : String
def initialize(@user, @password_renew_key, @new_password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "user not found"
end
if user.password_renew_key == @password_renew_key
user.password_hash = authd.hash_password @new_password
else
return Response::Error.new "renew key not valid"
end
user.password_renew_key = nil
authd.users_per_uid.update user.uid.to_s, user
Response::PasswordRecovered.new user.to_public
end
end
AuthD.requests << PasswordRecovery
IPC::JSON.message AskPasswordRecovery, 12 do
property user : Int32 | String
property email : String
def initialize(@user, @email)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "no such user"
end
if user.contact.email != @email
# Same error as when users are not found.
return Response::Error.new "no such user"
end
user.password_renew_key = UUID.random.to_s
authd.users_per_uid.update user.uid.to_s, user
unless (activation_url = authd.configuration.activation_url).nil?
field_from = authd.configuration.field_from.not_nil!
activation_url = authd.configuration.activation_url.not_nil!
# Once the user is created and stored, we try to contact him
if authd.configuration.print_password_recovery_parameters
pp! user.login,
user.contact.email.not_nil!,
field_from,
activation_url,
user.password_renew_key.not_nil!
end
unless Process.run("password-recovery-mailer", [
"-l", user.login,
"-e", user.contact.email.not_nil!,
"-t", "Password recovery email",
"-f", field_from,
"-u", activation_url,
"-a", user.password_renew_key.not_nil!
]).success?
return Response::Error.new "cannot contact the user for password recovery"
end
end
Response::PasswordRecoverySent.new user.to_public
end
end
AuthD.requests << AskPasswordRecovery
end

113
src/requests/permissions.cr Normal file
View File

@ -0,0 +1,113 @@
class AuthD::Request
IPC::JSON.message CheckPermission, 9 do
property shared_key : String? = nil
property token : String? = nil
property user : Int32 | String
property service : String
property resource : String
def initialize(@shared_key, @user, @service, @resource)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
authorized = false
if key = @shared_key
if key == authd.configuration.shared_key
authorized = true
else
return Response::Error.new "invalid key provided"
end
end
if token = @token
user = authd.get_user_from_token token
if user.nil?
return Response::Error.new "token does not match user"
end
if user.login != @user && user.uid != @user
return Response::Error.new "token does not match user"
end
authorized = true
end
unless authorized
return Response::Error.new "unauthorized"
end
user = case u = @user
when .is_a? Int32
authd.users_per_uid.get? u.to_s
else
authd.users_per_login.get? u
end
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
resource_permissions = service_permissions[@resource]?
if resource_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
return Response::PermissionCheck.new service, @resource, user.uid, resource_permissions
end
end
AuthD.requests << CheckPermission
IPC::JSON.message SetPermission, 10 do
property shared_key : String
property user : Int32 | String
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@shared_key, @user, @service, @resource, @permission)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
unless @shared_key == authd.configuration.shared_key
return Response::Error.new "unauthorized"
end
user = authd.users_per_uid.get? @user.to_s
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
service_permissions = Hash(String, User::PermissionLevel).new
user.permissions[service] = service_permissions
end
if @permission.none?
service_permissions.delete @resource
else
service_permissions[@resource] = @permission
end
authd.users_per_uid.update user.uid.to_s, user
Response::PermissionSet.new user.uid, service, @resource, @permission
end
end
AuthD.requests << SetPermission
end

93
src/requests/profile.cr Normal file
View File

@ -0,0 +1,93 @@
class AuthD::Request
IPC::JSON.message EditProfile, 14 do
property token : String
property new_profile : Hash(String, JSON::Any)
def initialize(@token, @new_profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
new_profile = @new_profile
profile = user.profile || Hash(String, JSON::Any).new
authd.configuration.read_only_profile_keys.each do |key|
if new_profile[key]? != profile[key]?
return Response::Error.new "tried to edit read only key"
end
end
user.profile = new_profile
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << EditProfile
# Same as above, but doesnt reset the whole profile, only resets elements
# for which keys are present in `new_profile`.
IPC::JSON.message EditProfileContent, 15 do
property token : String? = nil
property shared_key : String? = nil
property user : Int32 | String | Nil
property new_profile : Hash(String, JSON::Any)
def initialize(@shared_key, @user, @new_profile)
end
def initialize(@token, @new_profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = if token = @token
u = authd.get_user_from_token token
raise UserNotFound.new unless u
u
elsif shared_key = @shared_key
raise AdminAuthorizationException.new if shared_key != authd.configuration.shared_key
u = @user
raise UserNotFound.new unless u
u = if u.is_a? Int32
authd.users_per_uid.get? u.to_s
else
authd.users_per_login.get? u
end
raise UserNotFound.new unless u
u
else
raise AuthenticationInfoLacking.new
end
new_profile = user.profile || Hash(String, JSON::Any).new
unless @shared_key
authd.configuration.read_only_profile_keys.each do |key|
if @new_profile.has_key? key
return Response::Error.new "tried to edit read only key"
end
end
end
@new_profile.each do |key, value|
new_profile[key] = value
end
user.profile = new_profile
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << EditProfileContent
end

92
src/requests/register.cr Normal file
View File

@ -0,0 +1,92 @@
class AuthD::Request
IPC::JSON.message Register, 6 do
property login : String
property password : String
property email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
if ! authd.configuration.registrations
return Response::Error.new "registrations not allowed"
end
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
activation_url = authd.configuration.activation_url
if activation_url.nil?
# In this case we should not accept its registration.
return Response::Error.new "No activation URL were entered. Cannot send activation mails."
end
if ! @email.nil?
# Test on the email address format.
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
result = grok.parse @email.not_nil!
email = result["email"]?
if email.nil?
return Response::Error.new "invalid email format"
end
end
# In this case we should not accept its registration.
if @password.size < 4
return Response::Error.new "password too short"
end
uid = authd.new_uid
password = authd.hash_password @password
user = User.new uid, @login, password
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
user.date_registration = Time.local
begin
field_subject = authd.configuration.field_subject.not_nil!
field_from = authd.configuration.field_from.not_nil!
activation_url = authd.configuration.activation_url.not_nil!
u_login = user.login
u_email = user.contact.email.not_nil!
u_activation_key = user.contact.activation_key.not_nil!
# Once the user is created and stored, we try to contact him
unless Process.run("activation-mailer", [
"-l", u_login,
"-e", u_email,
"-t", field_subject,
"-f", field_from,
"-u", activation_url,
"-a", u_activation_key
]).success?
raise "cannot contact user #{user.login} address #{user.contact.email}"
end
rescue e
Baguette::Log.error "activation-mailer: #{e}"
return Response::Error.new "cannot contact the user (not registered)"
end
# add the user only if we were able to send the confirmation mail
authd.users << user
Response::UserAdded.new user.to_public
end
end
AuthD.requests << Register
end

View File

@ -1,896 +0,0 @@
class AuthD::Request
IPC::JSON.message GetToken, 0 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
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 @password
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
token = user.to_token
# 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
end
end
AuthD.requests << GetToken
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 email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@shared_key, @login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
# 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
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
password_hash = authd.hash_password @password
uid = authd.new_uid
user = User.new uid, @login, password_hash
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
# We consider adding the user as a registration
user.date_registration = Time.local
authd.users << user
Response::UserAdded.new user.to_public
end
end
AuthD.requests << AddUser
IPC::JSON.message ValidateUser, 2 do
property login : String
property activation_key : String
def initialize(@login, @activation_key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
if user.nil?
return Response::Error.new "user not found"
end
if user.contact.activation_key.nil?
return Response::Error.new "user already validated"
end
# remove the user contact activation key: the email is validated
if user.contact.activation_key == @activation_key
user.contact.activation_key = nil
else
return Response::Error.new "wrong activation key"
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserValidated.new user.to_public
end
end
AuthD.requests << ValidateUser
IPC::JSON.message GetUser, 3 do
property user : Int32 | String
def initialize(@user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "user not found"
end
Response::User.new user.to_public
end
end
AuthD.requests << GetUser
IPC::JSON.message GetUserByCredentials, 4 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
# change the date of the last connection
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << GetUserByCredentials
IPC::JSON.message ModUser, 5 do
property shared_key : String
property user : Int32 | String
property password : String? = nil
property email : String? = nil
property phone : String? = nil
property avatar : String? = nil
def initialize(@shared_key, @user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
if @shared_key != authd.configuration.shared_key
return Response::Error.new "invalid authentication key"
end
uid_or_login = @user
user = 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
end
unless user
return Response::Error.new "user not found"
end
@password.try do |s|
user.password_hash = authd.hash_password s
end
@email.try do |email|
user.contact.email = email
end
@phone.try do |phone|
user.contact.phone = phone
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << ModUser
IPC::JSON.message Register, 6 do
property login : String
property password : String
property email : String? = nil
property phone : String? = nil
property profile : Hash(String, JSON::Any)? = nil
def initialize(@login, @password, @email, @phone, @profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
if ! authd.configuration.registrations
return Response::Error.new "registrations not allowed"
end
if authd.users_per_login.get? @login
return Response::Error.new "login already used"
end
if authd.configuration.require_email && @email.nil?
return Response::Error.new "email required"
end
activation_url = authd.configuration.activation_url
if activation_url.nil?
# In this case we should not accept its registration.
return Response::Error.new "No activation URL were entered. Cannot send activation mails."
end
if ! @email.nil?
# Test on the email address format.
grok = Grok.new [ "%{EMAILADDRESS:email}" ]
result = grok.parse @email.not_nil!
email = result["email"]?
if email.nil?
return Response::Error.new "invalid email format"
end
end
# In this case we should not accept its registration.
if @password.size < 4
return Response::Error.new "password too short"
end
uid = authd.new_uid
password = authd.hash_password @password
user = User.new uid, @login, password
user.contact.email = @email unless @email.nil?
user.contact.phone = @phone unless @phone.nil?
@profile.try do |profile|
user.profile = profile
end
user.date_registration = Time.local
begin
field_subject = authd.configuration.field_subject.not_nil!
field_from = authd.configuration.field_from.not_nil!
activation_url = authd.configuration.activation_url.not_nil!
u_login = user.login
u_email = user.contact.email.not_nil!
u_activation_key = user.contact.activation_key.not_nil!
# Once the user is created and stored, we try to contact him
unless Process.run("activation-mailer", [
"-l", u_login,
"-e", u_email,
"-t", field_subject,
"-f", field_from,
"-u", activation_url,
"-a", u_activation_key
]).success?
raise "cannot contact user #{user.login} address #{user.contact.email}"
end
rescue e
Baguette::Log.error "activation-mailer: #{e}"
return Response::Error.new "cannot contact the user (not registered)"
end
# add the user only if we were able to send the confirmation mail
authd.users << user
Response::UserAdded.new user.to_public
end
end
AuthD.requests << Register
IPC::JSON.message UpdatePassword, 7 do
property login : String
property old_password : String
property new_password : String
def initialize(@login, @old_password, @new_password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@old_password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.password_hash = authd.hash_password @new_password
authd.users_per_uid.update user.uid.to_s, user
Response::UserEdited.new user.uid
end
end
AuthD.requests << UpdatePassword
IPC::JSON.message ListUsers, 8 do
property token : String? = nil
property key : String? = nil
def initialize(@token, @key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
# FIXME: Lines too long, repeatedly (>80c with 4c tabs).
@token.try do |token|
user = authd.get_user_from_token token
return Response::Error.new "unauthorized (user not found from token)"
return Response::Error.new "unauthorized (user not in authd group)" unless user.permissions["authd"]?.try(&.["*"].>=(User::PermissionLevel::Read))
end
@key.try do |key|
return Response::Error.new "unauthorized (wrong shared key)" unless key == authd.configuration.shared_key
end
return Response::Error.new "unauthorized (no key nor token)" unless @key || @token
Response::UsersList.new authd.users.to_h.map &.[1].to_public
end
end
AuthD.requests << ListUsers
IPC::JSON.message CheckPermission, 9 do
property shared_key : String? = nil
property token : String? = nil
property user : Int32 | String
property service : String
property resource : String
def initialize(@shared_key, @user, @service, @resource)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
authorized = false
if key = @shared_key
if key == authd.configuration.shared_key
authorized = true
else
return Response::Error.new "invalid key provided"
end
end
if token = @token
user = authd.get_user_from_token token
if user.nil?
return Response::Error.new "token does not match user"
end
if user.login != @user && user.uid != @user
return Response::Error.new "token does not match user"
end
authorized = true
end
unless authorized
return Response::Error.new "unauthorized"
end
user = case u = @user
when .is_a? Int32
authd.users_per_uid.get? u.to_s
else
authd.users_per_login.get? u
end
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
resource_permissions = service_permissions[@resource]?
if resource_permissions.nil?
return Response::PermissionCheck.new service, @resource, user.uid, User::PermissionLevel::None
end
return Response::PermissionCheck.new service, @resource, user.uid, resource_permissions
end
end
AuthD.requests << CheckPermission
IPC::JSON.message SetPermission, 10 do
property shared_key : String
property user : Int32 | String
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@shared_key, @user, @service, @resource, @permission)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
unless @shared_key == authd.configuration.shared_key
return Response::Error.new "unauthorized"
end
user = authd.users_per_uid.get? @user.to_s
if user.nil?
return Response::Error.new "no such user"
end
service = @service
service_permissions = user.permissions[service]?
if service_permissions.nil?
service_permissions = Hash(String, User::PermissionLevel).new
user.permissions[service] = service_permissions
end
if @permission.none?
service_permissions.delete @resource
else
service_permissions[@resource] = @permission
end
authd.users_per_uid.update user.uid.to_s, user
Response::PermissionSet.new user.uid, service, @resource, @permission
end
end
AuthD.requests << SetPermission
IPC::JSON.message PasswordRecovery, 11 do
property user : Int32 | String
property password_renew_key : String
property new_password : String
def initialize(@user, @password_renew_key, @new_password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "user not found"
end
if user.password_renew_key == @password_renew_key
user.password_hash = authd.hash_password @new_password
else
return Response::Error.new "renew key not valid"
end
user.password_renew_key = nil
authd.users_per_uid.update user.uid.to_s, user
Response::PasswordRecovered.new user.to_public
end
end
AuthD.requests << PasswordRecovery
IPC::JSON.message AskPasswordRecovery, 12 do
property user : Int32 | String
property email : String
def initialize(@user, @email)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "no such user"
end
if user.contact.email != @email
# Same error as when users are not found.
return Response::Error.new "no such user"
end
user.password_renew_key = UUID.random.to_s
authd.users_per_uid.update user.uid.to_s, user
unless (activation_url = authd.configuration.activation_url).nil?
field_from = authd.configuration.field_from.not_nil!
activation_url = authd.configuration.activation_url.not_nil!
# Once the user is created and stored, we try to contact him
if authd.configuration.print_password_recovery_parameters
pp! user.login,
user.contact.email.not_nil!,
field_from,
activation_url,
user.password_renew_key.not_nil!
end
unless Process.run("password-recovery-mailer", [
"-l", user.login,
"-e", user.contact.email.not_nil!,
"-t", "Password recovery email",
"-f", field_from,
"-u", activation_url,
"-a", user.password_renew_key.not_nil!
]).success?
return Response::Error.new "cannot contact the user for password recovery"
end
end
Response::PasswordRecoverySent.new user.to_public
end
end
AuthD.requests << AskPasswordRecovery
IPC::JSON.message SearchUser, 13 do
property user : String
def initialize(@user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
pattern = Regex.new @user, Regex::Options::IGNORE_CASE
matching_users = Array(AuthD::User::Public).new
users = authd.users.to_a
users.each do |u|
if pattern =~ u.login || u.profile.try do |profile|
full_name = profile["full_name"]?
if full_name.nil?
false
else
pattern =~ full_name.as_s
end
end
Baguette::Log.debug "#{u.login} matches #{pattern}"
matching_users << u.to_public
else
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
end
end
Response::MatchingUsers.new matching_users
end
end
AuthD.requests << SearchUser
IPC::JSON.message EditProfile, 14 do
property token : String
property new_profile : Hash(String, JSON::Any)
def initialize(@token, @new_profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
new_profile = @new_profile
profile = user.profile || Hash(String, JSON::Any).new
authd.configuration.read_only_profile_keys.each do |key|
if new_profile[key]? != profile[key]?
return Response::Error.new "tried to edit read only key"
end
end
user.profile = new_profile
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << EditProfile
# Same as above, but doesnt reset the whole profile, only resets elements
# for which keys are present in `new_profile`.
IPC::JSON.message EditProfileContent, 15 do
property token : String? = nil
property shared_key : String? = nil
property user : Int32 | String | Nil
property new_profile : Hash(String, JSON::Any)
def initialize(@shared_key, @user, @new_profile)
end
def initialize(@token, @new_profile)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = if token = @token
u = authd.get_user_from_token token
raise UserNotFound.new unless u
u
elsif shared_key = @shared_key
raise AdminAuthorizationException.new if shared_key != authd.configuration.shared_key
u = @user
raise UserNotFound.new unless u
u = if u.is_a? Int32
authd.users_per_uid.get? u.to_s
else
authd.users_per_login.get? u
end
raise UserNotFound.new unless u
u
else
raise AuthenticationInfoLacking.new
end
new_profile = user.profile || Hash(String, JSON::Any).new
unless @shared_key
authd.configuration.read_only_profile_keys.each do |key|
if @new_profile.has_key? key
return Response::Error.new "tried to edit read only key"
end
end
end
@new_profile.each do |key, value|
new_profile[key] = value
end
user.profile = new_profile
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << EditProfileContent
IPC::JSON.message EditContacts, 16 do
property token : String
property email : String? = nil
property phone : String? = nil
def initialize(@token)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
if email = @email
# FIXME: This *should* require checking the new mail, with
# a new activation key and everything else.
user.contact.email = email
end
authd.users_per_uid.update user
Response::UserEdited.new user.uid
end
end
AuthD.requests << EditContacts
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
property user : String | Int32
def initialize(@user, @login, @password)
end
def initialize(@user, @shared_key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
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
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
return Response::Error.new "invalid credentials"
end
end
# User or admin is now verified: let's proceed with the user deletion.
authd.users_per_login.delete user_to_delete.login
# TODO: better response
Response::User.new user_to_delete.to_public
end
end
AuthD.requests << Delete
IPC::JSON.message GetContacts, 18 do
property token : String
def initialize(@token)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.get_user_from_token @token
return Response::Error.new "invalid user" unless user
_c = user.contact
Response::Contacts.new user.uid, _c.email, _c.phone
end
end
AuthD.requests << GetContacts
end
class AuthD::Response
IPC::JSON.message Error, 0 do
property reason : String? = nil
def initialize(@reason)
end
end
IPC::JSON.message Token, 1 do
property uid : Int32
property token : String
def initialize(@token, @uid)
end
end
IPC::JSON.message User, 2 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UserAdded, 3 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UserEdited, 4 do
property uid : Int32
def initialize(@uid)
end
end
IPC::JSON.message UserValidated, 5 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UsersList, 6 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
IPC::JSON.message PermissionCheck, 7 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@service, @resource, @user, @permission)
end
end
IPC::JSON.message PermissionSet, 8 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@user, @service, @resource, @permission)
end
end
IPC::JSON.message PasswordRecoverySent, 9 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message PasswordRecovered, 10 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message MatchingUsers, 11 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
IPC::JSON.message Contacts, 12 do
property user : Int32
property email : String? = nil
property phone : String? = nil
def initialize(@user, @email, @phone)
end
end
end

34
src/requests/search.cr Normal file
View File

@ -0,0 +1,34 @@
class AuthD::Request
IPC::JSON.message SearchUser, 13 do
property user : String
def initialize(@user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
pattern = Regex.new @user, Regex::Options::IGNORE_CASE
matching_users = Array(AuthD::User::Public).new
users = authd.users.to_a
users.each do |u|
if pattern =~ u.login || u.profile.try do |profile|
full_name = profile["full_name"]?
if full_name.nil?
false
else
pattern =~ full_name.as_s
end
end
Baguette::Log.debug "#{u.login} matches #{pattern}"
matching_users << u.to_public
else
Baguette::Log.debug "#{u.login} doesn't match #{pattern}"
end
end
Response::MatchingUsers.new matching_users
end
end
AuthD.requests << SearchUser
end

34
src/requests/token.cr Normal file
View File

@ -0,0 +1,34 @@
class AuthD::Request
IPC::JSON.message GetToken, 0 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
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 @password
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
token = user.to_token
# 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
end
end
AuthD.requests << GetToken
end

84
src/requests/users.cr Normal file
View File

@ -0,0 +1,84 @@
class AuthD::Request
IPC::JSON.message ValidateUser, 2 do
property login : String
property activation_key : String
def initialize(@login, @activation_key)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
if user.nil?
return Response::Error.new "user not found"
end
if user.contact.activation_key.nil?
return Response::Error.new "user already validated"
end
# remove the user contact activation key: the email is validated
if user.contact.activation_key == @activation_key
user.contact.activation_key = nil
else
return Response::Error.new "wrong activation key"
end
authd.users_per_uid.update user.uid.to_s, user
Response::UserValidated.new user.to_public
end
end
AuthD.requests << ValidateUser
IPC::JSON.message GetUser, 3 do
property user : Int32 | String
def initialize(@user)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
uid_or_login = @user
user = 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
end
if user.nil?
return Response::Error.new "user not found"
end
Response::User.new user.to_public
end
end
AuthD.requests << GetUser
IPC::JSON.message GetUserByCredentials, 4 do
property login : String
property password : String
def initialize(@login, @password)
end
def handle(authd : AuthD::Service, event : IPC::Event::Events)
user = authd.users_per_login.get? @login
unless user
return Response::Error.new "invalid credentials"
end
if authd.hash_password(@password) != user.password_hash
return Response::Error.new "invalid credentials"
end
user.date_last_connection = Time.local
# change the date of the last connection
authd.users_per_uid.update user.uid.to_s, user
Response::User.new user.to_public
end
end
AuthD.requests << GetUserByCredentials
end

9
src/responses/contact.cr Normal file
View File

@ -0,0 +1,9 @@
class AuthD::Response
IPC::JSON.message Contacts, 12 do
property user : Int32
property email : String? = nil
property phone : String? = nil
def initialize(@user, @email, @phone)
end
end
end

7
src/responses/errors.cr Normal file
View File

@ -0,0 +1,7 @@
class AuthD::Response
IPC::JSON.message Error, 0 do
property reason : String? = nil
def initialize(@reason)
end
end
end

13
src/responses/password.cr Normal file
View File

@ -0,0 +1,13 @@
class AuthD::Response
IPC::JSON.message PasswordRecoverySent, 9 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message PasswordRecovered, 10 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
end

View File

@ -0,0 +1,19 @@
class AuthD::Response
IPC::JSON.message PermissionCheck, 7 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@service, @resource, @user, @permission)
end
end
IPC::JSON.message PermissionSet, 8 do
property user : Int32
property service : String
property resource : String
property permission : ::AuthD::User::PermissionLevel
def initialize(@user, @service, @resource, @permission)
end
end
end

8
src/responses/token.cr Normal file
View File

@ -0,0 +1,8 @@
class AuthD::Response
IPC::JSON.message Token, 1 do
property uid : Int32
property token : String
def initialize(@token, @uid)
end
end
end

37
src/responses/users.cr Normal file
View File

@ -0,0 +1,37 @@
class AuthD::Response
IPC::JSON.message User, 2 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UserAdded, 3 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UserEdited, 4 do
property uid : Int32
def initialize(@uid)
end
end
IPC::JSON.message UserValidated, 5 do
property user : ::AuthD::User::Public
def initialize(@user)
end
end
IPC::JSON.message UsersList, 6 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
IPC::JSON.message MatchingUsers, 11 do
property users : Array(::AuthD::User::Public)
def initialize(@users)
end
end
end