class RemoteStorageAuthorization < ApplicationRecord belongs_to :user belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true serialize :permissions, coder: YAML unless Rails.env.production? validates_presence_of :permissions validates_presence_of :client_id scope :valid, -> { where(expire_at: nil).or(where(expire_at: (DateTime.now)..)) } scope :expired, -> { where(expire_at: ..(DateTime.now)) } after_initialize do |a| a.permissions = [] if a.permissions == nil end before_create :generate_token before_create :store_token_in_redis before_create :find_or_create_web_app after_create :schedule_token_expiry after_create :notify_user before_destroy :delete_token_from_redis after_destroy :remove_token_expiry_job def url uri = URI.parse self.redirect_uri "#{uri.scheme}://#{client_id}" end def launch_url return url unless web_app && web_app.metadata[:start_url].present? start_url = web_app.metadata[:start_url] if start_url.match("^https?:\/\/") return start_url.start_with?(url) ? start_url : url else path = start_url.gsub(/^\.\.\//, "").gsub(/^\.\//, "").gsub(/^\//, "") "#{url}/#{path}" end end def delete_token_from_redis key = "authorizations:#{user.cn}:#{token}" redis.srem? key, redis.smembers(key) rescue => e Rails.logger.error e Sentry.capture_exception(e) if Setting.sentry_enabled? end private def redis @redis ||= Redis.new(url: Setting.rs_redis_url) end def generate_token(length=16) self.token = SecureRandom.hex(length) if self.token.blank? end def store_token_in_redis redis.sadd "authorizations:#{user.cn}:#{token}", permissions end def schedule_token_expiry return unless expire_at.present? RemoteStorageExpireAuthorizationJob.set(wait_until: expire_at) .perform_later(id) end def remove_token_expiry_job job_class = RemoteStorageExpireAuthorizationJob job_args = [id] query = SolidQueue::Job.where(class_name: job_class.to_s) case ActiveRecord::Base.connection.adapter_name.downcase when /sqlite/ query.where("json_extract(arguments, '$.arguments') = ?", job_args.to_json) when /postgres/ query.where("CAST(arguments AS jsonb)->>'arguments' = ?", job_args.to_json) else raise "Unsupported database adapter" end.destroy_all end def find_or_create_web_app if looks_like_hosted_origin? web_app = AppCatalog::WebApp.find_or_create_by!(url: self.url) web_app.update_metadata unless web_app.name.present? self.web_app = web_app self.app_name = web_app.name.presence || client_id else self.app_name = client_id end end def looks_like_hosted_origin? uri = URI.parse self.redirect_uri !!(uri.host =~ /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/) rescue URI::InvalidURIError false end def notify_user notify = user.preferences[:remotestorage_notify_auth_created] case notify when "xmpp" router = Router.new payload = { type: "normal", to: user.address, from: Setting.xmpp_notifications_from_address, body: "You have just granted '#{self.client_id}' access to your Kosmos Storage. Visit your Storage dashboard to check on your connected apps and revoke permissions anytime: #{router.services_storage_url}" } XmppSendMessageJob.perform_later(payload) when "email" NotificationMailer.with(user: user, auth: self) .remotestorage_auth_created.deliver_later end end end