#!/usr/bin/env bash
# written by Kyle Butler
# requires jq to be installed
# retrieves all errors (suppressed, passed) for Prisma Cloud Code Security across all onboarded projects and creates a report in csv format
# no user configuration needed
# will take a while to complete


source ./secrets/secrets
source ./func/func.sh


REPORT_DATE=$(date  +%m_%d_%y)

# function to refresh api token
retrieve_token () {
AUTH_PAYLOAD=$(cat <<EOF
{"username": "$PC_ACCESSKEY", "password": "$PC_SECRETKEY"}
EOF
)

PC_JWT_RESPONSE=$(curl --request POST \
                       --url "$PC_APIURL/login" \
                       --header 'Accept: application/json; charset=UTF-8' \
                       --header 'Content-Type: application/json; charset=UTF-8' \
                       --data "${AUTH_PAYLOAD}")



PC_JWT=$(printf %s "$PC_JWT_RESPONSE" | jq -r '.token' )
}

retrieve_token

# provides the repositories
REPOSITORY_LIST_REQUEST=$(curl --request GET \
                               --url "$PC_APIURL/code/api/v1/repositories" \
                               --header "content-type: applicaion/json; charset=UTF-8" \
                               --header "x-redlock-auth: $PC_JWT")


printf '%s' "$REPOSITORY_LIST_REQUEST" | jq '[.[] |{source: .source, ownerRepository: (.owner + "/" + .repository)}]' > ./temp/repository_list_$REPORT_DATE.json



REPOSITORY_LIST_ARRAY=( $(cat ./temp/repository_list_$REPORT_DATE.json | jq -r '.[]| .ownerRepository' ) )

REPO_INDEX=$( cat temp/repository_list_$REPORT_DATE.json | jq '.|length')

for index in $(seq 0 $(($REPO_INDEX -1))); do \

mkdir ./temp/$(printf '%05d' "$index")

SOURCE_TYPE=$(cat temp/repository_list_$REPORT_DATE.json | jq --argjson index "$index" '.[$index] | .source')
REPOSITORY=$(cat temp/repository_list_$REPORT_DATE.json | jq --argjson index "$index" '.[$index] | .ownerRepository')

ERROR_FILES_PAYLOAD=$(cat <<EOF
{
     "sourceTypes": [
          $SOURCE_TYPE
     ],
     "repository": $REPOSITORY,
     "categories": ["Vulnerabilities"]
}
EOF
)

# shows all the files with errors across repos
curl -s --request POST \
        --url "$PC_APIURL/code/api/v1/errors/files" \
        --header "authorization: $PC_JWT" \
        --header 'content-type: application/json' \
        --data "$ERROR_FILES_PAYLOAD" > ./temp/error_file_response_$(printf '%05d' "$index").json

FILE_PATH_ARRAY=($(cat ./temp/error_file_response_$(printf '%05d' "$index").json | jq '.data[].filePath' ))

FILE_ERROR_COUNT_ARRAY=($(cat ./temp/error_file_response_$(printf '%05d' "$index").json | jq '.data[].errorsCount' ))

if ! (( $index % 100)); then \
  retrieve_token
fi

for file_path in "${!FILE_PATH_ARRAY[@]}"; do

ERROR_FILE_PATH_PAYLOAD=$(cat <<EOF
{
  "sourceTypes": [
    $SOURCE_TYPE
  ],
    "repository": $REPOSITORY,
    "filePath": ${FILE_PATH_ARRAY[file_path]},
    "categories": ["Vulnerabilities"]
  }
EOF
)

for offset in $(seq 0 100 "${FILE_ERROR_COUNT_ARRAY[file_path]}"); do

echo "${FILE_ERROR_COUNT_ARRAY[file_path]} total errors in ${FILE_PATH_ARRAY[file_path]}. Request offset: $offset"

# pulls every error in each file
curl -s --request POST \
     --url "$PC_APIURL/code/api/v1/errors/file?limit=100&offset=$offset" \
     --header "authorization: $PC_JWT" \
     --header 'content-type: application/json' \
     --data "$ERROR_FILE_PATH_PAYLOAD" > "./temp/$(printf '%05d' "$index")/$(printf '%05d%05d' "$file_path" "$offset").json"&
   done
  done

wait
echo "collecting error data please wait for this to finish"


if [ -d "./temp/$(printf '%05d' $index)" ]
then
  if [ "$(ls -A ./temp/$(printf '%05d' $index))" ]; then
   cat ./temp/$(printf '%05d' $index)/*.json | jq --arg REPO "${REPOSITORY_LIST_ARRAY[$index]}" '.data[] | {repo: $REPO, fileMetadataUuid, filePath, sourceType, frameworkType, status, author, date, runtimeId, errorId, scannerType, cve: .cves[]}' > "./temp/finished_$(printf '%05d' "$index").json"
 fi
else
 echo "Directory ./temp/$repo not found."
fi

done

cat ./temp/finished_* | jq '{repo, fileMetadataUuid, filePath, sourceType, frameworkType, status, author, date, runtimeid, errorId, scannerType, cveUuid: .cve.uuid, cveStatus: .cve.cveStatus, vector: .cve.vector, severity: .cve.severity, cvss: .cve.cvss, description: .cve.description, link: .cve.link, packageName:  .cve.packageName, packageVersion: .cve.packageVersion, imageName: .cve.imageName, cveId: .cve.cveId, violationId: .cve.violationId, riskFactorDos: .cve.riskFactors.DoS?, riskFactorHasFix: .cve.riskFactors.HasFix?, riskFactorAttackVector: .cve.riskFactors.AttackVector?, riskFactorAttackComplexity: .cve.riskFactors.AttackComplexity?, riskFactorRecentVulnerability: .cve.riskFactors.RecentVulnerability?, publishedDate: .cve.publishedDate, isPrivateFix: .cve.isPrivateFix, status: .cve.status, fixVersion: .cve.fixVersion, packageId: .cve.packageId, causePackagename: .cve.causePackageName, causePackageVersion: .cve.causePackageVersion, registryUrl: .cve.registryUrl, errorLines: .cve.errorLines}' > ./temp/all_errors_$REPORT_DATE.json

PACKAGE_ARRAY=($(cat ./temp/all_errors_$REPORT_DATE.json | jq -r '.fileMetadataUuid' | sort | uniq))

mkdir ./temp/package

for package in "${!PACKAGE_ARRAY[@]}"; do \

if ! (( $package % 100)); then \
  retrieve_token
fi

mkdir ./temp/package/$(printf '%05d' "$package")

# license information
  for ((package_offset=0; ; package_offset=package_offset+100)); do \
    echo "making request to: $PC_APIURL/code/api/v1/vulnerabilities/packages/files/${PACKAGE_ARRAY[package]}?offset=$package_offset&limit=100"
    curl --request GET \
         --url "$PC_APIURL/code/api/v1/vulnerabilities/packages/files/${PACKAGE_ARRAY[package]}?offset=$package_offset&limit=100" \
         --header "authorization: $PC_JWT" \
         --header "Content-Type: application/json" > ./temp/package/$(printf '%05d' "$package")/$(printf '%05d%05d' "$package" "$package_offset").json
    package_pagination=$(cat ./temp/package/$(printf '%05d' "$package")/$(printf '%05d%05d' "$package" "$package_offset").json | jq '.hasNext')
    if [[ "$package_pagination" == false ]]
      then break
    fi
  done
  cat ./temp/package/$(printf '%05d' "$package")/*.json > ./temp/package/finished_$(printf '%05d' "$package").json
done



echo "Combining data this will take a while. Please don't quit the process"

cat ./temp/package/finished_*.json | jq '.packages[] | {packageId, packageName, packageVersion, packageLicenses: (.packageLicenses| @sh), fileMetadataUuid}' > "./temp/finished_package_data_$REPORT_DATE.json"

cat ./temp/all_errors_$REPORT_DATE.json |  jq '[inputs] | map({repo, filePath, sourceType, frameworkType, status, author, date, runtimeId, errorId, scannerType, cveUuid, cveStatus, vector, severity, cvss, description, link, packageName, packageVersion, imageName, cveId, violationId, riskFactorDos, riskFactorHasFix, riskFactorAttackVector, riskFactorAttackComplexity, riskFactorRecentVulnerability, publishedDate, isPrivateFix, status, fixVersion, packageId, causePackagename, causePackageVersion, registryUrl, packageUuid: [(.packageId as $packageId | $package_data |..| select(.packageId? and .packageId==$packageId))]})' --slurpfile package_data "./temp/finished_package_data_$REPORT_DATE.json" > "./temp/combined_package_error_$REPORT_DATE.json"

cat ./temp/combined_package_error_$REPORT_DATE.json | jq -r '[.[] | {repo, filePath, sourceType, frameworkType, status, author, date, runtimeId, errorId, scannerType, cveUuid, cveStatus, vector, severity, cvss, description, link, packageName, packageVersion, imageName, cveId, violationId, riskFactorDos, riskFactorHasFix, riskFactorAttackVector, riskFactorAttackComplexity, riskFactorRecentVulnerability, publishedDate, isPrivateFix, status, fixVersion, packageId, causePackagename, causePackageVersion, registryUrl, package: .packageUuid[]} | {repo, filePath, sourceType, frameworkType, status, author, date, runtimeId, errorId, scannerType, cveUuid, cveStatus, vector, severity, cvss, description, link, packageName, packageVersion, imageName, cveId, violationId, riskFactorDos, riskFactorHasFix, riskFactorAttackVector, riskFactorAttackComplexity, riskFactorRecentVulnerability, publishedDate, isPrivateFix, status, fixVersion, packageId, causePackagename, causePackageVersion, registryUrl, packageLicense: .package.packageLicenses}] | map({repo, filePath, sourceType, frameworkType, status, author, date, runtimeId, errorId, scannerType, packageName, packageVersion, packageLicense, fileMetadataUuid, packageId, cveStatus, cveId, cvss, description, fixVersion, link, publishedDate, severity, vector, violationId, resourceId, sourceId, riskFactorDos, riskFactorHasFix, riskFactorAttackVector, riskFactorAttackComplexity, registryUrl})| (first | keys_unsorted) as $keys | map([to_entries[] | .value]) as $rows | $keys,$rows[] | @csv' > "./reports/code_security_vuln_report_$REPORT_DATE.csv"

printf '\n%s\n' "All done your report is in the reports directory saved as: ./reports/code_security_vuln_report_$REPORT_DATE.csv"

# remove the lines below if you want to keep the response data
{
rm -rf ./temp/*
}
exit
