Compare commits
5 Commits
docs/deplo
...
docs/azure
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a298b2f6e | |||
| 3cccdf1c4b | |||
| 7bb1f66588 | |||
| 26f4a9242a | |||
| 6c1dc7d45f |
60
Azure-File-Picker-Request.md
Normal file
60
Azure-File-Picker-Request.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Request: Entra (Azure AD) App Registration — WP Suite SharePoint File Picker
|
||||||
|
|
||||||
|
**Requested by:** Nick Siegfried (n.siegfried@prime-controls.com)
|
||||||
|
**Date:** 2026-06-15
|
||||||
|
**Purpose:** Let users of the Work Package (WP) Suite pick files directly from the
|
||||||
|
project's SharePoint folders (Drawings / Specs / Data Sheets) and attach them to a
|
||||||
|
work package, instead of manually copying and pasting SharePoint links.
|
||||||
|
|
||||||
|
To do this the tool needs to call Microsoft's **OneDrive/SharePoint File Picker
|
||||||
|
(File Picker v8)** and **Microsoft Graph** on behalf of the signed-in Prime user.
|
||||||
|
That requires an Entra app registration. None of this grants the app standalone
|
||||||
|
access — every action runs **as the signed-in user**, so it can only see what that
|
||||||
|
user already has permission to in SharePoint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What we need created
|
||||||
|
|
||||||
|
A single **App registration** in the `prime-controls.com` (primecontrolsdallas) tenant.
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---|---|
|
||||||
|
| **Name** | `WP-Suite-File-Picker` |
|
||||||
|
| **Supported account types** | Single tenant — *Accounts in this organizational directory only* |
|
||||||
|
| **Platform** | **Single-page application (SPA)** |
|
||||||
|
| **Redirect URI(s)** | The URL(s) the tool is hosted at — e.g. `https://<wp-suite-host>/wp-creation-index.html` (and `http://localhost:<port>/...` for testing). *Nick to confirm final host.* |
|
||||||
|
|
||||||
|
### API permissions (Microsoft Graph — **Delegated**, not Application)
|
||||||
|
| Permission | Why |
|
||||||
|
|---|---|
|
||||||
|
| `Files.Read.All` | Read files the user selects in the picker |
|
||||||
|
| `Sites.Read.All` | Resolve the SharePoint site/folder the picker browses |
|
||||||
|
| `User.Read` | Basic sign-in (usually present by default) |
|
||||||
|
|
||||||
|
> Delegated = acts as the signed-in user. We are **not** requesting application
|
||||||
|
> (app-only) permissions, so the app cannot read anything on its own.
|
||||||
|
|
||||||
|
### Admin consent
|
||||||
|
- Please **grant admin consent** for the delegated permissions above so pilot users
|
||||||
|
aren't each prompted to consent individually.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What we DON'T need
|
||||||
|
- No client secret / certificate (SPA uses PKCE, no secret).
|
||||||
|
- No application (app-only) permissions.
|
||||||
|
- No write/modify permissions — read-only is sufficient for attaching files.
|
||||||
|
|
||||||
|
## What to send back to Nick
|
||||||
|
1. **Application (client) ID**
|
||||||
|
2. **Directory (tenant) ID**
|
||||||
|
3. Confirmation that the **redirect URI** was registered and **admin consent** granted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Background / scope
|
||||||
|
This is for an internal pilot of the WP Suite (Project SDE). Today users paste
|
||||||
|
SharePoint "Copy Link" URLs by hand. The app registration enables a proper
|
||||||
|
"Add files" picker scoped to the SOP's folders. Read-only, single-tenant,
|
||||||
|
delegated — lowest-privilege configuration that makes the picker work.
|
||||||
@@ -82,7 +82,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./scripts:/scripts:ro
|
- ./scripts:/scripts:ro
|
||||||
- ./backups:/backups
|
- ./backups:/backups
|
||||||
entrypoint: ["/bin/sh", "/scripts/backup-cron.sh"]
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Entry point for the `backup` sidecar container. Runs db-backup.sh on a fixed
|
# Entry point for the `backup` sidecar container's periodic loop. Runs
|
||||||
# interval (default: daily). Kept deliberately simple — a sleep loop instead of a
|
# db-backup.sh on a fixed interval (default: daily) -- a sleep loop instead of
|
||||||
# cron daemon — so it works in a bare postgres:16-alpine image.
|
# a cron daemon, kept deliberately simple so it works in a bare
|
||||||
|
# postgres:16-alpine image.
|
||||||
|
#
|
||||||
|
# Resolves db-backup.sh the same way entrypoint.sh resolves this file: prefer
|
||||||
|
# the live bind-mounted copy at /scripts (so edits don't need a rebuild), fall
|
||||||
|
# back to the copy baked into the image at build time if the mount is
|
||||||
|
# missing, empty, or stale. Resolving fresh on every loop iteration also means
|
||||||
|
# that if the mount comes back healthy later (e.g. someone fixes the host
|
||||||
|
# directory) this container picks it up on the very next run, with no
|
||||||
|
# restart needed.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
resolve() {
|
||||||
|
# $1 = script filename, e.g. db-backup.sh
|
||||||
|
if [ -f "/scripts/$1" ]; then
|
||||||
|
echo "/scripts/$1"
|
||||||
|
else
|
||||||
|
echo "/app/scripts-default/$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
INTERVAL="${BACKUP_INTERVAL_SECONDS:-86400}" # 86400 = once a day
|
INTERVAL="${BACKUP_INTERVAL_SECONDS:-86400}" # 86400 = once a day
|
||||||
echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14}, dir=${BACKUP_DIR:-/backups}"
|
echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14}, dir=${BACKUP_DIR:-/backups}"
|
||||||
|
|
||||||
@@ -11,6 +29,7 @@ echo "[backup] sidecar started; interval=${INTERVAL}s, keep=${BACKUP_KEEP:-14},
|
|||||||
# immediate restore point instead of waiting a whole interval.
|
# immediate restore point instead of waiting a whole interval.
|
||||||
sleep 20
|
sleep 20
|
||||||
while true; do
|
while true; do
|
||||||
sh /scripts/db-backup.sh || echo "[backup] run failed; will retry next interval" >&2
|
DB_BACKUP="$(resolve db-backup.sh)"
|
||||||
|
sh "$DB_BACKUP" || echo "[backup] run failed; will retry next interval" >&2
|
||||||
sleep "$INTERVAL"
|
sleep "$INTERVAL"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# Backup sidecar image: Postgres client tools (pg_dump/psql) + openssl for
|
# Backup sidecar image: Postgres client tools (pg_dump/psql) + openssl for
|
||||||
# at-rest encryption of dumps. The scripts themselves are bind-mounted at runtime
|
# at-rest encryption of dumps.
|
||||||
# (see the `backup` service in docker-compose.yml), so they're not COPYed here.
|
#
|
||||||
|
# The scripts are bind-mounted live at runtime (see the `backup` service in
|
||||||
|
# docker-compose.yml) so they can be edited without a rebuild -- but they're
|
||||||
|
# ALSO baked in here as a fallback default under /app/scripts-default/.
|
||||||
|
# entrypoint.sh prefers the live mount and only falls back to this baked-in
|
||||||
|
# copy if the mount is missing, empty, or stale. That fallback is what keeps
|
||||||
|
# a broken bind mount from crash-looping the container into an unreachable
|
||||||
|
# state (see entrypoint.sh for the full story).
|
||||||
FROM postgres:16-alpine
|
FROM postgres:16-alpine
|
||||||
RUN apk add --no-cache openssl
|
RUN apk add --no-cache openssl
|
||||||
|
|
||||||
|
COPY scripts/backup-cron.sh scripts/db-backup.sh scripts/db-restore.sh /app/scripts-default/
|
||||||
|
COPY scripts/entrypoint.sh /app/entrypoint.sh
|
||||||
|
RUN chmod +x /app/entrypoint.sh /app/scripts-default/*.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
|
CMD []
|
||||||
|
|||||||
43
scripts/entrypoint.sh
Normal file
43
scripts/entrypoint.sh
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Entrypoint for the `backup` sidecar. Prefers the live, bind-mounted copy of
|
||||||
|
# backup-cron.sh at /scripts (so it can be edited without a rebuild), and
|
||||||
|
# falls back to the copy baked into this image at build time if that bind
|
||||||
|
# mount is missing, empty, or stale.
|
||||||
|
#
|
||||||
|
# Why this exists: the previous entrypoint ran `/bin/sh /scripts/backup-cron.sh`
|
||||||
|
# directly. If that file wasn't there -- e.g. because the host directory
|
||||||
|
# backing the ./scripts bind mount hadn't been populated by whatever deploy
|
||||||
|
# process manages this stack -- the container failed instantly, and
|
||||||
|
# `restart: unless-stopped` retried in a tight crash loop forever: fast enough
|
||||||
|
# that the container was never "running" long enough for `docker exec` or
|
||||||
|
# Portainer's console to attach. That made the failure itself undiagnosable
|
||||||
|
# from inside the container -- you could only ever see it in the logs, and
|
||||||
|
# only by getting lucky with timing. This wrapper guarantees something always
|
||||||
|
# runs, and that the container always stays reachable, even in the worst case.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
LIVE="/scripts/backup-cron.sh"
|
||||||
|
FALLBACK="/app/scripts-default/backup-cron.sh"
|
||||||
|
|
||||||
|
if [ -f "$LIVE" ]; then
|
||||||
|
echo "[entrypoint] using live scripts from /scripts (bind mount present)"
|
||||||
|
exec /bin/sh "$LIVE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] WARNING: $LIVE not found." >&2
|
||||||
|
echo "[entrypoint] The ./scripts bind mount is missing, empty, or stale on the host." >&2
|
||||||
|
echo "[entrypoint] Check the directory backing that mount (see docker-compose.yml)." >&2
|
||||||
|
|
||||||
|
if [ -f "$FALLBACK" ]; then
|
||||||
|
echo "[entrypoint] Falling back to the scripts baked into this image at build time." >&2
|
||||||
|
echo "[entrypoint] Backups will still run, on whatever version was current when this" >&2
|
||||||
|
echo "[entrypoint] image was last built -- not any newer live edits to ./scripts." >&2
|
||||||
|
exec /bin/sh "$FALLBACK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[entrypoint] FATAL: no backup-cron.sh in the bind mount or the image." >&2
|
||||||
|
echo "[entrypoint] Staying up (idle) instead of crash-looping, so this container" >&2
|
||||||
|
echo "[entrypoint] can still be reached via 'docker exec' / the Portainer console." >&2
|
||||||
|
while true; do
|
||||||
|
sleep 3600
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user