298 lines
8.1 KiB
Ruby
298 lines
8.1 KiB
Ruby
require 'nostr'
|
|
|
|
class User < ApplicationRecord
|
|
include EmailValidatable
|
|
|
|
attr_accessor :current_password
|
|
attr_accessor :display_name
|
|
attr_accessor :avatar_new
|
|
attr_accessor :pgp_pubkey
|
|
|
|
serialize :preferences, coder: UserPreferences
|
|
|
|
#
|
|
# Relations
|
|
#
|
|
|
|
has_many :invitations, dependent: :destroy
|
|
has_one :invitation, inverse_of: :invitee, foreign_key: 'invited_user_id'
|
|
has_one :inviter, through: :invitation, source: :user
|
|
has_many :invitees, through: :invitations
|
|
has_many :donations, dependent: :nullify
|
|
has_many :remote_storage_authorizations
|
|
has_many :zaps
|
|
|
|
has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user",
|
|
primary_key: "lndhub_username", foreign_key: "login"
|
|
|
|
has_many :accounts, through: :lndhub_user
|
|
|
|
#
|
|
# Attachments
|
|
#
|
|
|
|
has_one_attached :avatar
|
|
|
|
#
|
|
# Validations
|
|
#
|
|
|
|
validates_uniqueness_of :cn, scope: :ou
|
|
validates_length_of :cn, minimum: 3
|
|
validates_format_of :cn, with: /\A([a-z0-9\-])*\z/,
|
|
if: Proc.new{ |u| u.cn.present? },
|
|
message: "is invalid. Please use only letters, numbers and -"
|
|
validates_format_of :cn, without: /\A-/,
|
|
if: Proc.new{ |u| u.cn.present? },
|
|
message: "is invalid. Usernames need to start with a letter."
|
|
# FIXME This needs a server restart to apply values
|
|
validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i,
|
|
message: "has already been taken",
|
|
unless: Proc.new{ |u| u.persisted? }
|
|
|
|
validates_uniqueness_of :email
|
|
validates :email, email: true
|
|
|
|
validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true,
|
|
if: -> { defined?(@display_name) }
|
|
|
|
|
|
validate :acceptable_avatar
|
|
|
|
validate :acceptable_pgp_key_format, if: -> { defined?(@pgp_pubkey) && @pgp_pubkey.present? }
|
|
|
|
#
|
|
# Scopes
|
|
#
|
|
|
|
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
|
scope :pending, -> { where(confirmed_at: nil) }
|
|
scope :all_except, -> (user) { where.not(id: user) }
|
|
|
|
#
|
|
# Encrypted database columns
|
|
#
|
|
|
|
encrypts :lndhub_password
|
|
|
|
# Include default devise modules. Others available are:
|
|
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
|
devise :ldap_authenticatable,
|
|
:confirmable,
|
|
:recoverable,
|
|
:validatable,
|
|
:timeoutable,
|
|
:rememberable
|
|
|
|
#
|
|
# Methods
|
|
#
|
|
|
|
def ldap_before_save
|
|
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
|
|
self.ou = dn.split(',')
|
|
.select{|e| e[0..1] == "ou"}.first
|
|
.delete_prefix("ou=")
|
|
|
|
if self.confirmed_at.blank? && self.confirmation_token.blank?
|
|
# User had an account with a trusted email address before akkounts was a thing
|
|
self.confirmed_at = DateTime.now
|
|
end
|
|
end
|
|
|
|
def devise_after_confirmation
|
|
if ldap_entry[:mail] != self.email
|
|
# E-Mail update confirmed
|
|
LdapManager::UpdateEmail.call(dn: self.dn, address: self.email)
|
|
else
|
|
# E-Mail from signup confirmed (i.e. account activation)
|
|
enable_default_services
|
|
|
|
# TODO enable in development when we have easy setup of ejabberd etc.
|
|
return if Rails.env.development? || !Setting.ejabberd_enabled?
|
|
|
|
XmppExchangeContactsJob.perform_later(inviter, self) if inviter.present?
|
|
XmppSetDefaultBookmarksJob.perform_later(self)
|
|
end
|
|
end
|
|
|
|
def send_devise_notification(notification, *args)
|
|
devise_mailer.send(notification, self, *args).deliver_later
|
|
end
|
|
|
|
def reset_password(new_password, new_password_confirmation)
|
|
self.password = new_password
|
|
self.password_confirmation = new_password_confirmation
|
|
return false unless valid?
|
|
|
|
Devise::LDAP::Adapter.update_password(login_with, new_password)
|
|
clear_reset_password_token
|
|
save
|
|
end
|
|
|
|
def is_admin?
|
|
@admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin)
|
|
!!admin.first
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def address
|
|
"#{self.cn}@#{self.ou}"
|
|
end
|
|
|
|
def mastodon_address
|
|
return nil unless Setting.mastodon_enabled?
|
|
"#{self.cn.gsub("-", "_")}@#{Setting.mastodon_address_domain}"
|
|
end
|
|
|
|
def valid_attribute?(attribute_name)
|
|
self.valid?
|
|
self.errors[attribute_name].blank?
|
|
end
|
|
|
|
def enable_default_services
|
|
enable_service Setting.default_services
|
|
end
|
|
|
|
def dn
|
|
return @dn if defined?(@dn)
|
|
@dn = Devise::LDAP::Adapter.get_dn(self.cn)
|
|
end
|
|
|
|
def ldap_entry(reload: false)
|
|
return @ldap_entry if defined?(@ldap_entry) && !reload
|
|
@ldap_entry = ldap.fetch_users(cn: self.cn).first
|
|
end
|
|
|
|
def add_to_ldap_array(attr_key, ldap_attr, value)
|
|
current_entries = ldap_entry[attr_key.to_sym] || []
|
|
new_entries = Array(value).map(&:to_s)
|
|
entries = (current_entries + new_entries).uniq.sort
|
|
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
|
|
end
|
|
|
|
def remove_from_ldap_array(attr_key, ldap_attr, value)
|
|
current_entries = ldap_entry[attr_key.to_sym] || []
|
|
entries_to_remove = Array(value).map(&:to_s)
|
|
entries = (current_entries - entries_to_remove).uniq.sort
|
|
ldap.replace_attribute(dn, ldap_attr.to_sym, entries)
|
|
end
|
|
|
|
def display_name
|
|
@display_name ||= ldap_entry[:display_name]
|
|
end
|
|
|
|
# TODO Variant keys are currently broken for some reason
|
|
# (They use the same key as the main blob, when it should be
|
|
# "/variants/#{key)"
|
|
# def avatar_variant(size: :medium)
|
|
# dimensions = case size
|
|
# when :large then [400, 400]
|
|
# when :medium then [256, 256]
|
|
# when :small then [64, 64]
|
|
# else [256, 256]
|
|
# end
|
|
# format = avatar.content_type == "image/png" ? :png : :jpeg
|
|
# avatar.variant(resize_to_fill: dimensions, format: format)
|
|
# end
|
|
|
|
def nostr_pubkey
|
|
@nostr_pubkey ||= ldap_entry[:nostr_key]
|
|
end
|
|
|
|
def nostr_pubkey_bech32
|
|
return nil unless nostr_pubkey.present?
|
|
Nostr::PublicKey.new(nostr_pubkey).to_bech32
|
|
end
|
|
|
|
def pgp_pubkey
|
|
@pgp_pubkey ||= ldap_entry[:pgp_key]
|
|
end
|
|
|
|
def gnupg_key
|
|
return nil unless pgp_pubkey.present?
|
|
GPGME::Key.import(pgp_pubkey)
|
|
GPGME::Key.get(pgp_fpr)
|
|
end
|
|
|
|
def pgp_pubkey_contains_user_address?
|
|
gnupg_key.uids.map(&:email).include?(address)
|
|
end
|
|
|
|
def wkd_hash
|
|
ZBase32.encode(Digest::SHA1.digest(cn))
|
|
end
|
|
|
|
def services_enabled
|
|
ldap_entry[:services_enabled] || []
|
|
end
|
|
|
|
def service_enabled?(name)
|
|
services_enabled.map(&:to_sym).include?(name.to_sym)
|
|
end
|
|
|
|
def enable_service(service)
|
|
add_to_ldap_array :services_enabled, :serviceEnabled, service
|
|
ldap_entry(reload: true)[:services_enabled]
|
|
end
|
|
|
|
def disable_service(service)
|
|
remove_from_ldap_array :services_enabled, :serviceEnabled, service
|
|
ldap_entry(reload: true)[:services_enabled]
|
|
end
|
|
|
|
def disable_all_services
|
|
ldap.delete_attribute(dn, :serviceEnabled)
|
|
end
|
|
|
|
def member_status
|
|
ldap_entry[:member_status] || []
|
|
end
|
|
|
|
def add_member_status(status)
|
|
add_to_ldap_array :member_status, :memberStatus, status
|
|
ldap_entry(reload: true)[:member_status]
|
|
end
|
|
|
|
def remove_member_status(status)
|
|
remove_from_ldap_array :member_status, :memberStatus, status
|
|
ldap_entry(reload: true)[:member_status]
|
|
end
|
|
|
|
def is_contributing_member?
|
|
member_status.map(&:to_sym).include?(:contributor)
|
|
end
|
|
|
|
def is_paying_member?
|
|
member_status.map(&:to_sym).include?(:sustainer)
|
|
end
|
|
|
|
private
|
|
|
|
def ldap
|
|
return @ldap_service if defined?(@ldap_service)
|
|
@ldap_service = LdapService.new
|
|
end
|
|
|
|
def acceptable_avatar
|
|
return unless avatar_new.present?
|
|
|
|
if avatar_new.size > 1.megabyte
|
|
errors.add(:avatar, "must be less than 1MB file size")
|
|
end
|
|
|
|
acceptable_types = ["image/jpeg", "image/png"]
|
|
unless acceptable_types.include?(avatar_new.content_type)
|
|
errors.add(:avatar, "must be a JPEG or PNG file")
|
|
end
|
|
end
|
|
|
|
def acceptable_pgp_key_format
|
|
unless GPGME::Key.valid?(pgp_pubkey)
|
|
errors.add(:pgp_pubkey, 'is not a valid armored PGP public key block')
|
|
end
|
|
end
|
|
end
|