feat: implement T09, T15, T21 — userinfo endpoint, LLDAP export, negative tests

- T09: /userinfo with RS256 JWT validation, scope-filtered claims
- T15: LLDAP→canonical export tool with validation, migration_event telemetry
- T21: Negative test suite (Scenario D) — all 7 unsupported features verified

All go tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 02:08:03 +01:00
parent 4097a7de8b
commit 3ee8090a98
9 changed files with 1156 additions and 2 deletions

View File

@@ -3,11 +3,63 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"keycape/internal/adapters/lldap"
"keycape/internal/migration/lldapexport"
"keycape/internal/server/telemetry"
"keycape/internal/validator"
"github.com/rs/zerolog"
)
func main() {
fmt.Fprintln(os.Stderr, "lldap-export: not yet implemented (T06+)")
os.Exit(1)
// Flags.
url := flag.String("url", "ldap://localhost:389", "LLDAP server URL (ldap:// or ldaps://)")
bindDN := flag.String("bind-dn", "", "Service account bind DN (required)")
bindPW := flag.String("bind-pw", "", "Service account password (required)")
baseDN := flag.String("base-dn", "", "LDAP search base DN (required)")
output := flag.String("output", "canonical-export.yaml", "Output file path")
tlsSkip := flag.Bool("tls-skip-verify", false, "Skip TLS certificate verification (dev only)")
flag.Parse()
if *bindDN == "" || *baseDN == "" {
fmt.Fprintln(os.Stderr, "lldap-export: --bind-dn and --base-dn are required")
flag.Usage()
os.Exit(1)
}
log := zerolog.New(os.Stderr).With().Timestamp().Logger()
emitter := telemetry.NewLogEmitter(log)
cfg := lldap.Config{
URL: *url,
BindDN: *bindDN,
BindPW: *bindPW,
BaseDN: *baseDN,
TLSSkipVerify: *tlsSkip,
}
repo := lldap.New(cfg)
exp := lldapexport.New(repo, validator.ModeProvisioning, emitter)
result, err := exp.Export(context.Background(), *output)
if err != nil {
fmt.Fprintf(os.Stderr, "lldap-export: export failed: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "Exported %d users, %d groups to %s\n",
len(result.Users), len(result.Groups), *output)
if len(result.IncompatibilityReport) > 0 {
fmt.Fprintln(os.Stderr, "Incompatibility report:")
for _, item := range result.IncompatibilityReport {
fmt.Fprintln(os.Stderr, " -", item)
}
os.Exit(2) // partial success: exported with warnings
}
}