Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate and delete ClientApplication Sequel model #15886

Merged
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Development
* Load config files as ERB templates to allow reading ENV values ([15881](https://github.com/CartoDB/cartodb/pull/15881))

### Bug fixes / enhancements
- Add DO geography key variables [#15882](https://github.com/CartoDB/cartodb/pull/15882)
* Add DO geography key variables [#15882](https://github.com/CartoDB/cartodb/pull/15882)
* Migrate `ClientApplication` model to `ActiveRecord` [#15886](https://github.com/CartoDB/cartodb/pull/15886)

4.42.0 (2020-09-28)
-------------------
Expand Down
1 change: 1 addition & 0 deletions app/controllers/admin/client_applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def regenerate_api_key
def regenerate_oauth
@client_application = current_user.client_application
return if request.get?

current_user.reset_client_application!

redirect_to CartoDB.url(self, 'oauth_credentials', params: { type: 'oauth' }, user: current_user),
Expand Down
38 changes: 38 additions & 0 deletions app/models/carto/client_application.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
require 'active_record'
require 'oauth'
require_dependency 'cartodb_config_utils'
require_dependency 'carto/domain_patcher_request_proxy'

module Carto
class ClientApplication < ActiveRecord::Base

extend CartoDB::ConfigUtils

belongs_to :user, class_name: Carto::User
has_many :oauth_tokens, class_name: Carto::OauthToken, dependent: :destroy
has_many :access_tokens, -> { where(type: 'AccessToken') }, class_name: Carto::OauthToken, dependent: :destroy

before_create :initialize_entity

def oauth_server
# check if this is used
@oauth_server ||= OAuth::Server.new('http://your.site')
end

def self.find_token(token_key)
return nil if token_key.nil?

token = Carto::RequestToken.find_by(token: token_key) || Carto::AccessToken.find_by(token: token_key)
token&.authorized? ? token : nil
end

def self.verify_request(request, options = {}, &block)
value = OAuth::Signature.build(request, options, &block).verify
if !value && !cartodb_com_hosted?
# Validation failed, try to see if it has been signed for cartodb.com
cartodb_request = Carto::DomainPatcherRequestProxy.new(request, options)
value = OAuth::Signature.build(cartodb_request, options, &block).verify
end
value
rescue OAuth::Signature::UnknownSignatureMethod
false
end

private

def initialize_entity
self.key = OAuth::Helper.generate_key(40)[0, 40]
self.secret = OAuth::Helper.generate_key(40)[0, 40]
end

end
end
10 changes: 10 additions & 0 deletions app/models/carto/helpers/user_commons.rb
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,14 @@ def active_record_organization
end
end
end

def new_client_application
Carto::ClientApplication.create!(user_id: id)
end

def reset_client_application!
client_application&.destroy
Carto::ClientApplication.create!(user_id: id)
end

end
7 changes: 1 addition & 6 deletions app/models/carto/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Carto::User < ActiveRecord::Base
has_many :permissions, inverse_of: :owner, foreign_key: :owner_id
has_many :connector_configurations, inverse_of: :user, dependent: :destroy

has_many :client_applications, class_name: Carto::ClientApplication, dependent: :destroy
has_one :client_application, class_name: Carto::ClientApplication, dependent: :destroy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this 🎉

has_many :tokens, class_name: Carto::OauthToken, dependent: :destroy

has_many :users_group, dependent: :destroy, class_name: Carto::UsersGroup
Expand Down Expand Up @@ -95,11 +95,6 @@ def carto_user; self end
after_destroy { rate_limit.destroy_completely(self) if rate_limit }
after_destroy :invalidate_varnish_cache

# Compatibility with ::User, where the association is defined as one_to_one
def client_application
client_applications.first
end

# Auto creates notifications on first access
def static_notifications_with_creation
static_notifications_without_creation || build_static_notifications(user: self, notifications: {})
Expand Down
83 changes: 0 additions & 83 deletions app/models/client_application.rb

This file was deleted.

18 changes: 5 additions & 13 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class User < Sequel::Model

self.strict_param_setting = false

one_to_one :client_application
one_to_many :synchronization_oauths
one_to_many :maps
one_to_many :assets
Expand All @@ -62,7 +61,7 @@ def self_feature_flags
many_through_many :groups, [[:users_groups, :user_id, :group_id]]

# Sequel setup & plugins
plugin :association_dependencies, :client_application => :destroy, :synchronization_oauths => :destroy
plugin :association_dependencies, synchronization_oauths: :destroy
plugin :validation_helpers
plugin :json_serializer
plugin :dirty
Expand Down Expand Up @@ -437,7 +436,7 @@ def before_destroy(skip_table_drop: false)

assign_search_tweets_to_organization_owner

ClientApplication.where(user_id: id).each(&:destroy)
Carto::ClientApplication.where(user_id: id).destroy_all
rescue StandardError => exception
error_happened = true
log_error(message: 'Error destroying user', current_user: self, exception: exception)
Expand Down Expand Up @@ -997,13 +996,6 @@ def get_last_ip_address
$users_metadata.HMGET(key, 'last_ip_address').first
end

def reset_client_application!
if client_application
client_application.destroy
end
ClientApplication.create(:user_id => self.id)
end

def self.find_with_custom_fields(user_id)
::User.filter(:id => user_id).select(:id,:email,:username,:crypted_password,:database_name,:admin).first
end
Expand Down Expand Up @@ -1236,16 +1228,16 @@ def has_organization_enabled?
end
end

def create_client_application
ClientApplication.create(:user_id => self.id)
def client_application
Carto::ClientApplication.find_by(user_id: id)
end

## User's databases setup methods
def setup_user
return if disabled?
db_service.set_database_name

create_client_application
new_client_application
if self.has_organization_enabled?
db_service.new_organization_user_main_db_setup
else
Expand Down
43 changes: 26 additions & 17 deletions app/services/carto/user_metadata_export_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,22 @@ def build_search_tweets_from_hash_export(exported_hash)
end

def save_imported_user(user)
# Keep client_application imported timestamps
if user.client_application
import_app_updated_at = user.client_application.updated_at
import_app_created_at = user.client_application.created_at
end

user.save!
::User[user.id].after_save

client_application = user.client_applications.first
if client_application
client_application.access_tokens.each do |t|
t.update!(type: 'AccessToken')
end
return unless user.client_application

user.client_application.access_tokens.each do |t|
t.update!(type: 'AccessToken')
end

user.client_application.update_columns(created_at: import_app_created_at, updated_at: import_app_updated_at)
end

def save_imported_search_tweet(search_tweet, user)
Expand Down Expand Up @@ -146,7 +153,7 @@ def build_user_from_hash(exported_user)

user.connector_configurations = build_connector_configurations_from_hash(exported_user[:connector_configurations])

user.client_applications = build_client_applications_from_hash(exported_user[:client_application])
user.client_application = build_client_application_from_hash(exported_user[:client_application])

user.oauth_app_users = build_oauth_app_users_from_hash(exported_user[:oauth_app_users])

Expand Down Expand Up @@ -222,23 +229,24 @@ def build_oauth_token_fom_hash(exported_oauth_token)
)
end

def build_client_applications_from_hash(client_app_hash)
return [] unless client_app_hash
def build_client_application_from_hash(client_app_hash)
return unless client_app_hash

client_application = Carto::ClientApplication.new(
client_application = Carto::ClientApplication.create(
name: client_app_hash[:name],
url: client_app_hash[:url],
support_url: client_app_hash[:support_url],
callback_url: client_app_hash[:callback_url],
key: client_app_hash[:key],
secret: client_app_hash[:secret],
created_at: client_app_hash[:created_at],
updated_at: client_app_hash[:updated_at],
oauth_tokens: client_app_hash[:oauth_tokens].map { |t| build_oauth_token_fom_hash(t) },
access_tokens: client_app_hash[:access_tokens].map { |t| build_oauth_token_fom_hash(t) }
access_tokens: client_app_hash[:access_tokens].map { |t| build_oauth_token_fom_hash(t) },
user_id: client_app_hash[:user_id]
)

[client_application]
# Overwrite fields that were created with ORM lifecycle callbacks
client_application.key = client_app_hash[:key]
client_application.secret = client_app_hash[:secret]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of achieving this could be to modify the model callbacks so those attributes are only assigned if not present yet, for example:

def initialize_entity
  self.key = OAuth::Helper.generate_key(40)[0, 40] unless key.present?
  self.secret = OAuth::Helper.generate_key(40)[0, 40] unless secret.present?
end

I don't have a strong opinion on this so choose what you think it's more intuitive to understand.

Maybe created_at is not overwritten by Rails if you pass it as an argument to the model constructor, but for updated_at you'll have to force it anyways.

client_application.created_at = client_app_hash[:created_at]
client_application.updated_at = client_app_hash[:updated_at]
client_application
end

def build_oauth_app_users_from_hash(oauth_app_users)
Expand Down Expand Up @@ -371,7 +379,8 @@ def export_client_application(app)
created_at: app.created_at,
updated_at: app.updated_at,
oauth_tokens: app.oauth_tokens.reject { |t| a_t_tokens.include?(t.token) }.map { |ot| export_oauth_token(ot) },
access_tokens: app.access_tokens.map { |ot| export_oauth_token(ot) }
access_tokens: app.access_tokens.map { |ot| export_oauth_token(ot) },
user_id: app.user_id
}
end

Expand Down
4 changes: 2 additions & 2 deletions config/initializers/warden.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ def authenticate!
# WARNING: The following code is a modified copy of the oauth10_token method from
# oauth-plugin-0.4.0.pre4/lib/oauth/controllers/application_controller_methods.rb
# It also checks token class like does the oauth10_access_token method of that same file
if ClientApplication.verify_request(request) do |request_proxy|
@oauth_token = ClientApplication.find_token(request_proxy.token)
if Carto::ClientApplication.verify_request(request) do |request_proxy|
@oauth_token = Carto::ClientApplication.find_token(request_proxy.token)
if @oauth_token.respond_to?(:provided_oauth_verifier=)
@oauth_token.provided_oauth_verifier=request_proxy.oauth_verifier
end
Expand Down
11 changes: 11 additions & 0 deletions lib/carto/domain_patcher_request_proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'oauth'

module Carto
class DomainPatcherRequestProxy < OAuth::RequestProxy::RackRequest

def uri
super.sub('carto.com', 'cartodb.com')
end

end
end
9 changes: 5 additions & 4 deletions spec/models/carto/helpers/user_migration_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module UserMigrationHelper
{ name: 'user-mover', description: 'insanity' }
]

agg_ds_config =
agg_ds_config =
{
aggregation_tables: {
'host' => 'localhost',
Expand Down Expand Up @@ -62,7 +62,7 @@ module UserMigrationHelper
puts export.log.entries if export.state != Carto::UserMigrationExport::STATE_COMPLETE
expect(export.state).to eq(Carto::UserMigrationExport::STATE_COMPLETE)

@carto_user.client_applications.each(&:destroy)
@carto_user.client_application.destroy
@table1.table_visualization.layers.each(&:destroy)
@table1.destroy
expect { @table1.records }.to raise_error
Expand Down Expand Up @@ -180,7 +180,8 @@ def import
end

def destroy_user
@carto_user.client_applications.each(&:destroy)
@carto_user.client_application&.destroy
@user.destroy
end
end

end
Loading