cartodb/app/models/carto/oauth_app.rb
2020-06-15 10:58:47 +08:00

117 lines
3.9 KiB
Ruby

require_dependency 'carto/helpers/url_validator'
module Carto
class OauthApp < ActiveRecord::Base
# Multiple of 3 for pretty base64
CLIENT_ID_RANDOM_BYTES = 9
CLIENT_SECRET_RANDOM_BYTES = 18
belongs_to :user, inverse_of: :oauth_apps
has_many :oauth_app_users, inverse_of: :oauth_app, dependent: :destroy
has_many :oauth_app_organizations, inverse_of: :oauth_app, dependent: :destroy
validates :user, presence: true, if: -> { sync_with_central? || !central_enabled? }
validates :name, presence: true
validates :website_url, presence: true, url: true
validates :client_id, presence: true
validates :client_secret, presence: true
validates :redirect_uris, presence: true
validates :icon_url, url: true, if: -> { icon_url.present? }
validates :oauth_app_organizations, absence: true, unless: :restricted?
validate :validate_uris
before_validation :ensure_keys_generated
after_create :create_central, if: :sync_with_central?
after_update :update_central, if: :sync_with_central?
after_destroy :delete_central, if: :sync_with_central?
before_destroy :collect_users, unless: :avoid_send_notification, prepend: true
after_destroy :send_app_removal_notification, unless: :avoid_send_notification
ALLOWED_SYNC_ATTRIBUTES = %i[id name client_id client_secret redirect_uris
icon_url restricted description website_url].freeze
attr_accessor :avoid_sync_central, :avoid_send_notification
# this should be exactly the same method as in Central
# mostly used for testing the Superadmin API
def api_attributes
attributes.symbolize_keys.slice(*ALLOWED_SYNC_ATTRIBUTES).merge(user_id: user.id)
end
def regenerate_client_secret!
self.client_secret = SecureRandom.urlsafe_base64(CLIENT_SECRET_RANDOM_BYTES)
save!
end
private
def collect_users
@user_ids = oauth_app_users.collect { |u| u.user.id }
end
def send_app_removal_notification
return if @user_ids.empty?
notification = Carto::Notification.create!(body: notification_body, icon: Notification::ICON_ALERT)
::Resque.enqueue(::Resque::UserJobs::Notifications::Send, @user_ids, notification.id)
rescue StandardError => e
CartoDB::Logger.warning(
message: "Couldn't notify users about oauth_app '#{name}' deletion",
notification_id: notification&.id,
exception: e)
raise e
end
def notification_body
"The app #{name} has signed off. You may find more information in their [website](#{website_url})."
end
def ensure_keys_generated
self.client_id ||= SecureRandom.urlsafe_base64(CLIENT_ID_RANDOM_BYTES)
self.client_secret ||= SecureRandom.urlsafe_base64(CLIENT_SECRET_RANDOM_BYTES)
end
def validate_uris
redirect_uris && redirect_uris.each { |uri| validate_uri(uri) }
end
def validate_uri(redirect_uri)
uri = URI.parse(redirect_uri)
errors.add(:redirect_uris, "must be absolute") unless uri.absolute?
errors.add(:redirect_uris, "must be https") unless uri.scheme == 'https'
errors.add(:redirect_uris, "must not contain a fragment") unless uri.fragment.nil?
rescue URI::InvalidURIError
errors.add(:redirect_uris, "must be valid")
end
def create_central
cartodb_central_client.create_oauth_app(user.username, sync_attributes)
end
def update_central
cartodb_central_client.update_oauth_app(user.username, id, sync_attributes)
end
def delete_central
cartodb_central_client.delete_oauth_app(user.username, id)
end
def cartodb_central_client
@cartodb_central_client ||= Cartodb::Central.new
end
def sync_with_central?
central_enabled? && !avoid_sync_central
end
def central_enabled?
Cartodb::Central.sync_data_with_cartodb_central?
end
def sync_attributes(attrs = attributes)
attrs.symbolize_keys.slice(*ALLOWED_SYNC_ATTRIBUTES)
end
end
end