e190e40b07
The cookie login redirect and other absolute URLs are built from Request.Host; behind a proxy that doesn't forward the Host header that's the internal IP:port, so hitting the domain 302'd to the server IP. Rewrite scheme+host to App__Domain on every request (after UseForwardedHeaders) so all generated URLs stay on the public domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
135 lines
6.1 KiB
Markdown
135 lines
6.1 KiB
Markdown
# SharePoint Toolbox
|
|
|
|
A web admin toolbox for Microsoft 365 / SharePoint Online, built with Blazor Server (.NET 10) and Microsoft Graph.
|
|
|
|
## Features
|
|
|
|
- **Site management** — bulk site creation, folder-structure provisioning, templates
|
|
- **Members & permissions** — bulk member add, permission inspection
|
|
- **Content tools** — search, duplicate finder, file transfer, storage usage, version cleanup
|
|
- **Reporting** — on-demand reports, scheduled reports (unattended via app-only cert auth)
|
|
- **Auditing** — tenant-wide user-access audit (SP + M365/AAD group expansion)
|
|
- **Directory** — user directory browsing
|
|
- Multi-tenant via connection profiles. EN / FR localization.
|
|
|
|
## Requirements
|
|
|
|
- An Entra ID (Azure AD) app registration — see [Configuration](#configuration)
|
|
- Docker, **or** the .NET 10 SDK for bare-metal
|
|
|
|
## Configuration
|
|
|
|
Authentication uses Microsoft OIDC (interactive sign-in) and, for scheduled reports, app-only certificate auth.
|
|
|
|
Set these as environment variables (or in `appsettings.json` under the `Oidc` section). .NET maps `Section__Key` to `Section:Key`.
|
|
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `Oidc__TenantId` | Entra tenant GUID |
|
|
| `Oidc__ClientId` | App registration client ID |
|
|
| `Oidc__ClientSecret` | App registration client secret |
|
|
| `App__Domain` | Public domain the app is reached at, e.g. `sptb.example.com` or `https://sptb.example.com` (scheme defaults to `https`). Pins the OIDC sign-in redirect (`/signin-oidc`) and derives the SharePoint-connect redirect URI. |
|
|
| `DataFolder` | Persistent data path (default `/data`) |
|
|
| `ASPNETCORE_ENVIRONMENT` | Must be `Production` to enable OIDC |
|
|
|
|
> In `Development`, OIDC is disabled — the app uses a cookie-only auto-login (hardcoded Admin) for local work.
|
|
|
|
### Two distinct OAuth flows — two redirect URIs
|
|
|
|
These are separate and registered on **different** Entra apps. Don't conflate them.
|
|
|
|
1. **App sign-in (OIDC).** Logging into the toolbox itself via "Sign in with Microsoft". Uses the `Oidc__*` app above. Callback path is the framework default `/signin-oidc` (not configurable here). When `App__Domain` is set, the redirect is pinned to `<domain>/signin-oidc`; otherwise it's derived from the request host (`X-Forwarded-Host`/`Host`).
|
|
→ On **this** app registration, add redirect URI `https://your-host/signin-oidc` under the **Web** platform. This app also needs the Graph permissions the audit/reporting features require: `GroupMember.Read.All`, `Group.Read.All`, `User.Read.All`.
|
|
|
|
2. **SharePoint connect (per-profile).** Getting a delegated SharePoint/Graph token for a client tenant. A PKCE public-client flow that uses **each connection profile's own `ClientId`/`TenantId`** — not the `Oidc__*` app. The callback for this flow is derived from `App__Domain` as `<domain>/connect/callback`; set `ClientConnect__RedirectUri` to override the full URL directly.
|
|
→ On **each client-tenant profile's** app registration, add that callback value (e.g. `https://your-host/connect/callback`) under the **Mobile and desktop / public client** platform.
|
|
|
|
> **HTTPS note.** The sign-in app is a confidential (Web) client, so Entra requires its `/signin-oidc` redirect URI to be **HTTPS** — plain HTTP is allowed only for `http://localhost`, not a LAN host/IP. To run OIDC on a plain-HTTP LAN deployment, put the app behind an HTTPS-terminating reverse proxy: register `https://your-host/signin-oidc`, and the app honours `X-Forwarded-Proto` (see `UseForwardedHeaders`) to build the correct `https` redirect. Without a proxy, OIDC sign-in won't work over a non-localhost HTTP host — use the local email/password login instead.
|
|
|
|
> **Reverse-proxy host.** Set `App__Domain` so the app builds every redirect (cookie login, OIDC) against the public domain regardless of what host the proxy forwards. Without it, a proxy that doesn't forward the `Host` header makes the app 302 to the internal `IP:port` it actually received.
|
|
|
|
Persistent state (profiles, settings, templates, logs, exports, certs) lives in `DataFolder`.
|
|
|
|
## Installation — Docker (prebuilt image)
|
|
|
|
Pulls the published image from the Gitea registry — no local build needed.
|
|
|
|
```bash
|
|
cp .env.example .env # then edit .env with your OIDC values
|
|
docker compose -f docker-compose.prebuilt.yml pull
|
|
docker compose -f docker-compose.prebuilt.yml up -d
|
|
```
|
|
|
|
The compose file reads config from `.env` (see [`.env.example`](.env.example)). Pin a
|
|
version with `SPTB_TAG`, e.g. `SPTB_TAG=v1.2.0` in `.env`. Don't quote values — the
|
|
list form embeds literal quotes and breaks OIDC discovery.
|
|
|
|
## Installation — Docker (build locally)
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
```
|
|
|
|
App listens on **http://localhost:8080**. Data persists in the `sptb-data` volume.
|
|
|
|
Set your OIDC values in `docker-compose.yml` under `environment:`, or pass an env file:
|
|
|
|
```yaml
|
|
environment:
|
|
- ASPNETCORE_ENVIRONMENT=Production
|
|
- DataFolder=/data
|
|
- Oidc__TenantId=...
|
|
- Oidc__ClientId=...
|
|
- Oidc__ClientSecret=...
|
|
- ClientConnect__RedirectUri=https://your-host/connect/callback
|
|
```
|
|
|
|
Plain Docker (no compose):
|
|
|
|
```bash
|
|
docker build -t sptb-web .
|
|
docker run -d -p 8080:8080 \
|
|
-v sptb-data:/data \
|
|
-e ASPNETCORE_ENVIRONMENT=Production \
|
|
-e Oidc__TenantId=... \
|
|
-e Oidc__ClientId=... \
|
|
-e Oidc__ClientSecret=... \
|
|
-e ClientConnect__RedirectUri=https://your-host/connect/callback \
|
|
sptb-web
|
|
```
|
|
|
|
## Installation — Bare metal
|
|
|
|
Requires the [.NET 10 SDK](https://dotnet.microsoft.com/download).
|
|
|
|
```bash
|
|
# Restore + build
|
|
dotnet restore
|
|
dotnet publish -c Release -o ./publish
|
|
|
|
# Configure (PowerShell example)
|
|
$env:ASPNETCORE_ENVIRONMENT = "Production"
|
|
$env:DataFolder = "C:\sptb-data"
|
|
$env:Oidc__TenantId = "..."
|
|
$env:Oidc__ClientId = "..."
|
|
$env:Oidc__ClientSecret = "..."
|
|
$env:ClientConnect__RedirectUri = "https://your-host/connect/callback"
|
|
|
|
# Run
|
|
dotnet ./publish/SharepointToolbox.Web.dll
|
|
```
|
|
|
|
By default it listens on the Kestrel port (`http://localhost:5000`). Override with `ASPNETCORE_URLS`, e.g. `http://+:8080`.
|
|
|
|
### Local development
|
|
|
|
```bash
|
|
dotnet run
|
|
```
|
|
|
|
Runs in `Development` mode — OIDC off, auto-login as Admin. No Entra config needed.
|
|
|
|
## Tech stack
|
|
|
|
.NET 10 · Blazor Server · Microsoft Graph SDK · PnP.Framework · Serilog · CsvHelper
|