#
# Copyright (c) 2006-2024 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
  module Extension
    module AdminUI
      module Controllers
        #
        # The authentication web page for BeEF.
        #
        class Authentication < BeEF::Extension::AdminUI::HttpController
          #
          # Constructor
          #
          def initialize
            super({
              'paths' => {
                '/' => method(:index),
                '/login' => method(:login),
                '/logout' => method(:logout)
              }
            })

            @session = BeEF::Extension::AdminUI::Session.instance
          end

          # Function managing the index web page
          def index
            @headers['Content-Type'] = 'text/html; charset=UTF-8'
            @headers['X-Frame-Options'] = 'sameorigin'
          end

          #
          # Function managing the login
          #
          def login
            username = @params['username-cfrm'] || ''
            password = @params['password-cfrm'] || ''
            @headers['Content-Type'] = 'application/json; charset=UTF-8'
            @headers['X-Frame-Options'] = 'sameorigin'
            @body = { success: false }.to_json

            config = BeEF::Core::Configuration.instance
            ua_ip = config.get('beef.http.allow_reverse_proxy') ? @request.ip : @request.get_header('REMOTE_ADDR')

            # check if source IP address is permitted to authenticate
            unless permitted_source?(ua_ip)
              BeEF::Core::Logger.instance.register('Authentication', "IP source address (#{ua_ip}) attempted to authenticate but is not within permitted subnet.")
              return
            end

            # check if under brute force attack
            return unless BeEF::Core::Rest.timeout?('beef.extension.admin_ui.login_fail_delay',
                                                    @session.get_auth_timestamp,
                                                    ->(time) { @session.set_auth_timestamp(time) })

            # check username and password
            unless username.eql?(config.get('beef.credentials.user')) && password.eql?(config.get('beef.credentials.passwd'))
              BeEF::Core::Logger.instance.register('Authentication', "User with ip #{ua_ip} has failed to authenticate in the application.")
              return
            end

            # establish an authenticated session
            @session.set_logged_in(ua_ip)
            session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
            Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: @session.get_id, path: '/', httponly: true })

            BeEF::Core::Logger.instance.register('Authentication', "User with ip #{ua_ip} has successfully authenticated in the application.")
            @body = { success: true }.to_json
          end

          #
          # Function managing the logout
          #
          def logout
            @body = { success: true }.to_json

            unless @session.valid_nonce?(@request)
              print_error 'invalid nonce'
              return
            end

            unless @session.valid_session?(@request)
              print_error 'invalid session'
              return
            end

            @headers['Content-Type'] = 'application/json; charset=UTF-8'
            @headers['X-Frame-Options'] = 'sameorigin'

            # set the session to be log out
            @session.set_logged_out

            # clean up UA and expire the session cookie
            config = BeEF::Core::Configuration.instance
            session_cookie_name = config.get('beef.extension.admin_ui.session_cookie_name') # get session cookie name
            Rack::Utils.set_cookie_header!(@headers, session_cookie_name, { value: '', path: '/', httponly: true, expires: Time.now })

            ua_ip = config.get('beef.http.allow_reverse_proxy') ? @request.ip : @request.get_header('REMOTE_ADDR')
            BeEF::Core::Logger.instance.register('Authentication', "User with ip #{ua_ip} has successfully logged out.")
          end

          #
          # Check the UI browser source IP is within the permitted subnet
          #
          def permitted_source?(ip)
            return false unless BeEF::Filters.is_valid_ip?(ip)

            permitted_ui_subnet = BeEF::Core::Configuration.instance.get('beef.restrictions.permitted_ui_subnet')
            return false if permitted_ui_subnet.nil?
            return false if permitted_ui_subnet.empty?

            permitted_ui_subnet.each do |subnet|
              return true if IPAddr.new(subnet).include?(ip)
            end

            false
          end
        end
      end
    end
  end
end
