Replace the hand-rolled user-agent regexes with the device_detector gem. Its UA database recognises many more clients (and, e.g., GNOME Web when its UA carries the Epiphany token). WebKit "Safari" user agents on Linux (WebKitGTK, which GNOME Web sends) are labelled "WebKit on Linux", since Safari never runs on Linux. Part of #254
143 lines
4.2 KiB
Ruby
143 lines
4.2 KiB
Ruby
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 expired?
|
|
expire_at.present? && expire_at.past?
|
|
end
|
|
|
|
def device_name
|
|
return nil if user_agent.blank?
|
|
|
|
client = DeviceDetector.new(user_agent)
|
|
name = client.name.presence
|
|
os = client.os_name.presence
|
|
os = "Linux" if os == "GNU/Linux"
|
|
os = "macOS" if os == "Mac"
|
|
|
|
# Safari never runs on Linux. A WebKit "Safari" user agent on Linux is a
|
|
# WebKitGTK browser (e.g. GNOME Web), which sends a Safari-compatible UA.
|
|
name = "WebKit" if name == "Safari" && os == "Linux"
|
|
|
|
[name, os].compact.join(" on ").presence
|
|
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
|