text: class WopiController < ActionController::Base
    include WopiUtil
  
    skip_before_action :verify_authenticity_token
    before_action :load_vars, :authenticate_user_from_token!
    before_action :verify_proof!
  
    # Only used for checkfileinfo
    def file_get_endpoint
      check_file_info
    end
  
    def file_contents_get_endpoint
      # get_file
      response.headers['X-WOPI-ItemVersion'] = @asset.version
      response.body = @asset.file.download
      send_data response.body, disposition: 'inline', content_type: 'text/plain'
    end
  
    def post_file_endpoint
      override = request.headers['X-WOPI-Override']
      case override
      when 'GET_LOCK'
        get_lock
      when 'PUT_RELATIVE'
        put_relative
      when 'LOCK'
        old_lock = request.headers['X-WOPI-OldLock']
        if old_lock.nil?
          lock
        else
          unlock_and_relock
        end
      when 'UNLOCK'
        unlock
      when 'REFRESH_LOCK'
        refresh_lock
      when 'GET_SHARE_URL'
        render body: nil, status: :not_implemented
      else
        render body: nil, status: :not_found
      end
    end
  
    # Only used for putfile
    def file_contents_post_endpoint
      logger.warn 'WOPI: post_file_contents called'
      put_file
    end
  
    private
  
    def check_file_info
      asset_owner_id = @asset.id.to_s
      asset_owner_id = @asset.created_by_id.to_s if @asset.created_by_id
  
      msg = {
        BaseFileName: @asset.file_name,
        OwnerId: asset_owner_id,
        Size: @asset.file_size,
        UserId: @user.id.to_s,
        Version: @asset.version.to_s,
        SupportsExtendedLockLength: true,
        SupportsGetLock: true,
        SupportsLocks: true,
        SupportsUpdate: true,
        # Setting all users to business until we figure out
        # which should NOT be business
        LicenseCheckForEditIsEnabled: true,
        UserFriendlyName: @user.name,
        UserCanWrite: @can_write,
        UserCanNotWriteRelative: true,
        CloseUrl: @close_url,
        DownloadUrl: url_for(controller: 'assets', action: 'download', id: @asset.id, host: ENV['WOPI_USER_HOST']),
        HostEditUrl: url_for(controller: 'assets', action: 'edit', id: @asset.id, host: ENV['WOPI_USER_HOST']),
        HostViewUrl: url_for(controller: 'assets', action: 'view', id: @asset.id, host: ENV['WOPI_USER_HOST']),
        BreadcrumbBrandName: @breadcrumb_brand_name,
        BreadcrumbBrandUrl: @breadcrumb_brand_url,
        BreadcrumbFolderName: @breadcrumb_folder_name,
        BreadcrumbFolderUrl: @breadcrumb_folder_url
      }
      response.headers['X-WOPI-HostEndpoint'] = ENV['WOPI_ENDPOINT_URL']
      response.headers['X-WOPI-MachineName'] = ENV['WOPI_ENDPOINT_URL']
      response.headers['X-WOPI-ServerVersion'] = Scinote::Application::VERSION
  
      render json: msg
    end
  
    def put_relative
      render body: nil, status: :not_implemented
    end
  
    def lock
      lock = request.headers['X-WOPI-Lock']
      logger.warn 'WOPI: lock; ' + lock.to_s
      return render body: nil, status: :not_found if lock.blank?
  
      @asset.with_lock do
        if @asset.locked?
          if @asset.lock == lock
            @asset.refresh_lock
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            return render body: nil, status: :ok
          else
            response.headers['X-WOPI-Lock'] = @asset.lock
            return render body: nil, status: :conflict
          end
        else
          @asset.lock_asset(lock)
          response.headers['X-WOPI-ItemVersion'] = @asset.version
          return render body: nil, status: :ok
        end
      end
    end
  
    def unlock_and_relock
      logger.warn 'lock and relock'
      lock = request.headers['X-WOPI-Lock']
      old_lock = request.headers['X-WOPI-OldLock']
  
      return render body: nil, status: :bad_request if lock.blank? || old_lock.blank?
  
      @asset.with_lock do
        if @asset.locked?
          if @asset.lock == old_lock
            @asset.unlock
            @asset.lock_asset(lock)
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            return render body: nil, status: :ok
          else
            response.headers['X-WOPI-Lock'] = @asset.lock
            return render body: nil, status: :conflict
          end
        else
          response.headers['X-WOPI-Lock'] = ' '
          return render body: nil, status: :conflict
        end
      end
    end
  
    def unlock
      lock = request.headers['X-WOPI-Lock']
      return render body: nil, status: :bad_request if lock.blank?
  
      @asset.with_lock do
        if @asset.locked?
          logger.warn "WOPI: current asset lock: #{@asset.lock}, unlocking lock #{lock}"
          if @asset.lock == lock
            @asset.unlock
            @asset.post_process_file # Space is already taken in put_file
            create_wopi_file_activity(@user, false)
  
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            return render body: nil, status: :ok
          else
            response.headers['X-WOPI-Lock'] = @asset.lock
            return render body: nil, status: :conflict
          end
        else
          logger.warn 'WOPI: tried to unlock non-locked file'
          response.headers['X-WOPI-Lock'] = ' '
          return render body: nil, status: :conflict
        end
      end
    end
  
    def refresh_lock
      lock = request.headers['X-WOPI-Lock']
      return render body: nil, status: :bad_request if lock.nil? || lock.blank?
  
      @asset.with_lock do
        if @asset.locked?
          if @asset.lock == lock
            @asset.refresh_lock
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            return render body: nil, status: :ok
          else
            response.headers['X-WOPI-Lock'] = @asset.lock
            return render body: nil, status: :conflict
          end
        else
          response.headers['X-WOPI-Lock'] = ' '
          return render body: nil, status: :conflict
        end
      end
    end
  
    def get_lock
      @asset.with_lock do
        response.headers['X-WOPI-Lock'] = @asset.locked? ? @asset.lock : ' '
        return render body: nil, status: :ok
      end
    end
  
    def put_file
      @asset.with_lock do
        lock = request.headers['X-WOPI-Lock']
        if @asset.locked?
          if @asset.lock == lock
            logger.warn 'WOPI: replacing file'
  
            @team.release_space(@asset.estimated_size)
            @asset.update_contents(request.body)
            @asset.last_modified_by = @user
            @asset.save
  
            @team.take_space(@asset.estimated_size)
            @team.save
  
            @protocol&.update(updated_at: Time.now.utc)
  
            response.headers['X-WOPI-ItemVersion'] = @asset.version
            return render body: nil, status: :ok
          else
            logger.warn 'WOPI: wrong lock used to try and modify file'
            response.headers['X-WOPI-Lock'] = @asset.lock
            return render body: nil, status: :conflict
          end
        elsif !@asset.file_size.nil? && @asset.file_size.zero?
          logger.warn 'WOPI: initializing empty file'
  
          @team.release_space(@asset.estimated_size)
          @asset.update_contents(request.body)
          @asset.last_modified_by = @user
          @asset.save
          @team.save
  
          response.headers['X-WOPI-ItemVersion'] = @asset.version
          return render body: nil, status: :ok
        else
          logger.warn 'WOPI: trying to modify unlocked file'
          response.headers['X-WOPI-Lock'] = ' '
          return render body: nil, status: :conflict
        end
      end
    end
  
    def load_vars
      @asset = Asset.find_by(id: params[:id])
      if @asset.nil?
        render body: nil, status: :not_found
      else
        logger.warn "Found asset: #{@asset.id}"
        step_assoc = @asset.step
        result_assoc = @asset.result
        repository_cell_assoc = @asset.repository_cell
        @assoc = step_assoc unless step_assoc.nil?
        @assoc = result_assoc unless result_assoc.nil?
        @assoc = repository_cell_assoc unless repository_cell_assoc.nil?
  
        if @assoc.class == Step
          @protocol = @asset.step.protocol
          @team = @protocol.team
        elsif @assoc.class == Result
          @my_module = @assoc.my_module
          @team = @my_module.experiment.project.team
        elsif @assoc.class == RepositoryCell
          @repository = @assoc.repository_column.repository
          @team = @repository.team
        end
      end
    end
  
    def authenticate_user_from_token!
      wopi_token = params[:access_token]
      if wopi_token.nil?
        logger.warn 'WOPI: nil wopi token'
        return render body: nil, status: :unauthorized
      end
  
      @user = User.find_by_valid_wopi_token(wopi_token)
      if @user.nil?
        logger.warn 'WOPI: no user with this token found'
        return render body: nil, status: :unauthorized
      end
      logger.warn "WOPI: user found by token #{wopi_token} ID: #{@user.id}"
  
      # This is what we get for settings permission methods with
      # current_user
      @current_user = @user
      if @assoc.class == Step
        if @protocol.in_module?
          @can_read = can_read_protocol_in_module?(@protocol)
          @can_write = can_manage_protocol_in_module?(@protocol)
          @close_url = protocols_my_module_url(@protocol.my_module, only_path: false, host: ENV['WOPI_USER_HOST'])
  
          project = @protocol.my_module.experiment.project
          @breadcrumb_brand_name = project.name
          @breadcrumb_brand_url = project_url(project, only_path: false, host: ENV['WOPI_USER_HOST'])
          @breadcrumb_folder_name = @protocol.my_module.name
        else
          @can_read = can_read_protocol_in_repository?(@protocol)
          @can_write = can_manage_protocol_in_repository?(@protocol)
          @close_url = protocols_url(only_path: false, host: ENV['WOPI_USER_HOST'])
  
          @breadcrump_brand_name = 'Projects'
          @breadcrumb_brand_url = root_url(only_path: false, host: ENV['WOPI_USER_HOST'])
          @breadcrumb_folder_name = 'Protocol managament'
        end
        @breadcrumb_folder_url = @close_url
      elsif @assoc.class == Result
        @can_read = can_read_experiment?(@my_module.experiment)
        @can_write = can_manage_module?(@my_module)
  
        @close_url = results_my_module_url(@my_module, only_path: false, host: ENV['WOPI_USER_HOST'])
  
        @breadcrumb_brand_name  = @my_module.experiment.project.name
        @breadcrumb_brand_url   = project_url(@my_module.experiment.project,
                                              only_path: false,
                                              host: ENV['WOPI_USER_HOST'])
        @breadcrumb_folder_name = @my_module.name
        @breadcrumb_folder_url  = @close_url
      elsif @assoc.class == RepositoryCell
        @can_read = can_read_repository?(@repository)
        @can_write = !@repository.is_a?(RepositorySnapshot) && can_edit_wopi_file_in_repository_rows?
  
        @close_url = repository_url(@repository, only_path: false, host: ENV['WOPI_USER_HOST'])
  
        @breadcrumb_brand_name  = @team.name
        @breadcrumb_brand_url   = @close_url
        @breadcrumb_folder_name = @assoc.repository_row.name
        @breadcrumb_folder_url  = @close_url
      end
  
      return render body: nil, status: :not_found unless @can_read
    end
  
    def verify_proof!
      token = params[:access_token].encode('utf-8')
      timestamp = request.headers['X-WOPI-TimeStamp'].to_i
      signed_proof = request.headers['X-WOPI-Proof']
      signed_proof_old = request.headers['X-WOPI-ProofOld']
      url = request.original_url.upcase.encode('utf-8')
  
      if convert_to_unix_timestamp(timestamp) + 20.minutes >= Time.now
        if current_wopi_discovery.verify_proof(token, timestamp, signed_proof, signed_proof_old, url)
          logger.warn 'WOPI: proof verification: successful'
        else
          logger.warn 'WOPI: proof verification: not verified'
          render body: nil, status: :internal_server_error
        end
      else
        logger.warn 'WOPI: proof verification: timestamp too old; ' +
                    timestamp.to_s
        render body: nil, status: :internal_server_error
      end
    rescue StandardError => e
      logger.warn 'WOPI: proof verification: failed; ' + e.message
      render body: nil, status: :internal_server_error
    end
  
    def can_edit_wopi_file_in_repository_rows?
      can_manage_repository_rows?(@repository)
    end
  end
  