3scale/porta

View on GitHub
app/models/authentication_provider.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

class AuthenticationProvider < ApplicationRecord
  belongs_to :account
  has_many :sso_authorizations, dependent: :delete_all

  class_attribute :authorization_scope, :oauth_config_required, :can_be_published
  self.oauth_config_required = true
  self.can_be_published = true

  include SystemName

  enum account_type: {developer: 'developer', provider: 'provider'}

  has_system_name uniqueness_scope: [:account_id]
  validates :kind, uniqueness: { scope: %i[account_id account_type], case_sensitive: true }, if: :developer?
  validate  :verify_valid_kind_for_account_type
  validates :name, presence: true, length: { maximum: 255 }
  validates :identifier_key, presence: true, length: { maximum: 255 }
  validates :system_name, :client_id, :client_secret, :token_url, :user_info_url,
            :authorize_url, :site, :username_key, :kind, :branding_state, :type,
            length: { maximum: 255 }

  validates :system_name, exclusion: [
      RedhatCustomerPortalSupport::RH_CUSTOMER_PORTAL_SYSTEM_NAME,
      ServiceDiscovery::AuthenticationProviderSupport::SERVICE_DISCOVERY_SYSTEM_NAME
  ]

  before_validation :set_defaults, on: :create
  before_create :set_defaults

  validates :client_id, :client_secret, presence: true, if: :oauth_config_required?

  with_options format: { with: URI.regexp(%w(http https)), allow_blank: true, message: :invalid_url } do |ops|
    ops.validates :site
    ops.validates :token_url
    ops.validates :authorize_url
    ops.validates :user_info_url
  end

  attr_readonly :type, :kind

  scope(:published, -> { where(published: true) })

  AVAILABLE = {
    account_types[:developer] => [self::Keycloak, self::Auth0, self::GitHub],
    account_types[:provider]  => [self::Keycloak, self::Auth0]
  }.with_indifferent_access.freeze
  private_constant :AVAILABLE

  def self.available(account_type = account_types[:developer])
    AVAILABLE[account_type]
  end

  def self.find_kind(kind, available_kinds = available)
    available_kinds.find do |model|
      model.model_name.element == kind
    end
  end

  def self.build_by_kind(kind:, account_type:, **attributes)
    kind_downcased = kind.to_s.downcase
    kind_class = find_kind(kind_downcased, available(account_type)) || Custom
    kind_class.new({kind: kind_downcased, account_type: account_type}.reverse_merge(attributes))
  end

  Credentials = Struct.new(:client_id, :client_secret)

  # @return [Credentials]
  def credentials
    Credentials.new(client_id.presence, client_secret.presence)
  end

  alias callback_account account

  def authorization_scope(action = nil)
    self.class.authorization_scope
  end

  def ready_to_be_custom_branded?
    in_social_scope? && client_id_and_client_secret?
  end

  def can_switch_at_will?
    custom_branded? || ready_to_be_custom_branded?
  end

  def in_iam_tools_scope?
    authorization_scope == :iam_tools
  end

  def in_social_scope?
    authorization_scope == :branding
  end

  alias has_3scale_branded_equivalent? in_social_scope?

  def client_id_and_client_secret?
    client_id.present? && client_secret.present?
  end

  def human_state_name
    I18n.t(branding_state_name, scope: [:authentication_provider, :state])
  end

  def self.human_kind
    I18n.t(kind, scope: [:authentication_provider, :kind], default: model_name.human)
  end

  def allowed_state_transitions
    branding_state_transitions.map do |transition|
      [I18n.t(transition.to_name, scope: [:authentication_provider, :state]), transition.event]
    end
  end

  def ssl_verify_mode
    skip_ssl_certificate_verification? ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
  end

  def self.kind
    model_name.element.freeze
  end

  def kind
    super || self.class.kind
  end

  delegate :human_kind, to: :class

  def self.branded_available?
    config = ThreeScale::OAuth2.config.fetch(kind.to_sym, {})

    return unless config[:enabled]

    credentials = config.slice(:client_id, :client_secret)
    credentials.values.any?(&:present?)
  end

  private

  def verify_valid_kind_for_account_type
    my_class = self.class
    return if developer? || (provider? && my_class.available(my_class.account_types[:provider]).include?(my_class))
    errors.add(:kind, :not_found)
    false
  end

  def set_defaults
    self.system_name ||= kind.to_s
    self.name ||= human_kind.to_s
  end
end