117 lines
3.9 KiB
Ruby
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
|