78 lines
3.3 KiB
Bash
Executable file
78 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Replays the six mutations of review axis 2 (entry, sessions, CSRF) against a COPY of platform/.
|
|
# It edits the copy, runs the packages that must redden, and reverts. Never point it at the repo.
|
|
#
|
|
# Usage: mutations-axis2.sh /path/to/copy/platform
|
|
# env: TM_PLATFORM_TEST_DSN TM_PLATFORM_TEST_ENGINE_BIN TM_PLATFORM_TEST_BOOK_TEMPLATE
|
|
set -u
|
|
P=${1:?path to the platform COPY}
|
|
case "$P" in *projects/textmachine*) echo "refusing to mutate the live repository"; exit 2;; esac
|
|
cd "$P" || exit 1
|
|
SNAP=$(mktemp -d); cp internal/auth/session.go internal/auth/cookie.go internal/httpapi/server.go \
|
|
internal/login/dev.go internal/pgstore/sessions.go "$SNAP/"
|
|
revert() { cp "$SNAP/session.go" internal/auth/; cp "$SNAP/cookie.go" internal/auth/;
|
|
cp "$SNAP/server.go" internal/httpapi/; cp "$SNAP/dev.go" internal/login/;
|
|
cp "$SNAP/sessions.go" internal/pgstore/; }
|
|
trap revert EXIT
|
|
|
|
sub() { python3 - "$1" "$2" "$3" <<'PY'
|
|
import sys
|
|
p,old,new=sys.argv[1],sys.argv[2],sys.argv[3]
|
|
s=open(p).read(); assert old in s, "anchor not found in "+p
|
|
open(p,'w').write(s.replace(old,new))
|
|
PY
|
|
}
|
|
run() { echo " -> $*"; go test "$@" -count=1 2>&1 | tail -4; }
|
|
|
|
echo "== M1 (control) Digest returns the plaintext token =="
|
|
sub internal/auth/session.go 'func Digest(token string) []byte {
|
|
sum := sha256.Sum256([]byte(token))
|
|
return sum[:]
|
|
}' 'func Digest(token string) []byte {
|
|
_ = sha256.Sum256([]byte(token))
|
|
return []byte(token)
|
|
}'
|
|
run ./internal/auth/; run ./internal/pgstore/ -run 'TestStoredCredentialIsAHashNotTheToken'
|
|
revert
|
|
|
|
echo "== M2 sign-out stops expiring the session cookie =="
|
|
sub internal/auth/cookie.go 'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", -time.Second) }' \
|
|
'func (c Cookies) ClearSession(w http.ResponseWriter) { c.set(w, c.SessionName(), "", time.Hour) }'
|
|
run ./internal/auth/ ./internal/login/ ./internal/httpapi/
|
|
revert
|
|
|
|
echo "== M2b no cookie is ever expired (session AND login state) =="
|
|
sub internal/auth/cookie.go ' maxAge := int(ttl.Seconds())
|
|
if ttl < 0 {
|
|
maxAge = -1
|
|
}' ' maxAge := int(ttl.Seconds())
|
|
if ttl < 0 {
|
|
maxAge = 3600
|
|
}'
|
|
run ./internal/auth/ ./internal/login/ ./internal/httpapi/
|
|
revert
|
|
|
|
echo "== M3 the /auth subtree loses the CSRF layer =="
|
|
sub internal/httpapi/server.go 'mux.Handle("/auth/", LimitBody(DefaultMaxBody)(csrf(d.Login.Routes(d.Auth.Require))))' \
|
|
'mux.Handle("/auth/", LimitBody(DefaultMaxBody)(d.Login.Routes(d.Auth.Require)))'
|
|
run ./internal/httpapi/
|
|
revert
|
|
|
|
echo "== M4 the development sign-in stops rotating the presented session =="
|
|
sub internal/login/dev.go ' if old, _, ok := auth.Present(r, d.cookies.SessionName()); ok {
|
|
if err := d.store.RevokeSession(r.Context(), auth.Digest(old), d.now()); err != nil {
|
|
d.log.ErrorContext(r.Context(), "cannot revoke the pre-login session", "err", err)
|
|
}
|
|
}
|
|
' ''
|
|
run ./internal/login/
|
|
revert
|
|
|
|
echo "== M5 the absolute ceiling written to the row is 100x the configured max age =="
|
|
sub internal/pgstore/sessions.go 'now.Add(idleTTL), now.Add(maxAge)); err != nil {' \
|
|
'now.Add(idleTTL), now.Add(100*maxAge)); err != nil {'
|
|
run ./internal/pgstore/ -run 'Session|Credential|Touch|Revoke'
|
|
run ./internal/config/
|
|
run ./internal/auth/ ./internal/login/ ./internal/httpapi/
|
|
revert
|
|
echo "== done; the copy is back to its original state =="
|