# file      : tests/rep-auth.testscript
# license   : MIT; see accompanying LICENSE file

.include common.testscript auth.testscript config.testscript remote.testscript

# There is no rep-auth command, and this testscript contains tests for various
# authentication scenarios throughout different stages of repositories
# preparation and consumption. Note that by that reason usage of $* is
# meaningless.
#

# Source repository:
#
# rep-auth
# |-- expired
# |   |-- foo-1.tar.gz
# |   |-- packages.manifest
# |   |-- repositories.manifest
# |   `-- signature.manifest
# `-- unsigned
#     |-- foo-1.tar.gz
#     `-- repositories.manifest

# Prepare repositories used by tests if running in the local mode.
#
+if! $remote
{
  rc = [cmdline] $rep_create 2>!

  # Create the 'unsigned1' repository.
  #
  cp -r $src/unsigned $out/unsigned1

  $rc $out/unsigned1 &$out/unsigned1/packages.manifest \
      &$out/unsigned1/signature.manifest

  # Create the 'unsigned2' repository. This is a copy of the just created
  # 'unsigned1' repository.
  #
  cp -r $out/unsigned1 $out/unsigned2

  # Create the 'signed' repository.
  #
  cp -r $src/unsigned $out/signed
  cat <<<$cert_manifest >+$out/signed/repositories.manifest

  $rc --key $key $out/signed &$out/signed/packages.manifest \
                             &$out/signed/signature.manifest

  # Create the 'self-match' repository. Note that its certificate name is
  # the '*build2.org' wildcard (matches build2.org and any single-level
  # subdomain).
  #
  cp -r $src/unsigned $out/self-match

  echo 'certificate:\'                 >+$out/self-match/repositories.manifest
  cat  <<<$src_base/auth/self-cert.pem >+$out/self-match/repositories.manifest
  echo '\'                             >+$out/self-match/repositories.manifest

  $rc --key $key $out/self-match &$out/self-match/packages.manifest \
                                 &$out/self-match/signature.manifest

  # Create the 'self-any-match' repository. Note that its certificate name is
  # the '**build2.org' wildcard (matches build2.org and any subdomain).
  #
  cp -r $src/unsigned $out/self-any-match

  echo 'certificate:\'                     >+$out/self-any-match/repositories.manifest
  cat  <<<$src_base/auth/self-any-cert.pem >+$out/self-any-match/repositories.manifest
  echo '\'                                 >+$out/self-any-match/repositories.manifest

  $rc --key $key $out/self-any-match &$out/self-any-match/packages.manifest \
                                     &$out/self-any-match/signature.manifest

  # Create the 'subdomain-match' repository. Note that its certificate name is
  # the '*.build2.org' wildcard (matches any single-level subdomain of
  # build2.org).
  #
  cp -r $src/unsigned $out/subdomain-match

  echo 'certificate:\'                      >+$out/subdomain-match/repositories.manifest
  cat  <<<$src_base/auth/subdomain-cert.pem >+$out/subdomain-match/repositories.manifest
  echo '\'                                  >+$out/subdomain-match/repositories.manifest

  $rc --key $key $out/subdomain-match &$out/subdomain-match/packages.manifest \
                                      &$out/subdomain-match/signature.manifest

  # Create the 'name-mismatch' repository. Note that its certificate name
  # mismatches the repository location.
  #
  cp -r $src/unsigned $out/name-mismatch

  echo 'certificate:\'                     >+$out/name-mismatch/repositories.manifest
  cat  <<<$src_base/auth/mismatch-cert.pem >+$out/name-mismatch/repositories.manifest
  echo '\'                                 >+$out/name-mismatch/repositories.manifest

  $rc --key $key $out/name-mismatch &$out/name-mismatch/packages.manifest \
                                    &$out/name-mismatch/signature.manifest

  # Create the 'expired' repository. This repository is "pre-created" and its
  # certificate is expired by now. So we just copy it from the source
  # directory.
  #
  cp -r $src/expired $out/expired

  # Create the 'sha256sum-mismatch' repository. This is a copy of the just
  # created 'signed' repository that has the sha256sum manifest value tampered.
  #
  cp -r $out/signed $out/sha256sum-mismatch

  v = 'd374c59b36fdbdbd0d4468665061d94fda9c6c687863dfe72b0bcc34ff9d5fb4'

  sed -i -e "s/^\(sha256sum: \).*\$/\\1$v/" \
      $out/sha256sum-mismatch/signature.manifest

  # Create the 'signature-mismatch' repository. This is a copy of the just
  # created 'signed' repository that has the signature manifest value tampered.
  #
  cp -r $out/signed $out/signature-mismatch

  # Here we tamper the last signature line (the one of 76 chars length, without
  # spaces and terminated with '=').
  #
  v = 'mnBAsS529NUdNIQy8EB4si/UK26ICaMywbLeHDVvWOB+AsqZ5rj8VjGDamLbmUrDr3ru7BU1gJU='
  sed -i -e "s%^[^ ]{75}=\$%$v%" $out/signature-mismatch/signature.manifest
}

pkg_status += -d cfg
rep_add    += -d cfg 2>!
rep_fetch  += -d cfg
rep_info   += 2>!

# Check if rep-fetch command was successfull or not.
#
fetched     = [cmdline] $pkg_status foo >'foo available 1'
not_fetched = [cmdline] $pkg_status foo >'foo unknown'

sc = " " # Space character to append to here-document line when required.

: no-auth
:
: Test that local repositories do not require authentication by default.
:
{{
  r = 1/signed
  +mkdir 1/
  +cp -r $src/unsigned $r
  +cat <<<$cert_manifest >+$r/repositories.manifest
  +$rep_create --key $key $r &$r/packages.manifest &$r/signature.manifest 2>!

  : rep-fetch
  :
  {
    $clone_root_cfg && $rep_add ../$r

    $rep_fetch 2>>/~%EOE%
      %fetching .+/no-auth/signed%
      %querying .+signed%
      %fetched .+/no-auth/signed%
      1 package(s) in 1 repository(s)
      EOE
  }

  : rep-info
  :
  $clone_root_cfg;
  $rep_info --cert-name ../$r >'name:build2.org'
}}

: signed
:
{{
  : rep-fetch
  :
  {{
    +$clone_root_cfg && $rep_add $rep/signed
    rep_fetch += --auth all &?cfg/.bpkg/certs/**

    : no-auth
    :
    {
      $clone_cfg

      $rep_fetch 2>>~"%EOE%" != 0
        fetching pkg:build2.org/rep-auth/signed
        %querying .+signed%
        warning: authenticity of the certificate for repository pkg:build2.org/rep-auth/signed cannot be established
        certificate is for build2.org, "Code Synthesis" <info@build2.org>
        certificate SHA256 fingerprint:
        $cert_fp
        trust this certificate? [y/n]$sc
        error: unable to read y/n answer from stdin
        EOE

      $not_fetched
    }

    : trust-fp
    :
    {
      $clone_cfg

      $rep_fetch --trust $cert_fp 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched
    }

    : trust-fp-no
    :
    {
      $clone_cfg

      $rep_fetch --trust-no --trust $cert_fp 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched
    }

    : trust-yes
    :
    {
      $clone_cfg

      $rep_fetch --trust-yes 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched
    }

    : trust-no
    :
    {
      $clone_cfg

      $rep_fetch --trust-no 2>>~%EOE% != 0
        fetching pkg:build2.org/rep-auth/signed
        %querying .+signed%
        error: authenticity of the certificate for repository pkg:build2.org/rep-auth/signed cannot be established
        EOE

      $not_fetched
    }

    : trust-yes-no
    :
    {
      $clone_cfg

      $rep_fetch --trust-yes --trust-no 2>>~%EOE% != 0
        fetching pkg:build2.org/rep-auth/signed
        %querying .+signed%
        error: --trust-yes and --trust-no are mutually exclusive
        EOE

      $not_fetched
    }

    : already-trusted
    :
    {
      $clone_cfg

      $rep_fetch --trust-yes 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $rep_fetch 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched

      $rep_fetch --trust-no 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/signed
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched
    }
  }}

  : rep-info
  :
  {{
    rep_info += --cert-name --auth all $rep/signed

    : no-auth
    :
    $rep_info 2>>~"%EOE%" != 0
      %.*
      warning: authenticity of the certificate for repository pkg:build2.org/rep-auth/signed cannot be established
      certificate is for build2.org, "Code Synthesis" <info@build2.org>
      certificate SHA256 fingerprint:
      $cert_fp
      trust this certificate? [y/n]$sc
      error: unable to read y/n answer from stdin
      EOE

    : trust-fp
    :
    $rep_info --trust $cert_fp >'name:build2.org' 2>!

    : trust-yes
    :
    $rep_info --trust-yes >'name:build2.org' 2>!

    : trust-no
    :
    $rep_info --trust-no 2>>~%EOE% != 0
      %.*
      error: authenticity of the certificate for repository pkg:build2.org/rep-auth/signed cannot be established
      EOE

    : already-trusted
    :
    {
      $clone_root_cfg
      rep_info += -d cfg

      $rep_info --trust "$cert_fp" &cfg/.bpkg/certs/** >>EOO 2>!
        name:build2.org
        EOO

      $rep_info >'name:build2.org' 2>!
    }
  }}

  : subdomain-wildcard
  :
  {{
    rep_info += --auth all --trust-yes --cert-name

    : self
    :
    {{
      : exact
      :
      $rep_info $rep/self-match >'name:*build2.org' 2>!

      : subdomain
      :
      if! $remote
      {{
        : first-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/)
          mkdir -p $r
          cp -r $rep/self-match $r

          $rep_info $r/self-match >'name:*build2.org'
        }

        : second-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/)
          mkdir -p $r
          cp -r $rep/self-match $r

          $rep_info $r/self-match 2>>~%EOE% != 0
            fetching pkg:b.a.build2.org/self-match
            %querying .+self-match%
            error: certificate name mismatch for repository pkg:b.a.build2.org/self-match
              info: certificate name is *build2.org
            EOE
        }
      }}
    }}

    : self-any
    :
    {{
      : exact
      :
      $rep_info $rep/self-any-match >'name:**build2.org' 2>!

      : subdomain
      :
      if! $remote
      {{
        : first-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/)
          mkdir -p $r
          cp -r $rep/self-any-match $r

          $rep_info $r/self-any-match >'name:**build2.org'
        }

        : second-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/)
          mkdir -p $r
          cp -r $rep/self-any-match $r

          $rep_info $r/self-any-match >'name:**build2.org'
        }
      }}
    }}

    : subdomain
    :
    {{
      : exact
      :
      $rep_info $rep/subdomain-match 2>>~%EOE% != 0
        %.*
        error: certificate name mismatch for repository pkg:build2.org/rep-auth/subdomain-match
          info: certificate name is *.build2.org
        EOE

      : subdomain
      :
      if! $remote
      {{
        : first-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/a.build2.org/)
          mkdir -p $r
          cp -r $rep/subdomain-match $r

          $rep_info $r/subdomain-match >'name:*.build2.org'
        }

        : second-level
        :
        {
          r = $canonicalize([dir_path] $~/pkg/1/b.a.build2.org/)
          mkdir -p $r
          cp -r $rep/subdomain-match $r

          $rep_info $r/subdomain-match 2>>~%EOE% != 0
            fetching pkg:b.a.build2.org/subdomain-match
            %querying .+subdomain-match%
            error: certificate name mismatch for repository pkg:b.a.build2.org/subdomain-match
              info: certificate name is *.build2.org
            EOE
        }
      }}
    }}
  }}
}}

: unsigned
:
{{
  : rep-fetch
  :
  {{
    +$clone_root_cfg && $rep_add $rep/unsigned1
    rep_fetch += --auth all

    : no-auth
    :
    {
      $clone_cfg

      $rep_fetch 2>>~%EOE% != 0
        fetching pkg:build2.org/rep-auth/unsigned1
        %querying .+unsigned1%
        warning: repository pkg:build2.org/rep-auth/unsigned1 is unsigned
        %continue without authenticating repositories at .+\? \[y/n\] %
        error: unable to read y/n answer from stdin
        EOE

      $not_fetched
    }

    : trust-yes
    :
    {
      $clone_cfg

      $rep_fetch --trust-yes 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/unsigned1
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched
    }

    : trust-no
    :
    {
      $clone_cfg

      $rep_fetch --trust-no 2>>~%EOE% != 0
        fetching pkg:build2.org/rep-auth/unsigned1
        %querying .+unsigned1%
        error: repository pkg:build2.org/rep-auth/unsigned1 is unsigned
        EOE

      $not_fetched
    }

    : already-trusted
    :
    {
      $clone_cfg

      $rep_fetch --trust-yes 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/unsigned1
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $rep_fetch 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/unsigned1
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched

      $rep_fetch --trust-no 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/unsigned1
        %.*
        1 package(s) in 1 repository(s)
        EOE

      $fetched

      $rep_add $rep/unsigned2

      $rep_fetch 2>>~%EOE%
        fetching pkg:build2.org/rep-auth/unsigned1
        %.*
        fetching pkg:build2.org/rep-auth/unsigned2
        %.*
        1 package(s) in 2 repository(s)
        EOE

      $fetched
    }
  }}

  : rep-info
  :
  {{
    rep_info += --name --auth all $rep/unsigned1

    : no-auth
    :
    $rep_info 2>>~%EOE% != 0
      %.*
      warning: repository pkg:build2.org/rep-auth/unsigned1 is unsigned
      %continue without authenticating repositories at .+\? \[y/n\] %
      error: unable to read y/n answer from stdin
      EOE

    : trust-yes
    :
    $rep_info --trust-yes >>"EOO" 2>!
      pkg:build2.org/rep-auth/unsigned1 ($rep/unsigned1)
      EOO

    : trust-no
    :
    $rep_info --trust-no 2>>~%EOE% != 0
      %.*
      error: repository pkg:build2.org/rep-auth/unsigned1 is unsigned
      EOE

    : already-trusted
    :
    {
      $clone_root_cfg
      rep_info += -d cfg

      $rep_info --trust-yes >>"EOO" 2>!
        pkg:build2.org/rep-auth/unsigned1 ($rep/unsigned1)
        EOO
      $rep_info >>"EOO" 2>!
        pkg:build2.org/rep-auth/unsigned1 ($rep/unsigned1)
        EOO
    }
  }}
}}

: faulty
:
{{
  rep_info += --auth all --trust-yes

  : name-mismatch
  :
  $rep_info $rep/name-mismatch 2>>~%EOE% != 0
    %.*
    error: certificate name mismatch for repository pkg:build2.org/rep-auth/name-mismatch
      info: certificate name is build2.org/mismatched/name
    EOE

  : expired
  :
  $rep_info $rep/expired 2>>~%EOE% != 0
    %.*
    error: certificate for repository pkg:build2.org/rep-auth/expired has expired
    EOE

  : sha256sum-mismatch
  :
  $rep_info $rep/sha256sum-mismatch 2>>~%EOE% != 0
    %.*
    error: packages manifest file checksum mismatch for pkg:build2.org/rep-auth/sha256sum-mismatch
      info: consider retrying this operation if this is a transient error
      info: consider reporting this to repository maintainers if this is a persistent error
    EOE

  : signature-mismatch
  :
  $rep_info $rep/signature-mismatch 2>>~%EOE% != 0
    %.*
    %error: unable to authenticate repository pkg:build2.org/rep-auth/signature-mismatch: .*%
    EOE

  : create-rep
  :
  {{
    : no-email
    :
    {
      cp -r $src/unsigned rep

      echo 'certificate:\'                    >+rep/repositories.manifest
      cat  <<<$src_base/auth/noemail-cert.pem >+rep/repositories.manifest
      echo '\'                                >+rep/repositories.manifest

      $rep_create --key $key rep &rep/packages.manifest 2>>/EOE != 0
        added foo 1
        error: invalid certificate for rep/: no email
        EOE
    }

    : expired
    :
    {
      cp -r $src/unsigned rep

      echo 'certificate:\'                    >+rep/repositories.manifest
      cat  <<<$src_base/auth/expired-cert.pem >+rep/repositories.manifest
      echo '\'                                >+rep/repositories.manifest

      $rep_create --key $key rep &rep/packages.manifest 2>>/EOE != 0
        added foo 1
        error: certificate for repository rep/ has expired
        EOE
    }
  }}
}}

: fetch-cache
:
{{
  # Enable the test-specific fetch caches.
  #
  options_guard = $~/.build2
  +mkdir $options_guard
  +echo '--no-default-options' >=$options_guard/bpkg.options
  +export -u BPKG_FETCH_CACHE
  rep_fetch += --fetch-cache-path cache

  rep_fetch += --auth all

  +$cfg_create -d cfg 2>! &cfg/***

  : signed
  :
  {
    $clone_cfg && $rep_add $rep/signed

    $rep_fetch --trust-yes &cache/*** 2>>~%EOE%
      fetching pkg:build2.org/rep-auth/signed
      %querying .+signed%
      %.*
      1 package(s) in 1 repository(s)
      EOE

    rm -r cfg cache/pkg/metadata

    $clone_cfg && $rep_add $rep/signed

    # Make sure we print the 'trusting certificate...' progress.
    #
    $rep_fetch &cfg/.bpkg/certs/* 2>>~%EOE%
      fetching pkg:build2.org/rep-auth/signed
      %querying .+signed%
      trusting certificate for repository pkg:build2.org/rep-auth/signed (cache)
      %.*
      1 package(s) in 1 repository(s)
      EOE

    rm -r cache/pkg/metadata

    # Make sure we don't print the 'trusting certificate...' progress since
    # the certificate comes from the configuration.
    #
    $rep_fetch 2>&1 | set out

    sed -n -e 's%^(trusting certificate for repository .+)$%\1%p' <$out >:''
  }

  : unsigned
  :
  {
    $clone_cfg && $rep_add $rep/unsigned1

    $rep_fetch --trust-yes &cache/*** 2>>~%EOE%
      fetching pkg:build2.org/rep-auth/unsigned1
      %querying .+unsigned1%
      %.*
      1 package(s) in 1 repository(s)
      EOE

    rm -r cfg cache/pkg/metadata

    $clone_cfg && $rep_add $rep/unsigned1

    # Make sure we print the 'trusting unsigned...' progress.
    #
    $rep_fetch 2>>~%EOE%
      fetching pkg:build2.org/rep-auth/unsigned1
      %querying .+unsigned1%
      trusting unsigned repository pkg:build2.org/rep-auth/unsigned1 (cache)
      %.*
      1 package(s) in 1 repository(s)
      EOE

    rm -r cache/pkg/metadata

    # Make sure we don't print the 'trusting unsigned...' progress since the
    # dummy certificate comes from the configuration.
    #
    $rep_fetch 2>&1 | set out

    sed -n -e 's%^(trusting unsigned repository .+)$%\1%p' <$out >:''
  }
}}
