lib/doorkeeper/oauth/pre_authorization.rb



# frozen_string_literal: true

module Doorkeeper
  module OAuth
    class PreAuthorization
      include Validations

      validate :client_id,             error: :invalid_request
      validate :client,                error: :invalid_client
      validate :redirect_uri,          error: :invalid_redirect_uri
      validate :params,                error: :invalid_request
      validate :response_type,         error: :unsupported_response_type
      validate :scopes,                error: :invalid_scope
      validate :code_challenge_method, error: :invalid_code_challenge_method
      validate :client_supports_grant_flow, error: :unauthorized_client

      attr_reader :server, :client_id, :client, :redirect_uri, :response_type, :state,
                  :code_challenge, :code_challenge_method, :missing_param

      def initialize(server, attrs = {})
        @server                = server
        @client_id             = attrs[:client_id]
        @response_type         = attrs[:response_type]
        @redirect_uri          = attrs[:redirect_uri]
        @scope                 = attrs[:scope]
        @state                 = attrs[:state]
        @code_challenge        = attrs[:code_challenge]
        @code_challenge_method = attrs[:code_challenge_method]
      end

      def authorizable?
        valid?
      end

      def validate_client_supports_grant_flow
        Doorkeeper.configuration.allow_grant_flow_for_client?(grant_type, client.application)
      end

      def scopes
        Scopes.from_string scope
      end

      def scope
        @scope.presence || (server.default_scopes.presence && build_scopes)
      end

      def error_response
        if error == :invalid_request
          OAuth::InvalidRequestResponse.from_request(self,
                                                     response_on_fragment: response_on_fragment?)
        else
          OAuth::ErrorResponse.from_request(self, response_on_fragment: response_on_fragment?)
        end
      end

      def as_json(attributes = {})
        return pre_auth_hash.merge(attributes.to_h) if attributes.respond_to?(:to_h)

        pre_auth_hash
      end

      private

      def build_scopes
        client_scopes = client.scopes
        if client_scopes.blank?
          server.default_scopes.to_s
        else
          (server.default_scopes & client_scopes).to_s
        end
      end

      def validate_client_id
        @missing_param = :client_id if client_id.blank?

        @missing_param.nil?
      end

      def validate_client
        @client = OAuth::Client.find(client_id)
        @client.present?
      end

      def validate_redirect_uri
        return false if redirect_uri.blank?

        Helpers::URIChecker.valid_for_authorization?(
          redirect_uri,
          client.redirect_uri
        )
      end

      def validate_params
        @missing_param = if response_type.blank?
                           :response_type
                         elsif @scope.blank? && server.default_scopes.blank?
                           :scope
                         end

        @missing_param.nil?
      end

      def validate_response_type
        server.authorization_response_types.include?(response_type)
      end

      def validate_scopes
        Helpers::ScopeChecker.valid?(
          scope_str: scope,
          server_scopes: server.scopes,
          app_scopes: client.scopes,
          grant_type: grant_type
        )
      end

      def grant_type
        response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT
      end

      def validate_code_challenge_method
        code_challenge.blank? ||
          (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
      end

      def response_on_fragment?
        response_type == "token"
      end

      def pre_auth_hash
        {
          client_id: client.uid,
          redirect_uri: redirect_uri,
          state: state,
          response_type: response_type,
          scope: scope,
          client_name: client.name,
          status: I18n.t("doorkeeper.pre_authorization.status"),
        }
      end
    end
  end
end