Skip to content

Commit e27d132

Browse files
committed
Improve security by removing hardcoded credentials and implement environment variable loading for test scripts
1 parent 16f3b50 commit e27d132

9 files changed

+895
-51
lines changed

README_TEST_SCRIPTS.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Test Scripts for Confluence V2 API
2+
3+
## Overview
4+
5+
These test scripts are used to test the Confluence V2 API implementation. They require credentials to connect to a Confluence instance.
6+
7+
## Setting Up Credentials
8+
9+
To run the test scripts, you need to set up your Confluence credentials.
10+
11+
### Step 1: Create a .env file
12+
13+
Create a `.env` file in the root directory of the project with the following format:
14+
15+
```
16+
CONFLUENCE_URL=https://your-instance.atlassian.net
17+
18+
CONFLUENCE_API_TOKEN=your-api-token
19+
CONFLUENCE_SPACE_KEY=SPACE
20+
```
21+
22+
Replace the values with your own credentials:
23+
- `CONFLUENCE_URL`: The URL of your Confluence instance
24+
- `CONFLUENCE_USERNAME`: Your Confluence username (usually an email)
25+
- `CONFLUENCE_API_TOKEN`: Your Confluence API token (can be generated in your Atlassian account settings)
26+
- `CONFLUENCE_SPACE_KEY`: The key of a space in your Confluence instance that you have access to
27+
28+
### Step 2: Install required packages
29+
30+
Make sure you have all required packages installed:
31+
32+
```
33+
pip install -r requirements-dev.txt
34+
```
35+
36+
### Step 3: Run the scripts
37+
38+
Now you can run the test scripts:
39+
40+
```
41+
python test_search.py
42+
python test_pages.py
43+
```
44+
45+
## Security Note
46+
47+
The `.env` file is listed in `.gitignore` to prevent accidentally committing your credentials to the repository. Never commit your credentials directly in code files.
48+
49+
If you need to find available spaces to use for testing, you can run:
50+
51+
```
52+
python get_valid_spaces.py
53+
```
54+
55+
This will output a list of spaces that you have access to, which can be used for the `CONFLUENCE_SPACE_KEY` environment variable.

atlassian/confluence_base.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,19 @@ def _get_paged(
209209
break
210210

211211
# Use the next URL directly
212-
url = next_url
213-
absolute = False
212+
# Check if the response has a base URL provided (common in Confluence v2 API)
213+
base_url = response.get("_links", {}).get("base")
214+
if base_url and next_url.startswith('/'):
215+
# Construct the full URL using the base URL from the response
216+
url = f"{base_url}{next_url}"
217+
absolute = True
218+
else:
219+
url = next_url
220+
# Check if the URL is absolute (has http:// or https://) or contains the server's domain
221+
if next_url.startswith(('http://', 'https://')) or self.url.split('/')[2] in next_url:
222+
absolute = True
223+
else:
224+
absolute = False
214225
params = {}
215226
trailing = False
216227

atlassian/confluence_v2.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ def __init__(self, url: str, *args, **kwargs):
3232
"""
3333
# Set API version to 2
3434
kwargs.setdefault('api_version', 2)
35+
36+
# Check if the URL already contains '/wiki'
37+
# This prevents a double '/wiki/wiki' issue when the parent class adds it again
38+
if ("atlassian.net" in url or "jira.com" in url) and ("/wiki" in url):
39+
# Remove the '/wiki' suffix since the parent class will add it
40+
url = url.rstrip("/")
41+
if url.endswith("/wiki"):
42+
url = url[:-5]
43+
3544
super(ConfluenceV2, self).__init__(url, *args, **kwargs)
3645
self._compatibility_method_mapping = {
3746
# V1 method => V2 method mapping
@@ -1980,10 +1989,44 @@ def get_whiteboard_ancestors(self, whiteboard_id: str) -> List[Dict[str, Any]]:
19801989
except Exception as e:
19811990
log.error(f"Failed to get ancestors for whiteboard {whiteboard_id}: {e}")
19821991
raise
1983-
1992+
1993+
def get_space_whiteboards(self,
1994+
space_id: str,
1995+
cursor: Optional[str] = None,
1996+
limit: int = 25) -> List[Dict[str, Any]]:
1997+
"""
1998+
Get all whiteboards in a space.
1999+
2000+
Args:
2001+
space_id: ID or key of the space
2002+
cursor: (optional) Cursor for pagination
2003+
limit: (optional) Maximum number of results to return (default: 25)
2004+
2005+
Returns:
2006+
List of whiteboards in the space
2007+
2008+
Raises:
2009+
HTTPError: If the API call fails
2010+
"""
2011+
endpoint = self.get_endpoint('whiteboard')
2012+
2013+
params = {
2014+
"spaceId": space_id,
2015+
"limit": limit
2016+
}
2017+
2018+
if cursor:
2019+
params["cursor"] = cursor
2020+
2021+
try:
2022+
return list(self._get_paged(endpoint, params=params))
2023+
except Exception as e:
2024+
log.error(f"Failed to get whiteboards for space {space_id}: {e}")
2025+
raise
2026+
19842027
"""
19852028
##############################################################################################
1986-
# Custom Content API v2 #
2029+
# Confluence Custom Content API (Cloud only) #
19872030
##############################################################################################
19882031
"""
19892032

get_valid_spaces.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
3+
import requests
4+
import os
5+
from dotenv import load_dotenv
6+
7+
# Load environment variables from .env file
8+
load_dotenv()
9+
10+
# Credentials from environment variables
11+
CONFLUENCE_URL = os.getenv("CONFLUENCE_URL")
12+
CONFLUENCE_USERNAME = os.getenv("CONFLUENCE_USERNAME")
13+
CONFLUENCE_API_TOKEN = os.getenv("CONFLUENCE_API_TOKEN")
14+
15+
# Check if environment variables are loaded
16+
if not all([CONFLUENCE_URL, CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN]):
17+
print("Error: Missing environment variables. Please create a .env file with the required variables.")
18+
exit(1)
19+
20+
print("Fetching available spaces...")
21+
response = requests.get(
22+
f"{CONFLUENCE_URL}/wiki/api/v2/spaces?limit=10",
23+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
24+
headers={"Accept": "application/json"}
25+
)
26+
27+
if response.status_code == 200:
28+
spaces = response.json().get("results", [])
29+
if spaces:
30+
print("\nAvailable spaces:")
31+
print("-------------------------")
32+
for i, space in enumerate(spaces, 1):
33+
print(f"{i}. Key: {space.get('key')}, Name: {space.get('name')}")
34+
else:
35+
print("No spaces found or you don't have access to any spaces.")
36+
else:
37+
print(f"Error fetching spaces: {response.status_code}")
38+
print(response.text)
39+
40+
print("\nUpdate your .env file or tests with a valid space key.")

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ coverage
1010
codecov
1111
# used for example confluence attach file
1212
python-magic
13+
python-dotenv
1314
pylint
1415
mypy>=0.812
1516
doc8

test_pages.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
3+
import requests
4+
import json
5+
import os
6+
from dotenv import load_dotenv
7+
8+
# Load environment variables from .env file
9+
load_dotenv()
10+
11+
# Credentials from environment variables
12+
CONFLUENCE_URL = os.getenv("CONFLUENCE_URL")
13+
CONFLUENCE_USERNAME = os.getenv("CONFLUENCE_USERNAME")
14+
CONFLUENCE_API_TOKEN = os.getenv("CONFLUENCE_API_TOKEN")
15+
SPACE_KEY = os.getenv("CONFLUENCE_SPACE_KEY")
16+
17+
# Check if environment variables are loaded
18+
if not all([CONFLUENCE_URL, CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN, SPACE_KEY]):
19+
print("Error: Missing environment variables. Please create a .env file with the required variables.")
20+
exit(1)
21+
22+
# Get pages with no space filtering
23+
print("Test 1: Getting pages with no filtering")
24+
response = requests.get(
25+
f"{CONFLUENCE_URL}/wiki/api/v2/pages",
26+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
27+
headers={"Accept": "application/json"},
28+
params={
29+
"limit": 5
30+
}
31+
)
32+
print(f"Status code: {response.status_code}")
33+
if response.status_code == 200:
34+
data = response.json()
35+
results = data.get("results", [])
36+
print(f"Found {len(results)} pages")
37+
if results:
38+
for i, page in enumerate(results, 1):
39+
print(f"{i}. ID: {page.get('id')}, Title: {page.get('title')}")
40+
space = page.get("space", {})
41+
print(f" Space Key: {space.get('key')}, Space Name: {space.get('name')}")
42+
else:
43+
print("No pages found.")
44+
else:
45+
print("Error:", response.text)
46+
47+
# Get specific space info
48+
print("\nTest 2: Get space info for TS")
49+
response = requests.get(
50+
f"{CONFLUENCE_URL}/wiki/api/v2/spaces",
51+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
52+
headers={"Accept": "application/json"},
53+
params={
54+
"keys": SPACE_KEY,
55+
"limit": 1
56+
}
57+
)
58+
print(f"Status code: {response.status_code}")
59+
if response.status_code == 200:
60+
data = response.json()
61+
results = data.get("results", [])
62+
print(f"Found {len(results)} spaces")
63+
if results:
64+
space = results[0]
65+
print(f"Space ID: {space.get('id')}")
66+
print(f"Space Key: {space.get('key')}")
67+
print(f"Space Name: {space.get('name')}")
68+
69+
# Now try getting pages with this space ID
70+
space_id = space.get('id')
71+
if space_id:
72+
print(f"\nGetting pages for space ID: {space_id}")
73+
page_response = requests.get(
74+
f"{CONFLUENCE_URL}/wiki/api/v2/pages",
75+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
76+
headers={"Accept": "application/json"},
77+
params={
78+
"space-id": space_id,
79+
"limit": 5
80+
}
81+
)
82+
print(f"Status code: {page_response.status_code}")
83+
if page_response.status_code == 200:
84+
page_data = page_response.json()
85+
page_results = page_data.get("results", [])
86+
print(f"Found {len(page_results)} pages in space {SPACE_KEY}")
87+
if page_results:
88+
for i, page in enumerate(page_results, 1):
89+
print(f"{i}. ID: {page.get('id')}, Title: {page.get('title')}")
90+
else:
91+
print("No pages found in this space.")
92+
else:
93+
print("Error getting pages:", page_response.text)
94+
else:
95+
print(f"No space found with key {SPACE_KEY}")
96+
else:
97+
print("Error getting space:", response.text)

test_search.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
3+
import requests
4+
import json
5+
import os
6+
from dotenv import load_dotenv
7+
8+
# Load environment variables from .env file
9+
load_dotenv()
10+
11+
# Credentials from environment variables
12+
CONFLUENCE_URL = os.getenv("CONFLUENCE_URL")
13+
CONFLUENCE_USERNAME = os.getenv("CONFLUENCE_USERNAME")
14+
CONFLUENCE_API_TOKEN = os.getenv("CONFLUENCE_API_TOKEN")
15+
SPACE_KEY = os.getenv("CONFLUENCE_SPACE_KEY")
16+
17+
# Check if environment variables are loaded
18+
if not all([CONFLUENCE_URL, CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN, SPACE_KEY]):
19+
print("Error: Missing environment variables. Please create a .env file with the required variables.")
20+
exit(1)
21+
22+
# Test with just a query
23+
print("Test 1: Search with simple query")
24+
query = "test"
25+
response = requests.get(
26+
f"{CONFLUENCE_URL}/wiki/api/v2/search",
27+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
28+
headers={"Accept": "application/json"},
29+
params={
30+
"query": query,
31+
"limit": 5,
32+
"content-type": "page"
33+
}
34+
)
35+
print(f"Status code: {response.status_code}")
36+
if response.status_code == 200:
37+
data = response.json()
38+
results = data.get("results", [])
39+
print(f"Found {len(results)} results")
40+
if results:
41+
print("First result title:", results[0].get("title"))
42+
else:
43+
print("Error:", response.text)
44+
45+
# Test with query and CQL
46+
print("\nTest 2: Search with query and CQL")
47+
response = requests.get(
48+
f"{CONFLUENCE_URL}/wiki/api/v2/search",
49+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
50+
headers={"Accept": "application/json"},
51+
params={
52+
"query": query,
53+
"cql": f'space="{SPACE_KEY}" AND type=page',
54+
"limit": 5,
55+
"content-type": "page"
56+
}
57+
)
58+
print(f"Status code: {response.status_code}")
59+
if response.status_code == 200:
60+
data = response.json()
61+
results = data.get("results", [])
62+
print(f"Found {len(results)} results")
63+
if results:
64+
print("First result title:", results[0].get("title"))
65+
else:
66+
print("Error:", response.text)
67+
68+
# Test with different approach - get pages in a space
69+
print("\nTest 3: Get pages in a space")
70+
response = requests.get(
71+
f"{CONFLUENCE_URL}/wiki/api/v2/pages",
72+
auth=(CONFLUENCE_USERNAME, CONFLUENCE_API_TOKEN),
73+
headers={"Accept": "application/json"},
74+
params={
75+
"space-id": SPACE_KEY,
76+
"limit": 5
77+
}
78+
)
79+
print(f"Status code: {response.status_code}")
80+
if response.status_code == 200:
81+
data = response.json()
82+
results = data.get("results", [])
83+
print(f"Found {len(results)} results")
84+
if results:
85+
print("First result title:", results[0].get("title"))
86+
else:
87+
print("Error:", response.text)

0 commit comments

Comments
 (0)