From c5cc687dcf711bbfea786e5d5cb1476c80b5de71 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 12 Jan 2025 19:57:09 -0500 Subject: [PATCH] Ok we have two working agents now as examples modified: .gitignore modified: README.rst modified: app.py modified: templates/login.html.j2 modified: templates/verify.html.j2 deleted: test_agent.sh new file: test_jwt_owner_agent.sh new file: test_otp_cookie_agent.sh --- .gitignore | 2 + README.rst | 2 +- app.py | 1 + templates/login.html.j2 | 1 + templates/verify.html.j2 | 1 + test_agent.sh | 150 -------------------- test_jwt_owner_agent.sh | 214 +++++++++++++++++++++++++++++ test_otp_cookie_agent.sh | 290 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 510 insertions(+), 151 deletions(-) delete mode 100644 test_agent.sh create mode 100644 test_jwt_owner_agent.sh create mode 100644 test_otp_cookie_agent.sh diff --git a/.gitignore b/.gitignore index d0a045b..343c4df 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ env/ __pycache__/ cookies.txt data/ + +*.html diff --git a/README.rst b/README.rst index 7d57101..b758319 100644 --- a/README.rst +++ b/README.rst @@ -126,7 +126,7 @@ PyraFiles uses a passwordless login system for users: JWT Agent Authentication ======================== -Agents authenticate with PyraFiles using JWT tokens, allowing programmatic interaction: +Agents authenticate with PyraFiles using Cookies or JWT tokens, allowing programmatic interaction: 1. **Generate Agent JWT** diff --git a/app.py b/app.py index c417959..980b64b 100644 --- a/app.py +++ b/app.py @@ -229,6 +229,7 @@ def generate_jwt_token(agent): "agent_id": agent.id, "agent_name": agent.name, "namespace_id": agent.namespace_id, + "namespace_short_id": agent.namespace.short_id, "role": agent.role, "token_version": agent.token_version, "iat": datetime.datetime.utcnow(), diff --git a/templates/login.html.j2 b/templates/login.html.j2 index 085aa1a..afe198f 100644 --- a/templates/login.html.j2 +++ b/templates/login.html.j2 @@ -7,6 +7,7 @@

Enter your email to receive a 6-digit verification code.

+
diff --git a/templates/verify.html.j2 b/templates/verify.html.j2 index 90c143c..274237b 100644 --- a/templates/verify.html.j2 +++ b/templates/verify.html.j2 @@ -7,6 +7,7 @@

A 6-digit code has been sent to your email. Please enter it below to verify your account.

+ input type="hidden" name="csrf_token" value="{{ request.session.get_csrf_token() }}">
diff --git a/test_agent.sh b/test_agent.sh deleted file mode 100644 index ed20a21..0000000 --- a/test_agent.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash -# -# A Bash script to demonstrate and test multiple authenticated routes -# in the pyrafiles application (or a similar Pyramid-based app). -# -# This script: -# 1. Checks if cookies.txt is older than a defined threshold (default 1 year). -# - If too old, it deletes cookies.txt so we force re-login. -# 2. If cookies.txt is still valid or absent, proceeds to the next steps: -# - If cookies.txt does not exist or was removed, it does the email + OTP flow. -# - If cookies.txt exists and is fresh, it reuses that session. -# 3. Fetches and prints the Profile page (GET /auth/profile) as a quick test. -# 4. Uploads a sample media file (POST /media/upload), marked public. -# 5. Parses the server's redirect "Location" header to extract user_short_id and media_short_id. -# 6. Views media details (GET /media///details). -# 7. Edits the newly uploaded media (POST /media///edit). -# 8. Deletes the media (POST /media///delete). -# 9. Lists all public media (GET /media/list). -# 10. Logs out (GET /auth/logout). -# -# The script exits on the first command error (set -e). -# Adjust BASE_URL, EMAIL, and file paths as needed. -# -# Usage: -# MAX_COOKIE_AGE= ./test_all_routes.sh /path/to/somefile.jpg -# By default, MAX_COOKIE_AGE=31536000 (1 year). -# If cookies.txt is younger than that, we skip re-login. Otherwise, we do the OTP flow. -# -# By default, uses http://localhost:6544 and agent@example.com - -set -e # Exit on first error - -BASE_URL="${BASE_URL:-http://localhost:6544}" -EMAIL="${EMAIL:-agent@example.com}" -MEDIA_FILE="$1" -MAX_COOKIE_AGE="${MAX_COOKIE_AGE:-31536000}" # default ~1 year in seconds - -if [[ -z "$MEDIA_FILE" ]]; then - echo "Usage: MAX_COOKIE_AGE= $0 /path/to/mediafile" - exit 1 -fi - -echo "=== Checking cookies.txt freshness ===" - -COOKIE_FRESH=0 -if [[ -f cookies.txt ]]; then - # 'stat -c %Y' returns the file modification time on Linux - # 'stat -f %m' does the same on BSD/macOS - MOD_TIME=$(stat -c %Y cookies.txt 2>/dev/null || stat -f %m cookies.txt 2>/dev/null) - NOW=$(date +%s) - AGE=$((NOW - MOD_TIME)) - - echo "cookies.txt is $AGE seconds old, threshold is $MAX_COOKIE_AGE." - if (( AGE < MAX_COOKIE_AGE )); then - COOKIE_FRESH=1 - echo "Reusing existing cookies.txt (fresh enough)." - else - echo "cookies.txt is too old. Removing it to force re-login." - rm -f cookies.txt - fi -else - echo "No cookies.txt found. A new session will be created." -fi - -if [[ "$COOKIE_FRESH" -eq 0 ]]; then - echo "" - echo "=== Starting new session and logging in with OTP flow ===" - curl -s -i -c cookies.txt -b cookies.txt "$BASE_URL/" >/dev/null - - echo "Logging in with email: $EMAIL (POST /auth/login)" - curl -s -i -c cookies.txt -b cookies.txt \ - -X POST \ - -F "email=$EMAIL" \ - "$BASE_URL/auth/login" - - echo "" - echo "Check your server logs or email for the 6-digit verification code." - read -p "Enter the 6-digit code: " VERIFICATION_CODE - - echo "Verifying code (POST /auth/verify)" - curl -s -i -c cookies.txt -b cookies.txt \ - -X POST \ - -F "code=$VERIFICATION_CODE" \ - "$BASE_URL/auth/verify" - - echo "Login/OTP flow complete. cookies.txt saved." -fi - -echo "" -echo "=== Quick check: Fetching Profile page (GET /auth/profile) ===" -curl -i -c cookies.txt -b cookies.txt "$BASE_URL/auth/profile" - -echo "" -echo "=== Uploading media (POST /media/upload) as public ===" -UPLOAD_RESPONSE=$(curl -i -c cookies.txt -b cookies.txt \ - -X POST \ - -F "title=TestUploadByAgent" \ - -F "media_file=@${MEDIA_FILE}" \ - -F "is_public=on" \ - "$BASE_URL/media/upload") - -echo "$UPLOAD_RESPONSE" -echo "" -echo "Parsing 'Location' header from upload response..." - -LOCATION_HEADER=$(echo "$UPLOAD_RESPONSE" | grep -i "^Location:" | head -n1 | sed 's/\r//g') -LOCATION_URL="${LOCATION_HEADER#Location: }" -LOCATION_URL=$(echo "$LOCATION_URL" | tr -d '[:space:]') -LOCATION_PATH="${LOCATION_URL#*//*/}" - -USER_SHORT_ID=$(echo "$LOCATION_PATH" | cut -d'/' -f2) -MEDIA_SHORT_ID=$(echo "$LOCATION_PATH" | cut -d'/' -f3) - -echo "user_short_id=$USER_SHORT_ID" -echo "media_short_id=$MEDIA_SHORT_ID" - -if [[ -z "$USER_SHORT_ID" || -z "$MEDIA_SHORT_ID" ]]; then - echo "Failed to parse user or media short ID. Exiting." - exit 1 -fi - -echo "" -echo "=== Viewing media details (GET /media/$USER_SHORT_ID/$MEDIA_SHORT_ID/details) ===" -curl -i -c cookies.txt -b cookies.txt \ - "$BASE_URL/media/$USER_SHORT_ID/$MEDIA_SHORT_ID/details" - -echo "" -echo "=== Editing media title to 'RenamedByAgent' (POST /media/$USER_SHORT_ID/$MEDIA_SHORT_ID/edit) ===" -curl -i -c cookies.txt -b cookies.txt \ - -X POST \ - -F "title=RenamedByAgent" \ - "$BASE_URL/media/$USER_SHORT_ID/$MEDIA_SHORT_ID/edit" - -echo "" -echo "=== Deleting the media (POST /media/$USER_SHORT_ID/$MEDIA_SHORT_ID/delete) ===" -curl -i -c cookies.txt -b cookies.txt \ - -X POST \ - "$BASE_URL/media/$USER_SHORT_ID/$MEDIA_SHORT_ID/delete" - -echo "" -echo "=== Listing all public media (GET /media/list) ===" -curl -i -c cookies.txt -b cookies.txt "$BASE_URL/media/list" - -echo "" -echo "=== Logging out (GET /auth/logout) ===" -curl -i -c cookies.txt -b cookies.txt "$BASE_URL/auth/logout" - -echo "" -echo "Test complete. We exercised multiple routes, including login/verify, profile, " -echo "upload/delete/edit media, and logout. If no errors appeared, all requests succeeded!" diff --git a/test_jwt_owner_agent.sh b/test_jwt_owner_agent.sh new file mode 100644 index 0000000..bc8b6f3 --- /dev/null +++ b/test_jwt_owner_agent.sh @@ -0,0 +1,214 @@ +#!/usr/bin/env bash + +set -euo pipefail +# Uncomment the next line for debugging +#set -x + +# A Bash script to test endpoints using a JWT token with owner privileges. +# All operations are performed on the namespace associated with the agent. + +# This script: +# 1. Extracts the namespace ID and namespace short ID from the JWT token. +# 2. Performs the following actions within that namespace: +# - Uploads media. +# - Edits and deletes the media. +# - Creates a temporary agent JWT. +# - Deletes the temporary agent using the agent_id from the agent JWT. +# 3. Does not invite a user. +# 4. Does not require any changes to the application. + +# Usage: +# JWT_TOKEN=your_owner_jwt_token ./jwt_owner_example.sh /path/to/mediafile.jpg + +# Prerequisites: +# - Replace 'your_owner_jwt_token' with a valid JWT token with 'owner' role. +# - `curl` and `python` must be installed. +# - Adjust BASE_URL as needed. + +# Variables +BASE_URL="${BASE_URL:-http://localhost:6544}" +JWT_TOKEN="${JWT_TOKEN:-}" +MEDIA_FILE="${1:-}" + +if [[ -z "$MEDIA_FILE" ]]; then + echo "Usage: JWT_TOKEN=your_owner_jwt_token $0 /path/to/mediafile.jpg" + exit 1 +fi + +if [[ -z "$JWT_TOKEN" ]]; then + echo "Error: JWT_TOKEN environment variable is not set." + exit 1 +fi + +# Function to URL-safe base64 decode +urlsafe_base64_decode() { + local input="$1" + local remainder=$(( ${#input} % 4 )) + if [ $remainder -eq 2 ]; then + input="${input}==" + elif [ $remainder -eq 3 ]; then + input="${input}=" + elif [ $remainder -eq 1 ]; then + input="${input}=" + fi + input=$(echo "$input" | tr '_-' '/+') + echo "$input" | base64 --decode 2>/dev/null || { + echo "Error: Failed to decode base64 input." + exit 1 + } +} + +echo "Extracting namespace IDs from JWT token..." + +# Extract the payload from the JWT +PAYLOAD_BASE64=$(echo "$JWT_TOKEN" | cut -d "." -f2) +if [[ -z "$PAYLOAD_BASE64" ]]; then + echo "Failed to extract payload from JWT token. Exiting." + exit 1 +fi + +# Decode the payload +PAYLOAD_JSON=$(urlsafe_base64_decode "$PAYLOAD_BASE64") +if [[ -z "$PAYLOAD_JSON" ]]; then + echo "Failed to decode JWT payload. Exiting." + exit 1 +fi + +# Use python to parse JSON and extract the necessary fields +NAMESPACE_ID=$(python -c "import sys, json; print(json.loads(sys.stdin.read()).get('namespace_id', ''))" <<< "$PAYLOAD_JSON") +NAMESPACE_SHORT_ID=$(python -c "import sys, json; print(json.loads(sys.stdin.read()).get('namespace_short_id', ''))" <<< "$PAYLOAD_JSON") + +if [[ -z "$NAMESPACE_ID" ]]; then + echo "Namespace ID not found in JWT token. Exiting." + exit 1 +fi + +if [[ -z "$NAMESPACE_SHORT_ID" ]]; then + echo "Namespace Short ID not found in JWT token. Exiting." + exit 1 +fi + +echo "Namespace ID: $NAMESPACE_ID" +echo "Namespace Short ID: $NAMESPACE_SHORT_ID" + +echo "" +echo "=== Uploading Media (POST /namespace/$NAMESPACE_SHORT_ID/media/upload) ===" +UPLOAD_RESPONSE=$(curl -s -i \ + -X POST \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -F "media_file=@${MEDIA_FILE}" \ + -F "title=SampleMediaByOwner" \ + -F "is_public=on" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/upload") + +echo "$UPLOAD_RESPONSE" + +echo "" +echo "Parsing 'Location' header to get the media short ID..." +MEDIA_SHORT_ID=$(echo "$UPLOAD_RESPONSE" | grep -Fi Location | tail -n 1 | awk -F '/' '{print $(NF-1)}' | tr -d '\r\n') +if [[ -z "$MEDIA_SHORT_ID" ]]; then + echo "Failed to obtain media short ID. Exiting." + exit 1 +fi +echo "Media Short ID: $MEDIA_SHORT_ID" + +echo "" +echo "=== Editing Media (POST /namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/edit) ===" +EDIT_RESPONSE=$(curl -s -i \ + -X POST \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -F "title=UpdatedByOwner" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/edit") + +echo "$EDIT_RESPONSE" + +if echo "$EDIT_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to edit media. You may not have permission." + exit 1 +fi + +echo "Media title updated." + +echo "" +echo "=== Deleting Media (POST /namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/delete) ===" +DELETE_RESPONSE=$(curl -s -i \ + -X POST \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/delete") + +echo "$DELETE_RESPONSE" + +if echo "$DELETE_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to delete media. You may not have permission." + exit 1 +fi + +echo "Media deleted." + +echo "" +echo "=== Generating Temporary Agent JWT (POST /namespace/$NAMESPACE_SHORT_ID/generate_agent_jwt) ===" +AGENT_NAME="TempAgent$(date +%s)" +GENERATE_JWT_RESPONSE=$(curl -s \ + -X POST \ + -H "Authorization: Bearer $JWT_TOKEN" \ + -F "agent_name=$AGENT_NAME" \ + -F "agent_role=editor" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/generate_agent_jwt") + +# Extract the agent JWT token from the HTML response +AGENT_JWT=$(echo "$GENERATE_JWT_RESPONSE" | grep -oP '(?<=
)[^<]*(?=
)') + +if [[ -z "$AGENT_JWT" ]]; then + echo "Failed to extract agent JWT token from response." + exit 1 +fi + +echo "Temporary agent '$AGENT_NAME' has been created." + +echo "" +echo "Extracting agent ID from agent JWT token..." + +# Extract the payload from the agent JWT +AGENT_PAYLOAD_BASE64=$(echo "$AGENT_JWT" | cut -d "." -f2) +if [[ -z "$AGENT_PAYLOAD_BASE64" ]]; then + echo "Failed to extract payload from agent JWT token. Exiting." + exit 1 +fi + +# Decode the payload +AGENT_PAYLOAD_JSON=$(urlsafe_base64_decode "$AGENT_PAYLOAD_BASE64") +if [[ -z "$AGENT_PAYLOAD_JSON" ]]; then + echo "Failed to decode agent JWT payload. Exiting." + exit 1 +fi + +# Extract the agent_id from the payload +AGENT_ID=$(python -c "import sys, json; print(json.loads(sys.stdin.read()).get('agent_id', ''))" <<< "$AGENT_PAYLOAD_JSON") + +if [[ -z "$AGENT_ID" ]]; then + echo "Failed to extract agent_id from agent JWT payload. Exiting." + exit 1 +fi + +echo "Agent ID: $AGENT_ID" + +echo "" +echo "=== Deleting Temporary Agent '$AGENT_NAME' ===" + +# Delete the temporary agent +DELETE_AGENT_RESPONSE=$(curl -s -i \ + -X POST \ + -H "Authorization: Bearer $JWT_TOKEN" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/agents/$AGENT_ID/delete") + +echo "$DELETE_AGENT_RESPONSE" + +if echo "$DELETE_AGENT_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to delete temporary agent '$AGENT_NAME'. You may not have permission." + exit 1 +fi + +echo "Temporary agent '$AGENT_NAME' has been deleted." + +echo "" +echo "Test complete. All actions performed successfully using a JWT token with owner privileges on the specified namespace." diff --git a/test_otp_cookie_agent.sh b/test_otp_cookie_agent.sh new file mode 100644 index 0000000..84c03c6 --- /dev/null +++ b/test_otp_cookie_agent.sh @@ -0,0 +1,290 @@ +#!/usr/bin/env bash + +set -euo pipefail +# Uncomment the next line for debugging +# set -x + +# A Bash script to test endpoints using cookie-based session authentication with OTP verification. +# All operations are performed on a namespace accessible to the authenticated user. + +# This script: +# 1. Authenticates a user by: +# - Sending a login request with an email address. +# - Prompting for the OTP code to complete verification. +# 2. Checks for existing namespaces; if none, creates a new one. +# 3. Performs the following actions within the namespace: +# - Uploads media. +# - Edits and deletes the media. +# - Creates a temporary agent. +# - Deletes the temporary agent. +# 4. Uses cookie-based session authentication for all requests. +# 5. Follows the routes specified in the provided OpenAPI specification. + +# Usage: +# ./test_otp_cookie_agent.sh [email] /path/to/mediafile.jpg + +# Prerequisites: +# - Replace 'email' with the user's email address. +# - 'curl' and 'python' must be installed. +# - Adjust BASE_URL as needed. + +# Variables +BASE_URL="${BASE_URL:-http://localhost:6544}" +EMAIL="${1:-}" +MEDIA_FILE="${2:-}" + +if [[ -z "$EMAIL" || -z "$MEDIA_FILE" ]]; then + echo "Usage: $0 [email] /path/to/mediafile.jpg" + exit 1 +fi + +# Temporary file to store cookies +COOKIE_JAR=$(mktemp) + +# Cleanup function to remove temporary files +cleanup() { + rm -f "$COOKIE_JAR" +} +trap cleanup EXIT + +echo "=== Starting Authentication Process ===" + +# Step 1: Send login request with email to receive OTP code +echo "Sending login request for email: $EMAIL" + +LOGIN_RESPONSE=$(curl -s -i \ + -X POST \ + -c "$COOKIE_JAR" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "email=$EMAIL" \ + "$BASE_URL/auth/login") + +# Check if the login request was successful +if echo "$LOGIN_RESPONSE" | grep -q "302 Found"; then + echo "Login request accepted. An OTP code has been sent to your email." +else + echo "Failed to initiate login process." + exit 1 +fi + +# Step 2: Prompt for OTP code +read -p "Enter the OTP code received via email: " code + +echo "Verifying OTP code..." + +# Step 3: Send OTP code to complete verification +VERIFY_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + -c "$COOKIE_JAR" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "code=$code" \ + "$BASE_URL/auth/verify") + +# Check if the verification was successful +if echo "$VERIFY_RESPONSE" | grep -q "302 Found"; then + echo "OTP verification successful. Authenticated." +else + echo "OTP verification failed. Please check the code and try again." + exit 1 +fi + +# Extract the session cookie +SESSION_COOKIE=$(grep -E 'session' "$COOKIE_JAR" | tail -n 1) +if [[ -z "$SESSION_COOKIE" ]]; then + echo "Failed to retrieve session cookie." + exit 1 +fi + +echo "Session cookie retrieved." + +echo "" +echo "=== Retrieving User Profile and Namespaces ===" + +# Step 4: Access user profile to retrieve owned namespaces +PROFILE_RESPONSE=$(curl -s \ + -b "$COOKIE_JAR" \ + "$BASE_URL/auth/profile") + +# Check if access to profile was successful +if [[ -z "$PROFILE_RESPONSE" ]]; then + echo "Failed to retrieve user profile." + exit 1 +fi + +echo "Retrieved user profile." + +# Extract namespace_short_ids from the profile page +# Exclude the 'create' link and any duplicates +NAMESPACE_SHORT_IDS=$(echo "$PROFILE_RESPONSE" | grep -oP 'href="[^"]+/namespace/\K[^/"]+(?=/manage")' | sort | uniq) + +if [[ -z "$NAMESPACE_SHORT_IDS" ]]; then + echo "No existing namespaces found. Creating a new namespace..." + # Create a new namespace + NEW_NAMESPACE_NAME="TestNamespace$(date +%s)" + CREATE_NAMESPACE_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + -F "name=$NEW_NAMESPACE_NAME" \ + -F "is_public=on" \ + "$BASE_URL/namespace/create") + + if echo "$CREATE_NAMESPACE_RESPONSE" | grep -q "302 Found"; then + echo "Namespace '$NEW_NAMESPACE_NAME' created successfully." + # Extract the new namespace_short_id from the 'Location' header + NEW_NAMESPACE_LOCATION=$(echo "$CREATE_NAMESPACE_RESPONSE" | grep -Fi Location | tail -n 1 | tr -d '\r\n') + NAMESPACE_SHORT_ID=$(echo "$NEW_NAMESPACE_LOCATION" | awk -F '/' '{print $(NF-1)}') + if [[ -z "$NAMESPACE_SHORT_ID" ]]; then + echo "Failed to extract new namespace_short_id from response." + exit 1 + fi + else + echo "Failed to create a new namespace." + exit 1 + fi +else + # Use the first existing namespace + NAMESPACE_SHORT_ID=$(echo "$NAMESPACE_SHORT_IDS" | head -n1) + echo "Namespace Short ID: $NAMESPACE_SHORT_ID" +fi + +echo "" +echo "=== Uploading Media (POST /namespace/$NAMESPACE_SHORT_ID/media/upload) ===" +UPLOAD_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + -F "media_file=@${MEDIA_FILE}" \ + -F "title=SampleMediaByUser" \ + -F "is_public=on" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/upload") + +echo "$UPLOAD_RESPONSE" + +echo "" +echo "Parsing 'Location' header to get the media short ID..." +MEDIA_SHORT_ID=$(echo "$UPLOAD_RESPONSE" | grep -Fi Location | tail -n 1 | awk -F '/' '{print $(NF-1)}' | tr -d '\r\n') +if [[ -z "$MEDIA_SHORT_ID" ]]; then + echo "Failed to obtain media short ID. Exiting." + exit 1 +fi +echo "Media Short ID: $MEDIA_SHORT_ID" + +echo "" +echo "=== Editing Media (POST /namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/edit) ===" +EDIT_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + -F "title=UpdatedByUser" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/edit") + +echo "$EDIT_RESPONSE" + +if echo "$EDIT_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to edit media. You may not have permission." + exit 1 +fi + +echo "Media title updated." + +echo "" +echo "=== Deleting Media (POST /namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/delete) ===" +DELETE_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/media/$MEDIA_SHORT_ID/delete") + +echo "$DELETE_RESPONSE" + +if echo "$DELETE_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to delete media. You may not have permission." + exit 1 +fi + +echo "Media deleted." + +echo "" +echo "=== Generating Temporary Agent (POST /namespace/$NAMESPACE_SHORT_ID/generate_agent_jwt) ===" +AGENT_NAME="TempAgent$(date +%s)" +GENERATE_AGENT_RESPONSE=$(curl -s \ + -X POST \ + -b "$COOKIE_JAR" \ + -F "agent_name=$AGENT_NAME" \ + -F "agent_role=editor" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/generate_agent_jwt") + +# Extract the agent JWT token from the HTML response +AGENT_JWT=$(echo "$GENERATE_AGENT_RESPONSE" | grep -oP '(?<=
)[^<]*(?=
)') + +if [[ -z "$AGENT_JWT" ]]; then + echo "Failed to extract agent JWT token from response." + exit 1 +fi + +echo "Temporary agent '$AGENT_NAME' has been created." + +echo "" +echo "Extracting agent ID from agent JWT token..." + +# Function to URL-safe base64 decode +urlsafe_base64_decode() { + local input="$1" + local remainder=$(( ${#input} % 4 )) + if [ $remainder -eq 2 ]; then + input="${input}==" + elif [ $remainder -eq 3 ]; then + input="${input}=" + elif [ $remainder -eq 1 ]; then + input="${input}=" + fi + input=$(echo "$input" | tr '_-' '/+') + echo "$input" | base64 --decode 2>/dev/null || { + echo "Error: Failed to decode base64 input." + exit 1 + } +} + +# Extract the payload from the agent JWT +AGENT_PAYLOAD_BASE64=$(echo "$AGENT_JWT" | cut -d "." -f2) +if [[ -z "$AGENT_PAYLOAD_BASE64" ]]; then + echo "Failed to extract payload from agent JWT token. Exiting." + exit 1 +fi + +# Decode the payload +AGENT_PAYLOAD_JSON=$(urlsafe_base64_decode "$AGENT_PAYLOAD_BASE64") +if [[ -z "$AGENT_PAYLOAD_JSON" ]]; then + echo "Failed to decode agent JWT payload. Exiting." + exit 1 +fi + +# Extract the agent_id from the payload +AGENT_ID=$(python -c "import sys, json; print(json.loads(sys.stdin.read()).get('agent_id', ''))" <<< "$AGENT_PAYLOAD_JSON") + +if [[ -z "$AGENT_ID" ]]; then + echo "Failed to extract agent_id from agent JWT payload. Exiting." + exit 1 +fi + +echo "Agent ID: $AGENT_ID" + +echo "" +echo "=== Deleting Temporary Agent '$AGENT_NAME' ===" + +# Revoke (delete) the temporary agent +DELETE_AGENT_RESPONSE=$(curl -s -i \ + -X POST \ + -b "$COOKIE_JAR" \ + -F "agent_id=$AGENT_ID" \ + "$BASE_URL/namespace/$NAMESPACE_SHORT_ID/revoke_agent") + +echo "$DELETE_AGENT_RESPONSE" + +if echo "$DELETE_AGENT_RESPONSE" | grep -q "403 Forbidden"; then + echo "Error: Failed to delete temporary agent '$AGENT_NAME'. You may not have permission." + exit 1 +fi + +echo "Temporary agent '$AGENT_NAME' has been deleted." + +echo "" +echo "Test complete. All actions performed successfully using cookie-based session authentication on the specified namespace."