Skip to content

Commit 8268769

Browse files
authored
Merge pull request #980 from semidark/cookie-based-auth
Add cookie‑based browser session auth for SharePoint Online (ClientContext.with_cookies)
2 parents 8f6ce06 + 484fd04 commit 8268769

File tree

16 files changed

+448
-9
lines changed

16 files changed

+448
-9
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export office365_python_sdk_securevars="{username};{password};{client_id};{client_secret}"\n

.github/workflows/python-app.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,13 @@ jobs:
6161
python -m pip install --upgrade pip
6262
pip install -r requirements.txt
6363
pip install -r requirements-dev.txt
64-
- name: Test with pytest
64+
- name: Test with pytest (skip entirely if secrets missing)
6565
env:
6666
office365_python_sdk_securevars: ${{ secrets.OFFICE365_PYTHON_SDK_SECUREVARS }}
6767
run: |
68-
echo "${{env.office365_python_sdk_securevars}}"
69-
pytest
68+
if [ -z "${{env.office365_python_sdk_securevars}}" ]; then \
69+
echo "No secrets available; skipping pytest"; \
70+
exit 0; \
71+
else \
72+
pytest; \
73+
fi

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,9 @@ Pipfile.lock
8282

8383
# Self signed certificates
8484
examples/*.pem
85+
86+
# Playwright
87+
storage_state.json
88+
89+
# Cursor
90+
.cursorignore

CONTRIBUTING.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
## Welcome Contributors! 🎉
2+
3+
Thank you for your interest in contributing to the Office365-REST-Python-Client library. This project provides a comprehensive Python client for Microsoft 365 and Microsoft Graph APIs.
4+
5+
## Table of Contents
6+
7+
1. Getting Started
8+
2. Development Environment Setup
9+
3. Code Style and Quality Standards
10+
4. Testing Guidelines
11+
5. Submitting Changes
12+
6. Issue Reporting
13+
7. Documentation
14+
8. Community Guidelines
15+
16+
## Getting Started
17+
18+
### Prerequisites
19+
20+
- Python 3.6+
21+
- Git
22+
- A Microsoft 365 tenant for testing (recommended)
23+
- Basic understanding of REST APIs and Microsoft Graph/SharePoint APIs
24+
25+
### Fork and Clone
26+
27+
1. Fork the repository on GitHub
28+
2. Clone your fork locally:
29+
30+
```bash
31+
git clone https://github.com/your-username/Office365-REST-Python-Client.git
32+
cd Office365-REST-Python-Client
33+
```
34+
35+
## Development Environment Setup
36+
37+
### Virtual Environment
38+
39+
```bash
40+
python3 -m venv venv
41+
. venv/bin/activate # On Windows: venv\Scripts\activate
42+
```
43+
44+
### Install Dependencies
45+
46+
```bash
47+
pip install -r requirements.txt
48+
pip install -r requirements-dev.txt
49+
```
50+
51+
### Pre-commit hooks (recommended)
52+
53+
```bash
54+
pip install pre-commit
55+
pre-commit install
56+
pre-commit run -a
57+
```
58+
59+
## Code Style and Quality Standards
60+
61+
The project uses the following tools (mirroring CI):
62+
63+
- Black (formatting)
64+
- Ruff (linting and import sorting)
65+
- Pylint (static analysis)
66+
67+
Line length: 121 characters (configured in `pyproject.toml`).
68+
69+
Run locally before pushing:
70+
71+
```bash
72+
black .
73+
ruff check .
74+
pylint office365
75+
```
76+
77+
## Testing Guidelines
78+
79+
Most tests are end-to-end and require actual Microsoft 365 credentials.
80+
81+
### Test Configuration
82+
83+
1. Create a `.env` file in the project root:
84+
85+
```bash
86+
export office365_python_sdk_securevars='{username};{password};{client_id};{client_secret}'
87+
```
88+
89+
2. Source the environment file:
90+
91+
```bash
92+
. .env
93+
```
94+
95+
Note: The order of values is significant because tests parse by index.
96+
97+
### Required Tenant Permissions
98+
99+
For comprehensive testing, your test tenant should have these admin roles:
100+
101+
- Global reader
102+
- Groups admin
103+
- Search admin
104+
- SharePoint admin
105+
- Teams service admin
106+
107+
### Running Tests
108+
109+
```bash
110+
pytest
111+
# or
112+
pytest -v
113+
# or a specific suite
114+
pytest tests/sharepoint/
115+
```
116+
117+
CI note: Full E2E tests in CI rely on repository secrets and may not run on forks. Please run tests locally; maintainers trigger full CI runs as needed.
118+
119+
### Forks and CI
120+
121+
- Forked pull requests do not receive repository secrets. The CI pipeline will run formatting and linting, and it will skip `pytest` automatically if secrets are unavailable.
122+
- To validate your changes, run tests locally using your own tenant credentials as described above.
123+
- Maintainers will run the full E2E test suite on branches with access to secrets before merging.
124+
125+
## Submitting Changes
126+
127+
### Branching Strategy
128+
129+
1. Create a feature branch from `master`:
130+
131+
```bash
132+
git checkout -b feature/your-feature-name
133+
```
134+
135+
2. Make your changes with clear, focused commits
136+
3. Ensure all tests pass and quality checks are satisfied
137+
138+
### Pull Request Process
139+
140+
1. CI Checks must pass:
141+
- Ruff linting
142+
- Black formatting
143+
- Pylint analysis
144+
- Pytest execution
145+
2. Await maintainer review
146+
3. Update documentation where applicable
147+
148+
### Commit Guidelines
149+
150+
- Use clear, descriptive commit messages
151+
- Reference issue numbers when applicable
152+
- Keep commits focused and atomic
153+
154+
## Issue Reporting
155+
156+
Before filing an issue:
157+
158+
1. Search existing issues
159+
2. Check documentation and examples
160+
3. Test with the latest version
161+
162+
Include in your report:
163+
164+
- Environment: Python version, OS, library version
165+
- Reproduction: minimal code example
166+
- Expected vs Actual behavior
167+
- Authentication method used
168+
- Targeted service area (SharePoint, Graph, etc.)
169+
170+
## Documentation
171+
172+
### API Coverage
173+
174+
The library supports multiple Microsoft 365 APIs, including SharePoint REST, Microsoft Graph, OneDrive, Outlook, Teams, OneNote, and Planner. See `examples/` for usage.
175+
176+
## Community Guidelines
177+
178+
This project is maintained by the community. Be respectful and constructive in all interactions.
179+
180+
### License
181+
182+
MIT License. By contributing, you agree that your contributions are licensed under these terms.
183+
184+

README-dev.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ So one has to configure his/her office/sharepoint credentials.
1515
To do so, create a file ```.env``` like this (replace the bracketed values by your values):
1616

1717
```
18-
export office365_python_sdk_securevars='{username};{password};{client_id};{client_password}'
18+
export office365_python_sdk_securevars='{username};{password};{client_id};{client_secret}'
1919
```
2020

2121
This file is in .gitignore, so it will never be committed.

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,29 @@ me = ctx.web.current_user.get().execute_query()
115115
print(me.login_name)
116116
```
117117

118+
#### 5. Browser session cookies (SharePoint Online)
119+
120+
Authenticate using cookies from a real browser session (e.g., Playwright). No Azure AD app registration required.
121+
122+
Usage:
123+
```python
124+
from office365.sharepoint.client_context import ClientContext
125+
126+
def cookie_source():
127+
# Return a dict with FedAuth/rtFa/SPOIDCRL values or an AuthCookies instance
128+
return {"FedAuth": "...", "rtFa": "...", "SPOIDCRL": "..."}
129+
130+
ctx = ClientContext("https://contoso.sharepoint.com/sites/demo").with_cookies(cookie_source, ttl_seconds=None)
131+
web = ctx.web.get().execute_query()
132+
print(web.title)
133+
```
134+
135+
Example: [auth_cookies.py](examples/sharepoint/auth_cookies.py)
136+
137+
Notes:
138+
- `ttl_seconds` is optional. Use it to periodically refresh cookies from your source if it’s cheap (e.g., file read).
139+
- Cookies are secrets. Do not log them; secure any storage (e.g., Playwright `storage_state.json`).
140+
118141
### Examples
119142

120143
There are **two approaches** available to perform API queries:
@@ -158,6 +181,7 @@ json = json.loads(response.content)
158181
web_title = json['d']['Title']
159182
print("Web title: {0}".format(web_title))
160183
```
184+
Tip: You can also authenticate `SharePointRequest` with cookies using `with_cookies(cookie_source, ttl_seconds=None)`.
161185

162186
For SharePoint-specific examples, see:
163187
📌 **[SharePoint examples guide](examples/sharepoint/README.md)**

examples/sharepoint/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,13 @@ This directory contains examples for SharePoint REST API v1
2424

2525
### Working with site
2626

27+
### Authentication using browser session cookies
28+
- **Authenticate with cookies**: [`../auth_cookies.py`](../auth_cookies.py)
29+
- Demonstrates loading `FedAuth`, `rtFa`, `SPOIDCRL` from Playwright `storage_state.json` and using `ClientContext.with_cookies(...)`.
30+
- Optional `ttl_seconds` parameter can periodically refresh cookies from the source.
31+
- **Capture cookies with Playwright (optional)**: [`./auth/capture_cookies_with_playwright.py`](./auth/capture_cookies_with_playwright.py)
32+
- Not a library dependency. Requires `pip install playwright` and `playwright install chromium`.
33+
- Launches a browser to log in, then saves `storage_state.json` which can be consumed by the cookie auth example.
34+
2735
---
2836

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""
2+
Acquire SharePoint Online browser-session cookies using Playwright and save them into
3+
storage_state.json (or a custom path), which can be consumed by examples/sharepoint/auth_cookies.py.
4+
5+
Requirements (not installed by the library):
6+
pip install playwright
7+
playwright install chromium
8+
9+
Usage:
10+
SP_SITE_URL="https://contoso.sharepoint.com/sites/demo" \
11+
PLAYWRIGHT_STORAGE_STATE="./storage_state.json" \
12+
HEADLESS=false \
13+
python examples/sharepoint/auth/capture_cookies_with_playwright.py
14+
15+
Notes:
16+
- The script opens a browser window. Complete the Microsoft login (including MFA) manually.
17+
- After login, return to the terminal and press Enter to persist cookies.
18+
- The resulting storage_state.json can be used by auth_cookies.py.
19+
"""
20+
21+
import os
22+
23+
from playwright.sync_api import sync_playwright
24+
25+
26+
def main() -> None:
27+
site_url = os.environ.get("SP_SITE_URL")
28+
if not site_url:
29+
raise SystemExit(
30+
"SP_SITE_URL is required, e.g. https://contoso.sharepoint.com/sites/demo"
31+
)
32+
33+
storage_state_path = os.environ.get(
34+
"PLAYWRIGHT_STORAGE_STATE", "./storage_state.json"
35+
)
36+
headless_env = os.environ.get("HEADLESS", "false").lower()
37+
headless = headless_env in ("1", "true", "yes")
38+
39+
with sync_playwright() as p:
40+
browser = p.chromium.launch(headless=headless)
41+
context = browser.new_context()
42+
page = context.new_page()
43+
page.goto(site_url)
44+
# Wait for network to be idle; login flow may redirect to Microsoft login pages
45+
page.wait_for_load_state("networkidle")
46+
47+
print(
48+
"\nA browser window is open. Complete the login (including MFA) if prompted."
49+
)
50+
input(
51+
"When the SharePoint page is fully loaded and you are authenticated, press Enter here to continue..."
52+
)
53+
54+
# Persist cookies and related state
55+
context.storage_state(path=storage_state_path)
56+
print(f"Saved Playwright storage state to: {storage_state_path}")
57+
58+
context.close()
59+
browser.close()
60+
61+
62+
if __name__ == "__main__":
63+
main()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import json
2+
import os
3+
from typing import Dict
4+
5+
from office365.sharepoint.client_context import ClientContext
6+
7+
8+
def load_cookies_from_storage_state(path: str) -> Dict[str, str]:
9+
"""Load cookies exported by Playwright storage_state.json and extract SPO cookies.
10+
11+
The library does not depend on Playwright; this is a helper to demonstrate usage.
12+
"""
13+
with open(path, "r", encoding="utf-8") as f:
14+
data = json.load(f)
15+
cookies = {}
16+
for c in data.get("cookies", []):
17+
name = c.get("name")
18+
if name in {"FedAuth", "rtFa", "SPOIDCRL"}:
19+
cookies[name] = c.get("value", "")
20+
return cookies
21+
22+
23+
if __name__ == "__main__":
24+
site_url = os.environ.get(
25+
"SP_SITE_URL", "https://contoso.sharepoint.com/sites/demo"
26+
)
27+
storage_state_path = os.environ.get(
28+
"PLAYWRIGHT_STORAGE_STATE", "./storage_state.json"
29+
)
30+
31+
def cookie_source():
32+
return load_cookies_from_storage_state(storage_state_path)
33+
34+
ctx = ClientContext(site_url).with_cookies(cookie_source)
35+
web = ctx.web.get().execute_query()
36+
print(f"Web title: {web.title}")

0 commit comments

Comments
 (0)