app/models/cinstance.rb
Mass assignment is not restricted using attr_accessible
Class `Cinstance` has 52 methods (exceeds 20 allowed). Consider refactoring.
File `cinstance.rb` has 344 lines of code (exceeds 250 allowed). Consider refactoring.
Cinstance has at least 48 methods
Cinstance has at least 5 instance variables
Cinstance assumes too much for instance variable '@validate_human_edition'
Cinstance assumes too much for instance variable '@validate_plan_is_unique'class Cinstance < Contract include SaveDestroyForServiceAssociation # Maximum number of cinstances permitted between provider and buyer MAX = 10 self.background_deletion = [:referrer_filters, :application_keys, [:alerts, { action: :delete }]] delegate :backend_version, to: :service, allow_nil: true belongs_to :plan, class_name: 'ApplicationPlan', foreign_key: :plan_id, inverse_of: :cinstances alias application_plan plan # TODO: verify the comment is still true and we can't use inverse_of belongs_to :service #, inverse_of: :cinstances # this inverse_of messes up some association autosave stuff has_many :alerts, dependent: :delete_all has_many :line_items # this needs to be before the include Backend::ModelExtensions::Cinstance # otherwise the application is deleted from backend before the appplication keys are removed, producing errors with_options(foreign_key: :application_id, dependent: :destroy, inverse_of: :application) do |backend| backend.has_many :referrer_filters, &::ReferrerFilter::AssociationExtension backend.has_many :application_keys, &::ApplicationKey::AssociationExtension end before_validation :set_service_id before_validation :set_user_key, on: :create before_create :set_service_id before_create :set_provider_public_key before_create :accept_on_create, :unless => :live? attr_readonly :service_id include WebHooksHelpers #TODO: make this inclusion more dsl-ish fires_human_web_hooks_on_events # this has to be before the include Backend::ModelExtensions::Cinstance # or callbacks order makes keys not to be saved in backend after_create :create_first_key # before_destroy :refund_fixed_cost after_commit :reject_if_pending, :on => :destroy include Logic::Contracting::ApplicationContract # FIXME: including Fields after other includes makes Fields break include Fields::Fields required_fields_are :name optional_fields_are :description default_fields_are :name, :description set_fields_account_source :user_account include Backend::ModelExtensions::Cinstance include Finance::VariableCost include Logic::Authentication::ApplicationContract include Logic::Keys::ApplicationContract include Indices::AccountIndex::ForDependency include ThreeScale::Search::Scopes def self.attributes_for_destroy_list %w( id user_account_id name description user_key plan_id state trial_period_expires_at created_at extra_fields) end self.allowed_sort_columns = %w{ cinstances.name cinstances.state accounts.org_name cinstances.created_at cinstances.first_daily_traffic_at } # can't order by plans.name, service.name - mysql blows up self.default_sort_column = :created_at self.default_sort_direction = :desc self.allowed_search_scopes = %w{ service_id plan_id plan_type state account account_query state name user_key active_since inactive_since } self.default_search_scopes = { } self.sort_columns_joins = { 'accounts.org_name' => [:user_account], 'plans.name' => [:plan], 'service.name' => [:service] } def redirect_url=(redirect_url) super(redirect_url.try(:strip)) end validates :conditions, acceptance: { :message => 'you should agree on the terms and conditions for this plan first' } validates :plan, presence: true validates :name, presence: { :if => :name_required? } after_commit :push_webhook_key_updated, :on => :update, :if => :user_key_updated? after_commit :push_application_updated_event, on: :update, unless: :only_traffic_updated? #this method marks that a human edition of the app is happening, thus description presence will be validated # this is done so e.g. to avoid change_plan to fail when the app misses description or nameCinstance has missing safe method 'validate_human_edition!' def validate_human_edition! @validate_human_edition = true end Cinstance has missing safe method 'validate_plan_is_unique!' def validate_plan_is_unique! @validate_plan_is_unique = true end def validate_plan_is_unique? @validate_plan_is_unique end validate :plan_is_unique, if: :validate_plan_is_unique? validate :application_id_is_unique, if: :validate_application_id_is_unique? validates :application_id, uniqueness: { scope: [:service_id], case_sensitive: true }, unless: :validate_application_id_is_unique? validate :user_key_is_unique, unless: :provider_can_duplicate_user_key? validates :user_key, uniqueness: { scope: [:service_id], case_sensitive: true }, if: :provider_can_duplicate_user_key? validate :same_service, on: :update, if: :plan_id_changed? # letter, number, underscore (_), hyphen-minus (-), dot (.), base64 format # In base64 encoding, the character set is [A-Z,a-z,0-9,and +], if rest length is less than 4, fill of '=' character. # ^([A-Za-z0-9+]{4})* means the String start with 0 time or more base64 group. # ([A-Za-z0-9+]{4}|[A-Za-z0-9+]{3}=|[A-Za-z0-9+]{2}==) means the String must end of 3 forms in [A-Za-z0-9+]{4} or [A-Za-z0-9+]{3}= or [A-Za-z0-9+]{2}== # matches also the non 64B case with (\A[\w\-\.]+\Z) # NOTE: base64 format also accepts forward slash (/), however we don't allow it because of the restriction on the backend USER_KEY_FORMAT = /(([\w\-.]+)|([A-Za-z0-9+]{4})*([A-Za-z0-9+]{4}|[A-Za-z0-9+]{3}=|[A-Za-z0-9+]{2}==))/.freeze # The following characters are accepted: # A-Z a-z 0-9 ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ # Spaces are not allowed validates :application_id, format: { with: /\A[\x21-\x7E]+\Z/ }, length: { in: 4..255 } validates :user_key, format: { with: /\A#{USER_KEY_FORMAT}\Z/ }, length: { maximum: 256 } scope :order_for_dev_portal, -> { order(service_id: :desc, created_at: :desc) } # Return only live cinstances # # Note that deprecated cinstances are still considered live. This is # intentional, because deprecated cinstances can still be used (so they are # "live", in a way). scope :live, -> { where(:state => ['live', 'deprecated'])} scope :active_since, ->(activity_time) { where.has { first_daily_traffic_at >= activity_time } } # Return only cinstances live at given time. The time can be single time or # period (Range object) (from..to). scope :live_at, ->(period) { period = period..period unless period.is_a?(Range) where(['cinstances.created_at <= ?', period.end]) } def self.provided_by(account) # we can access service through plan but also keep service.id in sync with plan.service.id # this is a simpler way to do the query used historically joins(:service).where.has { service.sift(:of_account, account) } end scope :not_bought_by, ->(account) { where.has { user_account_id != account.id } } scope :can_be_managed, -> { includes(plan: :service) .where(['services.buyers_manage_apps = ?', true]) .references(:services) } scope :latest, -> (count = 5) { reorder(created_at: :desc).limit(count)} scope :by_user_key, ->(user_key) { where({:user_key => user_key}) } scope :by_name, ->(query) do case query.strip when /\Auser_key:\s*#{Cinstance::USER_KEY_FORMAT}\z/ then by_user_key($1) else all.merge(Contract.by_name(query)) end end scope :by_application_id, ->(app_id) { where({:application_id => app_id}) } def self.by_service(service) if service == :all || service.blank? all else where { plan_id.in( my { Plan.issued_by(service).select(:id)} ) } end end scope :by_service_id, ->(service_id) { where(service_id: service_id) } scope :by_active_since, ->(date) { where('first_daily_traffic_at >= ?', date) } scope :by_inactive_since, ->(date) { where('first_daily_traffic_at <= ?', date) } scope :by_plan_system_name, ->(system_names) { names = [system_names].flatten proc_like_system_name = proc { |cinstance, name| cinstance.plan.system_name.like(name) } proc_query_chain = proc { |cinstance| names.map { |name| proc_like_system_name.call(cinstance, name)}.inject(:or) } joins(:plan).where.has { |cinstance| proc_query_chain.call(cinstance) } } ## # Instance Methods # and other stuff :( ## # maybe move both limit methods to their models? def self.serialization_preloading(format = nil) # With Rails 6.1 trying to include plan->issuer without service results in # > Cannot eagerly load the polymorphic association :issuer # When both have the same sub-includes, cache takes care of the duplicate queries. service_includes = %i[proxy account] plan_includes = [{issuer: service_includes}]Cinstance#self.serialization_preloading is controlled by argument 'format' if format == "xml" service_includes << :default_application_plan plan_includes << :original end includes(:user_account, service: service_includes, plan: plan_includes) end def keys_limit case service.try(:backend_version) when 'oauth' 1 else ApplicationKey::KEYS_LIMIT end end def filters_limit ReferrerFilter::REFERRER_FILTERS_LIMIT end def buyer_alerts_enabled? service && service.notify_alerts?(:buyer, :web) end def period created_at..Time.zone.now end def display_name name.present? ? name : default_name end def default_name "Application on plan #{plan.name}" end delegate :provided_by?, to: :plan # Time value. paid until that Exact time def variable_cost_paid_until self[:variable_cost_paid_until] || trial_period_expires_at || created_at end # Shortcut for plan.service.metrics def metrics service && service.all_metrics end # Is this cinstance bought by an account? def bought_by?(account) buyer == account end Cinstance has missing safe method 'change_provider_public_key!' def change_provider_public_key! update_attribute(:provider_public_key, generate_key) end Cinstance has missing safe method 'change_user_key!' def change_user_key! @webhook_event = 'user_key_updated' update_attribute(:user_key, generate_key) end def user_key_updated? saved_change_to_user_key? end def push_webhook_key_updated #Push only if updated by UserCinstance#push_webhook_key_updated calls 'User.current' 2 times self.web_hook_event!({user: User.current, event: "key_updated"}) if User.current end def push_application_updated_event Applications::ApplicationUpdatedEvent.create_and_publish!(self) end # Reson why cinstance was rejected. This is only set after +reject!+ is called attr_reader :rejection_reason # Reject pending cinstance. # # Note that this is just convenience method equivalent to: # cinstance.rejection_reason = "i don't like your mother" # cinstance.destroyCinstance has missing safe method 'reject!' def reject!(reason) @rejection_reason = reason destroy end def select_usersCinstance#select_users refers to 'c' more than self (maybe move it to another class?)
Cinstance#select_users has the variable name 'c' service.cinstances.collect {|c| [ c.user_name, c.id ] } end def available_application_plans plans_table = Plan.table_name stock_not_mine = ["(#{plans_table}.original_id = 0 OR #{plans_table}.original_id IS NULL) AND #{plans_table}.id <> ?", plan_id] service.application_plans.where(stock_not_mine) end # Get a usage status object for this cinstance. This object contains information about # how close this cinstance is to it's usage limits. See ServiceTransaction::Status for # more details. def usage_status(options = {}) Backend::Transaction.usage_status(provider_account, self, service, options) end Method `to_xml` has a Cognitive Complexity of 28 (exceeds 5 allowed). Consider refactoring.
Method `to_xml` has 45 lines of code (exceeds 25 allowed). Consider refactoring.
Cinstance#to_xml has approx 25 statements def to_xml(options = {}) result = options[:builder] || ThreeScale::XML::Builder.new result.application do |xml|Cinstance tests 'new_record?' at least 4 times unless new_record? xml.id_ id xml.created_at created_at.xmlschema xml.updated_at updated_at.xmlschema end xml.state state xml.user_account_id user_account_id xml.first_traffic_at first_traffic_at.try(:xmlschema) xml.first_daily_traffic_at first_daily_traffic_at.try(:xmlschema) xml.service_id service.id if service.present?Cinstance#to_xml calls 'service.backend_version' 2 times if service.backend_version.v1? xml.user_key( user_key ) xml.provider_verification_key( provider_public_key ) else #v2, oauth on enterprise xml.application_id( application_id ) if service.backend_version.oauth? xml.redirect_url redirect_url end unless destroyed? xml.keys do |keys_element|Cinstance#to_xml contains iterators nested 3 deep
Cinstance#to_xml has the variable name 'k' keys.each do |k| keys_element.key k end end end end plan.to_xml(:builder => xml) service.oidc_configuration.to_xml(builder: xml) if service.oidc? unless destroyed? fields_to_xml(xml) extra_fields_to_xml(xml) if persisted? if referrer_filters_required? xml.referrer_filters do |referer_filters_element| referrers.each do |rf| referer_filters_element.referrer_filter(rf) end end end end end end result.to_xml end delegate :custom_keys_enabled?, :referrer_filters_required?, :to => :service def reload(*) super ensure @backend_object = nil @validate_human_edition = nil end def backend_object @backend_object ||= provider_account.backend_object.application(self) end def create_origin origin = self[:create_origin] ActiveSupport::StringInquirer.new(origin.to_s) end def keys application_keys.pluck_values end def referrers referrer_filters.pluck_values end def app_plan_change_should_request_credit_card? service.plan_change_permission(ApplicationPlan) == :request_credit_card end protected def account_for_sphinx user_account_id end def correct_plan_subclass? unless self.plan.is_a? ApplicationPlan errors.add(:plan, 'plan must be an ApplicationPlan') end end private def only_traffic_updated? (saved_changes.keys - %w[first_traffic_at first_daily_traffic_at updated_at]).empty? end # It calls to `create_key_after_create` to check if it's possible to add # an application key. # # Return false if it isn't possible to create a first key def create_first_key create_key_after_create? ? application_keys.add : false end # # Overrides Contract protected method to run WebHooks after sucessful plan change # def change_plan_internal(new_plan) super do yield @webhook_event = 'plan_changed' end end def same_service return if plan.blank? || plan.service == service errors.add(:plan, :not_allowed) end def reject_if_pending notify_observers(:rejected) if pending? end def name_required? @validate_human_edition end def service_intentions_required? issuer && issuer.intentions_required? end def multiple_applications_allowed? provider_account && provider_account.multiple_applications_allowed? end # Custom validation that assures that there is only one non-deleted cinstance # per plan and user account. # # TODO: maybe i can remove this and use regular validates_uniquenes_of? # validates_uniqueness_of :plan_id, :scope => [:user_account_id], :unless => :multiple_applications_allowed?, :message => 'is already bought' # # SURE! If you get rid of acts_as_paranoid because it has to be non-deleted and it keeps cinstances in this table Method `plan_is_unique` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring. def plan_is_unique if plan && user_account && !multiple_applications_allowed? # All non-deleted cinstance with the same user_account as this one... others = plan.cinstances.bought_by(user_account) # ...except this one (if already exists in database). others = others.without_ids(self.id) unless new_record? Cinstance tests 'others.empty?' at least 3 times errors.add(:plan_id, 'is already bought') unless others.empty? end end Method `application_id_is_unique` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. def application_id_is_unique if provider_account others = provider_account.provided_cinstances.by_application_id(application_id) others = others.without_ids(self.id) unless new_record? errors.add(:application_id, :taken) unless others.empty? end end def validate_application_id_is_unique? !provider_account.try!(:provider_can_use?, :duplicate_application_id) end def provider_can_duplicate_user_key? provider_account.try!(:provider_can_use?, :duplicate_user_key) end Method `user_key_is_unique` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. def user_key_is_unique if provider_account others = provider_account.provided_cinstances.by_user_key(user_key) others = others.without_ids(self.id) unless new_record? errors.add(:user_key, :taken) unless others.empty? end end scope :without_ids, ->(id) { where(["#{table_name}.id <> ?", id]) } def set_user_key self.user_key ||= generate_key end def set_provider_public_key self.provider_public_key ||= generate_key end def set_service_id self.service_id ||= plan.try(:issuer_id) end def generate_key SecureRandom.hex(16) endend