diff --git a/StellaOps.Authority.Prompts.txt b/StellaOps.Authority.Prompts.txt deleted file mode 100644 index 9a4661ed..00000000 --- a/StellaOps.Authority.Prompts.txt +++ /dev/null @@ -1,46 +0,0 @@ -StellaOps Authority Project – Phased Execution Prompts - -Teams: -- Team 1: DevEx / Platform (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.DevEx.md) -- Team 2: Authority Core Service (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.AuthorityCore.md) -- Team 3: Plugin Workstream (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.Plugin.md) -- Team 4: Auth Libraries (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.AuthLibraries.md) -- Team 5: Feedser Integration (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.Feedser.md) -- Team 6: CLI (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.CLI.md) -- Team 7: DevOps / Observability (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.DevOps.md) -- Team 8: Security Guild (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.Security.md) -- Team 9: Docs & Enablement (see AGENTS.md, StellaOps.Authority.TODOS.md, StellaOps.Authority.TODOS.Docs.md) - -Phase 0 – Bootstrapping -- Prompt Team 1: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.DevEx.md. Complete FND1 → FND3 (solution scaffold, build props, AuthorityOptions binding). Report when the Authority solution builds clean.” -- Wait until FND1–FND3 are DONE before continuing. - -Phase 1 – Core Foundations -- Prompt Team 1: “Continue with StellaOps.Authority.TODOS.DevEx.md. Deliver FND4, FND5, and PLG5 (config samples, telemetry constants, plugin config loader).” -- Prompt Team 2: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.AuthorityCore.md. Implement CORE1 + CORE2 (minimal API host, OpenIddict endpoints). Verify /health and /ready before proceeding.” -- Prompt Team 3: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.Plugin.md. Execute PLG1–PLG3 (abstractions, plugin loader integration, Mongo-based Standard plugin stub). Coordinate schema details with Team 1.” -- Do not start Phase 2 until Team 2 finishes CORE1–CORE2 and Team 3 finishes PLG1–PLG3. - -Phase 2 – Core Expansion & Libraries -- Prompt Team 2: “Continue with StellaOps.Authority.TODOS.AuthorityCore.md tasks CORE3–CORE6 (Mongo stores, plugin capability wiring, bootstrap admin APIs).” -- Prompt Team 3: “Advance PLG4–PLG6 (capability metadata, config validation, plugin developer guide draft).” -- Prompt Team 4: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.AuthLibraries.md. Deliver LIB1–LIB4 (abstractions, NetworkMaskMatcher, ServerIntegration DI, Auth.Client).” -- Move to Phase 3 only after CORE3–CORE6, PLG4–PLG6, and LIB1–LIB4 are DONE. - -Phase 3 – Integration & Ops -- Prompt Team 2: “Finish CORE7–CORE10 (telemetry, rate limiting, revocation list, key rotation/JWKS).” -- Prompt Team 3: “Complete PLG6 handoff and draft PLG7 RFC if bandwidth allows.” -- Prompt Team 4: “Implement LIB5–LIB6 (Polly integration, packaging metadata).” -- Prompt Team 5: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.Feedser.md. Execute FSR1–FSR3 (config, auth wiring, bypass masks) then FSR4 docs updates.” -- Prompt Team 6: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.CLI.md. Deliver CLI1–CLI4 (config, auth commands, bearer injection, docs).” -- Prompt Team 7: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.DevOps.md. Execute OPS1–OPS5 (Dockerfile/compose, CI pipeline, key rotation tooling, backup docs, monitoring).” -- Prompt Team 9: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.Docs.md. Draft DOC1–DOC4 in parallel; update DOC5 once Feedser/CLI changes land.” -- Proceed to Phase 4 only when CORE10, PLG6, LIB6, FSR4, CLI4, OPS5, and DOC4 are complete. - -Phase 4 – Security & Final Integration -- Prompt Team 8: “Read AGENTS.md, StellaOps.Authority.TODOS.md, and StellaOps.Authority.TODOS.Security.md. Execute SEC1–SEC5 (password hashing, audit log review, lockout/rate-limit validation, revocation signing, threat model). Review Feedser/CLI for security compliance.” -- Prompt Team 5: “Run FSR5 (Authority ↔ Feedser integration tests) using the DevOps compose stack.” -- Prompt Team 6: “Finalize CLI auth enhancements and ensure tests reflect Security feedback.” -- Prompt Team 7: “Support integration testing, finalize runbooks, confirm monitoring dashboards.” -- Prompt Team 9: “Incorporate Security findings, finalize DOC3 migration guide, DOC5 README/quickstart updates, release notes.” -- Wrap up after SEC5 sign-off and successful FSR5 execution. diff --git a/StellaOps.Authority.TODOS.AuthLibraries.md b/StellaOps.Authority.TODOS.AuthLibraries.md deleted file mode 100644 index b34f7e1e..00000000 --- a/StellaOps.Authority.TODOS.AuthLibraries.md +++ /dev/null @@ -1,42 +0,0 @@ -# StellaOps Authority — Authentication Libraries Team - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Keep status synchronized across trackers. - -## Mission -Deliver shared authentication components consumed by resource servers, clients, and tooling: abstractions, DI helpers, token clients, and supporting utilities. - -## Task Breakdown - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | LIB1 | Stand up `StellaOps.Auth.Abstractions` (claims, scopes, principal builder, ProblemResultFactory). | DevEx FND1 | Unit tests covering claim normalization + problem responses. | -| 2 | LIB3 | Implement `NetworkMaskMatcher` with IPv4/IPv6 CIDR support; port tests from Serdica inspiration. | LIB1 | 100% branch coverage on mask utilities. | -| 3 | LIB2 | Build `StellaOps.Auth.ServerIntegration` (DI extension wiring JwtBearer, bypass masks, policy helpers). | LIB1, LIB3 | Add integration test with stub Authority JWKS. | -| 4 | LIB4 | Build `StellaOps.Auth.Client` (discovery, JWKS caching, password/client credential flows, token cache abstraction). | LIB1 | Provide `IStellaOpsTokenClient` interfaces. | -| 5 | LIB5 | Integrate Polly + HttpClientFactory patterns (configurable retries/backoff) in Auth.Client. | LIB4 | Config tested via options binding. | -| 6 | LIB6 | Prepare NuGet packaging metadata (license, tags) and update build pipeline to push once stabilized. | LIB1–LIB5 | Validate `dotnet pack` outputs signed packages. | - -## Implementation Notes -- All option classes should bind via `StellaOps.Configuration` naming conventions. -- Token client must support file-based cache (for CLI) and in-memory cache (for services). -- Provide sample usage snippets for Feedser integration (to hand off). -- Consider adding `IClaimsTransformation` helper for ASP.NET resource servers. -- Ensure authentication failures map to standard problem responses (missing/expired token, insufficient scope). - -## Deliverables -- Three new projects: `StellaOps.Auth.Abstractions`, `.ServerIntegration`, `.Client`. -- Unit + integration tests, coverage reports. -- Example integration docs/snippets for Feedser and CLI teams. -- Packaging metadata ready for CI once green-lit. - -## Coordination -- Weekly sync with Authority Core + Feedser Integration to align on scopes/policies. -- Share NuGet package versions with DevEx once published. -- Notify CLI team when client API stabilizes (unlock CLI1–CLI3). -- Coordinate with Security Guild on bypass mask semantics and default policies. - -## Status (2025-10-10) -- LIB1 DONE – Principal builder/problem factory complete with unit coverage. -- LIB3 DONE – `NetworkMaskMatcher` replaces Serdica helpers with IPv4/6 tests. -- LIB2 DONE – `AddStellaOpsResourceServerAuthentication` with scope/bypass policies implemented. -- LIB4 DONE – Auth client, discovery/JWKS caches, in-memory/file token caches with happy-path tests delivered. diff --git a/StellaOps.Authority.TODOS.AuthorityCore.md b/StellaOps.Authority.TODOS.AuthorityCore.md deleted file mode 100644 index 5e2596d5..00000000 --- a/StellaOps.Authority.TODOS.AuthorityCore.md +++ /dev/null @@ -1,56 +0,0 @@ -# StellaOps Authority — Core Service Team - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Update status in both TODO trackers. - -## Mission -Design and implement the Authority host (OpenIddict server, token lifecycles, administrative endpoints) on top of the DevEx scaffold, coordinating with Plugin, Library, and Security teams. - -## Work Breakdown - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | CORE1 | Wire minimal API host with configuration, logging, plugin discovery, `/health` + `/ready`. | DevEx FND1–FND5 | Manual smoke: `dotnet run` returns healthy responses. | -| 2 | CORE2 | Configure OpenIddict server endpoints & flows (password, client credentials, refresh, jwks). | CORE1 | Supports HTTPS enforcement toggle via options. | -| 3 | CORE3 | Implement Mongo repositories for users/clients/scopes/tokens/login attempts. | CORE1 | Collections + indices documented; unit tests for CRUD. | -| 4 | CORE4 | Integrate plugin contracts (`IIdentityProviderPlugin`, etc.) into DI; load capabilities. | PLG1 | Plugins registered through host on startup. | -| 5 | CORE5 | Port/customize OpenIddict handlers (password/client creds validation) to use plugin contracts. | CORE4 | Unit tests for success/failure scenarios. | -| 5a | CORE5A | Add integration tests covering token persistence & revocation via `IAuthorityTokenStore`. | CORE5 | Ensure revoked tokens denied + fixtures for access/reference tokens. | -| 5b | CORE5B | Document token persistence & enrichment flows for resource servers/plugins. | CORE5 | Docs updated with claim expectations + revocation sync guidance. | -| 6 | CORE6 | Implement bootstrap admin endpoints (`/internal/users`, `/internal/clients`) secured via bootstrap API key. | CORE5 | Add rate limiting + audit logs. | -| 7 | CORE7 & CORE8 | Add structured logging, OTEL spans, and ASP.NET rate limiting for `/token`, `/authorize`. | CORE5 | Verify via integration tests, metrics exported. | -| 8 | CORE9 | Implement token revocation + signed offline revocation manifest generation hooks. | CORE5 | CLI call returns signed JSON; tests confirm revoked tokens denied. | -| 9 | CORE10 | Configure signing/encryption key rotation, JWKS publishing, certificate loader. | CORE5 | Document rotation steps; integration test covers key rollover. | - -## Implementation Notes -- All Mongo repositories must align with offline-first design (no TTL for critical data unless configurable). -- Expose metrics counters (issued tokens, failed attempts) for DevOps consumption. -- Coordinate with Security Guild for password hashing options (Argon2 vs PBKDF2), lockout thresholds. -- Ensure plugin capability metadata is honored (e.g., if plugin lacks password support, reject password grants gracefully). -- Provide integration hooks for future LDAP plugin (capability flag + TODO comment). - -## Status - -- [x] CORE1 – Completed 2025-10-09. Minimal API host loads validated configuration, configures Serilog, registers plugins, and exposes `/health` + `/ready`. -- [x] CORE2 – Completed 2025-10-09. OpenIddict server configured with required endpoints, token lifetimes, sliding refresh tokens, and Development-only HTTPS relaxation. -- [x] CORE3 – Completed 2025-10-09. Mongo storage project created with indexed Authority collections, repositories, and bootstrap migration runner. -- [ ] CORE4 – Not started. -- [x] CORE5 – Completed 2025-10-10 with client-credentials validation, token validation handlers, and token persistence wired through plugin contracts. -- [ ] CORE5A – Pending integration tests for token persistence/revocation behaviour (QA + BE-Auth pairing). -- [ ] CORE5B – Pending documentation refresh covering claims enrichment + token store expectations. -- [x] CORE6 – Completed 2025-10-10. Bootstrap admin APIs behind API key provison users and clients through plugin stores. -- [ ] CORE7 – Not started. -- [ ] CORE8 – Not started. -- [ ] CORE9 – Not started. -- [ ] CORE10 – Not started. - -## Deliverables -- `StellaOps.Authority` project with tested endpoints and handlers. -- Repository docs summarizing API responses (shared with Docs team). -- Integration tests (Authority-only) verifying token issuance + revocation. -- Audit logging implemented (structured with trace IDs). - -## Coordination -- Daily stand-up with Plugin + Libraries teams until CORE5 complete (met objective 2025-10-10). -- Notify DevOps when `/token` contract stabilizes (OPS pipeline). -- Work with Docs to capture endpoint behavior for `docs/11_AUTHORITY.md`. -- Review PRs from Plugin & Libraries teams affecting Authority host. diff --git a/StellaOps.Authority.TODOS.CLI.md b/StellaOps.Authority.TODOS.CLI.md deleted file mode 100644 index 781b31b2..00000000 --- a/StellaOps.Authority.TODOS.CLI.md +++ /dev/null @@ -1,35 +0,0 @@ -# StellaOps Authority — CLI Team - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Keep status aligned in all trackers. - -## Mission -Enable `stellaops-cli` to authenticate against StellaOps Authority, manage tokens, and surface auth-related UX for operators. - -## Task Queue - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | CLI1 | Extend `StellaOpsCliOptions` and configuration bootstrap to include Authority settings (AuthorityUrl, ClientId/Secret, Username/Password). | LIB4 | **DONE (2025-10-10)** – Options bind authority fields, env fallbacks documented, and cache directory defaults to `~/.stellaops/tokens`. | -| 2 | CLI2 | Implement `auth` command group (`login`, `logout`, `status`) using `StellaOps.Auth.Client`. | CLI1, LIB4 | **DONE (2025-10-10)** – Commands support client-credentials/password flows, force re-auth, and surface status output. | -| 3 | CLI3 | Ensure all backend calls attach bearer tokens; handle 401/403 with clear messaging and retry guidance. | CLI2, LIB2 | **DONE (2025-10-10)** – Backend client now resolves cached tokens via shared helper and attaches Authorization headers on every call. | -| 4 | CLI4 | Update help text and docs (quickstart + API reference) to describe new auth workflow. | CLI1–CLI3 | Coordinate with Docs team for final copy. | -| 5 | OPTIONAL | Add `auth whoami` to display token scopes/expiry (post-MVP if time allows). | CLI2 | Non-blocking enhancement. | - -## Implementation Notes -- Token cache path defaults to `~/.stellaops/tokens`; allow override via config. -- Handle offline mode gracefully (cached token reuse, helpful errors). -- Provide verbose logging around token acquisition (without dumping secrets). -- Support non-interactive mode (env vars) for CI pipelines. -- Align CLI exit codes with backend problem types (401 -> exit 10, etc.). - -## Deliverables -- Updated CLI project + tests. -- Docs/help updates referencing Authority integration. -- Sample command snippets for operators (login, job trigger with scope). -- Changelog entry describing auth changes. - -## Coordination -- Collaborate with Auth Libraries team to stabilize client API. -- Sync with Feedser integration to ensure required scopes align. -- Provide feedback to Authority Core on error payloads for better CLI UX. -- Work with Docs team for documentation rollout. diff --git a/StellaOps.Authority.TODOS.DevEx.md b/StellaOps.Authority.TODOS.DevEx.md deleted file mode 100644 index e0e5d8e3..00000000 --- a/StellaOps.Authority.TODOS.DevEx.md +++ /dev/null @@ -1,35 +0,0 @@ -# StellaOps Authority — DevEx / Platform Workstream - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this file. -> Keep task status synced in both TODO trackers whenever items move (TODO → DOING → DONE/BLOCKED). - -## Scope -- Repository scaffolding, shared configuration plumbing, sample configs, telemetry constants. -- Provide the baseline everyone else builds on; unblock quickly, announce breaking changes on the shared channel. - -## Deliverables & Checklist - -| Order | Task ID | Description | Dependencies | Notes | -|-------|---------|-------------|--------------|-------| -| 1 | FND1 | Create `src/StellaOps.Authority` solution layout (Authority host, Plugins.Abstractions, Plugin.Standard stub, Auth libraries). | none | **DONE** – Solution scaffolding live with net10.0 preview defaults + project references. | -| 2 | FND2 | Update repository build props/targets for new projects; ensure analyzers + nullable + treat warnings as errors. | FND1 | **DONE** – Directory.Build props/targets extended; root `StellaOps.sln` added (root build still surfaced existing Feedser compile failures). | -| 3 | FND3 | Extend `StellaOps.Configuration` with `StellaOpsAuthorityOptions`, binder, validation stubs. | FND1 | **DONE** – Options schema + bootstrap helper + unit tests validating binding/normalisation. | -| 4 | FND4 | Publish `etc/authority.yaml.sample` (with plugin toggles) + README mention. | FND3 | **DONE** – Sample config added with env var guidance; README + quickstart updated. | -| 5 | FND5 | Register OTEL resource constants (service.name = `stellaops-authority`, etc.). | FND3 | **DONE** – Authority telemetry constants helper published for shared use. | -| 6 | PLG5 | Define plugin config directory structure (`etc/authority.plugins/*.yaml`), loader helpers, sample files. | FND3 | **DONE** – Schema + loader shipped, standard/ldap samples published. | -| 7 | OPS1 (support) | Pair with DevOps on Dockerfile/compose scaffolding to ensure directories, config names match. | FND4 | **DONE** – Provided distroless Dockerfile/compose guidance in `ops/authority/` for DevOps handoff. | - -### Exit Criteria -- `dotnet build` succeeds from repo root with new projects. -- Configuration sample + docs referenced in README/Authority TODO file. -- Telemetry/resource constants ready for Authority Core team. -- Plugin config loader available before Plugin Team begins feature work. - -### Risks / Mitigations -- **Risk:** Build props drift. → Run `dotnet format --verify-no-changes` before handoff. -- **Risk:** Config breaking changes mid-implementation. → Version `StellaOpsAuthorityOptions` and communicate via Slack + TODO updates. - -### Coordination -- Daily async update until FND3 complete. -- Hand off AuthorityOptions schema to all other teams once finalized (tag repository issue). -- Keep an eye on PR queue—DevEx reviews required for structure/config changes. diff --git a/StellaOps.Authority.TODOS.DevOps.md b/StellaOps.Authority.TODOS.DevOps.md deleted file mode 100644 index 16062329..00000000 --- a/StellaOps.Authority.TODOS.DevOps.md +++ /dev/null @@ -1,36 +0,0 @@ -# StellaOps Authority — DevOps & Observability Team - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Reflect status changes in both TODO trackers. - -## Mission -Deliver deployable artefacts, CI/CD automation, runtime observability, and operational runbooks for StellaOps Authority. - -## Task Matrix - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | OPS1 | Author distroless Dockerfile + docker-compose sample (Authority + Mongo + optional Redis). | FND4, CORE1 | **DONE (DevEx scaffold)** – see `ops/authority/` Dockerfile + compose; verify with production secrets before release. | -| 2 | OPS2 | Extend CI workflows (build/test/publish) for Authority + auth libraries (dotnet build/test, docker build, artefact publish). | OPS1 | **DONE** – Authority build/test/publish integrated into `.gitea/workflows/build-test-deploy.yml`. | -| 3 | OPS3 | Implement key rotation script/CLI and wire pipeline job (manual trigger) to rotate signing keys + update JWKS. | CORE10 | Document rotation process + store secrets securely. | -| 4 | OPS4 | Document backup/restore for Authority Mongo collections, plugin configs, key material. | CORE3 | Produce runbook in `/docs/ops`. | -| 5 | OPS5 | Define monitoring metrics/alerts (token issuance failure rate, lockout spikes, bypass usage). Provide dashboards (Prometheus/Otel). | CORE7 | Share Grafana JSON or equivalent. | -| 6 | SUPPORT | Assist other teams with docker-compose variations for integration tests (Feedser, CLI). | OPS1, FSR5 | Provide templates + guidance. | - -## Implementation Notes -- Container image must remain offline-friendly (no package installs at runtime). -- Compose sample should include environment variable settings referencing `etc/authority.yaml`. -- Store key rotation artefacts in secure storage (vault/secrets). -- Align metrics naming with existing StellaOps conventions. -- Provide fallback instructions for air-gapped deployments (manual image load, offline key rotation). - -## Deliverables -- Dockerfile(s), compose stack, and documentation. -- Updated CI pipeline definitions. -- Runbooks for rotation, backup, restore. -- Monitoring/alerting templates. - -## Coordination -- Sync with DevEx on configuration paths + plugin directories. -- Coordinate with Authority Core regarding key management endpoints. -- Work with Feedser Integration + CLI teams on integration test environments. -- Engage Security Guild to review key rotation + secret storage approach. diff --git a/StellaOps.Authority.TODOS.Docs.md b/StellaOps.Authority.TODOS.Docs.md deleted file mode 100644 index ac11e972..00000000 --- a/StellaOps.Authority.TODOS.Docs.md +++ /dev/null @@ -1,36 +0,0 @@ -# StellaOps Authority — Docs & Enablement Plan - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Keep progress synchronized across trackers. - -## Mission -Produce operator and developer documentation for the new Authority stack, including configuration guides, API references, plugin tutorials, migration playbooks, and release notes. - -## Task Pipeline - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | DOC1 | Draft `docs/11_AUTHORITY.md` (architecture overview, configuration, plugin model, deployment scenarios). | FND4, CORE1 | Reviewed by DevEx + Authority Core. | -| 2 | DOC2 | Generate API reference snippets for `/token`, `/jwks`, `/introspect`, `/revoke` (OpenAPI fragment + human-readable table). | CORE2, LIB4 | Linked from docs + README. | -| 3 | DOC3 | Write migration guide for Feedser moving from anonymous to secured mode (staged rollout, config updates). | FSR1–FSR3 | Includes rollback plan + FAQ. | -| 4 | DOC4 | Create plugin developer how-to (leveraging Plugin Team notes) covering packaging, capability flags, logging. | PLG1–PLG6 | **READY FOR DOCS REVIEW (2025-10-10)** – `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md` aligned with PLG6 scope; pending Docs copy-edit, diagram export, and LDAP RFC cross-linking. | -| 5 | DOC5 | Update root README, quickstarts (`docs/10_FEEDSER_CLI_QUICKSTART.md`), CLI help text references. | CLI4, FSR4 | Make sure new links validated. | -| 6 | Cross | Collaborate on inline XML docs for public APIs across libraries. | LIB1–LIB5 | Ensure DocFX/IntelliSense friendly summaries. | - -## Implementation Notes -- Maintain offline-friendly instructions (no implicit internet requirements). -- Highlight security-critical steps (bootstrap credentials, key rotation) in callouts. -- Include environment-variable tables for configuration. -- Provide diagrams where useful (architecture, plugin flow). -- Prepare release note entry summarizing Authority MVP deliverables and upgrade steps. - -## Deliverables -- New documentation pages + updated existing guides. -- OpenAPI snippet (JSON/YAML) committed to repo. -- Migration checklist for operators. -- Plugin developer tutorial ready for community/internal teams. - -## Coordination -- Attend cross-team syncs to capture latest API contracts. -- Request reviews from respective teams (Authority Core, Plugin, CLI, Security). -- Work with DevEx to ensure docs packaged in Offline Kit if applicable. -- Update docs as soon as breaking changes occur—subscribe to relevant PRs. diff --git a/StellaOps.Authority.TODOS.Feedser.md b/StellaOps.Authority.TODOS.Feedser.md deleted file mode 100644 index d5322097..00000000 --- a/StellaOps.Authority.TODOS.Feedser.md +++ /dev/null @@ -1,35 +0,0 @@ -# StellaOps Authority — Feedser Integration Team - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Update both TODO trackers as tasks progress. - -## Mission -Adopt the new authority stack inside Feedser: configure authentication, enforce scopes, update configuration, and validate end-to-end flows. - -## Task Timeline - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | FSR1 | Extend `etc/feedser.yaml` with Authority configuration block (issuer, client credentials, bypass masks, scopes). | DevEx FND4, LIB2 | Sample config + docs updated. | -| 2 | FSR2 | Update Feedser WebService startup to use `AddStellaOpsResourceServerAuthentication`; annotate endpoints with `[Authorize]` and scope policies. | LIB2 | **DONE (2025-10-10)** – Auth wiring is optional but enabled via config; `/jobs*` endpoints demand `feedser.jobs.trigger` and tests cover bypass mode. | -| 3 | FSR3 | Implement bypass mask handling for on-host cron jobs; log when mask used. | FSR2, LIB3 | Configurable via YAML; integration test ensures mask respected. | -| 4 | FSR4 | Refresh Feedser docs (quickstart, operator guide) to explain auth requirements + config knobs. | FSR1–FSR3 | Coordinate with Docs team for final wording. | -| 5 | FSR5 | Build integration test harness (Authority + Feedser docker-compose) verifying token issuance and job triggering. | CORE1–CORE5, LIB4 | CI job produces pass/fail artefact. | - -## Implementation Notes -- Add feature flag to allow temporary anonymous mode for staged rollout (document sunset date). -- Ensure CLI + API docs reference required scopes and sample client creation. -- Logs should capture client ID, user ID, and scopes when jobs triggered for audit (without leaking secrets). -- Avoid coupling tests to specific plugin implementations—use Standard plugin via configuration. -- Share any new scopes/policies with Auth Libraries and Docs teams. - -## Deliverables -- Updated Feedser configuration + startup code. -- Documentation updates in `docs/10_FEEDSER_CLI_QUICKSTART.md` and `docs/11_AUTHORITY.md` (in partnership with Docs team). -- Integration tests executed in CI (Authority + Feedser). -- Rollout checklist for existing deployments (feature flag, config changes). - -## Coordination -- Sync with Authority Core on policy naming (`feedser.jobs.trigger`, `feedser.merge`). -- Coordinate with CLI team for shared sample configs. -- Work closely with DevOps to integrate integration tests into pipeline. -- Notify Security Guild once bypass masks implemented for review. diff --git a/StellaOps.Authority.TODOS.Plugin.md b/StellaOps.Authority.TODOS.Plugin.md deleted file mode 100644 index eacbcf02..00000000 --- a/StellaOps.Authority.TODOS.Plugin.md +++ /dev/null @@ -1,38 +0,0 @@ -# StellaOps Authority — Plugin Workstream - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this document. Sync status across all trackers. - -## Scope -Deliver the plugin abstraction layer and the default Mongo-backed identity plugin (`StellaOps.Authority.Plugin.Standard`), plus lay groundwork for future LDAP integration. - -## Task Plan - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | PLG1 | Implement plugin abstractions: `IIdentityProviderPlugin`, `IUserCredentialStore`, `IClaimsEnricher`, `IClientProvisioningStore`, result models, constants. | DevEx FND1 | **DONE** – Abstractions published with XML docs and unit tests covering success/failure factories. | -| 2 | PLG2 | Integrate abstractions with plugin host (DI registration via `IAuthorityPluginRegistrar`). Emit diagnostics for load failures. | PLG1 | **DONE** – Authority host loads registrars, logs registration summary, and unit tests cover success/missing cases. | -| 3 | PLG3 | Build Mongo-backed `Plugin.Standard` implementing password auth, lockout, claim enrichment, admin seeding. | CORE3 | **DONE** – Standard plugin binds options, enforces password policy/lockout, seeds bootstrap user, and ships integration/unit tests. | -| 4 | PLG4 | Define capability metadata (supportsPassword, supportsMfa, supportsClientProvisioning). Update plugin registration to publish metadata. | PLG3 | **DONE (2025-10-10)** – Capability descriptors validated; Standard plugin enforces password flag and registry exposes aggregated metadata to the host. | -| 5 | PLG5 (support) | Collaborate with DevEx on plugin config schema (`etc/authority.plugins/*.yaml`). Implement config parser + validation. | DevEx PLG5 | Provide typed options class + tests. | -| 6 | PLG6 | Author plugin developer guide (structure, packaging, capability flags, logging expectations). | PLG1–PLG5 | **READY FOR DOCS REVIEW (2025-10-10)** – Guide finalised, includes capability metadata usage, ops alignment, and packaging checklist; handoff blocked only on Docs copy-edit + diagram export. | -| 7 | PLG7 (backlog design) | Produce technical RFC for future `Plugin.Ldap` (data flows, dependencies, TODO list). | PLG1–PLG4 | **RFC DRAFTED (2025-10-10)** – `docs/rfcs/authority-plugin-ldap.md` outlines architecture, configuration schema, implementation plan; awaiting guild review & sign-off. | - -## Implementation Notes -- Mongo plugin must support offline bootstrap: optional JSON file with initial users/clients hashed offline. -- Provide extensibility points for password hashing algorithm (allow Security team to swap Argon2). -- Ensure plugin logging leverages Authority logger, no console writes. -- Document expected configuration keys for plugin settings (`passwordPolicy`, `seedUsers`, etc.). -- Validate plugin configuration early at startup; fail fast with actionable errors. - -## Deliverables -- `StellaOps.Authority.Plugins.Abstractions` project. -- `StellaOps.Authority.Plugin.Standard` project with tests + seed data sample. -- Plugin dev documentation + sample configuration files. -- Diagnostic logging verifying plugin load, capabilities, configuration. -- Future plugin RFC for LDAP integration. - -## Coordination -- Coordinate with Authority Core for capability usage in handlers. -- Work with Security Guild on password hash settings/lockout thresholds. -- Notify DevEx when configuration schema changes. -- Review Docs PR for plugin developer guide. diff --git a/StellaOps.Authority.TODOS.Security.md b/StellaOps.Authority.TODOS.Security.md deleted file mode 100644 index b1a41432..00000000 --- a/StellaOps.Authority.TODOS.Security.md +++ /dev/null @@ -1,36 +0,0 @@ -# StellaOps Authority — Security Guild Plan - -> **Read first:** `AGENTS.md`, `StellaOps.Authority.TODOS.md`, and this plan. Track progress in both TODO files. - -## Mission -Define and verify the security posture of StellaOps Authority: password/secret policies, audit logging, throttling, threat modelling, and offline revocation guarantees. - -## Task Breakdown - -| Order | Task IDs | Description | Dependencies | Acceptance | -|-------|----------|-------------|--------------|------------| -| 1 | SEC1 | Select and configure password hashing (Argon2 preferred) + identity lockout parameters; contribute config defaults. | PLG3, CORE3 | Hash verified via unit test + red team review. | -| 2 | SEC2 | Specify audit log schema/content (principal, client, scopes, IP) and ensure Authority Core implementation meets requirements. | CORE5–CORE7 | Review sample logs; ensure PII handled safely. | -| 3 | SEC3 | Define lockout & rate limit policies (per user/IP) and validate implementation in Authority Core. | CORE8 | Test harness proves lockouts triggered appropriately. | -| 4 | SEC4 | Design offline revocation list format + signing procedure; review implementation with Core/DevOps. | CORE9, OPS3 | Provide verification script for downstream systems. | -| 5 | SEC5 | Conduct threat model / security review (STRIDE) covering plugins, token flows, admin endpoints; produce mitigation backlog if needed. | CORE1–CORE10 | Document stored in `/docs/security`. | -| 6 | Oversight | Perform security review of CLI/Feedser integration changes (token handling, bypass masks). | FSR2, CLI2 | Approve PRs or request hardening changes. | - -## Implementation Notes -- Require secrets (client, bootstrap API keys) to meet minimum entropy; document rotation expectations. -- Ensure bypass mask usage is fully logged + alertable. -- Recommend default TLS cipher suites for Authority deployments. -- Validate plugin capability metadata doesn’t expose insecure combinations (e.g., plugin without password support cannot be selected for password grant). -- Develop checklist for production readiness (penetration test, log review, key rotation rehearsal). - -## Deliverables -- Security configuration recommendations (encoded in options + documentation). -- Approved audit log schema & sample records. -- Threat model document + mitigation backlog (if gaps discovered). -- Sign-off memo to enable production rollout. - -## Coordination -- Work closely with Authority Core and Plugin teams during implementation; request changes early. -- Pair with DevOps on key rotation / secret storage solutions. -- Review Docs to ensure operator guidance includes security-critical steps. -- Attend weekly Auth Guild sync to surface risks/blockers. diff --git a/StellaOps.Authority.TODOS.md b/StellaOps.Authority.TODOS.md deleted file mode 100644 index dde2b4b2..00000000 --- a/StellaOps.Authority.TODOS.md +++ /dev/null @@ -1,120 +0,0 @@ -# StellaOps.Authority — Implementation Backlog - -> Status owner: Platform Authentication Guild -> Source inspiration: `inspiration/Ablera.Serdica.*` (do **not** copy-paste; align with StellaOps coding standards) - -## 0. Foundations - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| FND1 | Create solution scaffold under `src/StellaOps.Authority` (`StellaOps.Authority.sln` mirroring existing structure). | DevEx | **DONE** – Authority host + auth libraries + plugin stub scaffolded with net10.0 preview defaults. | -| FND2 | Extend `global.json`/Directory props to include new projects (net10.0). | DevEx | **DONE** – Directory props/targets cover Authority plugins; root `StellaOps.sln` enables repo-wide `dotnet build` (Feedser compile issues remain pre-existing). | -| FND3 | Define `StellaOpsAuthorityOptions` in `StellaOps.Configuration` (issuer, lifetimes, plugin directories, bypass masks). | BE-Base | **DONE** – Options class + bootstrapper with validation and tests; binds from YAML/JSON/env. | -| FND4 | Provide sample config `etc/authority.yaml.sample` with sensible defaults for offline-first deployments. | DevEx/Docs | **DONE** – Authority template published with token defaults + plug-in toggles and referenced in README/Quickstart. | -| FND5 | Add OpenTelemetry resource/version constants for Authority (service.name, namespace). | DevEx/Observability | **DONE** – Authority telemetry constants & helpers published for reuse by host/plugins. | - -## 1. Core Authority Service - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| CORE1 | Bootstrap ASP.NET minimal API host with `StellaOps.Configuration` and plugin loading (reuse Feedser plugin host). | BE-Base | **DONE (2025-10-09)** – Host loads Authority options, Serilog, plugin registry; `/health` and `/ready` return 200. | -| CORE2 | Integrate OpenIddict server: configure issuer, endpoints (`/authorize`, `/token`, `/jwks`, `/introspect`, `/revoke`), token lifetimes. | BE-Auth | **DONE (2025-10-09)** – OpenIddict server wired with required endpoints, lifetimes, sliding refresh tokens, dev-only HTTPS relaxation. | -| CORE3 | Implement Mongo-backed stores (`AuthorityUser`, `AuthorityClient`, `AuthorityScope`, `AuthorityToken`, `AuthorityLoginAttempt`). | BE-Auth Storage | **DONE (2025-10-09)** – Mongo storage project with indexed collections, repository layer, and bootstrap migration runner wired to host. | -| CORE4 | Add `IUserCredentialStore`, `IClaimsEnricher`, `IClientCredentialStore`, `IIdentityProviderPlugin` abstractions (plugin contracts). | BE-Auth | Live under `StellaOps.Authority.Plugins.Abstractions`. | -| CORE5 | Port/customize OpenIddict event handlers (password grant, client credentials, token validation) using plugin contracts. | BE-Auth | **DONE (2025-10-10)** – Password, client-credentials, and token-validation handlers now enforce plugin capabilities, persist issued tokens, and run revocation checks. | -| CORE5A | Author integration tests verifying token persistence + revocation (client creds & refresh) through `IAuthorityTokenStore`. | QA, BE-Auth | Ensure revoked tokens are denied via handler + store wiring; cover reference token retrieval when implemented. | -| CORE5B | Document token persistence behaviour (revocation, enrichment) for resource servers + bootstrap guide. | Docs, BE-Auth | Update `docs/11_AUTHORITY.md` and plugin dev guide with new claims + store expectations before GA. | -| CORE6 | Implement API key protected bootstrap endpoints (`POST /internal/clients`, `POST /internal/users`) for initial provisioning. | BE-Auth | **DONE (2025-10-10)** – `/internal` APIs gated by bootstrap API key create users/clients through plugin stores. | -| CORE7 | Wire structured logging + OTEL spans for `/token`, `/authorize`, plugin actions. | BE-Auth Observability | Follows StellaOps logging conventions. | -| CORE8 | Add rate limiting middleware on `/token` and `/authorize`. | BE-Auth | Configurable via options; tests ensure throttle triggered. | -| CORE9 | Implement revocation (refresh + access) and publish signed offline revocation list. | BE-Auth | CLI hook to export list for air-gapped sync. | -| CORE10 | Provide JWKS endpoint backed by rotating signing/encryption keys (pluggable certificate loader). | BE-Auth | Document rotation workflow. | - -## 2. Plugin System - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| PLG1 | Build `StellaOps.Authority.Plugins.Abstractions` (contracts, result models, constants). | BE-Auth | Align naming with StellaOps; add XML docs. | -| PLG2 | Implement plugin discovery via existing plugin host (search `PluginBinaries` for `StellaOps.Authority.Plugin.*`). | BE-Base | Provide diagnostics when plugin load fails. | -| PLG3 | Develop `StellaOps.Authority.Plugin.Standard` (Mongo-based user store, password hashing, lockout policy). | BE-Auth Storage | Includes configurable password policy + seed admin user. | -| PLG4 | Add plugin capability metadata (supportsPassword, supportsMfa, supportsClientProvisioning). | BE-Auth | **DONE (2025-10-10)** – Descriptor validation + registry logging wired; Standard plugin forces password capability and warns on misconfiguration. | -| PLG5 | Define plugin configuration schema under `etc/authority.plugins/*.yaml`; load via `StellaOps.Configuration`. | DevEx/Docs | **DONE** – Loader helpers + sample manifests committed; schema validated during bootstrap. | -| PLG6 | Publish developer guide for writing Authority plugins mirroring Feedser docs. | DevEx/Docs | **READY FOR DOCS REVIEW (2025-10-10)** – `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md` finalised with capability guidance, ops alignment, testing checklist; awaiting copy-edit & diagram polish by Docs guild. | -| PLG7 | Future placeholder: outline backlog for LDAP plugin (`StellaOps.Authority.Plugin.Ldap`) with story-level TODOs. | BE-Auth | **RFC DRAFTED (2025-10-10)** – See `docs/rfcs/authority-plugin-ldap.md` for architecture, configuration schema, testing plan, and open questions awaiting guild review. | - -## 3. Shared Auth Libraries - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| LIB1 | Create `StellaOps.Auth.Abstractions` (claims, scopes, ProblemResultFactory, PrincipalBuilder). | BE-Auth | **DONE (2025-10-10)** – Added claim/scope constants, deterministic principal builder, problem result helpers, and xUnit coverage for normalization paths. | -| LIB2 | Implement `StellaOps.Auth.ServerIntegration` DI extensions (JWT bearer, bypass masks, policy helpers). | BE-Auth | **DONE (2025-10-10)** – Delivered `AddStellaOpsResourceServerAuthentication`, scope policies, bypass evaluator, and integration tests. | -| LIB3 | Migrate CIDR-matching logic (`NetworkMaskMatcher`) with IPv4/6 support + tests. | BE-Auth | **DONE (2025-10-10)** – New matcher + `NetworkMask` parser with 100% branch coverage replacing legacy serdica helpers. | -| LIB4 | Add `StellaOps.Auth.Client` with discovery, JWKS caching, password/client credentials flows, token cache abstraction. | DevEx/CLI | **DONE (2025-10-10)** – Implemented typed client, discovery/JWKS caches, in-memory/file token caches, and CLI-focused unit tests. | -| LIB5 | Integrate Polly (configurable) and HttpClientFactory patterns in client library. | DevEx | Ensure retries/offline fallback configurable. | -| LIB6 | Publish NuGet packaging metadata (License, SourceLink) for new libraries. | DevEx | Align with repo packaging conventions. | - -## 4. Feedser Integration - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| FSR1 | Extend `etc/feedser.yaml` with Authority section (issuer, client credentials, bypass masks). | DevEx/Docs | Document mandatory vs optional settings. | -| FSR2 | Update Feedser WebService startup to call `AddStellaOpsResourceServerAuthentication` and enforce scopes/roles on job endpoints. | BE-Base | **DONE (2025-10-10)** – Feedser conditionally wires the resource server auth helper, protects all `/jobs` routes, and documents `authority` config. | -| FSR3 | Add configuration-driven fallback for on-host cron (network mask bypass). | BE-Base | Must be auditable via logs. | -| FSR4 | Adjust Feedser CLI doc references to note new auth requirements. | Docs | Update quickstart & CLI reference. | -| FSR5 | Write end-to-end integration tests (Authority + Feedser) verifying token issuance and job trigger flow (use docker-compose). | QA | Runs in CI nightly. | - -## 5. CLI Integration - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| CLI1 | Extend CLI config (`StellaOpsCliOptions`) with Authority fields (AuthorityUrl, ClientId, ClientSecret, Username, Password). | DevEx/CLI | Environment variable support. | -| CLI2 | Implement `stellaops-cli auth login/logout/status` commands using `StellaOps.Auth.Client`. | DevEx/CLI | Tokens stored via `ITokenCache`; support password + client creds. | -| CLI3 | Ensure all API calls attach bearer tokens; handle 401/403 with friendly output. | DevEx/CLI | Regression tests for unauthorized scenarios. | -| CLI4 | Update CLI docs & help text to reference authentication workflow. | Docs | Include example flows. | - -## 6. Deployment & Ops - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| OPS1 | Provide distroless Dockerfile + compose example (Authority + Mongo + optional Redis). | DevOps | **DONE (scaffold)** – Dockerfile + compose sample published under `ops/authority/`; offline-friendly mounts + volumes ready for DevOps hardening. | -| OPS2 | Implement CI pipeline stages (build, unit tests, integration tests, publish artifacts). | DevOps | **DONE** – CI workflow now builds/tests Authority, publishes artifacts, and builds container image alongside Feedser. | -| OPS3 | Add automated key rotation job (CLI or script) and document manual procedure. | DevOps/BE-Auth | Integrate with JWKS endpoint. | -| OPS4 | Document backup/restore steps for Authority Mongo collections and key material. | Docs/DevOps | Cover offline site restore. | -| OPS5 | Define monitoring/alerting rules (token issuance failure rates, auth errors). | Observability | Provide Prometheus/OpenTelemetry guidance. | - -## 7. Security & Compliance - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| SEC1 | Adopt ASP.NET Identity password hashing defaults (Argon2 if available). | BE-Auth | Verify with penetration test harness. | -| SEC2 | Implement audit log (structured) for token issuance, revocation, admin actions (including plugin events). | BE-Auth | Logs must include principal, scopes, client, IP. | -| SEC3 | Add configurable lockout/throttle rules (per user + per IP). | BE-Auth | Integration tests confirm lock after threshold. | -| SEC4 | Support offline revocation list generation/signing (for air-gapped exports). | BE-Auth/QA | CLI command + verification doc. | -| SEC5 | Conduct threat model review + update documentation with mitigations. | Security Guild | Include password grant hardening notes. | - -## 8. Documentation & Enablement - -| ID | Task | Owner | Notes / Acceptance | -|----|------|-------|---------------------| -| DOC1 | Author `docs/11_AUTHORITY.md` covering architecture, configuration, plugin model, operational playbooks. | Docs | Reference sample configs and CLI flows. | -| DOC2 | Produce API reference snippet (OpenAPI fragment) for `/token`, `/jwks`, `/introspect`, `/revoke`. | Docs/BE-Auth | Link in docs & README. | -| DOC3 | Write migration guide from anonymous Feedser to secured Feedser (staged rollout). | Docs/BE-Auth | Address bootstrap credentials and cut-over steps. | -| DOC4 | Create plugin developer how-to referencing new abstractions. | Docs/DevEx | Include example plugin skeleton. | -| DOC5 | Update repository README quickstart to point to Authority docs once live. | Docs | After Authority MVP lands. | - -## 9. Backlog / Future Enhancements - -| ID | Idea | Notes | -|----|------|-------| -| FUT1 | Multi-factor authentication plugin capability (TOTP / WebAuthn) via plugin metadata. | Requires UX + plugin changes. | -| FUT2 | Admin UI (React/Angular) for managing users/clients. | Defer until API stabilizes. | -| FUT3 | Federation with Microsoft Entra ID using OIDC upstream (Authority acts as broker). | Align with future integration strategy. | -| FUT4 | Device authorization flow support for offline agents. | Dependent on client library maturity. | -| FUT5 | Plugin marketplace packaging guidelines (versioning, signing). | Coordinate with product team. | - ---- - -**Coordination Notes** -- Dedicated triage meetings weekly (Auth Guild) to review progress and unblock module owners. -- Plugin + Authority changes must coordinate with QA for end-to-end scenarios (Authority ↔ Feedser ↔ CLI). -- Security reviews required before enabling Authority in production environments. diff --git a/TASKS.md b/TASKS.md deleted file mode 100644 index 75270dd0..00000000 --- a/TASKS.md +++ /dev/null @@ -1,17 +0,0 @@ -# TASKS -| Task | Owner(s) | Depends on | Notes | -|---|---|---|---| -|Merge identity graph & alias store|BE-Merge|Models, Storage.Mongo|**DONE** – alias store/resolver, component builder, reconcile job, persistence + diagnostics endpoint landed.| -|OSV alias consolidation & per-ecosystem snapshots|BE-Conn-OSV, QA|Merge, Testing|DONE – alias graph handles GHSA/CVE records and deterministic snapshots exist across ecosystems.| -|Oracle PSIRT pipeline completion|BE-Conn-Oracle|Source.Common, Core|**DONE** – Oracle mapper now emits CVE aliases, vendor affected packages, patch references, and resume/backfill flow is covered by integration tests.| -|VMware connector observability & resume coverage|BE-Conn-VMware, QA|Source.Common, Storage.Mongo|**DONE** – VMware diagnostics emit fetch/parse/map metrics, fetch dedupe uses hash cache, and integration test covers snapshot plus resume path.| -|Model provenance & range backlog|BE-Merge|Models|**DOING** – VMware/Oracle/Chromium, NVD, Debian, SUSE, Ubuntu, Adobe, ICS Kaspersky, CERT-In, CERT-FR, JVN, and KEV now emit RangePrimitives (KEV adds due-date/vendor extensions with deterministic snapshots). Remaining connectors (`Acsc`, `Cccs`, `CertBund`, `CertCc`, `Cve`, `Ghsa`, `Ics.Cisa`, `Kisa`, `Ru.Bdu`, `Ru.Nkcki`, `Vndr.Apple`, `Vndr.Cisco`, `Vndr.Msrc`) still need structured coverage.| -|Trivy DB exporter delta strategy|BE-Export|Exporters|**DONE** – planner promotes chained deltas back to full exports, OCI writer reuses base blobs, regression tests cover the delta→delta→full sequence, and a full-stack layer-reuse smoke test + operator docs landed (2025-10-10).| -|Red Hat fixture validation sweep|QA|Source.Distro.RedHat|**DOING** – finalize RHSA fixture regeneration once connector regression fixes land.| -|JVN VULDEF schema update|BE-Conn-JVN, QA|Source.Jvn|**DONE** – schema patched (vendor/product attrs, impact entries, err codes), parser tightened, fixtures/tests refreshed.| -|Build/test sweeps|QA|All modules|**DONE** – wired Authority plugin abstractions into the build, updated CLI export tests for the new overrides, and full `dotnet test` now succeeds (perf suite within budget).| -|Authority plugin PLG1–PLG3|BE-Auth Plugin|Authority DevEx|**DONE** – abstractions/tests shipped, plugin loader integrated, and Mongo-backed Standard plugin stub operational with bootstrap seeding.| -|Authority plugin PLG4–PLG6|BE-Auth Plugin, DevEx/Docs|Authority plugin PLG1–PLG3|**READY FOR DOCS REVIEW (2025-10-10)** – Capability metadata validated, configuration guardrails shipped, developer guide finalised; waiting on Docs polish + diagram export.| -|Authority plugin PLG7 RFC|BE-Auth Plugin|PLG4|**DRAFTED (2025-10-10)** – `docs/rfcs/authority-plugin-ldap.md` captured LDAP plugin architecture, configuration schema, and implementation plan; needs Auth/Security guild review.| -|Feedser modularity test sweep|BE-Conn/QA|Feedser build|**DONE (2025-10-10)** – AngleSharp upgrade applied, helper assemblies copy-local, Kaspersky fixtures updated; full `dotnet test src/StellaOps.Feedser.sln` now passes locally.| -|OSV vs GHSA parity checks|QA, BE-Merge|Merge|**DONE** – parity inspector/diagnostics wired into OSV connector regression sweep; fixtures validated via `OsvGhsaParityRegressionTests` (see docs/19_TEST_SUITE_OVERVIEW.md) and metrics emitted through `OsvGhsaParityDiagnostics`.| diff --git a/TODOS.md b/TODOS.md deleted file mode 100644 index f4d12bfc..00000000 --- a/TODOS.md +++ /dev/null @@ -1,36 +0,0 @@ -# Pending Task Backlog - -> Last updated: 2025-10-09 (UTC) - -## Common - -- **Build/test sweeps (QA – DONE)** - Full `dotnet test` is green again after wiring the Authority plugin abstractions into `StellaOps.Configuration` and updating CLI export tests for the new publish/include overrides. Keep running the sweep weekly and capture timings so we catch regressions early. - -- **OSV vs GHSA parity checks (QA & BE-Merge – TODO)** - Design and implement a diff detector comparing OSV advisories against GHSA records. The deliverable should flag mismatched aliases, missing affected ranges, or divergent severities, surface actionable telemetry/alerts, and include regression tests with canned OSV+GHSA fixtures. - -## Prerequisites - -- **Range primitives for SemVer/EVR/NEVRA metadata (BE-Merge – DOING)** - The core model supports range primitives, but several connectors still emit raw strings. Current gaps (snapshot 2025‑10‑09, post-Kaspersky/CERT-In/CERT-FR/JVN updates): `Acsc`, `Cccs`, `CertBund`, `CertCc`, `Cve`, `Ghsa`, `Ics.Cisa`, `Kev`, `Kisa`, `Ru.Bdu`, `Ru.Nkcki`, `Vndr.Apple`, `Vndr.Cisco`, `Vndr.Msrc`. We need to extend those mappers to populate the structured envelopes (SemVer/EVR/NEVRA plus vendor extensions) and add fixture coverage so merge/export layers see consistent telemetry. (Delivered: ICS.Kaspersky, CERT-In, CERT-FR emit vendor primitives; JVN captures version/build metadata.) - -- **Provenance envelope field masks (BE-Merge – DOING)** - Provenance needs richer categorisation (component category, severity bands, resume counters) and better dedupe metrics. Update the provenance model, extend diagnostics to emit the new tags, and refresh dashboards/tests to ensure determinism once additional metadata flows through. - -## Implementations - -- **Model provenance & range backlog (BE-Merge – DOING)** - With Adobe/Ubuntu now emitting range primitives, focus on the remaining connectors (e.g., Apple, smaller vendor PSIRTs). Update their pipelines, regenerate goldens, and confirm `feedser.range.primitives` metrics reflect the added telemetry. The task closes when every high-priority source produces structured ranges with provenance. - -- **Trivy DB exporter delta strategy (BE-Export – TODO)** - Finalise the delta-reset story in `ExportStateManager`: define when to invalidate baselines, how to reuse unchanged layers, and document operator workflows. Implement planner logic for layer reuse, update exporter tests, and exercise a delta→full→delta sequence. - -- **Red Hat fixture validation sweep (QA – DOING)** - Regenerate RHSA fixtures with the latest connector output and make sure the regenerated snapshots align once the outstanding connector tweaks land. Pending prerequisites: land the mapper reference-normalisation patch (local branch `redhat/ref-dedupe`) and the range provenance backfill (`RangePrimitives.GetCoverageTag`). Once those land, run `UPDATE_RHSA_FIXTURES=1 dotnet test src/StellaOps.Feedser.Source.Distro.RedHat.Tests/StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj`, review the refreshed `Fixtures/rhsa-*.json`, and sync the task status to **DONE**. - -- **Plan incremental/delta exports (BE-Export – DOING)** - `TrivyDbExportPlanner` now captures changed files but does not yet reuse existing OCI layers. Extend the planner to build per-file manifests, teach the writer to skip untouched layers, and add delta-cycle tests covering file removals, additions, and checksum changes. - -- **Scan execution & result upload workflow (DevEx/CLI & Ops Integrator – DOING)** - `stella scan run` now emits a structured `scan-run-*.json` alongside artefacts. Remaining work: add resilient upload retries/backoff, cover success/retry/cancellation with integration tests, and expand docs with docker/dotnet/native runner examples plus metadata troubleshooting tips. diff --git a/WEB-TODOS.md b/WEB-TODOS.md deleted file mode 100644 index 8ad4c38c..00000000 --- a/WEB-TODOS.md +++ /dev/null @@ -1,3 +0,0 @@ -# Web UI Follow-ups - -- Trivy DB exporter settings panel: surface `publishFull` / `publishDelta` and `includeFull` / `includeDelta` toggles, saving overrides via future `/exporters/trivy-db/settings` API. Include “run export now” button that reuses those overrides when triggering `export:trivy-db`. diff --git a/docs/09_API_CLI_REFERENCE.md b/docs/09_API_CLI_REFERENCE.md index 5266338f..483e2310 100755 --- a/docs/09_API_CLI_REFERENCE.md +++ b/docs/09_API_CLI_REFERENCE.md @@ -208,6 +208,17 @@ Configuration follows the same precedence chain everywhere: 3. `appsettings.yaml` → `appsettings.local.yaml` 4. Defaults (`ApiKey = ""`, `BackendUrl = ""`, cache folders under the current working directory) +**Authority auth client resilience settings** + +| Setting | Environment variable | Default | Purpose | +|---------|----------------------|---------|---------| +| `StellaOps:Authority:Resilience:EnableRetries` | `STELLAOPS_AUTHORITY_ENABLE_RETRIES` | `true` | Toggle Polly wait-and-retry handlers for discovery/token calls | +| `StellaOps:Authority:Resilience:RetryDelays` | `STELLAOPS_AUTHORITY_RETRY_DELAYS` | `1s,2s,5s` | Comma/space-separated backoff sequence (HH:MM:SS) | +| `StellaOps:Authority:Resilience:AllowOfflineCacheFallback` | `STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK` | `true` | Reuse cached discovery/JWKS metadata when Authority is temporarily unreachable | +| `StellaOps:Authority:Resilience:OfflineCacheTolerance` | `STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE` | `00:10:00` | Additional tolerance window added to the discovery/JWKS cache lifetime | + +See `docs/dev/32_AUTH_CLIENT_GUIDE.md` for recommended profiles (online vs. air-gapped) and testing guidance. + | Command | Purpose | Key Flags / Arguments | Notes | |---------|---------|-----------------------|-------| | `stellaops-cli scanner download` | Fetch and install scanner container | `--channel ` (default `stable`)
`--output `
`--overwrite`
`--no-install` | Saves artefact under `ScannerCacheDirectory`, verifies digest/signature, and executes `docker load` unless `--no-install` is supplied. | @@ -216,7 +227,7 @@ Configuration follows the same precedence chain everywhere: | `stellaops-cli db fetch` | Trigger connector jobs | `--source ` (e.g. `redhat`, `osv`)
`--stage ` (default `fetch`)
`--mode ` | Translates to `POST /jobs/source:{source}:{stage}` with `trigger=cli` | | `stellaops-cli db merge` | Run canonical merge reconcile | — | Calls `POST /jobs/merge:reconcile`; exit code `0` on acceptance, `1` on failures/conflicts | | `stellaops-cli db export` | Kick JSON / Trivy exports | `--format ` (default `json`)
`--delta`
`--publish-full/--publish-delta`
`--bundle-full/--bundle-delta` | Sets `{ delta = true }` parameter when requested and can override ORAS/bundle toggles per run | -| `stellaops-cli auth ` | Manage cached tokens for StellaOps Authority | `auth login --force` (ignore cache)
`auth status` | Uses `StellaOps.Auth.Client` under the hood; honours `StellaOps:Authority:*` configuration | +| `stellaops-cli auth ` | Manage cached tokens for StellaOps Authority | `auth login --force` (ignore cache)
`auth status`
`auth whoami` | Uses `StellaOps.Auth.Client`; honours `StellaOps:Authority:*` configuration, stores tokens under `~/.stellaops/tokens` by default, and `whoami` prints subject/scope/expiry | When running on an interactive terminal without explicit override flags, the CLI uses Spectre.Console prompts to let you choose per-run ORAS/offline bundle behaviour. | `stellaops-cli config show` | Display resolved configuration | — | Masks secret values; helpful for air‑gapped installs | @@ -275,8 +286,20 @@ feedser: **Authentication** - API key is sent as `Authorization: Bearer ` automatically when configured. -- Anonymous operation (empty key) is permitted for offline use cases but backend calls will fail with 401 unless the Feedser instance allows guest access. -- When `StellaOps:Authority:Url` is set the CLI initialises the StellaOps auth client. Use `stellaops-cli auth login` to obtain a token (password grant when `Username`/`Password` are set, otherwise client credentials). Tokens are cached under `~/.stellaops/tokens` by default; `auth status` shows expiry and `auth logout` removes the cached entry. +- Anonymous operation is permitted only when Feedser runs with + `authority.allowAnonymousFallback: true`. This flag is temporary—plan to disable + it before **2025-12-31 UTC** so bearer tokens become mandatory. + +Authority-backed auth workflow: +1. Configure Authority settings via config or env vars (see sample below). Minimum fields: `Url`, `ClientId`, and either `ClientSecret` (client credentials) or `Username`/`Password` (password grant). +2. Run `stellaops-cli auth login` to acquire and cache a token. Use `--force` if you need to ignore an existing cache entry. +3. Execute CLI commands as normal—the backend client injects the cached bearer token automatically and retries on transient 401/403 responses with operator guidance. +4. Inspect the cache with `stellaops-cli auth status` (shows expiry, scope, mode) or clear it via `stellaops-cli auth logout`. +5. Run `stellaops-cli auth whoami` to dump token subject, audience, issuer, scopes, and remaining lifetime (verbose mode prints additional claims). +6. Expect Feedser to emit audit logs for each `/jobs*` request showing `subject`, + `clientId`, `scopes`, `status`, and whether network bypass rules were applied. + +Tokens live in `~/.stellaops/tokens` unless `StellaOps:Authority:TokenCacheDirectory` overrides it. Cached tokens are reused offline until they expire; the CLI surfaces clear errors if refresh fails. **Configuration file template** diff --git a/docs/10_FEEDSER_CLI_QUICKSTART.md b/docs/10_FEEDSER_CLI_QUICKSTART.md index 38d5e1ff..7283d6d8 100644 --- a/docs/10_FEEDSER_CLI_QUICKSTART.md +++ b/docs/10_FEEDSER_CLI_QUICKSTART.md @@ -55,8 +55,9 @@ runtime wiring, CLI usage) and leaves connector/internal customization for later - `GET /ready` – performs a MongoDB `ping` - `GET /jobs` + `POST /jobs/{kind}` – inspect and trigger connector/export jobs - > **Security note** – authentication is not wired yet; guard the service with - > network controls or a reverse proxy until auth middleware ships. + > **Security note** – authentication now ships via StellaOps Authority. Keep + > `authority.allowAnonymousFallback: true` only during the staged rollout and + > disable it before **2025-12-31 UTC** so tokens become mandatory. ### Authority companion configuration (preview) @@ -90,18 +91,39 @@ defaults live in `src/StellaOps.Cli/appsettings.json` and expect overrides at ru | Setting | Environment variable | Default | Purpose | | ------- | -------------------- | ------- | ------- | | `BackendUrl` | `STELLAOPS_BACKEND_URL` | _empty_ | Base URL of the Feedser web service | -| `ApiKey` | `API_KEY` | _empty_ | Reserved for future auth; keep empty today | +| `ApiKey` | `API_KEY` | _empty_ | Reserved for legacy key auth; leave empty when using Authority | | `ScannerCacheDirectory` | `STELLAOPS_SCANNER_CACHE_DIRECTORY` | `scanners` | Local cache folder | | `ResultsDirectory` | `STELLAOPS_RESULTS_DIRECTORY` | `results` | Where scan outputs are written | +| `Authority.Url` | `STELLAOPS_AUTHORITY_URL` | _empty_ | StellaOps Authority issuer/token endpoint | +| `Authority.ClientId` | `STELLAOPS_AUTHORITY_CLIENT_ID` | _empty_ | Client identifier for the CLI | +| `Authority.ClientSecret` | `STELLAOPS_AUTHORITY_CLIENT_SECRET` | _empty_ | Client secret (omit when using username/password grant) | +| `Authority.Username` | `STELLAOPS_AUTHORITY_USERNAME` | _empty_ | Username for password grant flows | +| `Authority.Password` | `STELLAOPS_AUTHORITY_PASSWORD` | _empty_ | Password for password grant flows | +| `Authority.Scope` | `STELLAOPS_AUTHORITY_SCOPE` | `feedser.jobs.trigger` | OAuth scope requested for backend operations | +| `Authority.TokenCacheDirectory` | `STELLAOPS_AUTHORITY_TOKEN_CACHE_DIR` | `~/.stellaops/tokens` | Directory that persists cached tokens | +| `Authority.Resilience.EnableRetries` | `STELLAOPS_AUTHORITY_ENABLE_RETRIES` | `true` | Toggle Polly retry handler for Authority HTTP calls | +| `Authority.Resilience.RetryDelays` | `STELLAOPS_AUTHORITY_RETRY_DELAYS` | `1s,2s,5s` | Comma- or space-separated backoff delays (hh:mm:ss) | +| `Authority.Resilience.AllowOfflineCacheFallback` | `STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK` | `true` | Allow CLI to reuse cached discovery/JWKS metadata when Authority is offline | +| `Authority.Resilience.OfflineCacheTolerance` | `STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE` | `00:10:00` | Additional tolerance window applied to cached metadata | Example bootstrap: ```bash export STELLAOPS_BACKEND_URL="http://localhost:5000" export STELLAOPS_RESULTS_DIRECTORY="$HOME/.stellaops/results" +export STELLAOPS_AUTHORITY_URL="https://authority.local" +export STELLAOPS_AUTHORITY_CLIENT_ID="feedser-cli" +export STELLAOPS_AUTHORITY_CLIENT_SECRET="s3cr3t" dotnet run --project src/StellaOps.Cli -- db merge + +# Acquire a bearer token and confirm cache state +dotnet run --project src/StellaOps.Cli -- auth login +dotnet run --project src/StellaOps.Cli -- auth status +dotnet run --project src/StellaOps.Cli -- auth whoami ``` +Refer to `docs/dev/32_AUTH_CLIENT_GUIDE.md` for deeper guidance on tuning retry/offline settings and rollout checklists. + To persist configuration, you can create `stellaops-cli.yaml` next to the binary or rely on environment variables for ephemeral runners. @@ -212,21 +234,56 @@ a problem document. ## 5 · Next Steps -- Introduce authentication/authorization in the web service before exposing it on - shared networks. +- Enable authority-backed authentication in non-production first. Set + `authority.enabled: true` while keeping `authority.allowAnonymousFallback: true` + to observe logs, then flip it to `false` before 2025-12-31 UTC to enforce tokens. - Automate the workflow above via CI/CD (compose stack or Kubernetes CronJobs). - Pair with the Feedser connector teams when enabling additional sources so their module-specific requirements are pulled in safely. --- -## 6 · Microsoft Authentication Integration (Planned) +## 6 · Authority Integration + +- Feedser now authenticates callers through StellaOps Authority using OAuth 2.0 + resource server flows. Populate the `authority` block in `feedser.yaml`: + + ```yaml + authority: + enabled: true + allowAnonymousFallback: false # keep true only during the staged rollout window + issuer: "https://authority.example.org" + audiences: + - "api://feedser" + requiredScopes: + - "feedser.jobs.trigger" + clientId: "feedser-jobs" + clientSecretFile: "../secrets/feedser-jobs.secret" + clientScopes: + - "feedser.jobs.trigger" + bypassNetworks: + - "127.0.0.1/32" + - "::1/128" + ``` + +- Store the client secret outside of source control. Either provide it via + `authority.clientSecret` (environment variable `FEEDSER_AUTHORITY__CLIENTSECRET`) + or point `authority.clientSecretFile` to a file mounted at runtime. +- Cron jobs running on the same host can keep using the API thanks to the loopback + bypass mask. Add additional CIDR ranges as needed; every bypass is logged. +- Export the same configuration to Kubernetes or systemd by setting environment + variables such as: + + ```bash + export FEEDSER_AUTHORITY__ENABLED=true + export FEEDSER_AUTHORITY__ALLOWANONYMOUSFALLBACK=false + export FEEDSER_AUTHORITY__ISSUER="https://authority.example.org" + export FEEDSER_AUTHORITY__CLIENTID="feedser-jobs" + export FEEDSER_AUTHORITY__CLIENTSECRETFILE="/var/run/secrets/feedser/authority-client" + ``` -- The Feedser web service will integrate with the Microsoft identity stack (Entra ID) - using OAuth 2.0. Expect additional configuration keys for authority URLs, client - IDs/secrets, and audience scopes once the implementation lands. - CLI commands already pass `Authorization` headers when credentials are supplied. - When auth is enabled, point `stellaops-cli` at the token issuer (client credentials - flow) or run it behind a proxy that injects bearer tokens. -- Keep network-facing deployments behind reverse proxies or firewalls until the - authentication middleware ships and is fully validated. + Configure the CLI with matching Authority settings (`docs/09_API_CLI_REFERENCE.md`) + so that automation can obtain tokens with the same client credentials. Feedser + logs every job request with the client ID, subject (if present), scopes, and + a `bypass` flag so operators can audit cron traffic. diff --git a/docs/17_SECURITY_HARDENING_GUIDE.md b/docs/17_SECURITY_HARDENING_GUIDE.md index 068c01bb..92d33a10 100755 --- a/docs/17_SECURITY_HARDENING_GUIDE.md +++ b/docs/17_SECURITY_HARDENING_GUIDE.md @@ -140,10 +140,25 @@ cosign verify ghcr.io/stellaops/backend@sha256: \ | Control | Implementation | | ------------ | ----------------------------------------------------------------- | -| Log format | Serilog JSON; ship via Fluent‑Bit to ELK or Loki | -| Metrics | Prometheus /metrics endpoint; default Grafana dashboard in infra/ | -| Audit events | Redis stream audit; export daily to SIEM | -| Alert rules | Feed age  ≥ 48 h, P95 wall‑time > 5 s, Redis used memory > 75 % | +| Log format | Serilog JSON; ship via Fluent‑Bit to ELK or Loki | +| Metrics | Prometheus /metrics endpoint; default Grafana dashboard in infra/ | +| Audit events | Redis stream audit; export daily to SIEM | +| Alert rules | Feed age  ≥ 48 h, P95 wall‑time > 5 s, Redis used memory > 75 % | + +###  7.1 Feedser authorization audits + +- Enable the Authority integration for Feedser (`authority.enabled=true`). Keep + `authority.allowAnonymousFallback` set to `true` only during migration and plan + to disable it before **2025-12-31 UTC** so the `/jobs*` surface always demands + a bearer token. +- Store the Authority client secret using Docker/Kubernetes secrets and point + `authority.clientSecretFile` at the mounted path; the value is read at startup + and never logged. +- Watch the `Feedser.Authorization.Audit` logger. Each entry contains the HTTP + status, subject, client ID, scopes, remote IP, and a boolean `bypass` flag + showing whether a network bypass CIDR allowed the request. Configure your SIEM + to alert when unauthenticated requests (`status=401`) appear with + `bypass=true`, or when unexpected scopes invoke job triggers. ##  8 Update & patch strategy diff --git a/docs/21_INSTALL_GUIDE.md b/docs/21_INSTALL_GUIDE.md index 7b98b51d..310e44f4 100755 --- a/docs/21_INSTALL_GUIDE.md +++ b/docs/21_INSTALL_GUIDE.md @@ -65,20 +65,58 @@ $EDITOR .env # 5. Launch databases (MongoDB + Redis) docker compose --env-file .env -f docker-compose.infrastructure.yml up -d -# 6. Launch Stella Ops (first run pulls ~50 MB merged vuln DB) -docker compose --env-file .env -f docker-compose.stella-ops.yml up -d -```` - -*Default login:* `admin / changeme` -UI: [https://\<host\>:8443](https://<host>:8443) (self‑signed certificate) - -> **Pinning best‑practice** – in production environments replace -> `stella-ops:latest` with the immutable digest printed by -> `docker images --digests`. - ---- - -## 2 · Optional: request a free quota token +# 6. Launch Stella Ops (first run pulls ~50 MB merged vuln DB) +docker compose --env-file .env -f docker-compose.stella-ops.yml up -d +```` + +*Default login:* `admin / changeme` +UI: [https://\<host\>:8443](https://<host>:8443) (self‑signed certificate) + +> **Pinning best‑practice** – in production environments replace +> `stella-ops:latest` with the immutable digest printed by +> `docker images --digests`. + +### 1.1 · Feedser authority configuration + +The Feedser container reads configuration from `etc/feedser.yaml` plus +`FEEDSER_` environment variables. To enable the new Authority integration: + +1. Add the following keys to `.env` (replace values for your environment): + + ```bash + FEEDSER_AUTHORITY__ENABLED=true + FEEDSER_AUTHORITY__ALLOWANONYMOUSFALLBACK=true # temporary rollout only + FEEDSER_AUTHORITY__ISSUER="https://authority.internal" + FEEDSER_AUTHORITY__AUDIENCES__0="api://feedser" + FEEDSER_AUTHORITY__REQUIREDSCOPES__0="feedser.jobs.trigger" + FEEDSER_AUTHORITY__CLIENTID="feedser-jobs" + FEEDSER_AUTHORITY__CLIENTSECRETFILE="/run/secrets/feedser_authority_client" + FEEDSER_AUTHORITY__BYPASSNETWORKS__0="127.0.0.1/32" + FEEDSER_AUTHORITY__BYPASSNETWORKS__1="::1/128" + ``` + + Store the client secret outside source control (Docker secrets, mounted file, + or Kubernetes Secret). Feedser loads the secret during post-configuration, so + the value never needs to appear in the YAML template. + +2. Redeploy Feedser: + + ```bash + docker compose --env-file .env -f docker-compose.stella-ops.yml up -d feedser + ``` + +3. Tail the logs: `docker compose logs -f feedser`. Successful `/jobs*` calls now + emit `Feedser.Authorization.Audit` entries listing subject, client ID, scopes, + remote IP, and whether the bypass CIDR allowed the call. 401 denials always log + `bypassAllowed=false` so unauthenticated cron jobs are easy to catch. + +> **Enforcement deadline** – keep `FEEDSER_AUTHORITY__ALLOWANONYMOUSFALLBACK=true` +> only while validating the rollout. Set it to `false` (and restart Feedser) +> before **2025-12-31 UTC** to require tokens in production. + +--- + +## 2 · Optional: request a free quota token Anonymous installs allow **{{ quota\_anon }} scans per UTC day**. Email `token@stella-ops.org` to receive a signed JWT that raises the limit to diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 00000000..56aa1d62 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,19 @@ +# Docs & Enablement Guild + +## Mission +Produce and maintain offline-friendly documentation for StellaOps modules, covering architecture, configuration, operator workflows, and developer onboarding. + +## Scope Highlights +- Authority docs (`docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, upcoming `docs/11_AUTHORITY.md`). +- Feedser quickstarts, CLI guides, Offline Kit manuals. +- Release notes and migration playbooks. + +## Operating Principles +- Keep guides deterministic and in sync with shipped configuration samples. +- Prefer tables/checklists for operator steps; flag security-sensitive actions. +- Update `docs/TASKS.md` whenever work items change status (TODO/DOING/REVIEW/DONE/BLOCKED). + +## Coordination +- Authority Core & Plugin teams for auth-related changes. +- Security Guild for threat-model outputs and mitigations. +- DevEx for tooling diagrams and documentation pipeline. diff --git a/docs/TASKS.md b/docs/TASKS.md new file mode 100644 index 00000000..aa6c99f0 --- /dev/null +++ b/docs/TASKS.md @@ -0,0 +1,10 @@ +# Docs Guild Task Board (UTC 2025-10-10) + +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +|----|--------|----------|------------|-------------|---------------| +| DOC4.AUTH-PDG | REVIEW | Docs Guild, Plugin Team | PLG6.DOC | Copy-edit `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, export lifecycle diagram, add LDAP RFC cross-link. | ✅ PR merged with polish; ✅ Diagram committed; ✅ Slack handoff posted. | +| DOC1.AUTH | TODO | Docs Guild, Authority Core | CORE5B.DOC | Draft `docs/11_AUTHORITY.md` covering architecture, configuration, bootstrap flows. | ✅ Architecture + config sections approved by Core; ✅ Samples reference latest options; ✅ Offline note added. | +| DOC3.Feedser-Authority | DOING (2025-10-10) | Docs Guild, DevEx | FSR4 | Polish operator/runbook sections (DOC3/DOC5) to document Feedser authority rollout, bypass logging, and enforcement checklist. | ✅ DOC3/DOC5 updated; ✅ enforcement deadline highlighted; ✅ Docs guild sign-off. | +| DOC5.Feedser-Runbook | TODO | Docs Guild | DOC3.Feedser-Authority | Produce dedicated Feedser authority audit runbook covering log fields, monitoring recommendations, and troubleshooting steps. | ✅ Runbook published; ✅ linked from DOC3/DOC5; ✅ alerting guidance included. | + +> Update statuses (TODO/DOING/REVIEW/DONE/BLOCKED) as progress changes. Keep guides in sync with configuration samples under `etc/`. diff --git a/docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md b/docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md index c7869fdf..36a4e76b 100644 --- a/docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md +++ b/docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md @@ -38,6 +38,8 @@ Capability flags let the host reason about what your plug-in supports: **Operational reminder:** the Authority host surfaces capability summaries during startup (see `AuthorityIdentityProviderRegistry` log lines). Use those logs during smoke tests to ensure manifests align with expectations. +**Configuration path normalisation:** Manifest-relative paths (e.g., `tokenSigning.keyDirectory: "../keys"`) are resolved against the YAML file location and environment variables are expanded before validation. Plug-ins should expect to receive an absolute, canonical path when options are injected. + ## 4. Project Scaffold - Target **.NET 10 preview**, enable nullable, treat warnings as errors, and mark Authority plug-ins with `true`. - Minimum references: diff --git a/docs/dev/32_AUTH_CLIENT_GUIDE.md b/docs/dev/32_AUTH_CLIENT_GUIDE.md new file mode 100644 index 00000000..48587a41 --- /dev/null +++ b/docs/dev/32_AUTH_CLIENT_GUIDE.md @@ -0,0 +1,91 @@ +# StellaOps Auth Client — Integration Guide + +> **Status:** Drafted 2025-10-10 as part of LIB5. Consumer teams (Feedser, CLI, Agent) should review before wiring the new options into their configuration surfaces. + +The `StellaOps.Auth.Client` library provides a resilient OpenID Connect client for services and tools that talk to **StellaOps Authority**. LIB5 introduced configurable HTTP retry/backoff policies and an offline-fallback window so downstream components stay deterministic even when Authority is briefly unavailable. + +This guide explains how to consume the new settings, when to toggle them, and how to test your integration. + +## 1. Registering the client + +```csharp +services.AddStellaOpsAuthClient(options => +{ + options.Authority = configuration["StellaOps:Authority:Url"]!; + options.ClientId = configuration["StellaOps:Authority:ClientId"]!; + options.ClientSecret = configuration["StellaOps:Authority:ClientSecret"]; + options.DefaultScopes.Add("feedser.jobs.trigger"); + + options.EnableRetries = true; + options.RetryDelays.Clear(); + options.RetryDelays.Add(TimeSpan.FromMilliseconds(500)); + options.RetryDelays.Add(TimeSpan.FromSeconds(2)); + + options.AllowOfflineCacheFallback = true; + options.OfflineCacheTolerance = TimeSpan.FromMinutes(5); +}); +``` + +> **Reminder:** `AddStellaOpsAuthClient` binds the options via `IOptionsMonitor` so changes picked up from configuration reloads will be applied to future HTTP calls without restarting the host. + +## 2. Resilience options + +| Option | Default | Notes | +|--------|---------|-------| +| `EnableRetries` | `true` | When disabled, the shared Polly policy is a no-op and HTTP calls will fail fast. | +| `RetryDelays` | `1s, 2s, 5s` | Edit in ascending order; zero/negative entries are ignored. Clearing the list and leaving it empty keeps the defaults. | +| `AllowOfflineCacheFallback` | `true` | When `true`, stale discovery/JWKS responses are reused within the tolerance window if Authority is unreachable. | +| `OfflineCacheTolerance` | `00:10:00` | Added to the normal cache lifetime. E.g. a 10 minute JWKS cache plus 5 minute tolerance keeps keys for 15 minutes if Authority is offline. | + +The HTTP retry policy handles: + +- 5xx responses +- 429 responses +- Transient transport failures (`HttpRequestException`, timeouts, aborted sockets) + +Retries emit warnings via the `StellaOps.Auth.Client.HttpRetry` logger. Tune the delay values to honour your deployment’s SLOs. + +## 3. Configuration mapping + +Suggested configuration keys (coordinate with consuming teams before finalising): + +```yaml +StellaOps: + Authority: + Url: "https://authority.stella-ops.local" + ClientId: "feedser" + ClientSecret: "change-me" + AuthClient: + EnableRetries: true + RetryDelays: + - "00:00:01" + - "00:00:02" + - "00:00:05" + AllowOfflineCacheFallback: true + OfflineCacheTolerance: "00:10:00" +``` + +Environment variable binding follows the usual double-underscore rules, e.g. + +``` +STELLAOPS__AUTHORITY__AUTHCLIENT__RETRYDELAYS__0=00:00:02 +STELLAOPS__AUTHORITY__AUTHCLIENT__OFFLINECACHETOLERANCE=00:05:00 +``` + +CLI and Feedser teams should expose these knobs once they adopt the auth client. + +## 4. Testing recommendations + +1. **Unit tests:** assert option binding by configuring `StellaOpsAuthClientOptions` via a `ConfigurationBuilder` and ensuring `Validate()` normalises the retry delays and scope list. +2. **Offline fallback:** simulate an unreachable Authority by swapping `HttpMessageHandler` to throw `HttpRequestException` after priming the discovery/JWKS caches. Verify that tokens are still issued until the tolerance expires. +3. **Observability:** watch for `StellaOps.Auth.Client.HttpRetry` warnings in your logs. Excessive retries mean the upstream Authority cluster needs attention. +4. **Determinism:** keep retry delays deterministic. Avoid random jitter—operators can introduce jitter at the infrastructure layer if desired. + +## 5. Rollout checklist + +- [ ] Update consuming service/CLI configuration schema to include the new settings. +- [ ] Document recommended defaults for offline (air-gapped) versus connected deployments. +- [ ] Extend smoke tests to cover Authority outage scenarios. +- [ ] Coordinate with Docs Guild so user-facing quickstarts reference the new knobs. + +Once Feedser and CLI integrate these changes, we can mark LIB5 **DONE**; further packaging work is deferred until the backlog reintroduces it. diff --git a/docs/ops/authority-backup-restore.md b/docs/ops/authority-backup-restore.md new file mode 100644 index 00000000..f587c467 --- /dev/null +++ b/docs/ops/authority-backup-restore.md @@ -0,0 +1,97 @@ +# Authority Backup & Restore Runbook + +## Scope +- **Applies to:** StellaOps Authority deployments running the official `ops/authority/docker-compose.authority.yaml` stack or equivalent Kubernetes packaging. +- **Artifacts covered:** MongoDB (`stellaops-authority` database), Authority configuration (`etc/authority.yaml`), plugin manifests under `etc/authority.plugins/`, and signing key material stored in the `authority-keys` volume (defaults to `/app/keys` inside the container). +- **Frequency:** Run the full procedure prior to upgrades, before rotating keys, and at least once per 24 h in production. Store snapshots in an encrypted, access-controlled vault. + +## Inventory Checklist +| Component | Location (compose default) | Notes | +| --- | --- | --- | +| Mongo data | `mongo-data` volume (`/var/lib/docker/volumes/.../mongo-data`) | Contains all Authority collections (`AuthorityUser`, `AuthorityClient`, `AuthorityToken`, etc.). | +| Configuration | `etc/authority.yaml` | Mounted read-only into the container at `/etc/authority.yaml`. | +| Plugin manifests | `etc/authority.plugins/*.yaml` | Includes `standard.yaml` with `tokenSigning.keyDirectory`. | +| Signing keys | `authority-keys` volume -> `/app/keys` | Path is derived from `tokenSigning.keyDirectory` (defaults to `../keys` relative to the manifest). | + +> **TIP:** Confirm the deployed key directory via `tokenSigning.keyDirectory` in `etc/authority.plugins/standard.yaml`; some installations relocate keys to `/var/lib/stellaops/authority/keys`. + +## Hot Backup (no downtime) +1. **Create output directory:** `mkdir -p backup/$(date +%Y-%m-%d)` on the host. +2. **Dump Mongo:** + ```bash + docker compose -f ops/authority/docker-compose.authority.yaml exec mongo \ + mongodump --archive=/dump/authority-$(date +%Y%m%dT%H%M%SZ).gz \ + --gzip --db stellaops-authority + docker compose -f ops/authority/docker-compose.authority.yaml cp \ + mongo:/dump/authority-$(date +%Y%m%dT%H%M%SZ).gz backup/ + ``` + The `mongodump` archive preserves indexes and can be restored with `mongorestore --archive --gzip`. +3. **Capture configuration + manifests:** + ```bash + cp etc/authority.yaml backup/ + rsync -a etc/authority.plugins/ backup/authority.plugins/ + ``` +4. **Export signing keys:** the compose file maps `authority-keys` to a local Docker volume. Snapshot it without stopping the service: + ```bash + docker run --rm \ + -v authority-keys:/keys \ + -v "$(pwd)/backup:/backup" \ + busybox tar czf /backup/authority-keys-$(date +%Y%m%dT%H%M%SZ).tar.gz -C /keys . + ``` +5. **Checksum:** generate SHA-256 digests for every file and store them alongside the artefacts. +6. **Encrypt & upload:** wrap the backup folder using your secrets management standard (e.g., age, GPG) and upload to the designated offline vault. + +## Cold Backup (planned downtime) +1. Notify stakeholders and drain traffic (CLI clients should refresh tokens afterwards). +2. Stop services: + ```bash + docker compose -f ops/authority/docker-compose.authority.yaml down + ``` +3. Back up volumes directly using `tar`: + ```bash + docker run --rm -v mongo-data:/data -v "$(pwd)/backup:/backup" \ + busybox tar czf /backup/mongo-data-$(date +%Y%m%d).tar.gz -C /data . + docker run --rm -v authority-keys:/keys -v "$(pwd)/backup:/backup" \ + busybox tar czf /backup/authority-keys-$(date +%Y%m%d).tar.gz -C /keys . + ``` +4. Copy configuration + manifests as in the hot backup (steps 3–6). +5. Restart services and verify health: + ```bash + docker compose -f ops/authority/docker-compose.authority.yaml up -d + curl -fsS http://localhost:8080/ready + ``` + +## Restore Procedure +1. **Provision clean volumes:** remove existing volumes if you’re rebuilding a node (`docker volume rm mongo-data authority-keys`), then recreate the compose stack so empty volumes exist. +2. **Restore Mongo:** + ```bash + docker compose exec -T mongo mongorestore --archive --gzip --drop < backup/authority-YYYYMMDDTHHMMSSZ.gz + ``` + Use `--drop` to replace collections; omit if doing a partial restore. +3. **Restore configuration/manifests:** copy `authority.yaml` and `authority.plugins/*` into place before starting the Authority container. +4. **Restore signing keys:** untar into the mounted volume: + ```bash + docker run --rm -v authority-keys:/keys -v "$(pwd)/backup:/backup" \ + busybox tar xzf /backup/authority-keys-YYYYMMDD.tar.gz -C /keys + ``` + Ensure file permissions remain `600` for private keys (`chmod -R 600`). +5. **Start services & validate:** + ```bash + docker compose up -d + curl -fsS http://localhost:8080/health + ``` +6. **Validate JWKS and tokens:** call `/jwks` and issue a short-lived token via the CLI to confirm key material matches expectations. + +## Disaster Recovery Notes +- **Air-gapped replication:** replicate archives via the Offline Update Kit transport channels; never attach USB devices without scanning. +- **Retention:** maintain 30 daily snapshots + 12 monthly archival copies. Rotate encryption keys annually. +- **Key compromise:** if signing keys are suspected compromised, restore from the latest clean backup, rotate via OPS3 (key rotation tooling), and publish a revocation notice. +- **Mongo version:** keep dump/restore images pinned to the deployment version (compose uses `mongo:7`). Restoring across major versions requires a compatibility review. + +## Verification Checklist +- [ ] `/ready` reports all identity providers ready. +- [ ] OAuth flows issue tokens signed by the restored keys. +- [ ] `PluginRegistrationSummary` logs expected providers on startup. +- [ ] Revocation manifest export (`dotnet run --project src/StellaOps.Authority`) succeeds. +- [ ] Monitoring dashboards show metrics resuming (see OPS5 deliverables). + diff --git a/docs/ops/authority-grafana-dashboard.json b/docs/ops/authority-grafana-dashboard.json new file mode 100644 index 00000000..9f4cfc02 --- /dev/null +++ b/docs/ops/authority-grafana-dashboard.json @@ -0,0 +1,174 @@ +{ + "title": "StellaOps Authority - Token & Access Monitoring", + "uid": "authority-token-monitoring", + "schemaVersion": 38, + "version": 1, + "editable": true, + "timezone": "", + "graphTooltip": 0, + "time": { + "from": "now-6h", + "to": "now" + }, + "templating": { + "list": [ + { + "name": "datasource", + "type": "datasource", + "query": "prometheus", + "refresh": 1, + "hide": 0, + "current": {} + } + ] + }, + "panels": [ + { + "id": 1, + "title": "Token Requests – Success vs Failure", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "unit": "req/s", + "displayName": "{{grant_type}} ({{status}})" + }, + "overrides": [] + }, + "targets": [ + { + "refId": "A", + "expr": "sum by (grant_type, status) (rate(http_server_duration_seconds_count{service_name=\"stellaops-authority\", http_route=\"/token\"}[5m]))", + "legendFormat": "{{grant_type}} {{status}}" + } + ], + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + } + }, + { + "id": 2, + "title": "Rate Limiter Rejections", + "type": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "unit": "req/s", + "displayName": "{{limiter}}" + }, + "overrides": [] + }, + "targets": [ + { + "refId": "A", + "expr": "sum by (limiter) (rate(aspnetcore_rate_limiting_rejections_total{service_name=\"stellaops-authority\"}[5m]))", + "legendFormat": "{{limiter}}" + } + ] + }, + { + "id": 3, + "title": "Bypass Events (5m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "orange", "value": 1 }, + { "color": "red", "value": 5 } + ] + } + }, + "overrides": [] + }, + "targets": [ + { + "refId": "A", + "expr": "sum(rate(log_messages_total{message_template=\"Granting StellaOps bypass for remote {RemoteIp}; required scopes {RequiredScopes}.\"}[5m]))" + } + ], + "options": { + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "orientation": "horizontal", + "textMode": "auto" + } + }, + { + "id": 4, + "title": "Lockout Events (15m)", + "type": "stat", + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "orange", "value": 5 }, + { "color": "red", "value": 10 } + ] + } + }, + "overrides": [] + }, + "targets": [ + { + "refId": "A", + "expr": "sum(rate(log_messages_total{message_template=\"Plugin {PluginName} denied access for {Username} due to lockout (retry after {RetryAfter}).\"}[15m]))" + } + ], + "options": { + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "orientation": "horizontal", + "textMode": "auto" + } + }, + { + "id": 5, + "title": "Trace Explorer Shortcut", + "type": "text", + "options": { + "mode": "markdown", + "content": "[Open Trace Explorer](#/explore?left={\"datasource\":\"tempo\",\"queries\":[{\"query\":\"{service.name=\\\"stellaops-authority\\\", span_name=~\\\"authority.token.*\\\"}\",\"refId\":\"A\"}]})" + } + } + ], + "links": [] +} diff --git a/docs/ops/authority-monitoring.md b/docs/ops/authority-monitoring.md new file mode 100644 index 00000000..d25c1f35 --- /dev/null +++ b/docs/ops/authority-monitoring.md @@ -0,0 +1,81 @@ +# Authority Monitoring & Alerting Playbook + +## Telemetry Sources +- **Traces:** Activity source `StellaOps.Authority` emits spans for every token flow (`authority.token.validate_*`, `authority.token.handle_*`, `authority.token.validate_access`). Key tags include `authority.endpoint`, `authority.grant_type`, `authority.username`, `authority.client_id`, and `authority.identity_provider`. +- **Metrics:** OpenTelemetry instrumentation (`AddAspNetCoreInstrumentation`, `AddHttpClientInstrumentation`, custom meter `StellaOps.Authority`) exports: + - `http.server.request.duration` histogram (`http_route`, `http_status_code`, `authority.endpoint` tag via `aspnetcore` enrichment). + - `process.runtime.gc.*`, `process.runtime.dotnet.*` (from `AddRuntimeInstrumentation`). +- **Logs:** Serilog writes structured events to stdout. Notable templates: + - `"Password grant verification failed ..."` and `"Plugin {PluginName} denied access ... due to lockout"` (lockout spike detector). + - `"Granting StellaOps bypass for remote {RemoteIp}"` (bypass usage). + - `"Rate limit exceeded for path {Path} from {RemoteIp}"` (limiter alerts). + +## Prometheus Metrics to Collect +| Metric | Query | Purpose | +| --- | --- | --- | +| `token_requests_total` | `sum by (grant_type, status) (rate(http_server_duration_seconds_count{service_name="stellaops-authority", http_route="/token"}[5m]))` | Token issuance volume per grant type (`grant_type` comes via `authority.grant_type` span attribute → Exemplars in Grafana). | +| `token_failure_ratio` | `sum(rate(http_server_duration_seconds_count{service_name="stellaops-authority", http_route="/token", http_status_code=~"4..|5.."}[5m])) / sum(rate(http_server_duration_seconds_count{service_name="stellaops-authority", http_route="/token"}[5m]))` | Alert when > 5 % for 10 min. | +| `authorize_rate_limit_hits` | `sum(rate(aspnetcore_rate_limiting_rejections_total{service_name="stellaops-authority", limiter="authority-token"}[5m]))` | Detect rate limiting saturations (requires OTEL ASP.NET rate limiter exporter). | +| `lockout_events` | `sum by (plugin) (rate(log_messages_total{app="stellaops-authority", level="Warning", message_template="Plugin {PluginName} denied access for {Username} due to lockout (retry after {RetryAfter})."}[5m]))` | Derived from Loki/Promtail log counter. | +| `bypass_usage_total` | `sum(rate(log_messages_total{app="stellaops-authority", level="Information", message_template="Granting StellaOps bypass for remote {RemoteIp}; required scopes {RequiredScopes}."}[5m]))` | Track trusted bypass invocations. | + +> **Exporter note:** Enable `aspnetcore` meters (`dotnet-counters` name `Microsoft.AspNetCore.Hosting`), or configure the OpenTelemetry Collector `metrics` pipeline with `metric_statements` to remap histogram counts into the shown series. + +## Alert Rules +1. **Token Failure Surge** + - _Expression_: `token_failure_ratio > 0.05` + - _For_: `10m` + - _Labels_: `severity="critical"` + - _Annotations_: Include `topk(5, sum by (authority_identity_provider) (increase(authority_token_rejections_total[10m])))` as diagnostic hint (requires span → metric transformation). +2. **Lockout Spike** + - _Expression_: `sum(rate(log_messages_total{message_template="Plugin {PluginName} denied access for {Username} due to lockout (retry after {RetryAfter})."}[15m])) > 10` + - _For_: `15m` + - Investigate credential stuffing; consider temporarily tightening `RateLimiting.Token`. +3. **Bypass Threshold** + - _Expression_: `sum(rate(log_messages_total{message_template="Granting StellaOps bypass for remote {RemoteIp}; required scopes {RequiredScopes}."}[5m])) > 1` + - _For_: `5m` + - Alert severity `warning` — verify the calling host list. +4. **Rate Limiter Saturation** + - _Expression_: `sum(rate(aspnetcore_rate_limiting_rejections_total{service_name="stellaops-authority"}[5m])) > 0` + - Escalate if sustained for 5 min; confirm trusted clients aren’t misconfigured. + +## Grafana Dashboard +- Import `docs/ops/authority-grafana-dashboard.json` to provision baseline panels: + - **Token Success vs Failure** – stacked rate visualization split by grant type. + - **Rate Limiter Hits** – bar chart showing `authority-token` and `authority-authorize`. + - **Bypass & Lockout Events** – dual-stat panel using Loki-derived counters. + - **Trace Explorer Link** – panel links to `StellaOps.Authority` span search pre-filtered by `authority.grant_type`. + +## Collector Configuration Snippets +```yaml +receivers: + otlp: + protocols: + http: +exporters: + prometheus: + endpoint: "0.0.0.0:9464" +processors: + batch: + attributes/token_grant: + actions: + - key: grant_type + action: upsert + from_attribute: authority.grant_type +service: + pipelines: + metrics: + receivers: [otlp] + processors: [attributes/token_grant, batch] + exporters: [prometheus] + logs: + receivers: [otlp] + processors: [batch] + exporters: [loki] +``` + +## Operational Checklist +- [ ] Confirm `STELLAOPS_AUTHORITY__OBSERVABILITY__EXPORTERS` enables OTLP in production builds. +- [ ] Ensure Promtail captures container stdout with Serilog structured formatting. +- [ ] Periodically validate alert noise by running load tests that trigger the rate limiter. +- [ ] Include dashboard JSON in Offline Kit for air-gapped clusters; update version header when metrics change. diff --git a/etc/authority.plugins/standard.yaml b/etc/authority.plugins/standard.yaml index 768f8ba6..e2273e86 100644 --- a/etc/authority.plugins/standard.yaml +++ b/etc/authority.plugins/standard.yaml @@ -17,5 +17,6 @@ lockout: tokenSigning: # Path to the directory containing signing keys (relative paths resolve - # against this configuration file location). + # against the location of this manifest, environment variables are expanded, + # and the final value is normalised to an absolute path during startup. keyDirectory: "../keys" diff --git a/etc/feedser.yaml.sample b/etc/feedser.yaml.sample index 780b3635..6393a21f 100644 --- a/etc/feedser.yaml.sample +++ b/etc/feedser.yaml.sample @@ -38,6 +38,9 @@ telemetry: authority: enabled: false + # Temporary rollout flag. When true, Feedser logs anonymous access but does not fail requests + # without tokens. Set to false before 2025-12-31 UTC to enforce authentication fully. + allowAnonymousFallback: true # Issuer advertised by StellaOps Authority (e.g. https://authority.stella-ops.local). issuer: "https://authority.stella-ops.local" # Optional explicit metadata address; defaults to {issuer}/.well-known/openid-configuration. @@ -49,6 +52,13 @@ authority: - "api://feedser" requiredScopes: - "feedser.jobs.trigger" + # Outbound credentials Feedser can use to call Authority (client credentials flow). + clientId: "feedser-jobs" + # Prefer storing the secret outside of the config file. Provide either clientSecret or clientSecretFile. + clientSecret: "" + clientSecretFile: "" + clientScopes: + - "feedser.jobs.trigger" # Networks allowed to bypass authentication (loopback by default for on-host cron jobs). bypassNetworks: - "127.0.0.1/32" diff --git a/ops/authority/AGENTS.md b/ops/authority/AGENTS.md new file mode 100644 index 00000000..b19cf07d --- /dev/null +++ b/ops/authority/AGENTS.md @@ -0,0 +1,16 @@ +# Authority DevOps Crew + +## Mission +Operate and harden the StellaOps Authority platform in production and air-gapped environments: container images, deployment assets, observability defaults, backup/restore, and runtime key management. + +## Focus Areas +- **Build & Packaging** – Dockerfiles, OCI bundles, offline artefact refresh. +- **Deployment Tooling** – Compose/Kubernetes manifests, secrets bootstrap, upgrade paths. +- **Observability** – Logging defaults, metrics/trace exporters, dashboards, alert policies. +- **Continuity & Security** – Backup/restore guides, key rotation playbooks, revocation propagation. + +## Working Agreements +- Track work in `ops/authority/TASKS.md` (TODO → DOING → DONE/BLOCKED); keep entries dated. +- Validate container changes with the CI pipeline (`ops/authority` GitHub workflow) before marking DONE. +- Update operator documentation in `docs/` together with any behavioural change. +- Coordinate with Authority Core and Security Guild before altering sensitive defaults (rate limits, crypto providers, revocation jobs). diff --git a/ops/authority/Dockerfile b/ops/authority/Dockerfile index 07b65a22..af76ade5 100644 --- a/ops/authority/Dockerfile +++ b/ops/authority/Dockerfile @@ -14,7 +14,7 @@ WORKDIR /src # Restore & publish COPY . . -RUN dotnet restore StellaOps.sln +RUN dotnet restore src/StellaOps.sln RUN dotnet publish src/StellaOps.Authority/StellaOps.Authority/StellaOps.Authority.csproj \ -c Release \ -o /app/publish \ diff --git a/ops/authority/TASKS.md b/ops/authority/TASKS.md new file mode 100644 index 00000000..7c7ef95c --- /dev/null +++ b/ops/authority/TASKS.md @@ -0,0 +1,6 @@ +# Authority DevOps Task Board (UTC 2025-10-10) + +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +|----|--------|----------|------------|-------------|---------------| +| OPS3.KEY-ROTATION | BLOCKED | DevOps Crew, Authority Core | CORE10.JWKS | Implement key rotation tooling + pipeline hook once rotating JWKS lands. Document SOP and secret handling. | ✅ CLI/script rotates keys + updates JWKS; ✅ Pipeline job documented; ✅ docs/ops runbook updated. | diff --git a/src/StellaOps.Authority/AGENTS.md b/src/StellaOps.Authority/AGENTS.md new file mode 100644 index 00000000..63d2f8f6 --- /dev/null +++ b/src/StellaOps.Authority/AGENTS.md @@ -0,0 +1,20 @@ +# Authority Host Crew + +## Mission +Own the StellaOps Authority host service: ASP.NET minimal API, OpenIddict flows, plugin loading, storage orchestration, and cross-cutting security controls (rate limiting, audit logging, revocation exports). + +## Teams On Call +- Team 2 (Authority Core) +- Team 8 (Security Guild) — collaborates on security-sensitive endpoints + +## Operating Principles +- Deterministic responses, structured logging, cancellation-ready handlers. +- Use `StellaOps.Cryptography` abstractions for any crypto operations. +- Every change updates `TASKS.md` and related docs/tests. +- Coordinate with plugin teams before altering plugin-facing contracts. + +## Key Directories +- `src/StellaOps.Authority/` — host app +- `src/StellaOps.Authority.Tests/` — integration/unit tests +- `src/StellaOps.Authority.Storage.Mongo/` — data access helpers +- `src/StellaOps.Authority.Plugin.Standard/` — default identity provider plugin diff --git a/src/StellaOps.Authority/StellaOps.Auth.Abstractions/README.NuGet.md b/src/StellaOps.Authority/StellaOps.Auth.Abstractions/README.NuGet.md new file mode 100644 index 00000000..b6d3ea49 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Auth.Abstractions/README.NuGet.md @@ -0,0 +1,9 @@ +# StellaOps.Auth.Abstractions + +Shared authentication primitives for StellaOps services: + +- Scope and claim constants aligned with StellaOps Authority. +- Deterministic `PrincipalBuilder` and `ProblemResultFactory` helpers. +- Utility types used by resource servers, plug-ins, and client libraries. + +These abstractions are referenced by `StellaOps.Auth.ServerIntegration` and `StellaOps.Auth.Client`. Review `docs/dev/32_AUTH_CLIENT_GUIDE.md` for downstream integration patterns. diff --git a/src/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj b/src/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj index 752ad308..4150c960 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj +++ b/src/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj @@ -6,7 +6,32 @@ enable true + + StellaOps.Auth.Abstractions + Core authority authentication abstractions, scopes, and helpers for StellaOps services. + StellaOps + StellaOps + AGPL-3.0-or-later + https://stella-ops.org + https://git.stella-ops.org/stella-ops.org/git.stella-ops.org + git + true + true + true + snupkg + stellaops;authentication;authority;oauth2 + true + $(NoWarn);1591 + README.NuGet.md + 1.0.0-preview.1 + + + + + + + diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/ServiceCollectionExtensionsTests.cs b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/ServiceCollectionExtensionsTests.cs new file mode 100644 index 00000000..521f4598 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; +using StellaOps.Auth.Client; +using Xunit; + +namespace StellaOps.Auth.Client.Tests; + +public class ServiceCollectionExtensionsTests +{ + [Fact] + public async Task AddStellaOpsAuthClient_ConfiguresRetryPolicy() + { + var services = new ServiceCollection(); + services.AddLogging(); + + services.AddStellaOpsAuthClient(options => + { + options.Authority = "https://authority.test"; + options.RetryDelays.Clear(); + options.RetryDelays.Add(TimeSpan.FromMilliseconds(1)); + options.DiscoveryCacheLifetime = TimeSpan.FromMinutes(1); + options.JwksCacheLifetime = TimeSpan.FromMinutes(1); + options.AllowOfflineCacheFallback = false; + }); + + var recordedHandlers = new List(); + var attemptCount = 0; + + services.AddHttpClient() + .ConfigureHttpMessageHandlerBuilder(builder => + { + recordedHandlers = new List(builder.AdditionalHandlers); + + var responses = new Queue>(new[] + { + () => CreateResponse(HttpStatusCode.InternalServerError, "{}"), + () => CreateResponse(HttpStatusCode.OK, "{\"token_endpoint\":\"https://authority.test/connect/token\",\"jwks_uri\":\"https://authority.test/jwks\"}") + }); + + builder.PrimaryHandler = new LambdaHttpMessageHandler((_, _) => + { + attemptCount++; + + if (responses.Count == 0) + { + return Task.FromResult(CreateResponse(HttpStatusCode.OK, "{}")); + } + + var factory = responses.Dequeue(); + return Task.FromResult(factory()); + }); + }); + + using var provider = services.BuildServiceProvider(); + + var cache = provider.GetRequiredService(); + var configuration = await cache.GetAsync(CancellationToken.None); + + Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); + Assert.Equal(2, attemptCount); + Assert.NotEmpty(recordedHandlers); + Assert.Contains(recordedHandlers, handler => handler.GetType().Name.Contains("PolicyHttpMessageHandler", StringComparison.Ordinal)); + } + + private static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string jsonContent) + { + return new HttpResponseMessage(statusCode) + { + Content = new StringContent(jsonContent) + { + Headers = { ContentType = new MediaTypeHeaderValue("application/json") } + } + }; + } + private sealed class LambdaHttpMessageHandler : HttpMessageHandler + { + private readonly Func> responder; + + public LambdaHttpMessageHandler(Func> responder) + { + this.responder = responder; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + => responder(request, cancellationToken); + } +} diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOps.Auth.Client.Tests.csproj b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOps.Auth.Client.Tests.csproj index be6e9223..f52435fc 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOps.Auth.Client.Tests.csproj +++ b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOps.Auth.Client.Tests.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsAuthClientOptionsTests.cs b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsAuthClientOptionsTests.cs index 6469821e..e49deb14 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsAuthClientOptionsTests.cs +++ b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsAuthClientOptionsTests.cs @@ -23,6 +23,7 @@ public class StellaOpsAuthClientOptionsTests Assert.Equal(new[] { "authority.users.manage", "feedser.jobs.trigger" }, options.NormalizedScopes); Assert.Equal(new Uri("https://authority.test"), options.AuthorityUri); + Assert.Equal(options.RetryDelays, options.NormalizedRetryDelays); } [Fact] @@ -34,4 +35,50 @@ public class StellaOpsAuthClientOptionsTests Assert.Contains("Authority", exception.Message, StringComparison.OrdinalIgnoreCase); } + + [Fact] + public void Validate_NormalizesRetryDelays() + { + var options = new StellaOpsAuthClientOptions + { + Authority = "https://authority.test" + }; + options.RetryDelays.Clear(); + options.RetryDelays.Add(TimeSpan.Zero); + options.RetryDelays.Add(TimeSpan.FromSeconds(3)); + options.RetryDelays.Add(TimeSpan.FromMilliseconds(-1)); + + options.Validate(); + + Assert.Equal(new[] { TimeSpan.FromSeconds(3) }, options.NormalizedRetryDelays); + Assert.Equal(options.NormalizedRetryDelays, options.RetryDelays); + } + + [Fact] + public void Validate_DisabledRetries_ProducesEmptyDelays() + { + var options = new StellaOpsAuthClientOptions + { + Authority = "https://authority.test", + EnableRetries = false + }; + + options.Validate(); + + Assert.Empty(options.NormalizedRetryDelays); + } + + [Fact] + public void Validate_Throws_When_OfflineToleranceNegative() + { + var options = new StellaOpsAuthClientOptions + { + Authority = "https://authority.test", + OfflineCacheTolerance = TimeSpan.FromSeconds(-1) + }; + + var exception = Assert.Throws(() => options.Validate()); + + Assert.Contains("Offline cache tolerance", exception.Message, StringComparison.OrdinalIgnoreCase); + } } diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsDiscoveryCacheTests.cs b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsDiscoveryCacheTests.cs new file mode 100644 index 00000000..72e8fafd --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Auth.Client.Tests/StellaOpsDiscoveryCacheTests.cs @@ -0,0 +1,134 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Time.Testing; +using StellaOps.Auth.Client; +using Xunit; + +namespace StellaOps.Auth.Client.Tests; + +public class StellaOpsDiscoveryCacheTests +{ + [Fact] + public async Task GetAsync_UsesOfflineFallbackWithinTolerance() + { + var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-01-01T00:00:00Z")); + var callCount = 0; + var handler = new StubHttpMessageHandler((request, _) => + { + callCount++; + + if (callCount == 1) + { + return Task.FromResult(CreateJsonResponse("{\"token_endpoint\":\"https://authority.test/connect/token\",\"jwks_uri\":\"https://authority.test/jwks\"}")); + } + + throw new HttpRequestException("offline"); + }); + + var httpClient = new HttpClient(handler); + + var options = new StellaOpsAuthClientOptions + { + Authority = "https://authority.test", + DiscoveryCacheLifetime = TimeSpan.FromMinutes(1), + OfflineCacheTolerance = TimeSpan.FromMinutes(5), + AllowOfflineCacheFallback = true + }; + options.Validate(); + + var monitor = new TestOptionsMonitor(options); + var cache = new StellaOpsDiscoveryCache(httpClient, monitor, timeProvider, NullLogger.Instance); + + var configuration = await cache.GetAsync(CancellationToken.None); + Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); + + timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5)); + + configuration = await cache.GetAsync(CancellationToken.None); + Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); + Assert.Equal(2, callCount); + + var offlineExpiry = GetOfflineExpiry(cache); + Assert.True(offlineExpiry > timeProvider.GetUtcNow()); + + timeProvider.Advance(options.OfflineCacheTolerance + TimeSpan.FromSeconds(1)); + + Assert.True(offlineExpiry < timeProvider.GetUtcNow()); + + HttpRequestException? exception = null; + try + { + await cache.GetAsync(CancellationToken.None); + } + catch (HttpRequestException ex) + { + exception = ex; + } + + Assert.NotNull(exception); + Assert.Equal(3, callCount); + } + + private static HttpResponseMessage CreateJsonResponse(string json) + { + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(json) + { + Headers = { ContentType = new MediaTypeHeaderValue("application/json") } + } + }; + } + + private sealed class StubHttpMessageHandler : HttpMessageHandler + { + private readonly Func> responder; + + public StubHttpMessageHandler(Func> responder) + { + this.responder = responder; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + => responder(request, cancellationToken); + } + + private sealed class TestOptionsMonitor : IOptionsMonitor + where T : class + { + private readonly T value; + + public TestOptionsMonitor(T value) + { + this.value = value; + } + + public T CurrentValue => value; + + public T Get(string? name) => value; + + public IDisposable OnChange(Action listener) => NullDisposable.Instance; + + private sealed class NullDisposable : IDisposable + { + public static NullDisposable Instance { get; } = new(); + + public void Dispose() + { + } + } + } + + private static DateTimeOffset GetOfflineExpiry(StellaOpsDiscoveryCache cache) + { + var field = typeof(StellaOpsDiscoveryCache).GetField("offlineExpiresAt", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + Assert.NotNull(field); + return (DateTimeOffset)field!.GetValue(cache)!; + } +} diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/README.NuGet.md b/src/StellaOps.Authority/StellaOps.Auth.Client/README.NuGet.md new file mode 100644 index 00000000..3369a62e --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/README.NuGet.md @@ -0,0 +1,9 @@ +# StellaOps.Auth.Client + +Typed OpenID Connect client used by StellaOps services, agents, and tooling to talk to **StellaOps Authority**. It provides: + +- Discovery + JWKS caching with deterministic refresh windows. +- Password and client-credential flows with token cache abstractions. +- Configurable HTTP retry/backoff policies (Polly) and offline fallback support for air-gapped deployments. + +See `docs/dev/32_AUTH_CLIENT_GUIDE.md` in the repository for integration guidance, option descriptions, and rollout checklists. diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/ServiceCollectionExtensions.cs b/src/StellaOps.Authority/StellaOps.Auth.Client/ServiceCollectionExtensions.cs index c5fbd6c9..b3b43c8e 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client/ServiceCollectionExtensions.cs +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/ServiceCollectionExtensions.cs @@ -1,7 +1,12 @@ using System; +using System.Net; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Polly; +using Polly.Extensions.Http; namespace StellaOps.Auth.Client; @@ -28,19 +33,19 @@ public static class ServiceCollectionExtensions { var options = provider.GetRequiredService>().CurrentValue; client.Timeout = options.HttpTimeout; - }); + }).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider)); services.AddHttpClient((provider, client) => { var options = provider.GetRequiredService>().CurrentValue; client.Timeout = options.HttpTimeout; - }); + }).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider)); services.AddHttpClient((provider, client) => { var options = provider.GetRequiredService>().CurrentValue; client.Timeout = options.HttpTimeout; - }); + }).AddPolicyHandler(static (provider, _) => CreateRetryPolicy(provider)); return services; } @@ -62,4 +67,49 @@ public static class ServiceCollectionExtensions return services; } + + private static IAsyncPolicy CreateRetryPolicy(IServiceProvider provider) + { + var options = provider.GetRequiredService>().CurrentValue; + var delays = options.NormalizedRetryDelays; + if (delays.Count == 0) + { + return Policy.NoOpAsync(); + } + + var logger = provider.GetService()?.CreateLogger("StellaOps.Auth.Client.HttpRetry"); + + return HttpPolicyExtensions + .HandleTransientHttpError() + .OrResult(static response => response.StatusCode == HttpStatusCode.TooManyRequests) + .WaitAndRetryAsync( + delays.Count, + attempt => delays[attempt - 1], + (outcome, delay, attempt, _) => + { + if (logger is null) + { + return; + } + + if (outcome.Exception is not null) + { + logger.LogWarning( + outcome.Exception, + "Retrying Authority HTTP call ({Attempt}/{TotalAttempts}) after exception; waiting {Delay}.", + attempt, + delays.Count, + delay); + } + else + { + logger.LogWarning( + "Retrying Authority HTTP call ({Attempt}/{TotalAttempts}) due to status {StatusCode}; waiting {Delay}.", + attempt, + delays.Count, + outcome.Result!.StatusCode, + delay); + } + }); + } } diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj index 1f44cfbe..90e81d6f 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj @@ -6,14 +6,38 @@ enable true + + StellaOps.Auth.Client + Typed OAuth/OpenID client for StellaOps Authority with caching, retries, and token helpers. + StellaOps + StellaOps + AGPL-3.0-or-later + https://stella-ops.org + https://git.stella-ops.org/stella-ops.org/git.stella-ops.org + git + true + true + true + snupkg + stellaops;authentication;authority;oauth2;client + true + $(NoWarn);1591 + README.NuGet.md + 1.0.0-preview.1 + + + + + + <_Parameter1>StellaOps.Auth.Client.Tests diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsAuthClientOptions.cs b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsAuthClientOptions.cs index 7f06e192..089cfcb7 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsAuthClientOptions.cs +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsAuthClientOptions.cs @@ -10,7 +10,16 @@ namespace StellaOps.Auth.Client; /// public sealed class StellaOpsAuthClientOptions { + private static readonly TimeSpan[] DefaultRetryDelays = + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(5) + }; + private static readonly TimeSpan DefaultOfflineTolerance = TimeSpan.FromMinutes(10); + private readonly List scopes = new(); + private readonly List retryDelays = new(DefaultRetryDelays); /// /// Authority (issuer) base URL. @@ -32,6 +41,16 @@ public sealed class StellaOpsAuthClientOptions /// public IList DefaultScopes => scopes; + /// + /// Retry delays applied by HTTP retry policy (empty uses defaults). + /// + public IList RetryDelays => retryDelays; + + /// + /// Gets or sets a value indicating whether HTTP retry policies are enabled. + /// + public bool EnableRetries { get; set; } = true; + /// /// Timeout applied to discovery and token HTTP requests. /// @@ -52,6 +71,16 @@ public sealed class StellaOpsAuthClientOptions /// public TimeSpan ExpirationSkew { get; set; } = TimeSpan.FromSeconds(30); + /// + /// Gets or sets a value indicating whether cached discovery/JWKS responses may be served when the Authority is unreachable. + /// + public bool AllowOfflineCacheFallback { get; set; } = true; + + /// + /// Additional tolerance window during which stale cache entries remain valid if offline fallback is allowed. + /// + public TimeSpan OfflineCacheTolerance { get; set; } = DefaultOfflineTolerance; + /// /// Parsed Authority URI (populated after validation). /// @@ -62,6 +91,11 @@ public sealed class StellaOpsAuthClientOptions /// public IReadOnlyList NormalizedScopes { get; private set; } = Array.Empty(); + /// + /// Normalised retry delays (populated after validation). + /// + public IReadOnlyList NormalizedRetryDelays { get; private set; } = Array.Empty(); + /// /// Validates required values and normalises scope entries. /// @@ -97,8 +131,14 @@ public sealed class StellaOpsAuthClientOptions throw new InvalidOperationException("Expiration skew must be between 0 seconds and 5 minutes."); } + if (OfflineCacheTolerance < TimeSpan.Zero) + { + throw new InvalidOperationException("Offline cache tolerance must be greater than or equal to zero."); + } + AuthorityUri = authorityUri; NormalizedScopes = NormalizeScopes(scopes); + NormalizedRetryDelays = EnableRetries ? NormalizeRetryDelays(retryDelays) : Array.Empty(); } private static IReadOnlyList NormalizeScopes(IList values) @@ -140,4 +180,26 @@ public sealed class StellaOpsAuthClientOptions ? Array.Empty() : values.OrderBy(static scope => scope, StringComparer.Ordinal).ToArray(); } + + private static IReadOnlyList NormalizeRetryDelays(IList values) + { + for (var index = values.Count - 1; index >= 0; index--) + { + var delay = values[index]; + if (delay <= TimeSpan.Zero) + { + values.RemoveAt(index); + } + } + + if (values.Count == 0) + { + foreach (var delay in DefaultRetryDelays) + { + values.Add(delay); + } + } + + return values.ToArray(); + } } diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsDiscoveryCache.cs b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsDiscoveryCache.cs index 10232711..bfbab405 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsDiscoveryCache.cs +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsDiscoveryCache.cs @@ -21,6 +21,7 @@ public sealed class StellaOpsDiscoveryCache private OpenIdConfiguration? cachedConfiguration; private DateTimeOffset cacheExpiresAt; + private DateTimeOffset offlineExpiresAt; public StellaOpsDiscoveryCache(HttpClient httpClient, IOptionsMonitor optionsMonitor, TimeProvider? timeProvider = null, ILogger? logger = null) { @@ -44,41 +45,95 @@ public sealed class StellaOpsDiscoveryCache logger?.LogDebug("Fetching StellaOps discovery document from {DiscoveryUri}.", discoveryUri); - using var request = new HttpRequestMessage(HttpMethod.Get, discoveryUri); - using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - var document = await JsonSerializer.DeserializeAsync(stream, serializerOptions, cancellationToken).ConfigureAwait(false); - - if (document is null) + try { - throw new InvalidOperationException("Authority discovery document is empty."); - } + using var request = new HttpRequestMessage(HttpMethod.Get, discoveryUri); + using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); - if (string.IsNullOrWhiteSpace(document.TokenEndpoint)) + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var document = await JsonSerializer.DeserializeAsync(stream, serializerOptions, cancellationToken).ConfigureAwait(false); + + if (document is null) + { + throw new InvalidOperationException("Authority discovery document is empty."); + } + + if (string.IsNullOrWhiteSpace(document.TokenEndpoint)) + { + throw new InvalidOperationException("Authority discovery document does not expose token_endpoint."); + } + + if (string.IsNullOrWhiteSpace(document.JwksUri)) + { + throw new InvalidOperationException("Authority discovery document does not expose jwks_uri."); + } + + var configuration = new OpenIdConfiguration( + new Uri(document.TokenEndpoint, UriKind.Absolute), + new Uri(document.JwksUri, UriKind.Absolute)); + + cachedConfiguration = configuration; + cacheExpiresAt = now + options.DiscoveryCacheLifetime; + offlineExpiresAt = cacheExpiresAt + options.OfflineCacheTolerance; + + return configuration; + } + catch (Exception exception) when (IsOfflineCandidate(exception, cancellationToken) && TryUseOfflineFallback(options, now, exception)) { - throw new InvalidOperationException("Authority discovery document does not expose token_endpoint."); + return cachedConfiguration!; } - - if (string.IsNullOrWhiteSpace(document.JwksUri)) - { - throw new InvalidOperationException("Authority discovery document does not expose jwks_uri."); - } - - var configuration = new OpenIdConfiguration( - new Uri(document.TokenEndpoint, UriKind.Absolute), - new Uri(document.JwksUri, UriKind.Absolute)); - - cachedConfiguration = configuration; - cacheExpiresAt = now + options.DiscoveryCacheLifetime; - - return configuration; } private sealed record DiscoveryDocument( [property: System.Text.Json.Serialization.JsonPropertyName("token_endpoint")] string? TokenEndpoint, [property: System.Text.Json.Serialization.JsonPropertyName("jwks_uri")] string? JwksUri); + + private static bool IsOfflineCandidate(Exception exception, CancellationToken cancellationToken) + { + if (exception is HttpRequestException) + { + return true; + } + + if (exception is TaskCanceledException && !cancellationToken.IsCancellationRequested) + { + return true; + } + + if (exception is TimeoutException) + { + return true; + } + + return false; + } + + private bool TryUseOfflineFallback(StellaOpsAuthClientOptions options, DateTimeOffset now, Exception exception) + { + if (!options.AllowOfflineCacheFallback || cachedConfiguration is null) + { + return false; + } + + if (options.OfflineCacheTolerance <= TimeSpan.Zero) + { + return false; + } + + if (offlineExpiresAt == DateTimeOffset.MinValue) + { + return false; + } + + if (now >= offlineExpiresAt) + { + return false; + } + + logger?.LogWarning(exception, "Discovery document fetch failed; reusing cached configuration until {FallbackExpiresAt}.", offlineExpiresAt); + return true; + } } /// diff --git a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsJwksCache.cs b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsJwksCache.cs index 63fbee9e..dda3ed30 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsJwksCache.cs +++ b/src/StellaOps.Authority/StellaOps.Auth.Client/StellaOpsJwksCache.cs @@ -21,6 +21,7 @@ public sealed class StellaOpsJwksCache private JsonWebKeySet? cachedSet; private DateTimeOffset cacheExpiresAt; + private DateTimeOffset offlineExpiresAt; public StellaOpsJwksCache( HttpClient httpClient, @@ -44,17 +45,72 @@ public sealed class StellaOpsJwksCache return cachedSet; } + var options = optionsMonitor.CurrentValue; var configuration = await discoveryCache.GetAsync(cancellationToken).ConfigureAwait(false); logger?.LogDebug("Fetching JWKS from {JwksUri}.", configuration.JwksEndpoint); - using var response = await httpClient.GetAsync(configuration.JwksEndpoint, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); + try + { + using var response = await httpClient.GetAsync(configuration.JwksEndpoint, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); - var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - cachedSet = new JsonWebKeySet(json); - cacheExpiresAt = now + optionsMonitor.CurrentValue.JwksCacheLifetime; + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + cachedSet = new JsonWebKeySet(json); + cacheExpiresAt = now + options.JwksCacheLifetime; + offlineExpiresAt = cacheExpiresAt + options.OfflineCacheTolerance; - return cachedSet; + return cachedSet; + } + catch (Exception exception) when (IsOfflineCandidate(exception, cancellationToken) && TryUseOfflineFallback(options, now, exception)) + { + return cachedSet!; + } + } + + private static bool IsOfflineCandidate(Exception exception, CancellationToken cancellationToken) + { + if (exception is HttpRequestException) + { + return true; + } + + if (exception is TaskCanceledException && !cancellationToken.IsCancellationRequested) + { + return true; + } + + if (exception is TimeoutException) + { + return true; + } + + return false; + } + + private bool TryUseOfflineFallback(StellaOpsAuthClientOptions options, DateTimeOffset now, Exception exception) + { + if (!options.AllowOfflineCacheFallback || cachedSet is null) + { + return false; + } + + if (options.OfflineCacheTolerance <= TimeSpan.Zero) + { + return false; + } + + if (offlineExpiresAt == DateTimeOffset.MinValue) + { + return false; + } + + if (now >= offlineExpiresAt) + { + return false; + } + + logger?.LogWarning(exception, "JWKS fetch failed; reusing cached keys until {FallbackExpiresAt}.", offlineExpiresAt); + return true; } } diff --git a/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/README.NuGet.md b/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/README.NuGet.md new file mode 100644 index 00000000..1f7eba7f --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/README.NuGet.md @@ -0,0 +1,9 @@ +# StellaOps.Auth.ServerIntegration + +ASP.NET Core helpers that enable resource servers to authenticate with **StellaOps Authority**: + +- `AddStellaOpsResourceServerAuthentication` extension for JWT bearer + scope policies. +- Network bypass mask evaluation for on-host automation. +- Consistent `ProblemDetails` responses and policy helpers shared with Feedser/Backend services. + +Pair this package with `StellaOps.Auth.Abstractions` and `StellaOps.Auth.Client` for end-to-end Authority integration. diff --git a/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj b/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj index 03617542..33cf5438 100644 --- a/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj +++ b/src/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj @@ -6,6 +6,25 @@ enable true + + StellaOps.Auth.ServerIntegration + ASP.NET server integration helpers for StellaOps Authority, including JWT validation and bypass masks. + StellaOps + StellaOps + AGPL-3.0-or-later + https://stella-ops.org + https://git.stella-ops.org/stella-ops.org/git.stella-ops.org + git + true + true + true + snupkg + stellaops;authentication;authority;aspnet + true + $(NoWarn);1591 + README.NuGet.md + 1.0.0-preview.1 + @@ -14,6 +33,10 @@ + + + + diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginOptionsTests.cs b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginOptionsTests.cs index bf105a10..f0f32058 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginOptionsTests.cs +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginOptionsTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using StellaOps.Authority.Plugin.Standard; namespace StellaOps.Authority.Plugin.Standard.Tests; @@ -53,4 +54,46 @@ public class StandardPluginOptionsTests var ex = Assert.Throws(() => options.Validate("standard")); Assert.Contains("lockout.windowMinutes", ex.Message, StringComparison.OrdinalIgnoreCase); } + + [Fact] + public void Normalize_ResolvesRelativeTokenSigningDirectory() + { + var configDir = Path.Combine(Path.GetTempPath(), "stellaops-standard-plugin", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(configDir); + + try + { + var configPath = Path.Combine(configDir, "standard.yaml"); + var options = new StandardPluginOptions + { + TokenSigning = { KeyDirectory = "../keys" } + }; + + options.Normalize(configPath); + + var expected = Path.GetFullPath(Path.Combine(configDir, "../keys")); + Assert.Equal(expected, options.TokenSigning.KeyDirectory); + } + finally + { + if (Directory.Exists(configDir)) + { + Directory.Delete(configDir, recursive: true); + } + } + } + + [Fact] + public void Normalize_PreservesAbsoluteTokenSigningDirectory() + { + var absolute = Path.Combine(Path.GetTempPath(), "stellaops-standard-plugin", Guid.NewGuid().ToString("N"), "keys"); + var options = new StandardPluginOptions + { + TokenSigning = { KeyDirectory = absolute } + }; + + options.Normalize(Path.Combine(Path.GetTempPath(), "config", "standard.yaml")); + + Assert.Equal(Path.GetFullPath(absolute), options.TokenSigning.KeyDirectory); + } } diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginRegistrarTests.cs b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginRegistrarTests.cs index 3dec9cb5..19a432e6 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginRegistrarTests.cs +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/StandardPluginRegistrarTests.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; using Mongo2Go; using MongoDB.Driver; using StellaOps.Authority.Plugins.Abstractions; +using StellaOps.Authority.Plugin.Standard; using StellaOps.Authority.Plugin.Standard.Bootstrap; using StellaOps.Authority.Plugin.Standard.Storage; using StellaOps.Authority.Storage.Mongo.Documents; @@ -146,6 +149,61 @@ public class StandardPluginRegistrarTests using var provider = services.BuildServiceProvider(); Assert.Throws(() => provider.GetRequiredService()); } + + [Fact] + public void Register_NormalizesTokenSigningKeyDirectory() + { + using var runner = MongoDbRunner.Start(singleNodeReplSet: true); + var client = new MongoClient(runner.ConnectionString); + var database = client.GetDatabase("registrar-token-signing"); + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["tokenSigning:keyDirectory"] = "../keys" + }) + .Build(); + + var configDir = Path.Combine(Path.GetTempPath(), "stellaops-standard-plugin", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(configDir); + + try + { + var configPath = Path.Combine(configDir, "standard.yaml"); + var manifest = new AuthorityPluginManifest( + "standard", + "standard", + true, + typeof(StandardPluginRegistrar).Assembly.GetName().Name, + typeof(StandardPluginRegistrar).Assembly.Location, + new[] { AuthorityPluginCapabilities.Password }, + new Dictionary(), + configPath); + + var pluginContext = new AuthorityPluginContext(manifest, configuration); + var services = new ServiceCollection(); + services.AddLogging(); + services.AddSingleton(database); + services.AddSingleton(new InMemoryClientStore()); + + var registrar = new StandardPluginRegistrar(); + registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration)); + + using var provider = services.BuildServiceProvider(); + var optionsMonitor = provider.GetRequiredService>(); + var options = optionsMonitor.Get("standard"); + + var expected = Path.GetFullPath(Path.Combine(configDir, "../keys")); + Assert.Equal(expected, options.TokenSigning.KeyDirectory); + } + finally + { + if (Directory.Exists(configDir)) + { + Directory.Delete(configDir, recursive: true); + } + } + } } internal sealed class InMemoryClientStore : IAuthorityClientStore diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/AGENTS.md b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/AGENTS.md new file mode 100644 index 00000000..5dc29805 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/AGENTS.md @@ -0,0 +1,20 @@ +# Plugin Team Charter + +## Mission +Own the Mongo-backed Standard identity provider plug-in and shared Authority plug-in contracts. Deliver secure credential flows, configuration validation, and documentation that help other identity providers integrate cleanly. + +## Responsibilities +- Maintain `StellaOps.Authority.Plugin.Standard` and related test projects. +- Coordinate schema/option changes with Authority Core and Docs guilds. +- Ensure plugin options remain deterministic and offline-friendly. +- Surface open work on `TASKS.md`; update statuses (TODO/DOING/DONE/BLOCKED/REVIEW). + +## Key Paths +- `StandardPluginOptions` & registrar wiring +- `StandardUserCredentialStore` (Mongo persistence + lockouts) +- `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md` + +## Coordination +- Team 2 (Authority Core) for handler integration. +- Security Guild for password hashing, audit, revocation. +- Docs Guild for developer guide polish and diagrams. diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginOptions.cs b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginOptions.cs index c297d18c..27915982 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginOptions.cs +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginOptions.cs @@ -1,4 +1,5 @@ using System; +using System.IO; namespace StellaOps.Authority.Plugin.Standard; @@ -12,6 +13,11 @@ internal sealed class StandardPluginOptions public TokenSigningOptions TokenSigning { get; set; } = new(); + public void Normalize(string configPath) + { + TokenSigning.Normalize(configPath); + } + public void Validate(string pluginName) { BootstrapUser?.Validate(pluginName); @@ -90,4 +96,35 @@ internal sealed class LockoutOptions internal sealed class TokenSigningOptions { public string? KeyDirectory { get; set; } + + public void Normalize(string configPath) + { + if (string.IsNullOrWhiteSpace(KeyDirectory)) + { + KeyDirectory = null; + return; + } + + var resolved = KeyDirectory.Trim(); + if (string.IsNullOrEmpty(resolved)) + { + KeyDirectory = null; + return; + } + + resolved = Environment.ExpandEnvironmentVariables(resolved); + + if (!Path.IsPathRooted(resolved)) + { + var baseDirectory = Path.GetDirectoryName(configPath); + if (string.IsNullOrEmpty(baseDirectory)) + { + baseDirectory = Directory.GetCurrentDirectory(); + } + + resolved = Path.Combine(baseDirectory, resolved); + } + + KeyDirectory = Path.GetFullPath(resolved); + } } diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginRegistrar.cs b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginRegistrar.cs index be397907..570ae2fa 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginRegistrar.cs +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/StandardPluginRegistrar.cs @@ -29,9 +29,16 @@ internal sealed class StandardPluginRegistrar : IAuthorityPluginRegistrar context.Services.AddSingleton(); context.Services.AddSingleton(sp => sp.GetRequiredService()); + var configPath = context.Plugin.Manifest.ConfigPath; + context.Services.AddOptions(pluginName) .Bind(context.Plugin.Configuration) - .PostConfigure(options => options.Validate(pluginName)); + .PostConfigure(options => + { + options.Normalize(configPath); + options.Validate(pluginName); + }) + .ValidateOnStart(); context.Services.AddSingleton(sp => { diff --git a/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md new file mode 100644 index 00000000..737d0d82 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md @@ -0,0 +1,15 @@ +# Team 8 / Plugin Standard Backlog (UTC 2025-10-10) + +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +|----|--------|----------|------------|-------------|---------------| +| PLG6.DOC | BLOCKED (Docs) | BE-Auth Plugin, Docs Guild | PLG1–PLG5 | Final polish + diagrams for plugin developer guide. | Docs team delivers copy-edit + exported diagrams; PR merged. | +| SEC1.PLG | TODO | Security Guild, BE-Auth Plugin | SEC1.A (StellaOps.Cryptography) | Swap Standard plugin hashing to Argon2id via `StellaOps.Cryptography` abstractions; keep PBKDF2 verification for legacy. | ✅ `StandardUserCredentialStore` uses `ICryptoProvider` to hash/check; ✅ Transparent rehash on success; ✅ Unit tests cover tamper + legacy rehash. | +| SEC1.OPT | TODO | Security Guild | SEC1.PLG | Expose password hashing knobs in `StandardPluginOptions` (`memoryKiB`, `iterations`, `parallelism`, `algorithm`) with validation. | ✅ Options bound from YAML; ✅ Invalid configs throw; ✅ Docs include tuning guidance. | +| SEC2.PLG | TODO | Security Guild, Storage Guild | SEC2.A (audit contract) | Emit audit events from password verification outcomes and persist via `IAuthorityLoginAttemptStore`. | ✅ Serilog events enriched with subject/client/IP/outcome; ✅ Mongo records written per attempt; ✅ Tests assert success/lockout/failure cases. | +| SEC3.PLG | TODO | Security Guild, BE-Auth Plugin | CORE8, SEC3.A (rate limiter) | Ensure lockout responses and rate-limit metadata flow through plugin logs/events (include retry-after). | ✅ Audit record includes retry-after; ✅ Tests confirm lockout + limiter interplay. | +| SEC4.PLG | TODO | Security Guild | SEC4.A (revocation schema) | Provide plugin hooks so revoked users/clients write reasons for revocation bundle export. | ✅ Revocation exporter consumes plugin data; ✅ Tests cover revoked user/client output. | +| SEC5.PLG | TODO | Security Guild | SEC5.A (threat model) | Address plugin-specific mitigations (bootstrap user handling, password policy docs) in threat model backlog. | ✅ Threat model lists plugin attack surfaces; ✅ Mitigation items filed. | +| PLG4-6.CAPABILITIES | DOING (2025-10-10) | BE-Auth Plugin, Docs Guild | PLG1–PLG3 | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | ✅ Capability metadata + validation merged; ✅ Plugin guide updated with final copy & diagrams; ✅ Release notes mention new toggles. | +| PLG7.RFC | REVIEW | BE-Auth Plugin, Security Guild | PLG4 | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | ✅ Guild review sign-off recorded; ✅ Follow-up issues filed in module boards. | + +> Update statuses to DOING/DONE/BLOCKED as you make progress. Always run `dotnet test` for touched projects before marking DONE. diff --git a/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/ClientCredentialsAndTokenHandlersTests.cs b/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/ClientCredentialsAndTokenHandlersTests.cs index 174b6d74..46d6974c 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/ClientCredentialsAndTokenHandlersTests.cs +++ b/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/ClientCredentialsAndTokenHandlersTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Security.Claims; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; @@ -18,6 +19,8 @@ namespace StellaOps.Authority.Tests.OpenIddict; public class ClientCredentialsHandlersTests { + private static readonly ActivitySource TestActivitySource = new("StellaOps.Authority.Tests"); + [Fact] public async Task ValidateClientCredentials_Rejects_WhenScopeNotAllowed() { @@ -27,7 +30,7 @@ public class ClientCredentialsHandlersTests allowedScopes: "jobs:read"); var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument)); - var handler = new ValidateClientCredentialsHandler(new TestClientStore(clientDocument), registry); + var handler = new ValidateClientCredentialsHandler(new TestClientStore(clientDocument), registry, TestActivitySource, NullLogger.Instance); var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "jobs:write"); var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction); @@ -48,7 +51,7 @@ public class ClientCredentialsHandlersTests allowedScopes: "jobs:read jobs:trigger"); var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(clientDocument)); - var handler = new ValidateClientCredentialsHandler(new TestClientStore(clientDocument), registry); + var handler = new ValidateClientCredentialsHandler(new TestClientStore(clientDocument), registry, TestActivitySource, NullLogger.Instance); var transaction = CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "jobs:read"); var context = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction); @@ -75,7 +78,7 @@ public class ClientCredentialsHandlersTests var descriptor = CreateDescriptor(clientDocument); var registry = CreateRegistry(withClientProvisioning: true, clientDescriptor: descriptor); var tokenStore = new TestTokenStore(); - var handler = new HandleClientCredentialsHandler(registry, tokenStore, TimeProvider.System); + var handler = new HandleClientCredentialsHandler(registry, tokenStore, TimeProvider.System, TestActivitySource, NullLogger.Instance); var transaction = CreateTokenTransaction(clientDocument.ClientId, secret: null, scope: "jobs:trigger"); transaction.Options.AccessTokenLifetime = TimeSpan.FromMinutes(30); @@ -106,6 +109,8 @@ public class ClientCredentialsHandlersTests public class TokenValidationHandlersTests { + private static readonly ActivitySource TestActivitySource = new("StellaOps.Authority.Tests.TokenValidation"); + [Fact] public async Task ValidateAccessTokenHandler_Rejects_WhenTokenRevoked() { @@ -121,7 +126,9 @@ public class TokenValidationHandlersTests tokenStore, new TestClientStore(CreateClient()), CreateRegistry(withClientProvisioning: true, clientDescriptor: CreateDescriptor(CreateClient())), - TimeProvider.System); + TimeProvider.System, + TestActivitySource, + NullLogger.Instance); var transaction = new OpenIddictServerTransaction { @@ -161,7 +168,9 @@ public class TokenValidationHandlersTests new TestTokenStore(), new TestClientStore(clientDocument), registry, - TimeProvider.System); + TimeProvider.System, + TestActivitySource, + NullLogger.Instance); var transaction = new OpenIddictServerTransaction { diff --git a/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/TokenPersistenceIntegrationTests.cs b/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/TokenPersistenceIntegrationTests.cs new file mode 100644 index 00000000..ee8e3885 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Authority.Tests/OpenIddict/TokenPersistenceIntegrationTests.cs @@ -0,0 +1,208 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Claims; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Time.Testing; +using MongoDB.Driver; +using OpenIddict.Abstractions; +using OpenIddict.Server; +using StellaOps.Authority; +using StellaOps.Authority.OpenIddict; +using StellaOps.Authority.OpenIddict.Handlers; +using StellaOps.Authority.Plugins.Abstractions; +using StellaOps.Authority.Storage.Mongo; +using StellaOps.Authority.Storage.Mongo.Documents; +using StellaOps.Authority.Storage.Mongo.Extensions; +using StellaOps.Authority.Storage.Mongo.Initialization; +using StellaOps.Authority.Storage.Mongo.Stores; +using StellaOps.Feedser.Testing; +using Xunit; + +namespace StellaOps.Authority.Tests.OpenIddict; + +[Collection("mongo-fixture")] +public sealed class TokenPersistenceIntegrationTests +{ + private static readonly ActivitySource TestActivitySource = new("StellaOps.Authority.Tests.Persistence"); + private readonly MongoIntegrationFixture fixture; + + public TokenPersistenceIntegrationTests(MongoIntegrationFixture fixture) + => this.fixture = fixture ?? throw new ArgumentNullException(nameof(fixture)); + + [Fact] + public async Task HandleClientCredentials_PersistsTokenInMongo() + { + await ResetCollectionsAsync(); + + var issuedAt = new DateTimeOffset(2025, 10, 10, 12, 0, 0, TimeSpan.Zero); + var clock = new FakeTimeProvider(issuedAt); + + await using var provider = await BuildMongoProviderAsync(clock); + + var clientStore = provider.GetRequiredService(); + var tokenStore = provider.GetRequiredService(); + + var clientDocument = TestHelpers.CreateClient( + secret: "s3cr3t!", + allowedGrantTypes: "client_credentials", + allowedScopes: "jobs:trigger jobs:read"); + + await clientStore.UpsertAsync(clientDocument, CancellationToken.None); + + var registry = TestHelpers.CreateRegistry( + withClientProvisioning: true, + clientDescriptor: TestHelpers.CreateDescriptor(clientDocument)); + + var validateHandler = new ValidateClientCredentialsHandler(clientStore, registry, TestActivitySource, NullLogger.Instance); + var handleHandler = new HandleClientCredentialsHandler(registry, tokenStore, clock, TestActivitySource, NullLogger.Instance); + + var transaction = TestHelpers.CreateTokenTransaction(clientDocument.ClientId, "s3cr3t!", scope: "jobs:trigger"); + transaction.Options.AccessTokenLifetime = TimeSpan.FromMinutes(15); + + var validateContext = new OpenIddictServerEvents.ValidateTokenRequestContext(transaction); + await validateHandler.HandleAsync(validateContext); + Assert.False(validateContext.IsRejected); + + var handleContext = new OpenIddictServerEvents.HandleTokenRequestContext(transaction); + await handleHandler.HandleAsync(handleContext); + + Assert.True(handleContext.IsRequestHandled); + var principal = Assert.IsType(handleContext.Principal); + var tokenId = principal.GetClaim(OpenIddictConstants.Claims.JwtId); + Assert.False(string.IsNullOrWhiteSpace(tokenId)); + + var stored = await tokenStore.FindByTokenIdAsync(tokenId!, CancellationToken.None); + Assert.NotNull(stored); + Assert.Equal(clientDocument.ClientId, stored!.ClientId); + Assert.Equal(OpenIddictConstants.TokenTypeHints.AccessToken, stored.Type); + Assert.Equal("valid", stored.Status); + Assert.Equal(issuedAt, stored.CreatedAt); + Assert.Equal(issuedAt.AddMinutes(15), stored.ExpiresAt); + Assert.Equal(new[] { "jobs:trigger" }, stored.Scope); + } + + [Fact] + public async Task ValidateAccessTokenHandler_RejectsRevokedRefreshTokenPersistedInMongo() + { + await ResetCollectionsAsync(); + + var now = new DateTimeOffset(2025, 10, 10, 14, 0, 0, TimeSpan.Zero); + var clock = new FakeTimeProvider(now); + + await using var provider = await BuildMongoProviderAsync(clock); + + var clientStore = provider.GetRequiredService(); + var tokenStore = provider.GetRequiredService(); + + var clientDocument = TestHelpers.CreateClient( + secret: null, + clientType: "public", + allowedGrantTypes: "password refresh_token", + allowedScopes: "openid profile jobs:read"); + + await clientStore.UpsertAsync(clientDocument, CancellationToken.None); + + var descriptor = TestHelpers.CreateDescriptor(clientDocument); + var userDescriptor = new AuthorityUserDescriptor("subject-1", "alice", displayName: "Alice", requiresPasswordReset: false); + + var plugin = TestHelpers.CreatePlugin( + name: clientDocument.Plugin ?? "standard", + supportsClientProvisioning: true, + descriptor, + userDescriptor); + + var registry = new AuthorityIdentityProviderRegistry( + new[] { plugin }, + NullLogger.Instance); + + const string revokedTokenId = "refresh-token-1"; + var refreshToken = new AuthorityTokenDocument + { + TokenId = revokedTokenId, + Type = OpenIddictConstants.TokenTypeHints.RefreshToken, + SubjectId = userDescriptor.SubjectId, + ClientId = clientDocument.ClientId, + Scope = new List { "openid", "profile" }, + Status = "valid", + CreatedAt = now.AddMinutes(-5), + ExpiresAt = now.AddHours(4), + ReferenceId = "refresh-reference-1" + }; + + await tokenStore.InsertAsync(refreshToken, CancellationToken.None); + + var revokedAt = now.AddMinutes(1); + await tokenStore.UpdateStatusAsync(revokedTokenId, "revoked", revokedAt, CancellationToken.None); + + var handler = new ValidateAccessTokenHandler( + tokenStore, + clientStore, + registry, + clock, + TestActivitySource, + NullLogger.Instance); + + var transaction = new OpenIddictServerTransaction + { + EndpointType = OpenIddictServerEndpointType.Token, + Options = new OpenIddictServerOptions(), + Request = new OpenIddictRequest + { + GrantType = OpenIddictConstants.GrantTypes.RefreshToken + } + }; + + var principal = TestHelpers.CreatePrincipal( + clientDocument.ClientId, + revokedTokenId, + plugin.Name, + userDescriptor.SubjectId); + + var context = new OpenIddictServerEvents.ValidateTokenContext(transaction) + { + Principal = principal, + TokenId = revokedTokenId + }; + + await handler.HandleAsync(context); + + Assert.True(context.IsRejected); + Assert.Equal(OpenIddictConstants.Errors.InvalidToken, context.Error); + + var stored = await tokenStore.FindByTokenIdAsync(revokedTokenId, CancellationToken.None); + Assert.NotNull(stored); + Assert.Equal("revoked", stored!.Status); + Assert.Equal(revokedAt, stored.RevokedAt); + } + + private async Task ResetCollectionsAsync() + { + var tokens = fixture.Database.GetCollection(AuthorityMongoDefaults.Collections.Tokens); + await tokens.DeleteManyAsync(Builders.Filter.Empty); + + var clients = fixture.Database.GetCollection(AuthorityMongoDefaults.Collections.Clients); + await clients.DeleteManyAsync(Builders.Filter.Empty); + } + + private async Task BuildMongoProviderAsync(FakeTimeProvider clock) + { + var services = new ServiceCollection(); + services.AddSingleton(clock); + services.AddLogging(); + services.AddAuthorityMongoStorage(options => + { + options.ConnectionString = fixture.Runner.ConnectionString; + options.DatabaseName = fixture.Database.DatabaseNamespace.DatabaseName; + options.CommandTimeout = TimeSpan.FromSeconds(5); + }); + + var provider = services.BuildServiceProvider(); + + var initializer = provider.GetRequiredService(); + var database = provider.GetRequiredService(); + await initializer.InitialiseAsync(database, CancellationToken.None); + + return provider; + } +} diff --git a/src/StellaOps.Authority/StellaOps.Authority.Tests/RateLimiting/AuthorityRateLimiterTests.cs b/src/StellaOps.Authority/StellaOps.Authority.Tests/RateLimiting/AuthorityRateLimiterTests.cs new file mode 100644 index 00000000..6ad7e37e --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Authority.Tests/RateLimiting/AuthorityRateLimiterTests.cs @@ -0,0 +1,59 @@ +using System; +using System.Net; +using Microsoft.AspNetCore.Http; +using StellaOps.Authority; +using StellaOps.Configuration; +using Xunit; + +namespace StellaOps.Authority.Tests.RateLimiting; + +public class AuthorityRateLimiterTests +{ + [Fact] + public async Task TokenLimiter_Throttles_WhenLimitExceeded() + { + var options = CreateOptions(); + options.RateLimiting.Token.PermitLimit = 1; + options.RateLimiting.Token.Window = TimeSpan.FromMinutes(1); + + using var limiter = AuthorityRateLimiter.CreateGlobalLimiter(options); + + var context = new DefaultHttpContext(); + context.Request.Path = "/token"; + context.Connection.RemoteIpAddress = IPAddress.Parse("203.0.113.10"); + + var first = await limiter.AcquireAsync(context); + Assert.True(first.IsAcquired); + + var second = await limiter.AcquireAsync(context); + Assert.False(second.IsAcquired); + } + + [Fact] + public async Task AuthorizeLimiter_Allows_WhenDisabled() + { + var options = CreateOptions(); + options.RateLimiting.Authorize.Enabled = false; + + using var limiter = AuthorityRateLimiter.CreateGlobalLimiter(options); + + var context = new DefaultHttpContext(); + context.Request.Path = "/authorize"; + context.Connection.RemoteIpAddress = IPAddress.Parse("203.0.113.20"); + + var lease = await limiter.AcquireAsync(context); + Assert.True(lease.IsAcquired); + } + + private static StellaOpsAuthorityOptions CreateOptions() + { + var options = new StellaOpsAuthorityOptions + { + Issuer = new Uri("https://authority.stella-ops.test"), + SchemaVersion = 1 + }; + + options.Storage.ConnectionString = "mongodb://localhost/authority"; + return options; + } +} diff --git a/src/StellaOps.Authority/StellaOps.Authority.sln b/src/StellaOps.Authority/StellaOps.Authority.sln index 3978b59e..71a314ab 100644 --- a/src/StellaOps.Authority/StellaOps.Authority.sln +++ b/src/StellaOps.Authority/StellaOps.Authority.sln @@ -49,6 +49,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client.Tests", "StellaOps.Auth.Client.Tests\StellaOps.Auth.Client.Tests.csproj", "{2DB48E45-BEFE-40FC-8E7D-1697A8EB0749}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography", "..\StellaOps.Cryptography\StellaOps.Cryptography.csproj", "{35D22E43-729A-4D43-A289-5A0E96BA0199}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Tests", "..\StellaOps.Cryptography.Tests\StellaOps.Cryptography.Tests.csproj", "{84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -335,6 +339,30 @@ Global {2DB48E45-BEFE-40FC-8E7D-1697A8EB0749}.Release|x64.Build.0 = Release|Any CPU {2DB48E45-BEFE-40FC-8E7D-1697A8EB0749}.Release|x86.ActiveCfg = Release|Any CPU {2DB48E45-BEFE-40FC-8E7D-1697A8EB0749}.Release|x86.Build.0 = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|x64.ActiveCfg = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|x64.Build.0 = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|x86.ActiveCfg = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Debug|x86.Build.0 = Debug|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|Any CPU.Build.0 = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|x64.ActiveCfg = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|x64.Build.0 = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|x86.ActiveCfg = Release|Any CPU + {35D22E43-729A-4D43-A289-5A0E96BA0199}.Release|x86.Build.0 = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|x64.ActiveCfg = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|x64.Build.0 = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|x86.ActiveCfg = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Debug|x86.Build.0 = Debug|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|Any CPU.Build.0 = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|x64.ActiveCfg = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|x64.Build.0 = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|x86.ActiveCfg = Release|Any CPU + {84AEC0C8-EE60-4AB1-A59B-B8E7CCFC0A25}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/StellaOps.Authority/StellaOps.Authority/AuthorityRateLimiter.cs b/src/StellaOps.Authority/StellaOps.Authority/AuthorityRateLimiter.cs new file mode 100644 index 00000000..fd6252c9 --- /dev/null +++ b/src/StellaOps.Authority/StellaOps.Authority/AuthorityRateLimiter.cs @@ -0,0 +1,103 @@ +using System; +using System.Globalization; +using System.Net; +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RateLimiting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using StellaOps.Configuration; + +namespace StellaOps.Authority; + +internal static class AuthorityRateLimiter +{ + internal const string TokenLimiterName = "authority-token"; + internal const string AuthorizeLimiterName = "authority-authorize"; + + public static void Configure(RateLimiterOptions options, StellaOpsAuthorityOptions authorityOptions, ILogger? logger = null) + { + ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(authorityOptions); + + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + options.GlobalLimiter = CreateGlobalLimiter(authorityOptions); + options.OnRejected = async (context, cancellationToken) => + { + var httpContext = context.HttpContext; + var remoteIp = httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown"; + logger ??= httpContext.RequestServices.GetService(typeof(ILoggerFactory)) is ILoggerFactory loggerFactory + ? loggerFactory.CreateLogger("StellaOps.Authority.RateLimiting") + : null; + + logger?.LogWarning( + "Rate limit exceeded for path {Path} from {RemoteIp}.", + httpContext.Request.Path, + remoteIp); + + if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out TimeSpan retryAfter)) + { + httpContext.Response.Headers["Retry-After"] = Math.Ceiling(retryAfter.TotalSeconds).ToString(CultureInfo.InvariantCulture); + } + + await ValueTask.CompletedTask; + }; + } + + public static PartitionedRateLimiter CreateGlobalLimiter(StellaOpsAuthorityOptions authorityOptions) + { + ArgumentNullException.ThrowIfNull(authorityOptions); + + var tokenOptions = authorityOptions.RateLimiting.Token; + var authorizeOptions = authorityOptions.RateLimiting.Authorize; + + return PartitionedRateLimiter.Create(context => + { + var path = context.Request.Path; + + if (tokenOptions.Enabled && path.HasValue && path.Value!.Equals("/token", StringComparison.OrdinalIgnoreCase)) + { + return RateLimitPartition.GetFixedWindowLimiter( + $"{TokenLimiterName}:{ResolvePartitionKey(context)}", + _ => CreateFixedWindowOptions(tokenOptions)); + } + + if (authorizeOptions.Enabled && path.HasValue && path.Value!.Equals("/authorize", StringComparison.OrdinalIgnoreCase)) + { + return RateLimitPartition.GetFixedWindowLimiter( + $"{AuthorizeLimiterName}:{ResolvePartitionKey(context)}", + _ => CreateFixedWindowOptions(authorizeOptions)); + } + + return RateLimitPartition.GetNoLimiter(ResolvePartitionKey(context)); + }); + } + + private static FixedWindowRateLimiterOptions CreateFixedWindowOptions(AuthorityEndpointRateLimitOptions options) + { + return new FixedWindowRateLimiterOptions + { + PermitLimit = options.PermitLimit, + QueueLimit = options.QueueLimit, + QueueProcessingOrder = options.QueueProcessingOrder, + Window = options.Window, + AutoReplenishment = true + }; + } + + private static string ResolvePartitionKey(HttpContext context) + { + var remoteIp = context.Connection.RemoteIpAddress; + if (remoteIp is null) + { + return "unknown"; + } + + if (remoteIp.Equals(IPAddress.IPv6None) || remoteIp.Equals(IPAddress.Any) || remoteIp.Equals(IPAddress.IPv6Any)) + { + return "unknown"; + } + + return remoteIp.ToString(); + } +} diff --git a/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/ClientCredentialsHandlers.cs b/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/ClientCredentialsHandlers.cs index f0b5e06c..26942cb2 100644 --- a/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/ClientCredentialsHandlers.cs +++ b/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/ClientCredentialsHandlers.cs @@ -1,6 +1,8 @@ +using System.Diagnostics; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; +using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; using OpenIddict.Extensions; using OpenIddict.Server; @@ -17,13 +19,19 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle { private readonly IAuthorityClientStore clientStore; private readonly IAuthorityIdentityProviderRegistry registry; + private readonly ActivitySource activitySource; + private readonly ILogger logger; public ValidateClientCredentialsHandler( IAuthorityClientStore clientStore, - IAuthorityIdentityProviderRegistry registry) + IAuthorityIdentityProviderRegistry registry, + ActivitySource activitySource, + ILogger logger) { this.clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore)); this.registry = registry ?? throw new ArgumentNullException(nameof(registry)); + this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateTokenRequestContext context) @@ -35,9 +43,15 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle return; } + using var activity = activitySource.StartActivity("authority.token.validate_client_credentials", ActivityKind.Internal); + activity?.SetTag("authority.endpoint", "/token"); + activity?.SetTag("authority.grant_type", OpenIddictConstants.GrantTypes.ClientCredentials); + activity?.SetTag("authority.client_id", context.ClientId ?? string.Empty); + if (string.IsNullOrWhiteSpace(context.ClientId)) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Client identifier is required."); + logger.LogWarning("Client credentials validation failed: missing client identifier."); return; } @@ -45,6 +59,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle if (document is null || document.Disabled) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Unknown or disabled client identifier."); + logger.LogWarning("Client credentials validation failed for {ClientId}: client not found or disabled.", context.ClientId); return; } @@ -54,12 +69,14 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle if (!registry.TryGet(document.Plugin, out provider)) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Configured identity provider is unavailable."); + logger.LogWarning("Client credentials validation failed for {ClientId}: provider {Provider} unavailable.", context.ClientId, document.Plugin); return; } if (!provider.Capabilities.SupportsClientProvisioning || provider.ClientProvisioning is null) { context.Reject(OpenIddictConstants.Errors.UnauthorizedClient, "Associated identity provider does not support client provisioning."); + logger.LogWarning("Client credentials validation failed for {ClientId}: provider {Provider} lacks client provisioning capabilities.", context.ClientId, provider.Name); return; } } @@ -69,6 +86,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle !allowedGrantTypes.Any(static grant => string.Equals(grant, OpenIddictConstants.GrantTypes.ClientCredentials, StringComparison.Ordinal))) { context.Reject(OpenIddictConstants.Errors.UnauthorizedClient, "Client credentials grant is not permitted for this client."); + logger.LogWarning("Client credentials validation failed for {ClientId}: grant type not allowed.", document.ClientId); return; } @@ -78,6 +96,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle if (string.IsNullOrWhiteSpace(document.SecretHash)) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Client secret is not configured."); + logger.LogWarning("Client credentials validation failed for {ClientId}: secret not configured.", document.ClientId); return; } @@ -85,6 +104,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle !ClientCredentialHandlerHelpers.VerifySecret(context.ClientSecret, document.SecretHash)) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Invalid client credentials."); + logger.LogWarning("Client credentials validation failed for {ClientId}: secret verification failed.", document.ClientId); return; } } @@ -92,6 +112,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle !ClientCredentialHandlerHelpers.VerifySecret(context.ClientSecret, document.SecretHash)) { context.Reject(OpenIddictConstants.Errors.InvalidClient, "Invalid client credentials."); + logger.LogWarning("Client credentials validation failed for {ClientId}: secret verification failed.", document.ClientId); return; } @@ -103,6 +124,7 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle if (resolvedScopes.InvalidScope is not null) { context.Reject(OpenIddictConstants.Errors.InvalidScope, $"Scope '{resolvedScopes.InvalidScope}' is not allowed for this client."); + logger.LogWarning("Client credentials validation failed for {ClientId}: scope {Scope} not permitted.", document.ClientId, resolvedScopes.InvalidScope); return; } @@ -110,9 +132,11 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle if (provider is not null) { context.Transaction.Properties[AuthorityOpenIddictConstants.ClientProviderTransactionProperty] = provider.Name; + activity?.SetTag("authority.identity_provider", provider.Name); } context.Transaction.Properties[AuthorityOpenIddictConstants.ClientGrantedScopesProperty] = resolvedScopes.Scopes; + logger.LogInformation("Client credentials validated for {ClientId}.", document.ClientId); } } @@ -121,15 +145,21 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< private readonly IAuthorityIdentityProviderRegistry registry; private readonly IAuthorityTokenStore tokenStore; private readonly TimeProvider clock; + private readonly ActivitySource activitySource; + private readonly ILogger logger; public HandleClientCredentialsHandler( IAuthorityIdentityProviderRegistry registry, IAuthorityTokenStore tokenStore, - TimeProvider clock) + TimeProvider clock, + ActivitySource activitySource, + ILogger logger) { this.registry = registry ?? throw new ArgumentNullException(nameof(registry)); this.tokenStore = tokenStore ?? throw new ArgumentNullException(nameof(tokenStore)); this.clock = clock ?? throw new ArgumentNullException(nameof(clock)); + this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask HandleAsync(OpenIddictServerEvents.HandleTokenRequestContext context) @@ -141,6 +171,8 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< return; } + using var activity = activitySource.StartActivity("authority.token.handle_client_credentials", ActivityKind.Internal); + if (!context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.ClientTransactionProperty, out var value) || value is not AuthorityClientDocument document) { @@ -151,6 +183,9 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); identity.AddClaim(new Claim(OpenIddictConstants.Claims.Subject, document.ClientId)); identity.AddClaim(new Claim(OpenIddictConstants.Claims.ClientId, document.ClientId)); + activity?.SetTag("authority.client_id", document.ClientId); + activity?.SetTag("authority.endpoint", "/token"); + activity?.SetTag("authority.grant_type", OpenIddictConstants.GrantTypes.ClientCredentials); var tokenId = identity.GetClaim(OpenIddictConstants.Claims.JwtId); if (string.IsNullOrEmpty(tokenId)) @@ -171,6 +206,7 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< var (provider, descriptor) = await ResolveProviderAsync(context, document).ConfigureAwait(false); if (context.IsRejected) { + logger.LogWarning("Client credentials request rejected for {ClientId} during provider resolution.", document.ClientId); return; } @@ -179,11 +215,13 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< if (!string.IsNullOrWhiteSpace(document.Plugin)) { identity.SetClaim(StellaOpsClaimTypes.IdentityProvider, document.Plugin); + activity?.SetTag("authority.identity_provider", document.Plugin); } } else { identity.SetClaim(StellaOpsClaimTypes.IdentityProvider, provider.Name); + activity?.SetTag("authority.identity_provider", provider.Name); } var principal = new ClaimsPrincipal(identity); @@ -208,10 +246,11 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< await provider.ClaimsEnricher.EnrichAsync(identity, enrichmentContext, context.CancellationToken).ConfigureAwait(false); } - await PersistTokenAsync(context, document, tokenId, grantedScopes).ConfigureAwait(false); + await PersistTokenAsync(context, document, tokenId, grantedScopes, activity).ConfigureAwait(false); context.Principal = principal; context.HandleRequest(); + logger.LogInformation("Issued client credentials access token for {ClientId} with scopes {Scopes}.", document.ClientId, grantedScopes); } private async ValueTask<(IIdentityProviderPlugin? Provider, AuthorityClientDescriptor? Descriptor)> ResolveProviderAsync( @@ -255,7 +294,8 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< OpenIddictServerEvents.HandleTokenRequestContext context, AuthorityClientDocument document, string tokenId, - IReadOnlyCollection scopes) + IReadOnlyCollection scopes, + Activity? activity) { if (context.IsRejected) { @@ -282,6 +322,7 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler< await tokenStore.InsertAsync(record, context.CancellationToken).ConfigureAwait(false); context.Transaction.Properties[AuthorityOpenIddictConstants.TokenTransactionProperty] = record; + activity?.SetTag("authority.token_id", tokenId); } } diff --git a/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/PasswordGrantHandlers.cs b/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/PasswordGrantHandlers.cs index 15f75c21..09f55fc1 100644 --- a/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/PasswordGrantHandlers.cs +++ b/src/StellaOps.Authority/StellaOps.Authority/OpenIddict/Handlers/PasswordGrantHandlers.cs @@ -1,4 +1,6 @@ +using System.Diagnostics; using System.Security.Claims; +using Microsoft.Extensions.Logging; using OpenIddict.Abstractions; using OpenIddict.Extensions; using OpenIddict.Server; @@ -11,10 +13,17 @@ namespace StellaOps.Authority.OpenIddict.Handlers; internal sealed class ValidatePasswordGrantHandler : IOpenIddictServerHandler { private readonly IAuthorityIdentityProviderRegistry registry; + private readonly ActivitySource activitySource; + private readonly ILogger logger; - public ValidatePasswordGrantHandler(IAuthorityIdentityProviderRegistry registry) + public ValidatePasswordGrantHandler( + IAuthorityIdentityProviderRegistry registry, + ActivitySource activitySource, + ILogger logger) { this.registry = registry ?? throw new ArgumentNullException(nameof(registry)); + this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public ValueTask HandleAsync(OpenIddictServerEvents.ValidateTokenRequestContext context) @@ -26,20 +35,29 @@ internal sealed class ValidatePasswordGrantHandler : IOpenIddictServerHandler { private readonly IAuthorityIdentityProviderRegistry registry; + private readonly ActivitySource activitySource; + private readonly ILogger logger; - public HandlePasswordGrantHandler(IAuthorityIdentityProviderRegistry registry) + public HandlePasswordGrantHandler( + IAuthorityIdentityProviderRegistry registry, + ActivitySource activitySource, + ILogger logger) { this.registry = registry ?? throw new ArgumentNullException(nameof(registry)); + this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask HandleAsync(OpenIddictServerEvents.HandleTokenRequestContext context) @@ -62,6 +87,11 @@ internal sealed class HandlePasswordGrantHandler : IOpenIddictServerHandler logger; public ValidateAccessTokenHandler( IAuthorityTokenStore tokenStore, IAuthorityClientStore clientStore, IAuthorityIdentityProviderRegistry registry, - TimeProvider clock) + TimeProvider clock, + ActivitySource activitySource, + ILogger logger) { this.tokenStore = tokenStore ?? throw new ArgumentNullException(nameof(tokenStore)); this.clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore)); this.registry = registry ?? throw new ArgumentNullException(nameof(registry)); this.clock = clock ?? throw new ArgumentNullException(nameof(clock)); + this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateTokenContext context) @@ -43,6 +51,14 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler "/token", + OpenIddictServerEndpointType.Introspection => "/introspect", + _ => context.EndpointType.ToString() + }); + var tokenId = !string.IsNullOrWhiteSpace(context.TokenId) ? context.TokenId : context.Principal.GetClaim(OpenIddictConstants.Claims.JwtId); @@ -55,16 +71,19 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler>(Options.Create(authorityOptions)); +builder.Services.AddRateLimiter(rateLimiterOptions => +{ + AuthorityRateLimiter.Configure(rateLimiterOptions, authorityOptions); +}); + AuthorityPluginContext[] pluginContexts = AuthorityPluginConfigurationLoader .Load(authorityOptions, builder.Environment.ContentRootPath) .ToArray(); @@ -392,6 +398,7 @@ app.UseExceptionHandler(static errorApp => }); app.UseRouting(); +app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/src/StellaOps.Authority/TASKS.md b/src/StellaOps.Authority/TASKS.md new file mode 100644 index 00000000..a4cdc442 --- /dev/null +++ b/src/StellaOps.Authority/TASKS.md @@ -0,0 +1,15 @@ +# Authority Host Task Board (UTC 2025-10-10) + +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +|----|--------|----------|------------|-------------|---------------| +| CORE5B.DOC | TODO | Authority Core, Docs Guild | CORE5 | Document token persistence, revocation semantics, and enrichment expectations for resource servers/plugins. | ✅ `docs/11_AUTHORITY.md` + plugin guide updated with claims + token store notes; ✅ Samples include revocation sync guidance. | +| CORE9.REVOCATION | TODO | Authority Core, Security Guild | CORE5 | Implement revocation list persistence + export hooks (API + CLI). | ✅ Revoked tokens denied; ✅ Export endpoint/CLI returns manifest; ✅ Tests cover offline bundle flow. | +| CORE10.JWKS | TODO | Authority Core, DevOps | CORE9.REVOCATION | Provide JWKS rotation with pluggable key loader + documentation. | ✅ Signing/encryption keys rotate without downtime; ✅ JWKS endpoint updates; ✅ Docs describe rotation SOP. | +| CORE8.RL | BLOCKED (Team 2) | Authority Core | CORE8 | Deliver ASP.NET rate limiter plumbing (request metadata, dependency injection hooks) needed by Security Guild. | ✅ `/token` & `/authorize` pipelines expose limiter hooks; ✅ Tests cover throttle behaviour baseline. | +| SEC2.HOST | TODO | Security Guild, Authority Core | SEC2.A (audit contract) | Hook audit logger into OpenIddict handlers and bootstrap endpoints. | ✅ Audit events populated with correlationId, IP, client_id; ✅ Mongo login attempts persisted; ✅ Tests verify on success/failure/lockout. | +| SEC3.HOST | TODO | Security Guild | CORE8.RL, SEC3.A (rate policy) | Apply rate limiter policies (`AddRateLimiter`) to `/token` and `/internal/*` endpoints with configuration binding. | ✅ Policies configurable via `StellaOpsAuthorityOptions.Security.RateLimiting`; ✅ Integration tests hit 429 after limit; ✅ Docs updated. | +| SEC4.HOST | TODO | Security Guild, DevOps | SEC4.A (revocation schema) | Implement CLI/HTTP surface to export revocation bundle + detached JWS using `StellaOps.Cryptography`. | ✅ `stellaops auth revoke export` CLI/endpoint returns JSON + `.jws`; ✅ Verification script passes; ✅ Operator docs updated. | +| SEC4.KEY | TODO | Security Guild, DevOps | SEC4.HOST | Integrate signing keys with provider registry (initial ES256). | ✅ Keys loaded via `ICryptoProvider` signer; ✅ Rotation SOP documented. | +| SEC5.HOST | TODO | Security Guild | SEC5.A (threat model) | Feed Authority-specific mitigations (rate limiting, audit, revocation) into threat model + backlog. | ✅ Threat model updated; ✅ Backlog issues reference mitigations; ✅ Review sign-off captured. | + +> Update status columns (TODO / DOING / DONE / BLOCKED) together with code changes. Always run `dotnet test src/StellaOps.Authority.sln` when touching host logic. diff --git a/src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs b/src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs index d1e305c0..c7bfdce4 100644 --- a/src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs +++ b/src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs @@ -1,6 +1,8 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -8,14 +10,14 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using StellaOps.Auth.Abstractions; using StellaOps.Auth.Client; -using StellaOps.Cli.Commands; -using StellaOps.Cli.Configuration; -using StellaOps.Cli.Services; -using StellaOps.Cli.Services.Models; -using StellaOps.Cli.Telemetry; -using StellaOps.Cli.Tests.Testing; - -namespace StellaOps.Cli.Tests.Commands; +using StellaOps.Cli.Commands; +using StellaOps.Cli.Configuration; +using StellaOps.Cli.Services; +using StellaOps.Cli.Services.Models; +using StellaOps.Cli.Telemetry; +using StellaOps.Cli.Tests.Testing; + +namespace StellaOps.Cli.Tests.Commands; public sealed class CommandHandlersTests { @@ -244,6 +246,80 @@ public sealed class CommandHandlersTests } } + [Fact] + public async Task HandleAuthWhoAmIAsync_ReturnsErrorWhenTokenMissing() + { + var original = Environment.ExitCode; + using var tempDir = new TempDirectory(); + + try + { + var options = new StellaOpsCliOptions + { + ResultsDirectory = Path.Combine(tempDir.Path, "results"), + Authority = new StellaOpsCliAuthorityOptions + { + Url = "https://authority.example", + ClientId = "cli", + TokenCacheDirectory = tempDir.Path + } + }; + + var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: new StubTokenClient()); + + await CommandHandlers.HandleAuthWhoAmIAsync(provider, options, verbose: false, cancellationToken: CancellationToken.None); + + Assert.Equal(1, Environment.ExitCode); + } + finally + { + Environment.ExitCode = original; + } + } + + [Fact] + public async Task HandleAuthWhoAmIAsync_ReportsClaimsForJwtToken() + { + var original = Environment.ExitCode; + using var tempDir = new TempDirectory(); + + try + { + var options = new StellaOpsCliOptions + { + ResultsDirectory = Path.Combine(tempDir.Path, "results"), + Authority = new StellaOpsCliAuthorityOptions + { + Url = "https://authority.example", + ClientId = "cli", + TokenCacheDirectory = tempDir.Path + } + }; + + var tokenClient = new StubTokenClient(); + tokenClient.CachedEntry = new StellaOpsTokenCacheEntry( + CreateUnsignedJwt( + ("sub", "cli-user"), + ("aud", "feedser"), + ("iss", "https://authority.example"), + ("iat", 1_700_000_000), + ("nbf", 1_700_000_000)), + "Bearer", + DateTimeOffset.UtcNow.AddMinutes(30), + new[] { StellaOpsScopes.FeedserJobsTrigger }); + + var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: tokenClient); + + await CommandHandlers.HandleAuthWhoAmIAsync(provider, options, verbose: true, cancellationToken: CancellationToken.None); + + Assert.Equal(0, Environment.ExitCode); + } + finally + { + Environment.ExitCode = original; + } + } + [Fact] public async Task HandleAuthLogoutAsync_ClearsToken() { @@ -432,4 +508,26 @@ public sealed class CommandHandlersTests return Task.FromResult(_token); } } + + private static string CreateUnsignedJwt(params (string Key, object Value)[] claims) + { + var headerJson = "{\"alg\":\"none\",\"typ\":\"JWT\"}"; + var payload = new Dictionary(StringComparer.Ordinal); + foreach (var claim in claims) + { + payload[claim.Key] = claim.Value; + } + + var payloadJson = JsonSerializer.Serialize(payload); + return $"{Base64UrlEncode(headerJson)}.{Base64UrlEncode(payloadJson)}."; + } + + private static string Base64UrlEncode(string value) + { + var bytes = Encoding.UTF8.GetBytes(value); + return Convert.ToBase64String(bytes) + .TrimEnd('=') + .Replace('+', '-') + .Replace('/', '_'); + } } diff --git a/src/StellaOps.Cli.Tests/Configuration/CliBootstrapperTests.cs b/src/StellaOps.Cli.Tests/Configuration/CliBootstrapperTests.cs index 6666708a..d19deb4f 100644 --- a/src/StellaOps.Cli.Tests/Configuration/CliBootstrapperTests.cs +++ b/src/StellaOps.Cli.Tests/Configuration/CliBootstrapperTests.cs @@ -25,6 +25,10 @@ public sealed class CliBootstrapperTests : IDisposable Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_URL", "https://authority.env"); Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_CLIENT_ID", "cli-env"); Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_SCOPE", "feedser.jobs.trigger"); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_ENABLE_RETRIES", "false"); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_RETRY_DELAYS", "00:00:02,00:00:05"); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK", "false"); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE", "00:20:00"); try { @@ -35,6 +39,12 @@ public sealed class CliBootstrapperTests : IDisposable Assert.Equal("https://authority.env", options.Authority.Url); Assert.Equal("cli-env", options.Authority.ClientId); Assert.Equal("feedser.jobs.trigger", options.Authority.Scope); + + Assert.NotNull(options.Authority.Resilience); + Assert.False(options.Authority.Resilience.EnableRetries); + Assert.Equal(new[] { TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5) }, options.Authority.Resilience.RetryDelays); + Assert.False(options.Authority.Resilience.AllowOfflineCacheFallback); + Assert.Equal(TimeSpan.FromMinutes(20), options.Authority.Resilience.OfflineCacheTolerance); } finally { @@ -43,6 +53,10 @@ public sealed class CliBootstrapperTests : IDisposable Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_URL", null); Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_CLIENT_ID", null); Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_SCOPE", null); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_ENABLE_RETRIES", null); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_RETRY_DELAYS", null); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK", null); + Environment.SetEnvironmentVariable("STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE", null); } } diff --git a/src/StellaOps.Cli/Commands/CommandFactory.cs b/src/StellaOps.Cli/Commands/CommandFactory.cs index c25b9a4b..efe3d956 100644 --- a/src/StellaOps.Cli/Commands/CommandFactory.cs +++ b/src/StellaOps.Cli/Commands/CommandFactory.cs @@ -255,35 +255,51 @@ internal static class CommandFactory return CommandHandlers.HandleAuthStatusAsync(services, options, verbose, cancellationToken); }); + var whoami = new Command("whoami", "Display cached token claims (subject, scopes, expiry)."); + whoami.SetAction((parseResult, _) => + { + var verbose = parseResult.GetValue(verboseOption); + return CommandHandlers.HandleAuthWhoAmIAsync(services, options, verbose, cancellationToken); + }); + auth.Add(login); auth.Add(logout); auth.Add(status); + auth.Add(whoami); return auth; } - + private static Command BuildConfigCommand(StellaOpsCliOptions options) { var config = new Command("config", "Inspect CLI configuration state."); var show = new Command("show", "Display resolved configuration values."); - show.SetAction((_, _) => - { - var lines = new[] - { - $"Backend URL: {MaskIfEmpty(options.BackendUrl)}", - $"API Key: {DescribeSecret(options.ApiKey)}", - $"Scanner Cache: {options.ScannerCacheDirectory}", - $"Results Directory: {options.ResultsDirectory}", - $"Default Runner: {options.DefaultRunner}" - }; - - foreach (var line in lines) - { - Console.WriteLine(line); - } - - return Task.CompletedTask; - }); + show.SetAction((_, _) => + { + var authority = options.Authority ?? new StellaOpsCliAuthorityOptions(); + var lines = new[] + { + $"Backend URL: {MaskIfEmpty(options.BackendUrl)}", + $"API Key: {DescribeSecret(options.ApiKey)}", + $"Scanner Cache: {options.ScannerCacheDirectory}", + $"Results Directory: {options.ResultsDirectory}", + $"Default Runner: {options.DefaultRunner}", + $"Authority URL: {MaskIfEmpty(authority.Url)}", + $"Authority Client ID: {MaskIfEmpty(authority.ClientId)}", + $"Authority Client Secret: {DescribeSecret(authority.ClientSecret ?? string.Empty)}", + $"Authority Username: {MaskIfEmpty(authority.Username)}", + $"Authority Password: {DescribeSecret(authority.Password ?? string.Empty)}", + $"Authority Scope: {MaskIfEmpty(authority.Scope)}", + $"Authority Token Cache: {MaskIfEmpty(authority.TokenCacheDirectory ?? string.Empty)}" + }; + + foreach (var line in lines) + { + Console.WriteLine(line); + } + + return Task.CompletedTask; + }); config.Add(show); return config; diff --git a/src/StellaOps.Cli/Commands/CommandHandlers.cs b/src/StellaOps.Cli/Commands/CommandHandlers.cs index 0175fbe7..1d72f754 100644 --- a/src/StellaOps.Cli/Commands/CommandHandlers.cs +++ b/src/StellaOps.Cli/Commands/CommandHandlers.cs @@ -1,9 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Spectre.Console; @@ -492,11 +494,322 @@ internal static class CommandHandlers logger.LogInformation("Scopes: {Scopes}", string.Join(", ", entry.Scopes)); } } - - private static async Task TriggerJobAsync( - IBackendOperationsClient client, - ILogger logger, - string jobKind, + + public static async Task HandleAuthWhoAmIAsync( + IServiceProvider services, + StellaOpsCliOptions options, + bool verbose, + CancellationToken cancellationToken) + { + await using var scope = services.CreateAsyncScope(); + var logger = scope.ServiceProvider.GetRequiredService().CreateLogger("auth-whoami"); + Environment.ExitCode = 0; + + if (string.IsNullOrWhiteSpace(options.Authority?.Url)) + { + logger.LogInformation("Authority URL not configured. Set STELLAOPS_AUTHORITY_URL and run 'auth login'."); + Environment.ExitCode = 1; + return; + } + + var tokenClient = scope.ServiceProvider.GetService(); + if (tokenClient is null) + { + logger.LogInformation("Authority client not registered; no cached tokens available."); + Environment.ExitCode = 1; + return; + } + + var cacheKey = AuthorityTokenUtilities.BuildCacheKey(options); + if (string.IsNullOrWhiteSpace(cacheKey)) + { + logger.LogInformation("Authority configuration incomplete; no cached tokens available."); + Environment.ExitCode = 1; + return; + } + + var entry = await tokenClient.GetCachedTokenAsync(cacheKey, cancellationToken).ConfigureAwait(false); + if (entry is null) + { + logger.LogInformation("No cached token for {Authority}. Run 'auth login' to authenticate.", options.Authority.Url); + Environment.ExitCode = 1; + return; + } + + var grantType = string.IsNullOrWhiteSpace(options.Authority.Username) ? "client_credentials" : "password"; + var now = DateTimeOffset.UtcNow; + var remaining = entry.ExpiresAtUtc - now; + if (remaining < TimeSpan.Zero) + { + remaining = TimeSpan.Zero; + } + + logger.LogInformation("Authority: {Authority}", options.Authority.Url); + logger.LogInformation("Grant type: {GrantType}", grantType); + logger.LogInformation("Token type: {TokenType}", entry.TokenType); + logger.LogInformation("Expires: {Expires} ({Remaining})", entry.ExpiresAtUtc.ToString("u"), FormatDuration(remaining)); + + if (entry.Scopes.Count > 0) + { + logger.LogInformation("Scopes: {Scopes}", string.Join(", ", entry.Scopes)); + } + + if (TryExtractJwtClaims(entry.AccessToken, out var claims, out var issuedAt, out var notBefore)) + { + if (claims.TryGetValue("sub", out var subject) && !string.IsNullOrWhiteSpace(subject)) + { + logger.LogInformation("Subject: {Subject}", subject); + } + + if (claims.TryGetValue("client_id", out var clientId) && !string.IsNullOrWhiteSpace(clientId)) + { + logger.LogInformation("Client ID (token): {ClientId}", clientId); + } + + if (claims.TryGetValue("aud", out var audience) && !string.IsNullOrWhiteSpace(audience)) + { + logger.LogInformation("Audience: {Audience}", audience); + } + + if (claims.TryGetValue("iss", out var issuer) && !string.IsNullOrWhiteSpace(issuer)) + { + logger.LogInformation("Issuer: {Issuer}", issuer); + } + + if (issuedAt is not null) + { + logger.LogInformation("Issued at: {IssuedAt}", issuedAt.Value.ToString("u")); + } + + if (notBefore is not null) + { + logger.LogInformation("Not before: {NotBefore}", notBefore.Value.ToString("u")); + } + + var extraClaims = CollectAdditionalClaims(claims); + if (extraClaims.Count > 0 && verbose) + { + logger.LogInformation("Additional claims: {Claims}", string.Join(", ", extraClaims)); + } + } + else + { + logger.LogInformation("Access token appears opaque; claims are unavailable."); + } + } + + private static string FormatDuration(TimeSpan duration) + { + if (duration <= TimeSpan.Zero) + { + return "expired"; + } + + if (duration.TotalDays >= 1) + { + var days = (int)duration.TotalDays; + var hours = duration.Hours; + return hours > 0 + ? FormattableString.Invariant($"{days}d {hours}h") + : FormattableString.Invariant($"{days}d"); + } + + if (duration.TotalHours >= 1) + { + return FormattableString.Invariant($"{(int)duration.TotalHours}h {duration.Minutes}m"); + } + + if (duration.TotalMinutes >= 1) + { + return FormattableString.Invariant($"{(int)duration.TotalMinutes}m {duration.Seconds}s"); + } + + return FormattableString.Invariant($"{duration.Seconds}s"); + } + + private static bool TryExtractJwtClaims( + string accessToken, + out Dictionary claims, + out DateTimeOffset? issuedAt, + out DateTimeOffset? notBefore) + { + claims = new Dictionary(StringComparer.OrdinalIgnoreCase); + issuedAt = null; + notBefore = null; + + if (string.IsNullOrWhiteSpace(accessToken)) + { + return false; + } + + var parts = accessToken.Split('.'); + if (parts.Length < 2) + { + return false; + } + + if (!TryDecodeBase64Url(parts[1], out var payloadBytes)) + { + return false; + } + + try + { + using var document = JsonDocument.Parse(payloadBytes); + foreach (var property in document.RootElement.EnumerateObject()) + { + var value = FormatJsonValue(property.Value); + claims[property.Name] = value; + + if (issuedAt is null && property.NameEquals("iat") && TryParseUnixSeconds(property.Value, out var parsedIat)) + { + issuedAt = parsedIat; + } + + if (notBefore is null && property.NameEquals("nbf") && TryParseUnixSeconds(property.Value, out var parsedNbf)) + { + notBefore = parsedNbf; + } + } + + return true; + } + catch (JsonException) + { + claims.Clear(); + issuedAt = null; + notBefore = null; + return false; + } + } + + private static bool TryDecodeBase64Url(string value, out byte[] bytes) + { + bytes = Array.Empty(); + + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + var normalized = value.Replace('-', '+').Replace('_', '/'); + var padding = normalized.Length % 4; + if (padding is 2 or 3) + { + normalized = normalized.PadRight(normalized.Length + (4 - padding), '='); + } + else if (padding == 1) + { + return false; + } + + try + { + bytes = Convert.FromBase64String(normalized); + return true; + } + catch (FormatException) + { + return false; + } + } + + private static string FormatJsonValue(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString() ?? string.Empty, + JsonValueKind.Number => element.TryGetInt64(out var longValue) + ? longValue.ToString(CultureInfo.InvariantCulture) + : element.GetDouble().ToString(CultureInfo.InvariantCulture), + JsonValueKind.True => "true", + JsonValueKind.False => "false", + JsonValueKind.Null => "null", + JsonValueKind.Array => FormatArray(element), + JsonValueKind.Object => element.GetRawText(), + _ => element.GetRawText() + }; + } + + private static string FormatArray(JsonElement array) + { + var values = new List(); + foreach (var item in array.EnumerateArray()) + { + values.Add(FormatJsonValue(item)); + } + + return string.Join(", ", values); + } + + private static bool TryParseUnixSeconds(JsonElement element, out DateTimeOffset value) + { + value = default; + + if (element.ValueKind == JsonValueKind.Number) + { + if (element.TryGetInt64(out var seconds)) + { + value = DateTimeOffset.FromUnixTimeSeconds(seconds); + return true; + } + + if (element.TryGetDouble(out var doubleValue)) + { + value = DateTimeOffset.FromUnixTimeSeconds((long)doubleValue); + return true; + } + } + + if (element.ValueKind == JsonValueKind.String) + { + var text = element.GetString(); + if (!string.IsNullOrWhiteSpace(text) && long.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + { + value = DateTimeOffset.FromUnixTimeSeconds(seconds); + return true; + } + } + + return false; + } + + private static List CollectAdditionalClaims(Dictionary claims) + { + var result = new List(); + foreach (var pair in claims) + { + if (CommonClaimNames.Contains(pair.Key)) + { + continue; + } + + result.Add(FormattableString.Invariant($"{pair.Key}={pair.Value}")); + } + + result.Sort(StringComparer.OrdinalIgnoreCase); + return result; + } + + private static readonly HashSet CommonClaimNames = new(StringComparer.OrdinalIgnoreCase) + { + "aud", + "client_id", + "exp", + "iat", + "iss", + "nbf", + "scope", + "scopes", + "sub", + "token_type", + "jti" + }; + + private static async Task TriggerJobAsync( + IBackendOperationsClient client, + ILogger logger, + string jobKind, IDictionary parameters, CancellationToken cancellationToken) { @@ -522,6 +835,6 @@ internal static class CommandHandlers { logger.LogError("Job '{JobKind}' failed: {Message}", jobKind, result.Message); Environment.ExitCode = 1; - } - } -} + } + } +} diff --git a/src/StellaOps.Cli/Configuration/CliBootstrapper.cs b/src/StellaOps.Cli/Configuration/CliBootstrapper.cs index 269974b8..a84f5d3c 100644 --- a/src/StellaOps.Cli/Configuration/CliBootstrapper.cs +++ b/src/StellaOps.Cli/Configuration/CliBootstrapper.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using Microsoft.Extensions.Configuration; @@ -113,6 +115,82 @@ public static class CliBootstrapper authority.Password = string.IsNullOrWhiteSpace(authority.Password) ? null : authority.Password.Trim(); authority.Scope = string.IsNullOrWhiteSpace(authority.Scope) ? StellaOpsScopes.FeedserJobsTrigger : authority.Scope.Trim(); + authority.Resilience ??= new StellaOpsCliAuthorityResilienceOptions(); + authority.Resilience.RetryDelays ??= new List(); + var resilience = authority.Resilience; + + if (!resilience.EnableRetries.HasValue) + { + var raw = ResolveWithFallback( + string.Empty, + configuration, + "STELLAOPS_AUTHORITY_ENABLE_RETRIES", + "StellaOps:Authority:Resilience:EnableRetries", + "StellaOps:Authority:EnableRetries", + "Authority:Resilience:EnableRetries", + "Authority:EnableRetries"); + + if (TryParseBoolean(raw, out var parsed)) + { + resilience.EnableRetries = parsed; + } + } + + var retryDelaysRaw = ResolveWithFallback( + string.Empty, + configuration, + "STELLAOPS_AUTHORITY_RETRY_DELAYS", + "StellaOps:Authority:Resilience:RetryDelays", + "StellaOps:Authority:RetryDelays", + "Authority:Resilience:RetryDelays", + "Authority:RetryDelays"); + + if (!string.IsNullOrWhiteSpace(retryDelaysRaw)) + { + resilience.RetryDelays.Clear(); + foreach (var delay in ParseRetryDelays(retryDelaysRaw)) + { + if (delay > TimeSpan.Zero) + { + resilience.RetryDelays.Add(delay); + } + } + } + + if (!resilience.AllowOfflineCacheFallback.HasValue) + { + var raw = ResolveWithFallback( + string.Empty, + configuration, + "STELLAOPS_AUTHORITY_ALLOW_OFFLINE_CACHE_FALLBACK", + "StellaOps:Authority:Resilience:AllowOfflineCacheFallback", + "StellaOps:Authority:AllowOfflineCacheFallback", + "Authority:Resilience:AllowOfflineCacheFallback", + "Authority:AllowOfflineCacheFallback"); + + if (TryParseBoolean(raw, out var parsed)) + { + resilience.AllowOfflineCacheFallback = parsed; + } + } + + if (!resilience.OfflineCacheTolerance.HasValue) + { + var raw = ResolveWithFallback( + string.Empty, + configuration, + "STELLAOPS_AUTHORITY_OFFLINE_CACHE_TOLERANCE", + "StellaOps:Authority:Resilience:OfflineCacheTolerance", + "StellaOps:Authority:OfflineCacheTolerance", + "Authority:Resilience:OfflineCacheTolerance", + "Authority:OfflineCacheTolerance"); + + if (TimeSpan.TryParse(raw, CultureInfo.InvariantCulture, out var tolerance) && tolerance >= TimeSpan.Zero) + { + resilience.OfflineCacheTolerance = tolerance; + } + } + var defaultTokenCache = GetDefaultTokenCacheDirectory(); if (string.IsNullOrWhiteSpace(authority.TokenCacheDirectory)) { @@ -127,26 +205,66 @@ public static class CliBootstrapper return (bootstrap.Options, bootstrap.Configuration); } - + private static string ResolveWithFallback(string currentValue, IConfiguration configuration, params string[] keys) { if (!string.IsNullOrWhiteSpace(currentValue)) { return currentValue; } - - foreach (var key in keys) - { - var value = configuration[key]; - if (!string.IsNullOrWhiteSpace(value)) - { - return value; - } - } + + foreach (var key in keys) + { + var value = configuration[key]; + if (!string.IsNullOrWhiteSpace(value)) + { + return value; + } + } return string.Empty; } + private static bool TryParseBoolean(string value, out bool parsed) + { + if (string.IsNullOrWhiteSpace(value)) + { + parsed = default; + return false; + } + + if (bool.TryParse(value, out parsed)) + { + return true; + } + + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var numeric)) + { + parsed = numeric != 0; + return true; + } + + parsed = default; + return false; + } + + private static IEnumerable ParseRetryDelays(string raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + yield break; + } + + var separators = new[] { ',', ';', ' ' }; + foreach (var token in raw.Split(separators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (TimeSpan.TryParse(token, CultureInfo.InvariantCulture, out var delay) && delay > TimeSpan.Zero) + { + yield return delay; + } + } + } + private static string GetDefaultTokenCacheDirectory() { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); diff --git a/src/StellaOps.Cli/Configuration/StellaOpsCliOptions.cs b/src/StellaOps.Cli/Configuration/StellaOpsCliOptions.cs index fb3e894a..0cacb7cb 100644 --- a/src/StellaOps.Cli/Configuration/StellaOpsCliOptions.cs +++ b/src/StellaOps.Cli/Configuration/StellaOpsCliOptions.cs @@ -1,9 +1,11 @@ +using System; +using System.Collections.Generic; using StellaOps.Auth.Abstractions; namespace StellaOps.Cli.Configuration; - -public sealed class StellaOpsCliOptions -{ + +public sealed class StellaOpsCliOptions +{ public string ApiKey { get; set; } = string.Empty; public string BackendUrl { get; set; } = string.Empty; @@ -38,4 +40,17 @@ public sealed class StellaOpsCliAuthorityOptions public string Scope { get; set; } = StellaOpsScopes.FeedserJobsTrigger; public string TokenCacheDirectory { get; set; } = string.Empty; + + public StellaOpsCliAuthorityResilienceOptions Resilience { get; set; } = new(); +} + +public sealed class StellaOpsCliAuthorityResilienceOptions +{ + public bool? EnableRetries { get; set; } + + public IList RetryDelays { get; set; } = new List(); + + public bool? AllowOfflineCacheFallback { get; set; } + + public TimeSpan? OfflineCacheTolerance { get; set; } } diff --git a/src/StellaOps.Cli/Program.cs b/src/StellaOps.Cli/Program.cs index 12b9a60c..0d4b96d4 100644 --- a/src/StellaOps.Cli/Program.cs +++ b/src/StellaOps.Cli/Program.cs @@ -48,6 +48,31 @@ internal static class Program clientOptions.DefaultScopes.Add(string.IsNullOrWhiteSpace(options.Authority.Scope) ? StellaOps.Auth.Abstractions.StellaOpsScopes.FeedserJobsTrigger : options.Authority.Scope); + + var resilience = options.Authority.Resilience ?? new StellaOpsCliAuthorityResilienceOptions(); + clientOptions.EnableRetries = resilience.EnableRetries ?? true; + + if (resilience.RetryDelays is { Count: > 0 }) + { + clientOptions.RetryDelays.Clear(); + foreach (var delay in resilience.RetryDelays) + { + if (delay > TimeSpan.Zero) + { + clientOptions.RetryDelays.Add(delay); + } + } + } + + if (resilience.AllowOfflineCacheFallback.HasValue) + { + clientOptions.AllowOfflineCacheFallback = resilience.AllowOfflineCacheFallback.Value; + } + + if (resilience.OfflineCacheTolerance.HasValue && resilience.OfflineCacheTolerance.Value >= TimeSpan.Zero) + { + clientOptions.OfflineCacheTolerance = resilience.OfflineCacheTolerance.Value; + } }); var cacheDirectory = options.Authority.TokenCacheDirectory; diff --git a/src/StellaOps.Cli/TASKS.md b/src/StellaOps.Cli/TASKS.md index 0af6f8b8..47718e33 100644 --- a/src/StellaOps.Cli/TASKS.md +++ b/src/StellaOps.Cli/TASKS.md @@ -8,4 +8,7 @@ |Feedser DB operations passthrough|DevEx/CLI|Backend, Feedser APIs|**DONE** – `db fetch|merge|export` trigger `/jobs/*` endpoints with parameter binding and consistent exit codes.| |CLI observability & tests|QA|Command host|**DONE** – Added console logging defaults & configuration bootstrap tests; future metrics hooks tracked separately.| |Authority auth commands|DevEx/CLI|Auth libraries|**DONE** – `auth login/logout/status` wrap the shared auth client, manage token cache, and surface status messages.| -|Document authority workflow in CLI help & quickstart|Docs/CLI|Authority auth commands|**TODO** – Capture `stellaops-cli auth` usage, env vars, and cache location in docs/09 + CLI help; assign once we resume.| +|Document authority workflow in CLI help & quickstart|Docs/CLI|Authority auth commands|**DONE (2025-10-10)** – CLI help now surfaces Authority config fields and docs/09 + docs/10 describe env vars, auth login/status flow, and cache location.| +|Authority whoami command|DevEx/CLI|Authority auth commands|**DONE (2025-10-10)** – Added `auth whoami` verb that displays subject/audience/expiry from cached tokens and handles opaque tokens gracefully.| +|Expose auth client resilience settings|DevEx/CLI|Auth libraries LIB5|**DONE (2025-10-10)** – CLI options now bind resilience knobs, `AddStellaOpsAuthClient` honours them, and tests cover env overrides.| +|Document advanced Authority tuning|Docs/CLI|Expose auth client resilience settings|**DONE (2025-10-10)** – docs/09 and docs/10 describe retry/offline settings with env examples and point to the integration guide.| diff --git a/src/StellaOps.Configuration.Tests/StellaOpsAuthorityOptionsTests.cs b/src/StellaOps.Configuration.Tests/StellaOpsAuthorityOptionsTests.cs index 1645568e..0759c14f 100644 --- a/src/StellaOps.Configuration.Tests/StellaOpsAuthorityOptionsTests.cs +++ b/src/StellaOps.Configuration.Tests/StellaOpsAuthorityOptionsTests.cs @@ -102,7 +102,10 @@ public class StellaOpsAuthorityOptionsTests ["Authority:Storage:DatabaseName"] = "overrideDb", ["Authority:Storage:CommandTimeout"] = "00:01:30", ["Authority:PluginDirectories:0"] = "/var/lib/stellaops/plugins", - ["Authority:BypassNetworks:0"] = "127.0.0.1/32" + ["Authority:BypassNetworks:0"] = "127.0.0.1/32", + ["Authority:RateLimiting:Token:PermitLimit"] = "25", + ["Authority:RateLimiting:Token:Window"] = "00:00:30", + ["Authority:RateLimiting:Authorize:Enabled"] = "true" }); }; }); @@ -118,5 +121,24 @@ public class StellaOpsAuthorityOptionsTests Assert.Equal("mongodb://example/stellaops", options.Storage.ConnectionString); Assert.Equal("overrideDb", options.Storage.DatabaseName); Assert.Equal(TimeSpan.FromMinutes(1.5), options.Storage.CommandTimeout); + Assert.Equal(25, options.RateLimiting.Token.PermitLimit); + Assert.Equal(TimeSpan.FromSeconds(30), options.RateLimiting.Token.Window); + Assert.True(options.RateLimiting.Authorize.Enabled); + } + + [Fact] + public void Validate_Throws_When_RateLimitingInvalid() + { + var options = new StellaOpsAuthorityOptions + { + Issuer = new Uri("https://authority.stella-ops.test"), + SchemaVersion = 1 + }; + options.Storage.ConnectionString = "mongodb://localhost:27017/authority"; + options.RateLimiting.Token.PermitLimit = 0; + + var exception = Assert.Throws(() => options.Validate()); + + Assert.Contains("permitLimit", exception.Message, StringComparison.OrdinalIgnoreCase); } } diff --git a/src/StellaOps.Configuration/StellaOps.Configuration.csproj b/src/StellaOps.Configuration/StellaOps.Configuration.csproj index 54bbb91a..af97e2fd 100644 --- a/src/StellaOps.Configuration/StellaOps.Configuration.csproj +++ b/src/StellaOps.Configuration/StellaOps.Configuration.csproj @@ -13,6 +13,7 @@ + diff --git a/src/StellaOps.Configuration/StellaOpsAuthorityOptions.cs b/src/StellaOps.Configuration/StellaOpsAuthorityOptions.cs index 63110622..374036b8 100644 --- a/src/StellaOps.Configuration/StellaOpsAuthorityOptions.cs +++ b/src/StellaOps.Configuration/StellaOpsAuthorityOptions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.RateLimiting; using StellaOps.Authority.Plugins.Abstractions; namespace StellaOps.Configuration; @@ -74,6 +75,11 @@ public sealed class StellaOpsAuthorityOptions /// public AuthorityPluginSettings Plugins { get; } = new(); + /// + /// Rate limiting configuration applied to Authority endpoints. + /// + public AuthorityRateLimitingOptions RateLimiting { get; } = new(); + /// /// Validates configured values and normalises collections. /// @@ -109,6 +115,7 @@ public sealed class StellaOpsAuthorityOptions NormaliseList(pluginDirectories); NormaliseList(bypassNetworks); + RateLimiting.Validate(); Plugins.NormalizeAndValidate(); Storage.Validate(); Bootstrap.Validate(); @@ -158,6 +165,82 @@ public sealed class StellaOpsAuthorityOptions } } +public sealed class AuthorityRateLimitingOptions +{ + public AuthorityRateLimitingOptions() + { + Token = new AuthorityEndpointRateLimitOptions { PermitLimit = 30 }; + Authorize = new AuthorityEndpointRateLimitOptions { PermitLimit = 60 }; + } + + /// + /// Rate limiting configuration applied to the /token endpoint. + /// + public AuthorityEndpointRateLimitOptions Token { get; } + + /// + /// Rate limiting configuration applied to the /authorize endpoint. + /// + public AuthorityEndpointRateLimitOptions Authorize { get; } + + internal void Validate() + { + Token.Validate(nameof(Token)); + Authorize.Validate(nameof(Authorize)); + } +} + +public sealed class AuthorityEndpointRateLimitOptions +{ + /// + /// Gets or sets a value indicating whether rate limiting is enabled for the endpoint. + /// + public bool Enabled { get; set; } = true; + + /// + /// Maximum number of requests allowed within the configured window. + /// + public int PermitLimit { get; set; } = 60; + + /// + /// Size of the fixed window applied to the rate limiter. + /// + public TimeSpan Window { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Maximum number of queued requests awaiting permits. + /// + public int QueueLimit { get; set; } = 0; + + /// + /// Ordering strategy for queued requests. + /// + public QueueProcessingOrder QueueProcessingOrder { get; set; } = QueueProcessingOrder.OldestFirst; + + internal void Validate(string name) + { + if (!Enabled) + { + return; + } + + if (PermitLimit <= 0) + { + throw new InvalidOperationException($"Authority rate limiting '{name}' requires permitLimit to be greater than zero."); + } + + if (QueueLimit < 0) + { + throw new InvalidOperationException($"Authority rate limiting '{name}' queueLimit cannot be negative."); + } + + if (Window <= TimeSpan.Zero || Window > TimeSpan.FromHours(1)) + { + throw new InvalidOperationException($"Authority rate limiting '{name}' window must be greater than zero and no more than one hour."); + } + } +} + public sealed class AuthorityStorageOptions { /// diff --git a/src/StellaOps.Cryptography.Tests/PasswordHashOptionsTests.cs b/src/StellaOps.Cryptography.Tests/PasswordHashOptionsTests.cs new file mode 100644 index 00000000..f0187dcc --- /dev/null +++ b/src/StellaOps.Cryptography.Tests/PasswordHashOptionsTests.cs @@ -0,0 +1,24 @@ +using StellaOps.Cryptography; + +namespace StellaOps.Cryptography.Tests; + +public class PasswordHashOptionsTests +{ + [Fact] + public void Validate_DoesNotThrow_ForDefaults() + { + var options = new PasswordHashOptions(); + options.Validate(); + } + + [Fact] + public void Validate_Throws_WhenMemoryInvalid() + { + var options = new PasswordHashOptions + { + MemorySizeInKib = 0 + }; + + Assert.Throws(options.Validate); + } +} diff --git a/src/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj b/src/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj new file mode 100644 index 00000000..d3c903d3 --- /dev/null +++ b/src/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj @@ -0,0 +1,11 @@ + + + net10.0 + enable + enable + false + + + + + diff --git a/src/StellaOps.Cryptography/AGENTS.md b/src/StellaOps.Cryptography/AGENTS.md new file mode 100644 index 00000000..b1124feb --- /dev/null +++ b/src/StellaOps.Cryptography/AGENTS.md @@ -0,0 +1,21 @@ +# Team 8 — Security Guild (Authority & Shared Crypto) + +## Role + +Team 8 owns the end-to-end security posture for StellaOps Authority and its consumers. That includes password hashing policy, audit/event hygiene, rate-limit & lockout rules, revocation distribution, and sovereign cryptography abstractions that allow alternative algorithm suites (e.g., GOST) without touching feature code. + +## Operational Boundaries + +- Primary workspace: `src/StellaOps.Cryptography`, `src/StellaOps.Authority.Plugin.Standard`, `src/StellaOps.Authority.Storage.Mongo`, and Authority host (`src/StellaOps.Authority/StellaOps.Authority`). +- Coordinate cross-module changes via TASKS.md updates and PR descriptions. +- Never bypass deterministic behaviour (sorted keys, stable timestamps). +- Tests live alongside owning projects (`*.Tests`). Extend goldens instead of rewriting. + +## Expectations + +- Default to Argon2id (Konscious) for password hashing; PBKDF2 only for legacy verification with transparent rehash on success. +- Emit structured security events with minimal PII and clear correlation IDs. +- Rate-limit `/token` and bootstrap endpoints once CORE8 hooks are available. +- Deliver offline revocation bundles signed with detached JWS and provide a verification script. +- Maintain `docs/security/authority-threat-model.md` and ensure mitigations are tracked. +- All crypto consumption flows through `StellaOps.Cryptography` abstractions to enable sovereign crypto providers. diff --git a/src/StellaOps.Cryptography/CryptoProvider.cs b/src/StellaOps.Cryptography/CryptoProvider.cs new file mode 100644 index 00000000..77403920 --- /dev/null +++ b/src/StellaOps.Cryptography/CryptoProvider.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace StellaOps.Cryptography; + +/// +/// High-level cryptographic capabilities supported by StellaOps providers. +/// +public enum CryptoCapability +{ + PasswordHashing, + Signing, + Verification, + SymmetricEncryption, + KeyDerivation +} + +/// +/// Identifies a stored key or certificate handle. +/// +public sealed record CryptoKeyReference(string KeyId, string? ProviderHint = null); + +/// +/// Contract implemented by crypto providers (BCL, CryptoPro, OpenSSL, etc.). +/// +public interface ICryptoProvider +{ + string Name { get; } + + bool Supports(CryptoCapability capability, string algorithmId); + + IPasswordHasher GetPasswordHasher(string algorithmId); +} + +/// +/// Registry managing provider discovery and policy selection. +/// +public interface ICryptoProviderRegistry +{ + IReadOnlyCollection Providers { get; } + + bool TryResolve(string preferredProvider, out ICryptoProvider provider); + + ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId); +} diff --git a/src/StellaOps.Cryptography/PasswordHashing.cs b/src/StellaOps.Cryptography/PasswordHashing.cs new file mode 100644 index 00000000..34feddba --- /dev/null +++ b/src/StellaOps.Cryptography/PasswordHashing.cs @@ -0,0 +1,81 @@ +using System; + +namespace StellaOps.Cryptography; + +/// +/// Supported password hashing algorithms. +/// +public enum PasswordHashAlgorithm +{ + Argon2id, + Pbkdf2 +} + +/// +/// Options describing password hashing requirements. +/// Values follow OWASP baseline guidance by default. +/// +public sealed record PasswordHashOptions +{ + /// + /// Algorithm to use when hashing new passwords. + /// + public PasswordHashAlgorithm Algorithm { get; init; } = PasswordHashAlgorithm.Argon2id; + + /// + /// Memory cost in KiB (default 19 MiB). + /// + public int MemorySizeInKib { get; init; } = 19 * 1024; + + /// + /// Iteration count / time cost. + /// + public int Iterations { get; init; } = 2; + + /// + /// Parallelism / degree of concurrency. + /// + public int Parallelism { get; init; } = 1; + + /// + /// Validates the option values and throws when invalid. + /// + public void Validate() + { + if (MemorySizeInKib <= 0) + { + throw new InvalidOperationException("Password hashing memory cost must be greater than zero."); + } + + if (Iterations <= 0) + { + throw new InvalidOperationException("Password hashing iteration count must be greater than zero."); + } + + if (Parallelism <= 0) + { + throw new InvalidOperationException("Password hashing parallelism must be greater than zero."); + } + } +} + +/// +/// Abstraction for password hashing implementations. +/// +public interface IPasswordHasher +{ + /// + /// Produces an encoded hash for the supplied password. + /// + string Hash(string password, PasswordHashOptions options); + + /// + /// Verifies the supplied password against a stored hash. + /// + bool Verify(string password, string encodedHash); + + /// + /// Detects when an existing encoded hash no longer satisfies the desired options. + /// + bool NeedsRehash(string encodedHash, PasswordHashOptions desired); +} diff --git a/src/StellaOps.Cryptography/StellaOps.Cryptography.csproj b/src/StellaOps.Cryptography/StellaOps.Cryptography.csproj new file mode 100644 index 00000000..514869b9 --- /dev/null +++ b/src/StellaOps.Cryptography/StellaOps.Cryptography.csproj @@ -0,0 +1,9 @@ + + + net10.0 + preview + enable + enable + true + + diff --git a/src/StellaOps.Cryptography/TASKS.md b/src/StellaOps.Cryptography/TASKS.md new file mode 100644 index 00000000..b334d159 --- /dev/null +++ b/src/StellaOps.Cryptography/TASKS.md @@ -0,0 +1,25 @@ +# Team 8 — Security Guild Task Board (UTC 2025-10-10) + +| ID | Status | Owner | Description | Dependencies | Exit Criteria | +|----|--------|-------|-------------|--------------|---------------| +| SEC1.A | TODO | Security Guild | Introduce `Argon2idPasswordHasher` backed by Konscious defaults. Wire options into `StandardPluginOptions` (`PasswordHashOptions`) and `StellaOpsAuthorityOptions.Security.PasswordHashing`. | PLG3, CORE3 | ✅ Hashes emit PHC string `$argon2id$v=19$m=19456,t=2,p=1$...`; ✅ `NeedsRehash` promotes PBKDF2 → Argon2; ✅ Integration tests cover tamper, legacy rehash, perf p95 < 250 ms. | +| SEC1.B | TODO | Security Guild | Add compile-time switch to enable libsodium/Core variants later (`STELLAOPS_CRYPTO_SODIUM`). Document build variable. | SEC1.A | ✅ Conditional compilation path compiles; ✅ README snippet in `docs/security/password-hashing.md`. | +| SEC2.A | TODO | Security Guild + Core | Define audit event contract (`AuthEventRecord`) with subject/client/scope/IP/outcome/correlationId and PII tags. | CORE5–CORE7 | ✅ Contract shipped in `StellaOps.Cryptography` (or shared abstractions); ✅ Docs in `docs/security/audit-events.md`. | +| SEC2.B | TODO | Security Guild | Emit audit records from OpenIddict handlers (password + client creds) and bootstrap APIs. Persist via `IAuthorityLoginAttemptStore`. | SEC2.A | ✅ Tests assert three flows (success/failure/lockout); ✅ Serilog output contains correlationId + PII tagging; ✅ Mongo store holds summary rows. | +| SEC3.A | BLOCKED (CORE8) | Security Guild + Core | Configure ASP.NET rate limiter (`AddRateLimiter`) with fixed-window policy keyed by IP + `client_id`. Apply to `/token` and `/internal/*`. | CORE8 completion | ✅ Middleware active; ✅ Configurable limits via options; ✅ Integration test hits 429. | +| SEC3.B | TODO | Security Guild | Document lockout + rate-limit tuning guidance and escalation thresholds. | SEC3.A | ✅ Section in `docs/security/rate-limits.md`; ✅ Includes SOC alert recommendations. | +| SEC4.A | TODO | Security Guild + DevOps | Define revocation JSON schema (`revocation_bundle.schema.json`) and detached JWS workflow. | CORE9, OPS3 | ✅ Schema + sample committed; ✅ CLI command `stellaops auth revoke export` scaffolded with acceptance tests; ✅ Verification script + docs. | +| SEC4.B | TODO | Security Guild | Integrate signing keys with crypto provider abstraction (initially ES256 via BCL). | SEC4.A, D5 | ✅ `ICryptoProvider.GetSigner` stub + default BCL signer; ✅ Unit tests verifying signature roundtrip. | +| SEC5.A | TODO | Security Guild | Author STRIDE threat model (`docs/security/authority-threat-model.md`) covering token, bootstrap, revocation, CLI, plugin surfaces. | All SEC1–SEC4 in progress | ✅ DFDs + trust boundaries drawn; ✅ Risk table with owners/actions; ✅ Follow-up backlog issues created. | +| D5.A | TODO | Security Guild | Flesh out `StellaOps.Cryptography` provider registry, policy, and DI helpers enabling sovereign crypto selection. | SEC1.A, SEC4.B | ✅ `ICryptoProviderRegistry` implementation with provider selection rules; ✅ `StellaOps.Cryptography.DependencyInjection` extensions; ✅ Tests covering fallback ordering. | + +## Notes +- Target Argon2 parameters follow OWASP Cheat Sheet (memory ≈ 19 MiB, iterations 2, parallelism 1). Allow overrides via configuration. +- When CORE8 lands, pair with Team 2 to expose request context information required by the rate limiter (client_id enrichment). +- Revocation bundle must be consumable offline; include issue timestamp, signing key metadata, and reasons. +- All crypto usage in Authority code should funnel through the new abstractions (`ICryptoProvider`), enabling future CryptoPro/OpenSSL providers. + +## Done Definition +- Code merges include unit/integration tests and documentation updates. +- `TASKS.md` status transitions (TODO → DOING → DONE/BLOCKED) must happen in the same PR as the work. +- Prior to marking DONE: run `dotnet test` for touched solutions and attach excerpt to PR description. diff --git a/src/StellaOps.Feedser.Merge/TASKS.md b/src/StellaOps.Feedser.Merge/TASKS.md index 9fb33bfc..6afac019 100644 --- a/src/StellaOps.Feedser.Merge/TASKS.md +++ b/src/StellaOps.Feedser.Merge/TASKS.md @@ -7,7 +7,8 @@ |Debian EVR comparer plus tests|BE-Merge (Distro WG)|Debian fixtures|DONE – DebianEvr comparer mirrors dpkg ordering with tilde/epoch handling and unit coverage.| |SemVer range resolver plus tests|BE-Merge (OSS WG)|OSV/GHSA fixtures|DONE – SemanticVersionRangeResolver covers introduced/fixed/lastAffected semantics with SemVer ordering tests.| |Canonical hash and merge_event writer|BE-Merge|Models, Storage.Mongo|DONE – Hash calculator + MergeEventWriter compute canonical SHA-256 digests and persist merge events.| -|Conflict detection and metrics|BE-Merge|Core|**DONE** – merge meters emit override/conflict counters and structured audits (`AdvisoryPrecedenceMerger`).| -|End-to-end determinism test|QA|Merge, key connectors|**DONE** – `MergePrecedenceIntegrationTests.MergePipeline_IsDeterministicAcrossRuns` guards determinism.| -|Override audit logging|BE-Merge|Observability|DONE – override audits now emit structured logs plus bounded-tag metrics suitable for prod telemetry.| -|Configurable precedence table|BE-Merge|Architecture|DONE – precedence options bind via feedser:merge:precedence:ranks with docs/tests covering operator workflow.| +|Conflict detection and metrics|BE-Merge|Core|**DONE** – merge meters emit override/conflict counters and structured audits (`AdvisoryPrecedenceMerger`).| +|End-to-end determinism test|QA|Merge, key connectors|**DONE** – `MergePrecedenceIntegrationTests.MergePipeline_IsDeterministicAcrossRuns` guards determinism.| +|Override audit logging|BE-Merge|Observability|DONE – override audits now emit structured logs plus bounded-tag metrics suitable for prod telemetry.| +|Configurable precedence table|BE-Merge|Architecture|DONE – precedence options bind via feedser:merge:precedence:ranks with docs/tests covering operator workflow.| +|Range primitives backlog|BE-Merge|Connector WGs|**DOING** – Coordinate remaining connectors (`Acsc`, `Cccs`, `CertBund`, `CertCc`, `Cve`, `Ghsa`, `Ics.Cisa`, `Kisa`, `Ru.Bdu`, `Ru.Nkcki`, `Vndr.Apple`, `Vndr.Cisco`, `Vndr.Msrc`) to emit canonical RangePrimitives with provenance tags; track progress/fixtures here.| diff --git a/src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md b/src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md index c96cc89d..2c431a34 100644 --- a/src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md +++ b/src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md @@ -11,5 +11,6 @@ |Golden mapping fixtures|QA|Fixtures|**DONE** – fixture validation test now snapshots RHSA-2025:0001/0002/0003 with env-driven regeneration.| |Job scheduling defaults for source:redhat tasks|BE-Core|JobScheduler|**DONE** – Cron windows + per-job timeouts defined for fetch/parse/map.| |Express unaffected/investigation statuses without overloading range fields|BE-Conn-RH|Models|**DONE** – Introduced AffectedPackageStatus collection and updated mapper/tests.| -|Reference dedupe & ordering in mapper|BE-Conn-RH|Models|DONE – mapper consolidates by URL, merges metadata, deterministic ordering validated in tests.| -|Hydra summary fetch through SourceFetchService|BE-Conn-RH|Source.Common|DONE – summary pages now fetched via SourceFetchService with cache + conditional headers.| +|Reference dedupe & ordering in mapper|BE-Conn-RH|Models|DONE – mapper consolidates by URL, merges metadata, deterministic ordering validated in tests.| +|Hydra summary fetch through SourceFetchService|BE-Conn-RH|Source.Common|DONE – summary pages now fetched via SourceFetchService with cache + conditional headers.| +|Fixture validation sweep|QA|Testing|**DOING (2025-10-10)** – Regenerate RHSA fixtures once mapper fixes land, review snapshot diffs, and update docs; blocked by outstanding range provenance patches.| diff --git a/src/StellaOps.Feedser.WebService.Tests/FeedserOptionsPostConfigureTests.cs b/src/StellaOps.Feedser.WebService.Tests/FeedserOptionsPostConfigureTests.cs new file mode 100644 index 00000000..9c6ebef8 --- /dev/null +++ b/src/StellaOps.Feedser.WebService.Tests/FeedserOptionsPostConfigureTests.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using StellaOps.Feedser.WebService.Options; +using Xunit; + +namespace StellaOps.Feedser.WebService.Tests; + +public sealed class FeedserOptionsPostConfigureTests +{ + [Fact] + public void Apply_LoadsClientSecretFromRelativeFile() + { + var tempDirectory = Directory.CreateTempSubdirectory(); + try + { + var secretPath = Path.Combine(tempDirectory.FullName, "authority.secret"); + File.WriteAllText(secretPath, " feedser-secret "); + + var options = new FeedserOptions + { + Authority = new FeedserOptions.AuthorityOptions + { + ClientSecretFile = "authority.secret" + } + }; + + FeedserOptionsPostConfigure.Apply(options, tempDirectory.FullName); + + Assert.Equal("feedser-secret", options.Authority.ClientSecret); + } + finally + { + if (Directory.Exists(tempDirectory.FullName)) + { + Directory.Delete(tempDirectory.FullName, recursive: true); + } + } + } + + [Fact] + public void Apply_ThrowsWhenSecretFileMissing() + { + var options = new FeedserOptions + { + Authority = new FeedserOptions.AuthorityOptions + { + ClientSecretFile = "missing.secret" + } + }; + + var exception = Assert.Throws(() => + FeedserOptionsPostConfigure.Apply(options, AppContext.BaseDirectory)); + + Assert.Contains("Authority client secret file", exception.Message); + } +} diff --git a/src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs b/src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs index 30858843..c6718432 100644 --- a/src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs +++ b/src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http.Json; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Testing; +using System.Linq; +using System.Net; +using System.Net.Http.Json; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Mongo2Go; using StellaOps.Feedser.Core.Jobs; using StellaOps.Feedser.WebService.Jobs; @@ -214,27 +218,103 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime [Fact] public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled() { - using var factory = new FeedserApplicationFactory(_runner.ConnectionString, authority => + var environment = new Dictionary { - authority.Enabled = true; - authority.Issuer = "https://authority.example"; - authority.RequireHttpsMetadata = false; - authority.Audiences.Clear(); - authority.Audiences.Add("api://feedser"); - authority.RequiredScopes.Clear(); - authority.RequiredScopes.Add(StellaOpsScopes.FeedserJobsTrigger); - authority.BypassNetworks.Clear(); - authority.BypassNetworks.Add("127.0.0.1/32"); - authority.BypassNetworks.Add("::1/128"); - }); + ["FEEDSER_AUTHORITY__ENABLED"] = "true", + ["FEEDSER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false", + ["FEEDSER_AUTHORITY__ISSUER"] = "https://authority.example", + ["FEEDSER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false", + ["FEEDSER_AUTHORITY__AUDIENCES__0"] = "api://feedser", + ["FEEDSER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.FeedserJobsTrigger, + ["FEEDSER_AUTHORITY__BYPASSNETWORKS__0"] = "127.0.0.1/32", + ["FEEDSER_AUTHORITY__BYPASSNETWORKS__1"] = "::1/128", + ["FEEDSER_AUTHORITY__CLIENTID"] = "feedser-jobs", + ["FEEDSER_AUTHORITY__CLIENTSECRET"] = "test-secret", + ["FEEDSER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.FeedserJobsTrigger, + }; + + using var factory = new FeedserApplicationFactory( + _runner.ConnectionString, + authority => + { + authority.Enabled = true; + authority.AllowAnonymousFallback = false; + authority.Issuer = "https://authority.example"; + authority.RequireHttpsMetadata = false; + authority.Audiences.Clear(); + authority.Audiences.Add("api://feedser"); + authority.RequiredScopes.Clear(); + authority.RequiredScopes.Add(StellaOpsScopes.FeedserJobsTrigger); + authority.BypassNetworks.Clear(); + authority.BypassNetworks.Add("127.0.0.1/32"); + authority.BypassNetworks.Add("::1/128"); + authority.ClientId = "feedser-jobs"; + authority.ClientSecret = "test-secret"; + }, + environment); var handler = factory.Services.GetRequiredService(); handler.Definitions = new[] { new JobDefinition("demo", typeof(DemoJob), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(1), null, true) }; using var client = factory.CreateClient(); + client.DefaultRequestHeaders.Add("X-Test-RemoteAddr", "127.0.0.1"); var response = await client.GetAsync("/jobs/definitions"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var auditLogs = factory.LoggerProvider.Snapshot("Feedser.Authorization.Audit"); + var bypassLog = Assert.Single(auditLogs, entry => entry.TryGetState("Bypass", out var state) && state is bool flag && flag); + Assert.True(bypassLog.TryGetState("RemoteAddress", out var remoteObj) && string.Equals(remoteObj?.ToString(), "127.0.0.1", StringComparison.Ordinal)); + Assert.True(bypassLog.TryGetState("StatusCode", out var statusObj) && Convert.ToInt32(statusObj) == (int)HttpStatusCode.OK); + } + + [Fact] + public async Task JobsEndpointsRequireAuthWhenFallbackDisabled() + { + var enforcementEnvironment = new Dictionary + { + ["FEEDSER_AUTHORITY__ENABLED"] = "true", + ["FEEDSER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false", + ["FEEDSER_AUTHORITY__ISSUER"] = "https://authority.example", + ["FEEDSER_AUTHORITY__REQUIREHTTPSMETADATA"] = "false", + ["FEEDSER_AUTHORITY__AUDIENCES__0"] = "api://feedser", + ["FEEDSER_AUTHORITY__REQUIREDSCOPES__0"] = StellaOpsScopes.FeedserJobsTrigger, + ["FEEDSER_AUTHORITY__CLIENTID"] = "feedser-jobs", + ["FEEDSER_AUTHORITY__CLIENTSECRET"] = "test-secret", + ["FEEDSER_AUTHORITY__CLIENTSCOPES__0"] = StellaOpsScopes.FeedserJobsTrigger, + }; + + using var factory = new FeedserApplicationFactory( + _runner.ConnectionString, + authority => + { + authority.Enabled = true; + authority.AllowAnonymousFallback = false; + authority.Issuer = "https://authority.example"; + authority.RequireHttpsMetadata = false; + authority.Audiences.Clear(); + authority.Audiences.Add("api://feedser"); + authority.RequiredScopes.Clear(); + authority.RequiredScopes.Add(StellaOpsScopes.FeedserJobsTrigger); + authority.BypassNetworks.Clear(); + authority.ClientId = "feedser-jobs"; + authority.ClientSecret = "test-secret"; + }, + enforcementEnvironment); + + var resolved = factory.Services.GetRequiredService>().Value; + Assert.False(resolved.Authority.AllowAnonymousFallback); + + using var client = factory.CreateClient(); + client.DefaultRequestHeaders.Add("X-Test-RemoteAddr", "127.0.0.1"); + var response = await client.GetAsync("/jobs/definitions"); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + + var auditLogs = factory.LoggerProvider.Snapshot("Feedser.Authorization.Audit"); + var enforcementLog = Assert.Single(auditLogs); + Assert.True(enforcementLog.TryGetState("BypassAllowed", out var bypassAllowedObj) && bypassAllowedObj is bool bypassAllowed && bypassAllowed == false); + Assert.True(enforcementLog.TryGetState("HasPrincipal", out var principalObj) && principalObj is bool hasPrincipal && hasPrincipal == false); } private sealed class FeedserApplicationFactory : WebApplicationFactory @@ -248,43 +328,62 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime private readonly string? _previousTelemetryTracing; private readonly string? _previousTelemetryMetrics; private readonly Action? _authorityConfigure; + private readonly IDictionary _additionalPreviousEnvironment = new Dictionary(StringComparer.OrdinalIgnoreCase); + public CollectingLoggerProvider LoggerProvider { get; } = new(); - public FeedserApplicationFactory(string connectionString, Action? authorityConfigure = null) + public FeedserApplicationFactory( + string connectionString, + Action? authorityConfigure = null, + IDictionary? environmentOverrides = null) { _connectionString = connectionString; _authorityConfigure = authorityConfigure; _previousDsn = Environment.GetEnvironmentVariable("FEEDSER_STORAGE__DSN"); _previousDriver = Environment.GetEnvironmentVariable("FEEDSER_STORAGE__DRIVER"); - _previousTimeout = Environment.GetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS"); - _previousTelemetryEnabled = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED"); - _previousTelemetryLogging = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING"); - _previousTelemetryTracing = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING"); - _previousTelemetryMetrics = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS"); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DSN", connectionString); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DRIVER", "mongo"); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS", "30"); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED", "false"); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING", "false"); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING", "false"); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS", "false"); - } - - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.ConfigureAppConfiguration((context, configurationBuilder) => - { - var settings = new Dictionary - { - ["Plugins:Directory"] = Path.Combine(context.HostingEnvironment.ContentRootPath, "PluginBinaries"), - }; - - configurationBuilder.AddInMemoryCollection(settings!); - }); - - builder.ConfigureServices(services => - { - services.AddSingleton(); - services.AddSingleton(sp => sp.GetRequiredService()); + _previousTimeout = Environment.GetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS"); + _previousTelemetryEnabled = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED"); + _previousTelemetryLogging = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING"); + _previousTelemetryTracing = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING"); + _previousTelemetryMetrics = Environment.GetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS"); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DSN", connectionString); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DRIVER", "mongo"); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS", "30"); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED", "false"); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING", "false"); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING", "false"); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS", "false"); + if (environmentOverrides is not null) + { + foreach (var kvp in environmentOverrides) + { + var previous = Environment.GetEnvironmentVariable(kvp.Key); + _additionalPreviousEnvironment[kvp.Key] = previous; + Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); + } + } + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureAppConfiguration((context, configurationBuilder) => + { + var settings = new Dictionary + { + ["Plugins:Directory"] = Path.Combine(context.HostingEnvironment.ContentRootPath, "PluginBinaries"), + }; + + configurationBuilder.AddInMemoryCollection(settings!); + }); + + builder.ConfigureLogging(logging => + { + logging.AddProvider(LoggerProvider); + }); + + builder.ConfigureServices(services => + { + services.AddSingleton(); + services.AddSingleton(sp => sp.GetRequiredService()); services.PostConfigure(options => { options.Storage.Driver = "mongo"; @@ -299,20 +398,176 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime _authorityConfigure?.Invoke(options.Authority); }); }); + + builder.ConfigureTestServices(services => + { + services.AddSingleton(); + }); } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DSN", _previousDsn); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DRIVER", _previousDriver); - Environment.SetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS", _previousTimeout); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED", _previousTelemetryEnabled); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING", _previousTelemetryLogging); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING", _previousTelemetryTracing); - Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS", _previousTelemetryMetrics); - } - } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DSN", _previousDsn); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__DRIVER", _previousDriver); + Environment.SetEnvironmentVariable("FEEDSER_STORAGE__COMMANDTIMEOUTSECONDS", _previousTimeout); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLED", _previousTelemetryEnabled); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLELOGGING", _previousTelemetryLogging); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLETRACING", _previousTelemetryTracing); + Environment.SetEnvironmentVariable("FEEDSER_TELEMETRY__ENABLEMETRICS", _previousTelemetryMetrics); + foreach (var kvp in _additionalPreviousEnvironment) + { + Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); + } + + LoggerProvider.Dispose(); + } + + private sealed class RemoteIpStartupFilter : IStartupFilter + { + public Action Configure(Action next) + { + return app => + { + app.Use(async (context, nextMiddleware) => + { + if (context.Request.Headers.TryGetValue("X-Test-RemoteAddr", out var values) + && values.Count > 0 + && IPAddress.TryParse(values[0], out var remote)) + { + context.Connection.RemoteIpAddress = remote; + } + + await nextMiddleware(); + }); + + next(app); + }; + } + } + + public sealed record LogEntry( + string LoggerName, + LogLevel Level, + EventId EventId, + string? Message, + Exception? Exception, + IReadOnlyList> State) + { + public bool TryGetState(string name, out object? value) + { + foreach (var kvp in State) + { + if (string.Equals(kvp.Key, name, StringComparison.Ordinal)) + { + value = kvp.Value; + return true; + } + } + + value = null; + return false; + } + } + + public sealed class CollectingLoggerProvider : ILoggerProvider + { + private readonly object syncRoot = new(); + private readonly List entries = new(); + private bool disposed; + + public ILogger CreateLogger(string categoryName) => new CollectingLogger(categoryName, this); + + public IReadOnlyList Snapshot(string loggerName) + { + lock (syncRoot) + { + return entries + .Where(entry => string.Equals(entry.LoggerName, loggerName, StringComparison.Ordinal)) + .ToArray(); + } + } + + public void Dispose() + { + disposed = true; + lock (syncRoot) + { + entries.Clear(); + } + } + + private void Append(LogEntry entry) + { + if (disposed) + { + return; + } + + lock (syncRoot) + { + entries.Add(entry); + } + } + + private sealed class CollectingLogger : ILogger + { + private readonly string categoryName; + private readonly CollectingLoggerProvider provider; + + public CollectingLogger(string categoryName, CollectingLoggerProvider provider) + { + this.categoryName = categoryName; + this.provider = provider; + } + + public IDisposable? BeginScope(TState state) where TState : notnull => NullScope.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (formatter is null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + var message = formatter(state, exception); + var kvps = ExtractState(state); + var entry = new LogEntry(categoryName, logLevel, eventId, message, exception, kvps); + provider.Append(entry); + } + + private static IReadOnlyList> ExtractState(TState state) + { + if (state is IReadOnlyList> list) + { + return list; + } + + if (state is IEnumerable> enumerable) + { + return enumerable.ToArray(); + } + + if (state is null) + { + return Array.Empty>(); + } + + return new[] { new KeyValuePair("State", state) }; + } + } + + private sealed class NullScope : IDisposable + { + public static readonly NullScope Instance = new(); + public void Dispose() + { + } + } + } + } private sealed record HealthPayload(string Status, DateTimeOffset StartedAt, double UptimeSeconds, StoragePayload Storage, TelemetryPayload Telemetry); diff --git a/src/StellaOps.Feedser.WebService/Filters/JobAuthorizationAuditFilter.cs b/src/StellaOps.Feedser.WebService/Filters/JobAuthorizationAuditFilter.cs new file mode 100644 index 00000000..3ef28d2b --- /dev/null +++ b/src/StellaOps.Feedser.WebService/Filters/JobAuthorizationAuditFilter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using StellaOps.Auth.Abstractions; +using StellaOps.Feedser.WebService.Options; + +namespace StellaOps.Feedser.WebService.Filters; + +/// +/// Emits structured audit logs for job endpoint authorization decisions, including bypass usage. +/// +public sealed class JobAuthorizationAuditFilter : IEndpointFilter +{ + internal const string LoggerName = "Feedser.Authorization.Audit"; + + public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(next); + + var httpContext = context.HttpContext; + var options = httpContext.RequestServices.GetRequiredService>().Value; + var authority = options.Authority; + + if (authority is null || !authority.Enabled) + { + return await next(context).ConfigureAwait(false); + } + + var logger = httpContext.RequestServices + .GetRequiredService() + .CreateLogger(LoggerName); + + var remoteAddress = httpContext.Connection.RemoteIpAddress; + var matcher = new NetworkMaskMatcher(authority.BypassNetworks); + var user = httpContext.User; + var isAuthenticated = user?.Identity?.IsAuthenticated ?? false; + var bypassUsed = !isAuthenticated && matcher.IsAllowed(remoteAddress); + + var result = await next(context).ConfigureAwait(false); + + var scopes = ExtractScopes(user); + var subject = user?.FindFirst(StellaOpsClaimTypes.Subject)?.Value; + var clientId = user?.FindFirst(StellaOpsClaimTypes.ClientId)?.Value; + + logger.LogInformation( + "Feedser authorization audit route={Route} status={StatusCode} subject={Subject} clientId={ClientId} scopes={Scopes} bypass={Bypass} remote={RemoteAddress}", + httpContext.Request.Path.Value ?? string.Empty, + httpContext.Response.StatusCode, + string.IsNullOrWhiteSpace(subject) ? "(anonymous)" : subject, + string.IsNullOrWhiteSpace(clientId) ? "(none)" : clientId, + scopes.Length == 0 ? "(none)" : string.Join(',', scopes), + bypassUsed, + remoteAddress?.ToString() ?? IPAddress.None.ToString()); + + return result; + } + + private static string[] ExtractScopes(ClaimsPrincipal? principal) + { + if (principal is null) + { + return Array.Empty(); + } + + var values = new HashSet(StringComparer.Ordinal); + + foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem)) + { + if (string.IsNullOrWhiteSpace(claim.Value)) + { + continue; + } + + values.Add(claim.Value); + } + + foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope)) + { + if (string.IsNullOrWhiteSpace(claim.Value)) + { + continue; + } + + var parts = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var part in parts) + { + var normalized = StellaOpsScopes.Normalize(part); + if (!string.IsNullOrEmpty(normalized)) + { + values.Add(normalized); + } + } + } + + return values.Count == 0 ? Array.Empty() : values.ToArray(); + } +} diff --git a/src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs b/src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs index 65ac9484..d5df1bd7 100644 --- a/src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs +++ b/src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs @@ -59,6 +59,8 @@ public sealed class FeedserOptions { public bool Enabled { get; set; } + public bool AllowAnonymousFallback { get; set; } = true; + public string Issuer { get; set; } = string.Empty; public string? MetadataAddress { get; set; } @@ -74,5 +76,13 @@ public sealed class FeedserOptions public IList RequiredScopes { get; set; } = new List(); public IList BypassNetworks { get; set; } = new List(); + + public string? ClientId { get; set; } + + public string? ClientSecret { get; set; } + + public string? ClientSecretFile { get; set; } + + public IList ClientScopes { get; set; } = new List(); } } diff --git a/src/StellaOps.Feedser.WebService/Options/FeedserOptionsPostConfigure.cs b/src/StellaOps.Feedser.WebService/Options/FeedserOptionsPostConfigure.cs new file mode 100644 index 00000000..6de16e6e --- /dev/null +++ b/src/StellaOps.Feedser.WebService/Options/FeedserOptionsPostConfigure.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; + +namespace StellaOps.Feedser.WebService.Options; + +/// +/// Post-configuration helpers for . +/// +public static class FeedserOptionsPostConfigure +{ + /// + /// Applies derived settings that require filesystem access, such as loading client secrets from disk. + /// + /// The options to mutate. + /// Application content root used to resolve relative paths. + public static void Apply(FeedserOptions options, string contentRootPath) + { + ArgumentNullException.ThrowIfNull(options); + + options.Authority ??= new FeedserOptions.AuthorityOptions(); + + var authority = options.Authority; + if (string.IsNullOrWhiteSpace(authority.ClientSecret) + && !string.IsNullOrWhiteSpace(authority.ClientSecretFile)) + { + var resolvedPath = authority.ClientSecretFile!; + if (!Path.IsPathRooted(resolvedPath)) + { + resolvedPath = Path.Combine(contentRootPath, resolvedPath); + } + + if (!File.Exists(resolvedPath)) + { + throw new InvalidOperationException($"Authority client secret file '{resolvedPath}' was not found."); + } + + var secret = File.ReadAllText(resolvedPath).Trim(); + if (string.IsNullOrEmpty(secret)) + { + throw new InvalidOperationException($"Authority client secret file '{resolvedPath}' is empty."); + } + + authority.ClientSecret = secret; + } + } +} diff --git a/src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs b/src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs index 0b567cb4..9d6e4883 100644 --- a/src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs +++ b/src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs @@ -32,12 +32,26 @@ public static class FeedserOptionsValidator NormalizeList(options.Authority.Audiences, toLower: false); NormalizeList(options.Authority.RequiredScopes, toLower: true); NormalizeList(options.Authority.BypassNetworks, toLower: false); + NormalizeList(options.Authority.ClientScopes, toLower: true); if (options.Authority.RequiredScopes.Count == 0) { options.Authority.RequiredScopes.Add(StellaOpsScopes.FeedserJobsTrigger); } + if (options.Authority.ClientScopes.Count == 0) + { + foreach (var scope in options.Authority.RequiredScopes) + { + options.Authority.ClientScopes.Add(scope); + } + } + + if (options.Authority.ClientScopes.Count == 0) + { + options.Authority.ClientScopes.Add(StellaOpsScopes.FeedserJobsTrigger); + } + if (options.Authority.BackchannelTimeoutSeconds <= 0) { throw new InvalidOperationException("Authority backchannelTimeoutSeconds must be greater than zero."); @@ -74,6 +88,19 @@ public static class FeedserOptionsValidator { throw new InvalidOperationException("Authority audiences must include at least one entry when authority is enabled."); } + + if (!options.Authority.AllowAnonymousFallback) + { + if (string.IsNullOrWhiteSpace(options.Authority.ClientId)) + { + throw new InvalidOperationException("Authority clientId must be configured when anonymous fallback is disabled."); + } + + if (string.IsNullOrWhiteSpace(options.Authority.ClientSecret)) + { + throw new InvalidOperationException("Authority clientSecret must be configured when anonymous fallback is disabled."); + } + } } if (!Enum.TryParse(options.Telemetry.MinimumLogLevel, ignoreCase: true, out LogLevel _)) diff --git a/src/StellaOps.Feedser.WebService/Program.cs b/src/StellaOps.Feedser.WebService/Program.cs index 393633c4..094dd0d0 100644 --- a/src/StellaOps.Feedser.WebService/Program.cs +++ b/src/StellaOps.Feedser.WebService/Program.cs @@ -17,11 +17,12 @@ using StellaOps.Feedser.Core.Jobs; using StellaOps.Feedser.Storage.Mongo; using StellaOps.Feedser.WebService.Diagnostics; using Serilog; -using StellaOps.Feedser.Merge; +using StellaOps.Feedser.Merge; using StellaOps.Feedser.Merge.Services; using StellaOps.Feedser.WebService.Extensions; using StellaOps.Feedser.WebService.Jobs; using StellaOps.Feedser.WebService.Options; +using StellaOps.Feedser.WebService.Filters; using Serilog.Events; using StellaOps.Plugin.DependencyInjection; using StellaOps.Plugin.Hosting; @@ -43,13 +44,23 @@ builder.Configuration.AddStellaOpsDefaults(options => }; }); -var feedserOptions = builder.Configuration.BindOptions(postConfigure: (opts, _) => FeedserOptionsValidator.Validate(opts)); -builder.Services.AddOptions() - .Bind(builder.Configuration) - .PostConfigure(FeedserOptionsValidator.Validate) - .ValidateOnStart(); - -builder.ConfigureFeedserTelemetry(feedserOptions); +var contentRootPath = builder.Environment.ContentRootPath; + +var feedserOptions = builder.Configuration.BindOptions(postConfigure: (opts, _) => +{ + FeedserOptionsPostConfigure.Apply(opts, contentRootPath); + FeedserOptionsValidator.Validate(opts); +}); +builder.Services.AddOptions() + .Bind(builder.Configuration) + .PostConfigure(options => + { + FeedserOptionsPostConfigure.Apply(options, contentRootPath); + FeedserOptionsValidator.Validate(options); + }) + .ValidateOnStart(); + +builder.ConfigureFeedserTelemetry(feedserOptions); builder.Services.AddMongoStorage(storageOptions => { @@ -64,9 +75,9 @@ builder.Services.AddBuiltInFeedserJobs(); builder.Services.AddSingleton(sp => new ServiceStatus(sp.GetRequiredService())); -var authorityEnabled = feedserOptions.Authority is { Enabled: true }; +var authorityConfigured = feedserOptions.Authority is { Enabled: true }; -if (authorityEnabled) +if (authorityConfigured) { builder.Services.AddStellaOpsResourceServerAuthentication( builder.Configuration, @@ -109,9 +120,20 @@ var pluginHostOptions = BuildPluginOptions(feedserOptions, builder.Environment.C builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions); builder.Services.AddEndpointsApiExplorer(); - + var app = builder.Build(); +var resolvedFeedserOptions = app.Services.GetRequiredService>().Value; +var resolvedAuthority = resolvedFeedserOptions.Authority ?? new FeedserOptions.AuthorityOptions(); +authorityConfigured = resolvedAuthority.Enabled; +var enforceAuthority = resolvedAuthority.Enabled && !resolvedAuthority.AllowAnonymousFallback; + +if (resolvedAuthority.Enabled && resolvedAuthority.AllowAnonymousFallback) +{ + app.Logger.LogWarning( + "Authority authentication is configured but anonymous fallback remains enabled. Set authority.allowAnonymousFallback to false before 2025-12-31 to complete the rollout."); +} + var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); jsonOptions.Converters.Add(new JsonStringEnumConverter()); @@ -160,7 +182,46 @@ app.UseExceptionHandler(errorApp => }); }); -if (authorityEnabled) +if (authorityConfigured) +{ + app.Use(async (context, next) => + { + await next().ConfigureAwait(false); + + if (!context.Request.Path.StartsWithSegments("/jobs", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + if (context.Response.StatusCode != StatusCodes.Status401Unauthorized) + { + return; + } + + var optionsMonitor = context.RequestServices.GetRequiredService>().Value.Authority; + if (optionsMonitor is null || !optionsMonitor.Enabled) + { + return; + } + + var logger = context.RequestServices + .GetRequiredService() + .CreateLogger(JobAuthorizationAuditFilter.LoggerName); + + var matcher = new NetworkMaskMatcher(optionsMonitor.BypassNetworks); + var remote = context.Connection.RemoteIpAddress; + var bypassAllowed = matcher.IsAllowed(remote); + + logger.LogWarning( + "Feedser authorization denied route={Route} remote={RemoteAddress} bypassAllowed={BypassAllowed} hasPrincipal={HasPrincipal}", + context.Request.Path.Value ?? string.Empty, + remote?.ToString() ?? "unknown", + bypassAllowed, + context.User?.Identity?.IsAuthenticated ?? false); + }); +} + +if (authorityConfigured) { app.UseAuthentication(); app.UseAuthorization(); @@ -360,12 +421,12 @@ var jobsListEndpoint = app.MapGet("/jobs", async (string? kind, int? limit, IJob var runs = await coordinator.GetRecentRunsAsync(kind, take, cancellationToken).ConfigureAwait(false); var payload = runs.Select(JobRunResponse.FromSnapshot).ToArray(); return JsonResult(payload); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { jobsListEndpoint.RequireAuthorization(JobsPolicyName); } - + var jobByIdEndpoint = app.MapGet("/jobs/{runId:guid}", async (Guid runId, IJobCoordinator coordinator, HttpContext context, CancellationToken cancellationToken) => { ApplyNoCache(context.Response); @@ -377,12 +438,12 @@ var jobByIdEndpoint = app.MapGet("/jobs/{runId:guid}", async (Guid runId, IJobCo } return JsonResult(JobRunResponse.FromSnapshot(run)); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { jobByIdEndpoint.RequireAuthorization(JobsPolicyName); } - + var jobDefinitionsEndpoint = app.MapGet("/jobs/definitions", async (IJobCoordinator coordinator, HttpContext context, CancellationToken cancellationToken) => { ApplyNoCache(context.Response); @@ -402,14 +463,14 @@ var jobDefinitionsEndpoint = app.MapGet("/jobs/definitions", async (IJobCoordina lastRuns.TryGetValue(definition.Kind, out var lastRun); responses.Add(JobDefinitionResponse.FromDefinition(definition, lastRun)); } - + return JsonResult(responses); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { jobDefinitionsEndpoint.RequireAuthorization(JobsPolicyName); } - + var jobDefinitionEndpoint = app.MapGet("/jobs/definitions/{kind}", async (string kind, IJobCoordinator coordinator, HttpContext context, CancellationToken cancellationToken) => { ApplyNoCache(context.Response); @@ -424,15 +485,15 @@ var jobDefinitionEndpoint = app.MapGet("/jobs/definitions/{kind}", async (string var lastRuns = await coordinator.GetLastRunsAsync(new[] { definition.Kind }, cancellationToken).ConfigureAwait(false); lastRuns.TryGetValue(definition.Kind, out var lastRun); - + var response = JobDefinitionResponse.FromDefinition(definition, lastRun); return JsonResult(response); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { jobDefinitionEndpoint.RequireAuthorization(JobsPolicyName); } - + var jobDefinitionRunsEndpoint = app.MapGet("/jobs/definitions/{kind}/runs", async (string kind, int? limit, IJobCoordinator coordinator, HttpContext context, CancellationToken cancellationToken) => { ApplyNoCache(context.Response); @@ -445,29 +506,29 @@ var jobDefinitionRunsEndpoint = app.MapGet("/jobs/definitions/{kind}/runs", asyn return Problem(context, "Job definition not found", StatusCodes.Status404NotFound, ProblemTypes.NotFound, $"Job kind '{kind}' is not registered."); } - var take = Math.Clamp(limit.GetValueOrDefault(20), 1, 200); - var runs = await coordinator.GetRecentRunsAsync(kind, take, cancellationToken).ConfigureAwait(false); + var take = Math.Clamp(limit.GetValueOrDefault(20), 1, 200); + var runs = await coordinator.GetRecentRunsAsync(kind, take, cancellationToken).ConfigureAwait(false); var payload = runs.Select(JobRunResponse.FromSnapshot).ToArray(); return JsonResult(payload); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { jobDefinitionRunsEndpoint.RequireAuthorization(JobsPolicyName); } - + var activeJobsEndpoint = app.MapGet("/jobs/active", async (IJobCoordinator coordinator, HttpContext context, CancellationToken cancellationToken) => { ApplyNoCache(context.Response); - - var runs = await coordinator.GetActiveRunsAsync(cancellationToken).ConfigureAwait(false); + + var runs = await coordinator.GetActiveRunsAsync(cancellationToken).ConfigureAwait(false); var payload = runs.Select(JobRunResponse.FromSnapshot).ToArray(); return JsonResult(payload); -}); -if (authorityEnabled) +}).AddEndpointFilter(); +if (enforceAuthority) { activeJobsEndpoint.RequireAuthorization(JobsPolicyName); } - + var triggerJobEndpoint = app.MapPost("/jobs/{*jobKind}", async (string jobKind, JobTriggerRequest request, IJobCoordinator coordinator, HttpContext context) => { ApplyNoCache(context.Response); @@ -542,13 +603,13 @@ var triggerJobEndpoint = app.MapPost("/jobs/{*jobKind}", async (string jobKind, return Problem(context, "Job execution failed", StatusCodes.Status500InternalServerError, ProblemTypes.JobFailure, result.ErrorMessage, extensions); } - - default: - JobMetrics.TriggerFailureCounter.Add(1, tags); - return Problem(context, "Unexpected job outcome", StatusCodes.Status500InternalServerError, ProblemTypes.JobFailure, $"Job '{jobKind}' returned outcome '{outcome}'."); - } -}); -if (authorityEnabled) + + default: + JobMetrics.TriggerFailureCounter.Add(1, tags); + return Problem(context, "Unexpected job outcome", StatusCodes.Status500InternalServerError, ProblemTypes.JobFailure, $"Job '{jobKind}' returned outcome '{outcome}'."); + } +}).AddEndpointFilter(); +if (enforceAuthority) { triggerJobEndpoint.RequireAuthorization(JobsPolicyName); } diff --git a/src/StellaOps.Feedser.WebService/TASKS.md b/src/StellaOps.Feedser.WebService/TASKS.md index ad7af9d6..849aa9db 100644 --- a/src/StellaOps.Feedser.WebService/TASKS.md +++ b/src/StellaOps.Feedser.WebService/TASKS.md @@ -14,4 +14,10 @@ |Endpoint smoke tests (health/ready/jobs error paths)|QA|WebService|DONE – WebServiceEndpointsTests assert success and problem responses for health, ready, and job trigger error paths.| |Batch job definition last-run lookup|BE-Base|Core|DONE – definitions endpoint now precomputes kinds array and reuses batched last-run dictionary; manual smoke verified via local GET `/jobs/definitions`.| |Add no-cache headers to health/readiness/jobs APIs|BE-Base|WebService|DONE – helper applies Cache-Control/Pragma/Expires on all health/ready/jobs endpoints; awaiting automated probe tests once connector fixtures stabilize.| -|Document authority toggle & scope requirements|Docs/Feedser|Authority integration|**TODO** – Update Feedser operator docs/sample configs explaining `authority.*` settings, bypass CIDRs, and required scopes before enabling in prod.| +|Authority configuration parity (FSR1)|DevEx/Feedser|Authority options schema|**DONE (2025-10-10)** – Options post-config loads clientSecretFile fallback, validators normalize scopes/audiences, and sample config documents issuer/credential/bypass settings.| +|Document authority toggle & scope requirements|Docs/Feedser|Authority integration|**DOING (2025-10-10)** – Quickstart updated with staging flag, client credentials, env overrides; operator guide refresh pending Docs guild review.| +|Plumb Authority client resilience options|BE-Base|Auth libraries LIB5|**TODO** – Bind retry/offline settings from the `authority` config block, flow them into `AddStellaOpsAuthClient`, and cover via WebService integration test.| +|Author ops guidance for resilience tuning|Docs/Feedser|Plumb Authority client resilience options|**TODO** – Extend operator/quickstart docs with recommended retry profiles, offline-tolerance guidance, and monitoring cues.| +|Document authority bypass logging patterns|Docs/Feedser|FSR3 logging|**TODO** – Capture new audit log fields (bypass, remote IP, subject) in operator docs and add troubleshooting guidance for cron bypasses.| +|Update Feedser operator guide for enforcement cutoff|Docs/Feedser|FSR1 rollout|**TODO** – Add `allowAnonymousFallback` sunset timeline and checklist to operator guide / runbooks before 2025-12-31 enforcement.| +|Authority resilience adoption|Feedser WebService, Docs|Plumb Authority client resilience options|**BLOCKED (2025-10-10)** – Roll out retry/offline knobs to deployment docs and confirm CLI parity once LIB5 lands; unblock after resilience options wired and tested.| diff --git a/src/StellaOps.Web/AGENTS.md b/src/StellaOps.Web/AGENTS.md new file mode 100644 index 00000000..03209b69 --- /dev/null +++ b/src/StellaOps.Web/AGENTS.md @@ -0,0 +1,24 @@ +# StellaOps Web Frontend + +## Mission +Design and build the StellaOps web user experience that surfaces backend capabilities (Authority, Feedser, Exporters) through an offline-friendly Angular application. + +## Team Composition +- **UX Specialist** – defines user journeys, interaction patterns, accessibility guidelines, and visual design language. +- **Angular Engineers** – implement the SPA, integrate with backend APIs, and ensure deterministic builds suitable for air-gapped deployments. + +## Operating Principles +- Favor modular Angular architecture (feature modules, shared UI kit) with strong typing via latest TypeScript/Angular releases. +- Align UI flows with backend contracts; coordinate with Authority and Feedser teams for API changes. +- Keep assets and build outputs deterministic and cacheable for Offline Kit packaging. +- Track work using the local `TASKS.md` board; keep statuses (TODO/DOING/REVIEW/BLOCKED/DONE) up to date. + +## Key Paths +- `src/StellaOps.Web` — Angular workspace (to be scaffolded). +- `docs/` — UX specs and mockups (to be added). +- `ops/` — Web deployment manifests for air-gapped environments (future). + +## Coordination +- Sync with DevEx for project scaffolding and build pipelines. +- Partner with Docs Guild to translate UX decisions into operator guides. +- Collaborate with Security Guild to validate authentication flows and session handling. diff --git a/src/StellaOps.Web/TASKS.md b/src/StellaOps.Web/TASKS.md new file mode 100644 index 00000000..65306a0d --- /dev/null +++ b/src/StellaOps.Web/TASKS.md @@ -0,0 +1,5 @@ +# StellaOps Web Task Board (UTC 2025-10-10) + +| ID | Status | Owner(s) | Depends on | Description | Exit Criteria | +|----|--------|----------|------------|-------------|---------------| +| WEB1.TRIVY-SETTINGS | TODO | UX Specialist, Angular Eng | Backend `/exporters/trivy-db` contract | Implement Trivy DB exporter settings panel with `publishFull`, `publishDelta`, `includeFull`, `includeDelta` toggles and “Run export now” action using future `/exporters/trivy-db/settings` API. | ✅ Panel wired to mocked API; ✅ Overrides persisted via settings endpoint; ✅ Manual run button reuses overrides. | diff --git a/StellaOps.sln b/src/StellaOps.sln similarity index 86% rename from StellaOps.sln rename to src/StellaOps.sln index 42c944e2..4c2d8122 100644 --- a/StellaOps.sln +++ b/src/StellaOps.sln @@ -1,1272 +1,1302 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Authority", "StellaOps.Authority", "{361838C4-72E2-1C48-5D76-CA6D1A861242}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Abstractions", "src\StellaOps.Authority\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj", "{D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client", "src\StellaOps.Authority\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj", "{5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Configuration", "src\StellaOps.Configuration\StellaOps.Configuration.csproj", "{8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegration", "src\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj", "{46D35B4F-6A04-47FF-958B-5E6A73FCC059}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.DependencyInjection", "src\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj", "{44A1241B-8ECF-4AFA-9972-452C39AD43D6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority", "src\StellaOps.Authority\StellaOps.Authority\StellaOps.Authority.csproj", "{85AB3BB7-C493-4387-B39A-EB299AC37312}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugins.Abstractions", "src\StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj", "{5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugin.Standard", "src\StellaOps.Authority\StellaOps.Authority.Plugin.Standard\StellaOps.Authority.Plugin.Standard.csproj", "{93DB06DC-B254-48A9-8F2C-6130A5658F27}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Plugin", "src\StellaOps.Plugin\StellaOps.Plugin.csproj", "{03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cli", "src\StellaOps.Cli\StellaOps.Cli.csproj", "{40094279-250C-42AE-992A-856718FEFBAC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cli.Tests", "src\StellaOps.Cli.Tests\StellaOps.Cli.Tests.csproj", "{B2967228-F8F7-4931-B257-1C63CB58CE1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Testing", "src\StellaOps.Feedser.Testing\StellaOps.Feedser.Testing.csproj", "{6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Common", "src\StellaOps.Feedser.Source.Common\StellaOps.Feedser.Source.Common.csproj", "{37F203A3-624E-4794-9C99-16CAC22C17DF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Storage.Mongo", "src\StellaOps.Feedser.Storage.Mongo\StellaOps.Feedser.Storage.Mongo.csproj", "{3FF93987-A30A-4D50-8815-7CF3BB7CAE05}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core", "src\StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj", "{AACE8717-0760-42F2-A225-8FCCE876FB65}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Models", "src\StellaOps.Feedser.Models\StellaOps.Feedser.Models.csproj", "{4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Normalization", "src\StellaOps.Feedser.Normalization\StellaOps.Feedser.Normalization.csproj", "{85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core.Tests", "src\StellaOps.Feedser.Core.Tests\StellaOps.Feedser.Core.Tests.csproj", "{FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.Json", "src\StellaOps.Feedser.Exporter.Json\StellaOps.Feedser.Exporter.Json.csproj", "{D0FB54BA-4D14-4A32-B09F-7EC94F369460}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.Json.Tests", "src\StellaOps.Feedser.Exporter.Json.Tests\StellaOps.Feedser.Exporter.Json.Tests.csproj", "{69C9E010-CBDD-4B89-84CF-7AB56D6A078A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.TrivyDb", "src\StellaOps.Feedser.Exporter.TrivyDb\StellaOps.Feedser.Exporter.TrivyDb.csproj", "{E471176A-E1F3-4DE5-8D30-0865903A217A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.TrivyDb.Tests", "src\StellaOps.Feedser.Exporter.TrivyDb.Tests\StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj", "{FA013511-DF20-45F7-8077-EBA2D6224D64}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Merge", "src\StellaOps.Feedser.Merge\StellaOps.Feedser.Merge.csproj", "{B9F84697-54FE-4648-B173-EE3D904FFA4D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Merge.Tests", "src\StellaOps.Feedser.Merge.Tests\StellaOps.Feedser.Merge.Tests.csproj", "{6751A76C-8ED8-40F4-AE2B-069DB31395FE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Models.Tests", "src\StellaOps.Feedser.Models.Tests\StellaOps.Feedser.Models.Tests.csproj", "{DDBFA2EF-9CAE-473F-A438-369CAC25C66A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Normalization.Tests", "src\StellaOps.Feedser.Normalization.Tests\StellaOps.Feedser.Normalization.Tests.csproj", "{063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Acsc", "src\StellaOps.Feedser.Source.Acsc\StellaOps.Feedser.Source.Acsc.csproj", "{35350FAB-FC51-4FE8-81FB-011003134C37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cccs", "src\StellaOps.Feedser.Source.Cccs\StellaOps.Feedser.Source.Cccs.csproj", "{1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertBund", "src\StellaOps.Feedser.Source.CertBund\StellaOps.Feedser.Source.CertBund.csproj", "{C4A65377-22F7-4D15-92A3-4F05847D167E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertCc", "src\StellaOps.Feedser.Source.CertCc\StellaOps.Feedser.Source.CertCc.csproj", "{BDDE59E1-C643-4C87-8608-0F9A7A54DE09}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertFr", "src\StellaOps.Feedser.Source.CertFr\StellaOps.Feedser.Source.CertFr.csproj", "{0CC116C8-A7E5-4B94-9688-32920177FF97}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertFr.Tests", "src\StellaOps.Feedser.Source.CertFr.Tests\StellaOps.Feedser.Source.CertFr.Tests.csproj", "{E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertIn", "src\StellaOps.Feedser.Source.CertIn\StellaOps.Feedser.Source.CertIn.csproj", "{84DEDF05-A5BD-4644-86B9-6B7918FE3F31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertIn.Tests", "src\StellaOps.Feedser.Source.CertIn.Tests\StellaOps.Feedser.Source.CertIn.Tests.csproj", "{9DEB1F54-94B5-40C4-AC44-220E680B016D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Common.Tests", "src\StellaOps.Feedser.Source.Common.Tests\StellaOps.Feedser.Source.Common.Tests.csproj", "{7C3E87F2-93D8-4968-95E3-52C46947D46C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cve", "src\StellaOps.Feedser.Source.Cve\StellaOps.Feedser.Source.Cve.csproj", "{C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Debian", "src\StellaOps.Feedser.Source.Distro.Debian\StellaOps.Feedser.Source.Distro.Debian.csproj", "{31B05493-104F-437F-9FA7-CA5286CE697C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Debian.Tests", "src\StellaOps.Feedser.Source.Distro.Debian.Tests\StellaOps.Feedser.Source.Distro.Debian.Tests.csproj", "{937AF12E-D770-4534-8FF8-C59042609C2A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.RedHat", "src\StellaOps.Feedser.Source.Distro.RedHat\StellaOps.Feedser.Source.Distro.RedHat.csproj", "{5A028B04-9D76-470B-B5B3-766CE4CE860C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.RedHat.Tests", "src\StellaOps.Feedser.Source.Distro.RedHat.Tests\StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj", "{749DE4C8-F733-43F8-B2A8-6649E71C7570}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Suse", "src\StellaOps.Feedser.Source.Distro.Suse\StellaOps.Feedser.Source.Distro.Suse.csproj", "{56D2C79E-2737-4FF9-9D19-150065F568D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Suse.Tests", "src\StellaOps.Feedser.Source.Distro.Suse.Tests\StellaOps.Feedser.Source.Distro.Suse.Tests.csproj", "{E41F6DC4-68B5-4EE3-97AE-801D725A2C13}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Ubuntu", "src\StellaOps.Feedser.Source.Distro.Ubuntu\StellaOps.Feedser.Source.Distro.Ubuntu.csproj", "{285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Ubuntu.Tests", "src\StellaOps.Feedser.Source.Distro.Ubuntu.Tests\StellaOps.Feedser.Source.Distro.Ubuntu.Tests.csproj", "{26055403-C7F5-4709-8813-0F7387102791}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ghsa", "src\StellaOps.Feedser.Source.Ghsa\StellaOps.Feedser.Source.Ghsa.csproj", "{0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Cisa", "src\StellaOps.Feedser.Source.Ics.Cisa\StellaOps.Feedser.Source.Ics.Cisa.csproj", "{258327E9-431E-475C-933B-50893676E452}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Kaspersky", "src\StellaOps.Feedser.Source.Ics.Kaspersky\StellaOps.Feedser.Source.Ics.Kaspersky.csproj", "{42AF60C8-A5E1-40E0-86F8-98256364AF6F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Kaspersky.Tests", "src\StellaOps.Feedser.Source.Ics.Kaspersky.Tests\StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj", "{88C6A9C3-B433-4C36-8767-429C8C2396F8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Jvn", "src\StellaOps.Feedser.Source.Jvn\StellaOps.Feedser.Source.Jvn.csproj", "{6B7099AB-01BF-4EC4-87D0-5C9C032266DE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Jvn.Tests", "src\StellaOps.Feedser.Source.Jvn.Tests\StellaOps.Feedser.Source.Jvn.Tests.csproj", "{14C918EA-693E-41FE-ACAE-2E82DF077BEA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kev", "src\StellaOps.Feedser.Source.Kev\StellaOps.Feedser.Source.Kev.csproj", "{81111B26-74F6-4912-9084-7115FD119945}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kisa", "src\StellaOps.Feedser.Source.Kisa\StellaOps.Feedser.Source.Kisa.csproj", "{80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Nvd", "src\StellaOps.Feedser.Source.Nvd\StellaOps.Feedser.Source.Nvd.csproj", "{8D0F501D-01B1-4E24-958B-FAF35B267705}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Nvd.Tests", "src\StellaOps.Feedser.Source.Nvd.Tests\StellaOps.Feedser.Source.Nvd.Tests.csproj", "{5BA91095-7F10-4717-B296-49DFBFC1C9C2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Osv", "src\StellaOps.Feedser.Source.Osv\StellaOps.Feedser.Source.Osv.csproj", "{99616566-4EF1-4DC7-B655-825FE43D203D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Osv.Tests", "src\StellaOps.Feedser.Source.Osv.Tests\StellaOps.Feedser.Source.Osv.Tests.csproj", "{EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ru.Bdu", "src\StellaOps.Feedser.Source.Ru.Bdu\StellaOps.Feedser.Source.Ru.Bdu.csproj", "{A3B19095-2D95-4B09-B07E-2C082C72394B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ru.Nkcki", "src\StellaOps.Feedser.Source.Ru.Nkcki\StellaOps.Feedser.Source.Ru.Nkcki.csproj", "{807837AF-B392-4589-ADF1-3FDB34D6C5BF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Adobe", "src\StellaOps.Feedser.Source.Vndr.Adobe\StellaOps.Feedser.Source.Vndr.Adobe.csproj", "{64EAFDCF-8283-4D5C-AC78-7969D5FE926A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Adobe.Tests", "src\StellaOps.Feedser.Source.Vndr.Adobe.Tests\StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj", "{68F4D8A1-E32F-487A-B460-325F36989BE3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Apple", "src\StellaOps.Feedser.Source.Vndr.Apple\StellaOps.Feedser.Source.Vndr.Apple.csproj", "{4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Chromium", "src\StellaOps.Feedser.Source.Vndr.Chromium\StellaOps.Feedser.Source.Vndr.Chromium.csproj", "{606C751B-7CF1-47CF-A25C-9248A55C814F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Chromium.Tests", "src\StellaOps.Feedser.Source.Vndr.Chromium.Tests\StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj", "{0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Cisco", "src\StellaOps.Feedser.Source.Vndr.Cisco\StellaOps.Feedser.Source.Vndr.Cisco.csproj", "{CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Msrc", "src\StellaOps.Feedser.Source.Vndr.Msrc\StellaOps.Feedser.Source.Vndr.Msrc.csproj", "{5CCE0DB7-C115-4B21-A7AE-C8488C22A853}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Oracle", "src\StellaOps.Feedser.Source.Vndr.Oracle\StellaOps.Feedser.Source.Vndr.Oracle.csproj", "{A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Oracle.Tests", "src\StellaOps.Feedser.Source.Vndr.Oracle.Tests\StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj", "{06DC817F-A936-4F83-8929-E00622B32245}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Vmware", "src\StellaOps.Feedser.Source.Vndr.Vmware\StellaOps.Feedser.Source.Vndr.Vmware.csproj", "{2C999476-0291-4161-B3E9-1AA99A3B1139}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Vmware.Tests", "src\StellaOps.Feedser.Source.Vndr.Vmware.Tests\StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj", "{476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Storage.Mongo.Tests", "src\StellaOps.Feedser.Storage.Mongo.Tests\StellaOps.Feedser.Storage.Mongo.Tests.csproj", "{0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.WebService", "src\StellaOps.Feedser.WebService\StellaOps.Feedser.WebService.csproj", "{0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.WebService.Tests", "src\StellaOps.Feedser.WebService.Tests\StellaOps.Feedser.WebService.Tests.csproj", "{8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Configuration.Tests", "src\StellaOps.Configuration.Tests\StellaOps.Configuration.Tests.csproj", "{C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugins.Abstractions.Tests", "src\StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions.Tests\StellaOps.Authority.Plugins.Abstractions.Tests.csproj", "{50140A32-6D3C-47DB-983A-7166CBA51845}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Tests", "src\StellaOps.Authority\StellaOps.Authority.Tests\StellaOps.Authority.Tests.csproj", "{031979F2-6ABA-444F-A6A4-80115DC487CE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugin.Standard.Tests", "src\StellaOps.Authority\StellaOps.Authority.Plugin.Standard.Tests\StellaOps.Authority.Plugin.Standard.Tests.csproj", "{D71B0DA5-80A3-419E-898D-40E77A9A7F19}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Storage.Mongo", "src\StellaOps.Authority\StellaOps.Authority.Storage.Mongo\StellaOps.Authority.Storage.Mongo.csproj", "{B2C877D9-B521-4901-8817-76B5DAA62FCE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Abstractions.Tests", "src\StellaOps.Authority\StellaOps.Auth.Abstractions.Tests\StellaOps.Auth.Abstractions.Tests.csproj", "{08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegration.Tests", "src\StellaOps.Authority\StellaOps.Auth.ServerIntegration.Tests\StellaOps.Auth.ServerIntegration.Tests.csproj", "{7116DD6B-2491-49E1-AB27-5210E949F753}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client.Tests", "src\StellaOps.Authority\StellaOps.Auth.Client.Tests\StellaOps.Auth.Client.Tests.csproj", "{7DBE31A6-D2FD-499E-B675-4092723175AD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kev.Tests", "src\StellaOps.Feedser.Source.Kev.Tests\StellaOps.Feedser.Source.Kev.Tests.csproj", "{D99E6EAE-D278-4480-AA67-85F025383E47}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cve.Tests", "src\StellaOps.Feedser.Source.Cve.Tests\StellaOps.Feedser.Source.Cve.Tests.csproj", "{D3825714-3DDA-44B7-A99C-5F3E65716691}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ghsa.Tests", "src\StellaOps.Feedser.Source.Ghsa.Tests\StellaOps.Feedser.Source.Ghsa.Tests.csproj", "{FAB78D21-7372-48FE-B2C3-DE1807F1157D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x64.ActiveCfg = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x64.Build.0 = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x86.ActiveCfg = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x86.Build.0 = Debug|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|Any CPU.Build.0 = Release|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x64.ActiveCfg = Release|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x64.Build.0 = Release|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x86.ActiveCfg = Release|Any CPU - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x86.Build.0 = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x64.ActiveCfg = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x64.Build.0 = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x86.ActiveCfg = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x86.Build.0 = Debug|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|Any CPU.Build.0 = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x64.ActiveCfg = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x64.Build.0 = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x86.ActiveCfg = Release|Any CPU - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x86.Build.0 = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x64.Build.0 = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x86.Build.0 = Debug|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|Any CPU.Build.0 = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x64.ActiveCfg = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x64.Build.0 = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x86.ActiveCfg = Release|Any CPU - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x86.Build.0 = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|Any CPU.Build.0 = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x64.ActiveCfg = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x64.Build.0 = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x86.ActiveCfg = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x86.Build.0 = Debug|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|Any CPU.ActiveCfg = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|Any CPU.Build.0 = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x64.ActiveCfg = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x64.Build.0 = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x86.ActiveCfg = Release|Any CPU - {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x86.Build.0 = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x64.ActiveCfg = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x64.Build.0 = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x86.ActiveCfg = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x86.Build.0 = Debug|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|Any CPU.Build.0 = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x64.ActiveCfg = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x64.Build.0 = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x86.ActiveCfg = Release|Any CPU - {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x86.Build.0 = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x64.ActiveCfg = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x64.Build.0 = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x86.ActiveCfg = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x86.Build.0 = Debug|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|Any CPU.Build.0 = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x64.ActiveCfg = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x64.Build.0 = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x86.ActiveCfg = Release|Any CPU - {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x86.Build.0 = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x64.ActiveCfg = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x64.Build.0 = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x86.ActiveCfg = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x86.Build.0 = Debug|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|Any CPU.Build.0 = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x64.ActiveCfg = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x64.Build.0 = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x86.ActiveCfg = Release|Any CPU - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x86.Build.0 = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x64.ActiveCfg = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x64.Build.0 = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x86.ActiveCfg = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x86.Build.0 = Debug|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|Any CPU.Build.0 = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x64.ActiveCfg = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x64.Build.0 = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x86.ActiveCfg = Release|Any CPU - {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x86.Build.0 = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x64.ActiveCfg = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x64.Build.0 = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x86.ActiveCfg = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x86.Build.0 = Debug|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|Any CPU.Build.0 = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x64.ActiveCfg = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x64.Build.0 = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x86.ActiveCfg = Release|Any CPU - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x86.Build.0 = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x64.ActiveCfg = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x64.Build.0 = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x86.ActiveCfg = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x86.Build.0 = Debug|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|Any CPU.Build.0 = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|x64.ActiveCfg = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|x64.Build.0 = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|x86.ActiveCfg = Release|Any CPU - {40094279-250C-42AE-992A-856718FEFBAC}.Release|x86.Build.0 = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x64.ActiveCfg = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x64.Build.0 = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x86.ActiveCfg = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x86.Build.0 = Debug|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|Any CPU.Build.0 = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x64.ActiveCfg = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x64.Build.0 = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x86.ActiveCfg = Release|Any CPU - {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x86.Build.0 = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x64.ActiveCfg = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x64.Build.0 = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x86.ActiveCfg = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x86.Build.0 = Debug|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|Any CPU.Build.0 = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x64.ActiveCfg = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x64.Build.0 = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x86.ActiveCfg = Release|Any CPU - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x86.Build.0 = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x64.ActiveCfg = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x64.Build.0 = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x86.ActiveCfg = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x86.Build.0 = Debug|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|Any CPU.Build.0 = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x64.ActiveCfg = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x64.Build.0 = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x86.ActiveCfg = Release|Any CPU - {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x86.Build.0 = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x64.ActiveCfg = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x64.Build.0 = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x86.ActiveCfg = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x86.Build.0 = Debug|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|Any CPU.Build.0 = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x64.ActiveCfg = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x64.Build.0 = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x86.ActiveCfg = Release|Any CPU - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x86.Build.0 = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x64.ActiveCfg = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x64.Build.0 = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x86.ActiveCfg = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x86.Build.0 = Debug|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|Any CPU.Build.0 = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x64.ActiveCfg = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x64.Build.0 = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x86.ActiveCfg = Release|Any CPU - {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x86.Build.0 = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x64.ActiveCfg = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x64.Build.0 = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x86.ActiveCfg = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x86.Build.0 = Debug|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|Any CPU.Build.0 = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x64.ActiveCfg = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x64.Build.0 = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x86.ActiveCfg = Release|Any CPU - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x86.Build.0 = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x64.ActiveCfg = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x64.Build.0 = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x86.ActiveCfg = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x86.Build.0 = Debug|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|Any CPU.Build.0 = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x64.ActiveCfg = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x64.Build.0 = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x86.ActiveCfg = Release|Any CPU - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x86.Build.0 = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x64.ActiveCfg = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x64.Build.0 = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x86.ActiveCfg = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x86.Build.0 = Debug|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|Any CPU.Build.0 = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x64.ActiveCfg = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x64.Build.0 = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x86.ActiveCfg = Release|Any CPU - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x86.Build.0 = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x64.ActiveCfg = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x64.Build.0 = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x86.ActiveCfg = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x86.Build.0 = Debug|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|Any CPU.Build.0 = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x64.ActiveCfg = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x64.Build.0 = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x86.ActiveCfg = Release|Any CPU - {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x86.Build.0 = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x64.ActiveCfg = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x64.Build.0 = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x86.ActiveCfg = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x86.Build.0 = Debug|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|Any CPU.Build.0 = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x64.ActiveCfg = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x64.Build.0 = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x86.ActiveCfg = Release|Any CPU - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x86.Build.0 = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x64.ActiveCfg = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x64.Build.0 = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x86.ActiveCfg = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x86.Build.0 = Debug|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|Any CPU.Build.0 = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x64.ActiveCfg = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x64.Build.0 = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x86.ActiveCfg = Release|Any CPU - {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x86.Build.0 = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x64.ActiveCfg = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x64.Build.0 = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x86.ActiveCfg = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x86.Build.0 = Debug|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|Any CPU.Build.0 = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x64.ActiveCfg = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x64.Build.0 = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x86.ActiveCfg = Release|Any CPU - {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x86.Build.0 = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x64.ActiveCfg = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x64.Build.0 = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x86.ActiveCfg = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x86.Build.0 = Debug|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|Any CPU.Build.0 = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x64.ActiveCfg = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x64.Build.0 = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x86.ActiveCfg = Release|Any CPU - {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x86.Build.0 = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x64.ActiveCfg = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x64.Build.0 = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x86.ActiveCfg = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x86.Build.0 = Debug|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|Any CPU.Build.0 = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x64.ActiveCfg = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x64.Build.0 = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x86.ActiveCfg = Release|Any CPU - {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x86.Build.0 = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x64.ActiveCfg = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x64.Build.0 = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x86.ActiveCfg = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x86.Build.0 = Debug|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|Any CPU.Build.0 = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x64.ActiveCfg = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x64.Build.0 = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x86.ActiveCfg = Release|Any CPU - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x86.Build.0 = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x64.ActiveCfg = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x64.Build.0 = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x86.ActiveCfg = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x86.Build.0 = Debug|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|Any CPU.Build.0 = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x64.ActiveCfg = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x64.Build.0 = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x86.ActiveCfg = Release|Any CPU - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x86.Build.0 = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x64.ActiveCfg = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x64.Build.0 = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x86.ActiveCfg = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x86.Build.0 = Debug|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|Any CPU.Build.0 = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x64.ActiveCfg = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x64.Build.0 = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x86.ActiveCfg = Release|Any CPU - {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x86.Build.0 = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x64.ActiveCfg = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x64.Build.0 = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x86.ActiveCfg = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x86.Build.0 = Debug|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|Any CPU.Build.0 = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x64.ActiveCfg = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x64.Build.0 = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x86.ActiveCfg = Release|Any CPU - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x86.Build.0 = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x64.ActiveCfg = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x64.Build.0 = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x86.ActiveCfg = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x86.Build.0 = Debug|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|Any CPU.Build.0 = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x64.ActiveCfg = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x64.Build.0 = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x86.ActiveCfg = Release|Any CPU - {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x86.Build.0 = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x64.ActiveCfg = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x64.Build.0 = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x86.ActiveCfg = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x86.Build.0 = Debug|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|Any CPU.Build.0 = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x64.ActiveCfg = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x64.Build.0 = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x86.ActiveCfg = Release|Any CPU - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x86.Build.0 = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x64.ActiveCfg = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x64.Build.0 = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x86.ActiveCfg = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x86.Build.0 = Debug|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|Any CPU.Build.0 = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x64.ActiveCfg = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x64.Build.0 = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x86.ActiveCfg = Release|Any CPU - {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x86.Build.0 = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x64.ActiveCfg = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x64.Build.0 = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x86.ActiveCfg = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x86.Build.0 = Debug|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|Any CPU.Build.0 = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x64.ActiveCfg = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x64.Build.0 = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x86.ActiveCfg = Release|Any CPU - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x86.Build.0 = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x64.ActiveCfg = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x64.Build.0 = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x86.ActiveCfg = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x86.Build.0 = Debug|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|Any CPU.Build.0 = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x64.ActiveCfg = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x64.Build.0 = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x86.ActiveCfg = Release|Any CPU - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x86.Build.0 = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x64.ActiveCfg = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x64.Build.0 = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x86.ActiveCfg = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x86.Build.0 = Debug|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|Any CPU.Build.0 = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x64.ActiveCfg = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x64.Build.0 = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x86.ActiveCfg = Release|Any CPU - {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x86.Build.0 = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x64.ActiveCfg = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x64.Build.0 = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x86.ActiveCfg = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x86.Build.0 = Debug|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|Any CPU.Build.0 = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x64.ActiveCfg = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x64.Build.0 = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x86.ActiveCfg = Release|Any CPU - {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x86.Build.0 = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x64.ActiveCfg = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x64.Build.0 = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x86.ActiveCfg = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x86.Build.0 = Debug|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|Any CPU.Build.0 = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x64.ActiveCfg = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x64.Build.0 = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x86.ActiveCfg = Release|Any CPU - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x86.Build.0 = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x64.ActiveCfg = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x64.Build.0 = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x86.ActiveCfg = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x86.Build.0 = Debug|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|Any CPU.Build.0 = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x64.ActiveCfg = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x64.Build.0 = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x86.ActiveCfg = Release|Any CPU - {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x86.Build.0 = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x64.ActiveCfg = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x64.Build.0 = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x86.ActiveCfg = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x86.Build.0 = Debug|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|Any CPU.Build.0 = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x64.ActiveCfg = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x64.Build.0 = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x86.ActiveCfg = Release|Any CPU - {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x86.Build.0 = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x64.ActiveCfg = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x64.Build.0 = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x86.ActiveCfg = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x86.Build.0 = Debug|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|Any CPU.Build.0 = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x64.ActiveCfg = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x64.Build.0 = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x86.ActiveCfg = Release|Any CPU - {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x86.Build.0 = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|Any CPU.Build.0 = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x64.ActiveCfg = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x64.Build.0 = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x86.ActiveCfg = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x86.Build.0 = Debug|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|Any CPU.ActiveCfg = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|Any CPU.Build.0 = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x64.ActiveCfg = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x64.Build.0 = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x86.ActiveCfg = Release|Any CPU - {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x86.Build.0 = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x64.ActiveCfg = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x64.Build.0 = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x86.ActiveCfg = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x86.Build.0 = Debug|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|Any CPU.Build.0 = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x64.ActiveCfg = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x64.Build.0 = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x86.ActiveCfg = Release|Any CPU - {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x86.Build.0 = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x64.ActiveCfg = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x64.Build.0 = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x86.ActiveCfg = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x86.Build.0 = Debug|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|Any CPU.Build.0 = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x64.ActiveCfg = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x64.Build.0 = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x86.ActiveCfg = Release|Any CPU - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x86.Build.0 = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|Any CPU.Build.0 = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x64.ActiveCfg = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x64.Build.0 = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x86.ActiveCfg = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x86.Build.0 = Debug|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|Any CPU.ActiveCfg = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|Any CPU.Build.0 = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x64.ActiveCfg = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x64.Build.0 = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x86.ActiveCfg = Release|Any CPU - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x86.Build.0 = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|x64.ActiveCfg = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|x64.Build.0 = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|x86.ActiveCfg = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Debug|x86.Build.0 = Debug|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|Any CPU.Build.0 = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|x64.ActiveCfg = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|x64.Build.0 = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|x86.ActiveCfg = Release|Any CPU - {26055403-C7F5-4709-8813-0F7387102791}.Release|x86.Build.0 = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x64.Build.0 = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x86.ActiveCfg = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x86.Build.0 = Debug|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|Any CPU.Build.0 = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x64.ActiveCfg = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x64.Build.0 = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x86.ActiveCfg = Release|Any CPU - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x86.Build.0 = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|Any CPU.Build.0 = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|x64.ActiveCfg = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|x64.Build.0 = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|x86.ActiveCfg = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Debug|x86.Build.0 = Debug|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|Any CPU.ActiveCfg = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|Any CPU.Build.0 = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|x64.ActiveCfg = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|x64.Build.0 = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|x86.ActiveCfg = Release|Any CPU - {258327E9-431E-475C-933B-50893676E452}.Release|x86.Build.0 = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x64.ActiveCfg = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x64.Build.0 = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x86.ActiveCfg = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x86.Build.0 = Debug|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|Any CPU.Build.0 = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x64.ActiveCfg = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x64.Build.0 = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x86.ActiveCfg = Release|Any CPU - {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x86.Build.0 = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x64.ActiveCfg = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x64.Build.0 = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x86.ActiveCfg = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x86.Build.0 = Debug|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|Any CPU.Build.0 = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x64.ActiveCfg = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x64.Build.0 = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x86.ActiveCfg = Release|Any CPU - {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x86.Build.0 = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x64.ActiveCfg = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x64.Build.0 = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x86.ActiveCfg = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x86.Build.0 = Debug|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|Any CPU.Build.0 = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x64.ActiveCfg = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x64.Build.0 = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x86.ActiveCfg = Release|Any CPU - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x86.Build.0 = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x64.ActiveCfg = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x64.Build.0 = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x86.ActiveCfg = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x86.Build.0 = Debug|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|Any CPU.Build.0 = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x64.ActiveCfg = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x64.Build.0 = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x86.ActiveCfg = Release|Any CPU - {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x86.Build.0 = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|Any CPU.Build.0 = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|x64.ActiveCfg = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|x64.Build.0 = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|x86.ActiveCfg = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Debug|x86.Build.0 = Debug|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|Any CPU.ActiveCfg = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|Any CPU.Build.0 = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|x64.ActiveCfg = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|x64.Build.0 = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|x86.ActiveCfg = Release|Any CPU - {81111B26-74F6-4912-9084-7115FD119945}.Release|x86.Build.0 = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x64.ActiveCfg = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x64.Build.0 = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x86.ActiveCfg = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x86.Build.0 = Debug|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|Any CPU.Build.0 = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x64.ActiveCfg = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x64.Build.0 = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x86.ActiveCfg = Release|Any CPU - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x86.Build.0 = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x64.ActiveCfg = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x64.Build.0 = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x86.ActiveCfg = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x86.Build.0 = Debug|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x64.ActiveCfg = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x64.Build.0 = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x86.ActiveCfg = Release|Any CPU - {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x86.Build.0 = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x64.ActiveCfg = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x64.Build.0 = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x86.ActiveCfg = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x86.Build.0 = Debug|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|Any CPU.Build.0 = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x64.ActiveCfg = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x64.Build.0 = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x86.ActiveCfg = Release|Any CPU - {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x86.Build.0 = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x64.ActiveCfg = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x64.Build.0 = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x86.ActiveCfg = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x86.Build.0 = Debug|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|Any CPU.Build.0 = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x64.ActiveCfg = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x64.Build.0 = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x86.ActiveCfg = Release|Any CPU - {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x86.Build.0 = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x64.ActiveCfg = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x64.Build.0 = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x86.ActiveCfg = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x86.Build.0 = Debug|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|Any CPU.Build.0 = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x64.ActiveCfg = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x64.Build.0 = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x86.ActiveCfg = Release|Any CPU - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x86.Build.0 = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x64.ActiveCfg = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x64.Build.0 = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x86.ActiveCfg = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x86.Build.0 = Debug|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|Any CPU.Build.0 = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x64.ActiveCfg = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x64.Build.0 = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x86.ActiveCfg = Release|Any CPU - {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x86.Build.0 = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x64.ActiveCfg = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x64.Build.0 = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x86.ActiveCfg = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x86.Build.0 = Debug|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|Any CPU.Build.0 = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x64.ActiveCfg = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x64.Build.0 = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x86.ActiveCfg = Release|Any CPU - {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x86.Build.0 = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x64.ActiveCfg = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x64.Build.0 = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x86.ActiveCfg = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x86.Build.0 = Debug|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|Any CPU.Build.0 = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x64.ActiveCfg = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x64.Build.0 = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x86.ActiveCfg = Release|Any CPU - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x86.Build.0 = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x64.ActiveCfg = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x64.Build.0 = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x86.ActiveCfg = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x86.Build.0 = Debug|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|Any CPU.Build.0 = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x64.ActiveCfg = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x64.Build.0 = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x86.ActiveCfg = Release|Any CPU - {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x86.Build.0 = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x64.ActiveCfg = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x64.Build.0 = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x86.ActiveCfg = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x86.Build.0 = Debug|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|Any CPU.Build.0 = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x64.ActiveCfg = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x64.Build.0 = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x86.ActiveCfg = Release|Any CPU - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x86.Build.0 = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x64.ActiveCfg = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x64.Build.0 = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x86.ActiveCfg = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x86.Build.0 = Debug|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|Any CPU.Build.0 = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x64.ActiveCfg = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x64.Build.0 = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x86.ActiveCfg = Release|Any CPU - {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x86.Build.0 = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x64.ActiveCfg = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x64.Build.0 = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x86.ActiveCfg = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x86.Build.0 = Debug|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|Any CPU.Build.0 = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x64.ActiveCfg = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x64.Build.0 = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x86.ActiveCfg = Release|Any CPU - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x86.Build.0 = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x64.ActiveCfg = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x64.Build.0 = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x86.ActiveCfg = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x86.Build.0 = Debug|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|Any CPU.Build.0 = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x64.ActiveCfg = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x64.Build.0 = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x86.ActiveCfg = Release|Any CPU - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x86.Build.0 = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x64.ActiveCfg = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x64.Build.0 = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x86.ActiveCfg = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x86.Build.0 = Debug|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|Any CPU.Build.0 = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x64.ActiveCfg = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x64.Build.0 = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x86.ActiveCfg = Release|Any CPU - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x86.Build.0 = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x64.ActiveCfg = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x64.Build.0 = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x86.ActiveCfg = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x86.Build.0 = Debug|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|Any CPU.Build.0 = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x64.ActiveCfg = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x64.Build.0 = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x86.ActiveCfg = Release|Any CPU - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x86.Build.0 = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|Any CPU.Build.0 = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x64.ActiveCfg = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x64.Build.0 = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x86.ActiveCfg = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x86.Build.0 = Debug|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|Any CPU.ActiveCfg = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|Any CPU.Build.0 = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|x64.ActiveCfg = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|x64.Build.0 = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|x86.ActiveCfg = Release|Any CPU - {06DC817F-A936-4F83-8929-E00622B32245}.Release|x86.Build.0 = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x64.ActiveCfg = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x64.Build.0 = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x86.ActiveCfg = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x86.Build.0 = Debug|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|Any CPU.Build.0 = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x64.ActiveCfg = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x64.Build.0 = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x86.ActiveCfg = Release|Any CPU - {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x86.Build.0 = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x64.ActiveCfg = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x64.Build.0 = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x86.ActiveCfg = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x86.Build.0 = Debug|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|Any CPU.Build.0 = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x64.ActiveCfg = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x64.Build.0 = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x86.ActiveCfg = Release|Any CPU - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x86.Build.0 = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x64.ActiveCfg = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x64.Build.0 = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x86.ActiveCfg = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x86.Build.0 = Debug|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|Any CPU.Build.0 = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x64.ActiveCfg = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x64.Build.0 = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x86.ActiveCfg = Release|Any CPU - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x86.Build.0 = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x64.ActiveCfg = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x64.Build.0 = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x86.ActiveCfg = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x86.Build.0 = Debug|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|Any CPU.Build.0 = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x64.ActiveCfg = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x64.Build.0 = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x86.ActiveCfg = Release|Any CPU - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x86.Build.0 = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x64.ActiveCfg = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x64.Build.0 = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x86.ActiveCfg = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x86.Build.0 = Debug|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|Any CPU.Build.0 = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x64.ActiveCfg = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x64.Build.0 = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x86.ActiveCfg = Release|Any CPU - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x86.Build.0 = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x64.ActiveCfg = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x64.Build.0 = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x86.ActiveCfg = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x86.Build.0 = Debug|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|Any CPU.Build.0 = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x64.ActiveCfg = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x64.Build.0 = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x86.ActiveCfg = Release|Any CPU - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x86.Build.0 = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x64.ActiveCfg = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x64.Build.0 = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x86.ActiveCfg = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x86.Build.0 = Debug|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|Any CPU.Build.0 = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x64.ActiveCfg = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x64.Build.0 = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x86.ActiveCfg = Release|Any CPU - {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x86.Build.0 = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x64.ActiveCfg = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x64.Build.0 = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x86.ActiveCfg = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x86.Build.0 = Debug|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|Any CPU.Build.0 = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x64.ActiveCfg = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x64.Build.0 = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x86.ActiveCfg = Release|Any CPU - {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x86.Build.0 = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x64.ActiveCfg = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x64.Build.0 = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x86.ActiveCfg = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x86.Build.0 = Debug|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|Any CPU.Build.0 = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x64.ActiveCfg = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x64.Build.0 = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x86.ActiveCfg = Release|Any CPU - {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x86.Build.0 = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x64.ActiveCfg = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x64.Build.0 = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x86.ActiveCfg = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x86.Build.0 = Debug|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|Any CPU.Build.0 = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x64.ActiveCfg = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x64.Build.0 = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x86.ActiveCfg = Release|Any CPU - {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x86.Build.0 = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x64.ActiveCfg = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x64.Build.0 = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x86.ActiveCfg = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x86.Build.0 = Debug|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|Any CPU.Build.0 = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x64.ActiveCfg = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x64.Build.0 = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x86.ActiveCfg = Release|Any CPU - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x86.Build.0 = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x64.ActiveCfg = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x64.Build.0 = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x86.ActiveCfg = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x86.Build.0 = Debug|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|Any CPU.Build.0 = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x64.ActiveCfg = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x64.Build.0 = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x86.ActiveCfg = Release|Any CPU - {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x86.Build.0 = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x64.ActiveCfg = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x64.Build.0 = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x86.ActiveCfg = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x86.Build.0 = Debug|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|Any CPU.Build.0 = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x64.ActiveCfg = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x64.Build.0 = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x86.ActiveCfg = Release|Any CPU - {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x86.Build.0 = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x64.ActiveCfg = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x64.Build.0 = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x86.ActiveCfg = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x86.Build.0 = Debug|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|Any CPU.Build.0 = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x64.ActiveCfg = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x64.Build.0 = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x86.ActiveCfg = Release|Any CPU - {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x86.Build.0 = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x64.ActiveCfg = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x64.Build.0 = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x86.ActiveCfg = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x86.Build.0 = Debug|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|Any CPU.Build.0 = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x64.ActiveCfg = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x64.Build.0 = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x86.ActiveCfg = Release|Any CPU - {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x86.Build.0 = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x64.ActiveCfg = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x64.Build.0 = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x86.ActiveCfg = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x86.Build.0 = Debug|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|Any CPU.Build.0 = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x64.ActiveCfg = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x64.Build.0 = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x86.ActiveCfg = Release|Any CPU - {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {361838C4-72E2-1C48-5D76-CA6D1A861242} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {46D35B4F-6A04-47FF-958B-5E6A73FCC059} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {44A1241B-8ECF-4AFA-9972-452C39AD43D6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {85AB3BB7-C493-4387-B39A-EB299AC37312} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {93DB06DC-B254-48A9-8F2C-6130A5658F27} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {40094279-250C-42AE-992A-856718FEFBAC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {B2967228-F8F7-4931-B257-1C63CB58CE1D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {37F203A3-624E-4794-9C99-16CAC22C17DF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {3FF93987-A30A-4D50-8815-7CF3BB7CAE05} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {AACE8717-0760-42F2-A225-8FCCE876FB65} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {D0FB54BA-4D14-4A32-B09F-7EC94F369460} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {69C9E010-CBDD-4B89-84CF-7AB56D6A078A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {E471176A-E1F3-4DE5-8D30-0865903A217A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {FA013511-DF20-45F7-8077-EBA2D6224D64} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {B9F84697-54FE-4648-B173-EE3D904FFA4D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {6751A76C-8ED8-40F4-AE2B-069DB31395FE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {DDBFA2EF-9CAE-473F-A438-369CAC25C66A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {35350FAB-FC51-4FE8-81FB-011003134C37} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {C4A65377-22F7-4D15-92A3-4F05847D167E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {BDDE59E1-C643-4C87-8608-0F9A7A54DE09} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0CC116C8-A7E5-4B94-9688-32920177FF97} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {84DEDF05-A5BD-4644-86B9-6B7918FE3F31} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {9DEB1F54-94B5-40C4-AC44-220E680B016D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {7C3E87F2-93D8-4968-95E3-52C46947D46C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {31B05493-104F-437F-9FA7-CA5286CE697C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {937AF12E-D770-4534-8FF8-C59042609C2A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {5A028B04-9D76-470B-B5B3-766CE4CE860C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {749DE4C8-F733-43F8-B2A8-6649E71C7570} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {56D2C79E-2737-4FF9-9D19-150065F568D5} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {E41F6DC4-68B5-4EE3-97AE-801D725A2C13} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {26055403-C7F5-4709-8813-0F7387102791} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {258327E9-431E-475C-933B-50893676E452} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {42AF60C8-A5E1-40E0-86F8-98256364AF6F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {88C6A9C3-B433-4C36-8767-429C8C2396F8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {6B7099AB-01BF-4EC4-87D0-5C9C032266DE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {14C918EA-693E-41FE-ACAE-2E82DF077BEA} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {81111B26-74F6-4912-9084-7115FD119945} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {8D0F501D-01B1-4E24-958B-FAF35B267705} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {5BA91095-7F10-4717-B296-49DFBFC1C9C2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {99616566-4EF1-4DC7-B655-825FE43D203D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {A3B19095-2D95-4B09-B07E-2C082C72394B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {807837AF-B392-4589-ADF1-3FDB34D6C5BF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {64EAFDCF-8283-4D5C-AC78-7969D5FE926A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {68F4D8A1-E32F-487A-B460-325F36989BE3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {606C751B-7CF1-47CF-A25C-9248A55C814F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {5CCE0DB7-C115-4B21-A7AE-C8488C22A853} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {06DC817F-A936-4F83-8929-E00622B32245} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {2C999476-0291-4161-B3E9-1AA99A3B1139} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {50140A32-6D3C-47DB-983A-7166CBA51845} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {031979F2-6ABA-444F-A6A4-80115DC487CE} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {D71B0DA5-80A3-419E-898D-40E77A9A7F19} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {B2C877D9-B521-4901-8817-76B5DAA62FCE} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {7116DD6B-2491-49E1-AB27-5210E949F753} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {7DBE31A6-D2FD-499E-B675-4092723175AD} = {361838C4-72E2-1C48-5D76-CA6D1A861242} - {D99E6EAE-D278-4480-AA67-85F025383E47} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {D3825714-3DDA-44B7-A99C-5F3E65716691} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {FAB78D21-7372-48FE-B2C3-DE1807F1157D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.Authority", "StellaOps.Authority", "{361838C4-72E2-1C48-5D76-CA6D1A861242}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Abstractions", "StellaOps.Authority\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj", "{D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client", "StellaOps.Authority\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj", "{5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Configuration", "StellaOps.Configuration\StellaOps.Configuration.csproj", "{8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegration", "StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj", "{46D35B4F-6A04-47FF-958B-5E6A73FCC059}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.DependencyInjection", "StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj", "{44A1241B-8ECF-4AFA-9972-452C39AD43D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority", "StellaOps.Authority\StellaOps.Authority\StellaOps.Authority.csproj", "{85AB3BB7-C493-4387-B39A-EB299AC37312}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugins.Abstractions", "StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj", "{5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugin.Standard", "StellaOps.Authority\StellaOps.Authority.Plugin.Standard\StellaOps.Authority.Plugin.Standard.csproj", "{93DB06DC-B254-48A9-8F2C-6130A5658F27}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Plugin", "StellaOps.Plugin\StellaOps.Plugin.csproj", "{03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cli", "StellaOps.Cli\StellaOps.Cli.csproj", "{40094279-250C-42AE-992A-856718FEFBAC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cli.Tests", "StellaOps.Cli.Tests\StellaOps.Cli.Tests.csproj", "{B2967228-F8F7-4931-B257-1C63CB58CE1D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Testing", "StellaOps.Feedser.Testing\StellaOps.Feedser.Testing.csproj", "{6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Common", "StellaOps.Feedser.Source.Common\StellaOps.Feedser.Source.Common.csproj", "{37F203A3-624E-4794-9C99-16CAC22C17DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Storage.Mongo", "StellaOps.Feedser.Storage.Mongo\StellaOps.Feedser.Storage.Mongo.csproj", "{3FF93987-A30A-4D50-8815-7CF3BB7CAE05}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core", "StellaOps.Feedser.Core\StellaOps.Feedser.Core.csproj", "{AACE8717-0760-42F2-A225-8FCCE876FB65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Models", "StellaOps.Feedser.Models\StellaOps.Feedser.Models.csproj", "{4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Normalization", "StellaOps.Feedser.Normalization\StellaOps.Feedser.Normalization.csproj", "{85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core.Tests", "StellaOps.Feedser.Core.Tests\StellaOps.Feedser.Core.Tests.csproj", "{FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.Json", "StellaOps.Feedser.Exporter.Json\StellaOps.Feedser.Exporter.Json.csproj", "{D0FB54BA-4D14-4A32-B09F-7EC94F369460}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.Json.Tests", "StellaOps.Feedser.Exporter.Json.Tests\StellaOps.Feedser.Exporter.Json.Tests.csproj", "{69C9E010-CBDD-4B89-84CF-7AB56D6A078A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.TrivyDb", "StellaOps.Feedser.Exporter.TrivyDb\StellaOps.Feedser.Exporter.TrivyDb.csproj", "{E471176A-E1F3-4DE5-8D30-0865903A217A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.TrivyDb.Tests", "StellaOps.Feedser.Exporter.TrivyDb.Tests\StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj", "{FA013511-DF20-45F7-8077-EBA2D6224D64}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Merge", "StellaOps.Feedser.Merge\StellaOps.Feedser.Merge.csproj", "{B9F84697-54FE-4648-B173-EE3D904FFA4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Merge.Tests", "StellaOps.Feedser.Merge.Tests\StellaOps.Feedser.Merge.Tests.csproj", "{6751A76C-8ED8-40F4-AE2B-069DB31395FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Models.Tests", "StellaOps.Feedser.Models.Tests\StellaOps.Feedser.Models.Tests.csproj", "{DDBFA2EF-9CAE-473F-A438-369CAC25C66A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Normalization.Tests", "StellaOps.Feedser.Normalization.Tests\StellaOps.Feedser.Normalization.Tests.csproj", "{063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Acsc", "StellaOps.Feedser.Source.Acsc\StellaOps.Feedser.Source.Acsc.csproj", "{35350FAB-FC51-4FE8-81FB-011003134C37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cccs", "StellaOps.Feedser.Source.Cccs\StellaOps.Feedser.Source.Cccs.csproj", "{1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertBund", "StellaOps.Feedser.Source.CertBund\StellaOps.Feedser.Source.CertBund.csproj", "{C4A65377-22F7-4D15-92A3-4F05847D167E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertCc", "StellaOps.Feedser.Source.CertCc\StellaOps.Feedser.Source.CertCc.csproj", "{BDDE59E1-C643-4C87-8608-0F9A7A54DE09}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertFr", "StellaOps.Feedser.Source.CertFr\StellaOps.Feedser.Source.CertFr.csproj", "{0CC116C8-A7E5-4B94-9688-32920177FF97}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertFr.Tests", "StellaOps.Feedser.Source.CertFr.Tests\StellaOps.Feedser.Source.CertFr.Tests.csproj", "{E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertIn", "StellaOps.Feedser.Source.CertIn\StellaOps.Feedser.Source.CertIn.csproj", "{84DEDF05-A5BD-4644-86B9-6B7918FE3F31}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.CertIn.Tests", "StellaOps.Feedser.Source.CertIn.Tests\StellaOps.Feedser.Source.CertIn.Tests.csproj", "{9DEB1F54-94B5-40C4-AC44-220E680B016D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Common.Tests", "StellaOps.Feedser.Source.Common.Tests\StellaOps.Feedser.Source.Common.Tests.csproj", "{7C3E87F2-93D8-4968-95E3-52C46947D46C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cve", "StellaOps.Feedser.Source.Cve\StellaOps.Feedser.Source.Cve.csproj", "{C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Debian", "StellaOps.Feedser.Source.Distro.Debian\StellaOps.Feedser.Source.Distro.Debian.csproj", "{31B05493-104F-437F-9FA7-CA5286CE697C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Debian.Tests", "StellaOps.Feedser.Source.Distro.Debian.Tests\StellaOps.Feedser.Source.Distro.Debian.Tests.csproj", "{937AF12E-D770-4534-8FF8-C59042609C2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.RedHat", "StellaOps.Feedser.Source.Distro.RedHat\StellaOps.Feedser.Source.Distro.RedHat.csproj", "{5A028B04-9D76-470B-B5B3-766CE4CE860C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.RedHat.Tests", "StellaOps.Feedser.Source.Distro.RedHat.Tests\StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj", "{749DE4C8-F733-43F8-B2A8-6649E71C7570}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Suse", "StellaOps.Feedser.Source.Distro.Suse\StellaOps.Feedser.Source.Distro.Suse.csproj", "{56D2C79E-2737-4FF9-9D19-150065F568D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Suse.Tests", "StellaOps.Feedser.Source.Distro.Suse.Tests\StellaOps.Feedser.Source.Distro.Suse.Tests.csproj", "{E41F6DC4-68B5-4EE3-97AE-801D725A2C13}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Ubuntu", "StellaOps.Feedser.Source.Distro.Ubuntu\StellaOps.Feedser.Source.Distro.Ubuntu.csproj", "{285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.Ubuntu.Tests", "StellaOps.Feedser.Source.Distro.Ubuntu.Tests\StellaOps.Feedser.Source.Distro.Ubuntu.Tests.csproj", "{26055403-C7F5-4709-8813-0F7387102791}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ghsa", "StellaOps.Feedser.Source.Ghsa\StellaOps.Feedser.Source.Ghsa.csproj", "{0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Cisa", "StellaOps.Feedser.Source.Ics.Cisa\StellaOps.Feedser.Source.Ics.Cisa.csproj", "{258327E9-431E-475C-933B-50893676E452}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Kaspersky", "StellaOps.Feedser.Source.Ics.Kaspersky\StellaOps.Feedser.Source.Ics.Kaspersky.csproj", "{42AF60C8-A5E1-40E0-86F8-98256364AF6F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ics.Kaspersky.Tests", "StellaOps.Feedser.Source.Ics.Kaspersky.Tests\StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj", "{88C6A9C3-B433-4C36-8767-429C8C2396F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Jvn", "StellaOps.Feedser.Source.Jvn\StellaOps.Feedser.Source.Jvn.csproj", "{6B7099AB-01BF-4EC4-87D0-5C9C032266DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Jvn.Tests", "StellaOps.Feedser.Source.Jvn.Tests\StellaOps.Feedser.Source.Jvn.Tests.csproj", "{14C918EA-693E-41FE-ACAE-2E82DF077BEA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kev", "StellaOps.Feedser.Source.Kev\StellaOps.Feedser.Source.Kev.csproj", "{81111B26-74F6-4912-9084-7115FD119945}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kisa", "StellaOps.Feedser.Source.Kisa\StellaOps.Feedser.Source.Kisa.csproj", "{80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Nvd", "StellaOps.Feedser.Source.Nvd\StellaOps.Feedser.Source.Nvd.csproj", "{8D0F501D-01B1-4E24-958B-FAF35B267705}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Nvd.Tests", "StellaOps.Feedser.Source.Nvd.Tests\StellaOps.Feedser.Source.Nvd.Tests.csproj", "{5BA91095-7F10-4717-B296-49DFBFC1C9C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Osv", "StellaOps.Feedser.Source.Osv\StellaOps.Feedser.Source.Osv.csproj", "{99616566-4EF1-4DC7-B655-825FE43D203D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Osv.Tests", "StellaOps.Feedser.Source.Osv.Tests\StellaOps.Feedser.Source.Osv.Tests.csproj", "{EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ru.Bdu", "StellaOps.Feedser.Source.Ru.Bdu\StellaOps.Feedser.Source.Ru.Bdu.csproj", "{A3B19095-2D95-4B09-B07E-2C082C72394B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ru.Nkcki", "StellaOps.Feedser.Source.Ru.Nkcki\StellaOps.Feedser.Source.Ru.Nkcki.csproj", "{807837AF-B392-4589-ADF1-3FDB34D6C5BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Adobe", "StellaOps.Feedser.Source.Vndr.Adobe\StellaOps.Feedser.Source.Vndr.Adobe.csproj", "{64EAFDCF-8283-4D5C-AC78-7969D5FE926A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Adobe.Tests", "StellaOps.Feedser.Source.Vndr.Adobe.Tests\StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj", "{68F4D8A1-E32F-487A-B460-325F36989BE3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Apple", "StellaOps.Feedser.Source.Vndr.Apple\StellaOps.Feedser.Source.Vndr.Apple.csproj", "{4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Chromium", "StellaOps.Feedser.Source.Vndr.Chromium\StellaOps.Feedser.Source.Vndr.Chromium.csproj", "{606C751B-7CF1-47CF-A25C-9248A55C814F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Chromium.Tests", "StellaOps.Feedser.Source.Vndr.Chromium.Tests\StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj", "{0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Cisco", "StellaOps.Feedser.Source.Vndr.Cisco\StellaOps.Feedser.Source.Vndr.Cisco.csproj", "{CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Msrc", "StellaOps.Feedser.Source.Vndr.Msrc\StellaOps.Feedser.Source.Vndr.Msrc.csproj", "{5CCE0DB7-C115-4B21-A7AE-C8488C22A853}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Oracle", "StellaOps.Feedser.Source.Vndr.Oracle\StellaOps.Feedser.Source.Vndr.Oracle.csproj", "{A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Oracle.Tests", "StellaOps.Feedser.Source.Vndr.Oracle.Tests\StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj", "{06DC817F-A936-4F83-8929-E00622B32245}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Vmware", "StellaOps.Feedser.Source.Vndr.Vmware\StellaOps.Feedser.Source.Vndr.Vmware.csproj", "{2C999476-0291-4161-B3E9-1AA99A3B1139}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Vndr.Vmware.Tests", "StellaOps.Feedser.Source.Vndr.Vmware.Tests\StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj", "{476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Storage.Mongo.Tests", "StellaOps.Feedser.Storage.Mongo.Tests\StellaOps.Feedser.Storage.Mongo.Tests.csproj", "{0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.WebService", "StellaOps.Feedser.WebService\StellaOps.Feedser.WebService.csproj", "{0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.WebService.Tests", "StellaOps.Feedser.WebService.Tests\StellaOps.Feedser.WebService.Tests.csproj", "{8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Configuration.Tests", "StellaOps.Configuration.Tests\StellaOps.Configuration.Tests.csproj", "{C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugins.Abstractions.Tests", "StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions.Tests\StellaOps.Authority.Plugins.Abstractions.Tests.csproj", "{50140A32-6D3C-47DB-983A-7166CBA51845}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Tests", "StellaOps.Authority\StellaOps.Authority.Tests\StellaOps.Authority.Tests.csproj", "{031979F2-6ABA-444F-A6A4-80115DC487CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Plugin.Standard.Tests", "StellaOps.Authority\StellaOps.Authority.Plugin.Standard.Tests\StellaOps.Authority.Plugin.Standard.Tests.csproj", "{D71B0DA5-80A3-419E-898D-40E77A9A7F19}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Authority.Storage.Mongo", "StellaOps.Authority\StellaOps.Authority.Storage.Mongo\StellaOps.Authority.Storage.Mongo.csproj", "{B2C877D9-B521-4901-8817-76B5DAA62FCE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Abstractions.Tests", "StellaOps.Authority\StellaOps.Auth.Abstractions.Tests\StellaOps.Auth.Abstractions.Tests.csproj", "{08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.ServerIntegration.Tests", "StellaOps.Authority\StellaOps.Auth.ServerIntegration.Tests\StellaOps.Auth.ServerIntegration.Tests.csproj", "{7116DD6B-2491-49E1-AB27-5210E949F753}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Auth.Client.Tests", "StellaOps.Authority\StellaOps.Auth.Client.Tests\StellaOps.Auth.Client.Tests.csproj", "{7DBE31A6-D2FD-499E-B675-4092723175AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Kev.Tests", "StellaOps.Feedser.Source.Kev.Tests\StellaOps.Feedser.Source.Kev.Tests.csproj", "{D99E6EAE-D278-4480-AA67-85F025383E47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Cve.Tests", "StellaOps.Feedser.Source.Cve.Tests\StellaOps.Feedser.Source.Cve.Tests.csproj", "{D3825714-3DDA-44B7-A99C-5F3E65716691}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Ghsa.Tests", "StellaOps.Feedser.Source.Ghsa.Tests\StellaOps.Feedser.Source.Ghsa.Tests.csproj", "{FAB78D21-7372-48FE-B2C3-DE1807F1157D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography", "StellaOps.Cryptography\StellaOps.Cryptography.csproj", "{EADFA337-B0FA-4712-A24A-7C08235BDF98}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Cryptography.Tests", "StellaOps.Cryptography.Tests\StellaOps.Cryptography.Tests.csproj", "{110F7EC2-3149-4D1B-A972-E69E79F1EBF5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x64.ActiveCfg = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x64.Build.0 = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x86.ActiveCfg = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Debug|x86.Build.0 = Debug|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|Any CPU.Build.0 = Release|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x64.ActiveCfg = Release|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x64.Build.0 = Release|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x86.ActiveCfg = Release|Any CPU + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB}.Release|x86.Build.0 = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x64.Build.0 = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Debug|x86.Build.0 = Debug|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|Any CPU.Build.0 = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x64.ActiveCfg = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x64.Build.0 = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x86.ActiveCfg = Release|Any CPU + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2}.Release|x86.Build.0 = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x64.ActiveCfg = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x64.Build.0 = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x86.ActiveCfg = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Debug|x86.Build.0 = Debug|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|Any CPU.Build.0 = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x64.ActiveCfg = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x64.Build.0 = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x86.ActiveCfg = Release|Any CPU + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6}.Release|x86.Build.0 = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x64.ActiveCfg = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x64.Build.0 = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x86.ActiveCfg = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Debug|x86.Build.0 = Debug|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|Any CPU.Build.0 = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x64.ActiveCfg = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x64.Build.0 = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x86.ActiveCfg = Release|Any CPU + {46D35B4F-6A04-47FF-958B-5E6A73FCC059}.Release|x86.Build.0 = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x64.ActiveCfg = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x64.Build.0 = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x86.ActiveCfg = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Debug|x86.Build.0 = Debug|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|Any CPU.Build.0 = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x64.ActiveCfg = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x64.Build.0 = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x86.ActiveCfg = Release|Any CPU + {44A1241B-8ECF-4AFA-9972-452C39AD43D6}.Release|x86.Build.0 = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x64.ActiveCfg = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x64.Build.0 = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x86.ActiveCfg = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Debug|x86.Build.0 = Debug|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|Any CPU.Build.0 = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x64.ActiveCfg = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x64.Build.0 = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x86.ActiveCfg = Release|Any CPU + {85AB3BB7-C493-4387-B39A-EB299AC37312}.Release|x86.Build.0 = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x64.Build.0 = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x86.ActiveCfg = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Debug|x86.Build.0 = Debug|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|Any CPU.Build.0 = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x64.ActiveCfg = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x64.Build.0 = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x86.ActiveCfg = Release|Any CPU + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3}.Release|x86.Build.0 = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x64.ActiveCfg = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x64.Build.0 = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x86.ActiveCfg = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Debug|x86.Build.0 = Debug|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|Any CPU.Build.0 = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x64.ActiveCfg = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x64.Build.0 = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x86.ActiveCfg = Release|Any CPU + {93DB06DC-B254-48A9-8F2C-6130A5658F27}.Release|x86.Build.0 = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x64.ActiveCfg = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x64.Build.0 = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x86.ActiveCfg = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Debug|x86.Build.0 = Debug|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|Any CPU.Build.0 = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x64.ActiveCfg = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x64.Build.0 = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x86.ActiveCfg = Release|Any CPU + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A}.Release|x86.Build.0 = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x64.ActiveCfg = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x64.Build.0 = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x86.ActiveCfg = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Debug|x86.Build.0 = Debug|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|Any CPU.Build.0 = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|x64.ActiveCfg = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|x64.Build.0 = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|x86.ActiveCfg = Release|Any CPU + {40094279-250C-42AE-992A-856718FEFBAC}.Release|x86.Build.0 = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x64.Build.0 = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Debug|x86.Build.0 = Debug|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|Any CPU.Build.0 = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x64.ActiveCfg = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x64.Build.0 = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x86.ActiveCfg = Release|Any CPU + {B2967228-F8F7-4931-B257-1C63CB58CE1D}.Release|x86.Build.0 = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x64.Build.0 = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Debug|x86.Build.0 = Debug|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|Any CPU.Build.0 = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x64.ActiveCfg = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x64.Build.0 = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x86.ActiveCfg = Release|Any CPU + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9}.Release|x86.Build.0 = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x64.ActiveCfg = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x64.Build.0 = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x86.ActiveCfg = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Debug|x86.Build.0 = Debug|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|Any CPU.Build.0 = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x64.ActiveCfg = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x64.Build.0 = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x86.ActiveCfg = Release|Any CPU + {37F203A3-624E-4794-9C99-16CAC22C17DF}.Release|x86.Build.0 = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x64.ActiveCfg = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x64.Build.0 = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x86.ActiveCfg = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Debug|x86.Build.0 = Debug|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|Any CPU.Build.0 = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x64.ActiveCfg = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x64.Build.0 = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x86.ActiveCfg = Release|Any CPU + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05}.Release|x86.Build.0 = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x64.ActiveCfg = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x64.Build.0 = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x86.ActiveCfg = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Debug|x86.Build.0 = Debug|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|Any CPU.Build.0 = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x64.ActiveCfg = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x64.Build.0 = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x86.ActiveCfg = Release|Any CPU + {AACE8717-0760-42F2-A225-8FCCE876FB65}.Release|x86.Build.0 = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x64.ActiveCfg = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x64.Build.0 = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x86.ActiveCfg = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Debug|x86.Build.0 = Debug|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|Any CPU.Build.0 = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x64.ActiveCfg = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x64.Build.0 = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x86.ActiveCfg = Release|Any CPU + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D}.Release|x86.Build.0 = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x64.ActiveCfg = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x64.Build.0 = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x86.ActiveCfg = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Debug|x86.Build.0 = Debug|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|Any CPU.Build.0 = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x64.ActiveCfg = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x64.Build.0 = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x86.ActiveCfg = Release|Any CPU + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3}.Release|x86.Build.0 = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x64.Build.0 = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Debug|x86.Build.0 = Debug|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|Any CPU.Build.0 = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x64.ActiveCfg = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x64.Build.0 = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x86.ActiveCfg = Release|Any CPU + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0}.Release|x86.Build.0 = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x64.ActiveCfg = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x64.Build.0 = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x86.ActiveCfg = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Debug|x86.Build.0 = Debug|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|Any CPU.Build.0 = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x64.ActiveCfg = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x64.Build.0 = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x86.ActiveCfg = Release|Any CPU + {D0FB54BA-4D14-4A32-B09F-7EC94F369460}.Release|x86.Build.0 = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x64.ActiveCfg = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x64.Build.0 = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x86.ActiveCfg = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Debug|x86.Build.0 = Debug|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|Any CPU.Build.0 = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x64.ActiveCfg = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x64.Build.0 = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x86.ActiveCfg = Release|Any CPU + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A}.Release|x86.Build.0 = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x64.Build.0 = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Debug|x86.Build.0 = Debug|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|Any CPU.Build.0 = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x64.ActiveCfg = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x64.Build.0 = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x86.ActiveCfg = Release|Any CPU + {E471176A-E1F3-4DE5-8D30-0865903A217A}.Release|x86.Build.0 = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x64.ActiveCfg = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x64.Build.0 = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Debug|x86.Build.0 = Debug|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|Any CPU.Build.0 = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x64.ActiveCfg = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x64.Build.0 = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x86.ActiveCfg = Release|Any CPU + {FA013511-DF20-45F7-8077-EBA2D6224D64}.Release|x86.Build.0 = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x64.Build.0 = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Debug|x86.Build.0 = Debug|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|Any CPU.Build.0 = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x64.ActiveCfg = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x64.Build.0 = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x86.ActiveCfg = Release|Any CPU + {B9F84697-54FE-4648-B173-EE3D904FFA4D}.Release|x86.Build.0 = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x64.Build.0 = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x86.ActiveCfg = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Debug|x86.Build.0 = Debug|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|Any CPU.Build.0 = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x64.ActiveCfg = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x64.Build.0 = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x86.ActiveCfg = Release|Any CPU + {6751A76C-8ED8-40F4-AE2B-069DB31395FE}.Release|x86.Build.0 = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x64.ActiveCfg = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x64.Build.0 = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x86.ActiveCfg = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Debug|x86.Build.0 = Debug|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|Any CPU.Build.0 = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x64.ActiveCfg = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x64.Build.0 = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x86.ActiveCfg = Release|Any CPU + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A}.Release|x86.Build.0 = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x64.ActiveCfg = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x64.Build.0 = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x86.ActiveCfg = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Debug|x86.Build.0 = Debug|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|Any CPU.Build.0 = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x64.ActiveCfg = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x64.Build.0 = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x86.ActiveCfg = Release|Any CPU + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22}.Release|x86.Build.0 = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x64.ActiveCfg = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x64.Build.0 = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x86.ActiveCfg = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Debug|x86.Build.0 = Debug|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|Any CPU.Build.0 = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x64.ActiveCfg = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x64.Build.0 = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x86.ActiveCfg = Release|Any CPU + {35350FAB-FC51-4FE8-81FB-011003134C37}.Release|x86.Build.0 = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x64.ActiveCfg = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x64.Build.0 = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x86.ActiveCfg = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Debug|x86.Build.0 = Debug|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|Any CPU.Build.0 = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x64.ActiveCfg = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x64.Build.0 = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x86.ActiveCfg = Release|Any CPU + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519}.Release|x86.Build.0 = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x64.Build.0 = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Debug|x86.Build.0 = Debug|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|Any CPU.Build.0 = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x64.ActiveCfg = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x64.Build.0 = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x86.ActiveCfg = Release|Any CPU + {C4A65377-22F7-4D15-92A3-4F05847D167E}.Release|x86.Build.0 = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x64.ActiveCfg = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x64.Build.0 = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x86.ActiveCfg = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Debug|x86.Build.0 = Debug|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|Any CPU.Build.0 = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x64.ActiveCfg = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x64.Build.0 = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x86.ActiveCfg = Release|Any CPU + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09}.Release|x86.Build.0 = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x64.Build.0 = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Debug|x86.Build.0 = Debug|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|Any CPU.Build.0 = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x64.ActiveCfg = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x64.Build.0 = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x86.ActiveCfg = Release|Any CPU + {0CC116C8-A7E5-4B94-9688-32920177FF97}.Release|x86.Build.0 = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x64.Build.0 = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Debug|x86.Build.0 = Debug|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|Any CPU.Build.0 = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x64.ActiveCfg = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x64.Build.0 = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x86.ActiveCfg = Release|Any CPU + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E}.Release|x86.Build.0 = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x64.ActiveCfg = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x64.Build.0 = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x86.ActiveCfg = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Debug|x86.Build.0 = Debug|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|Any CPU.Build.0 = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x64.ActiveCfg = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x64.Build.0 = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x86.ActiveCfg = Release|Any CPU + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31}.Release|x86.Build.0 = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x64.ActiveCfg = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x64.Build.0 = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x86.ActiveCfg = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Debug|x86.Build.0 = Debug|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|Any CPU.Build.0 = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x64.ActiveCfg = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x64.Build.0 = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x86.ActiveCfg = Release|Any CPU + {9DEB1F54-94B5-40C4-AC44-220E680B016D}.Release|x86.Build.0 = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x64.ActiveCfg = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x64.Build.0 = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x86.ActiveCfg = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Debug|x86.Build.0 = Debug|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|Any CPU.Build.0 = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x64.ActiveCfg = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x64.Build.0 = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x86.ActiveCfg = Release|Any CPU + {7C3E87F2-93D8-4968-95E3-52C46947D46C}.Release|x86.Build.0 = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x64.Build.0 = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Debug|x86.Build.0 = Debug|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|Any CPU.Build.0 = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x64.ActiveCfg = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x64.Build.0 = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x86.ActiveCfg = Release|Any CPU + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2}.Release|x86.Build.0 = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x64.ActiveCfg = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x64.Build.0 = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x86.ActiveCfg = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Debug|x86.Build.0 = Debug|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|Any CPU.Build.0 = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x64.ActiveCfg = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x64.Build.0 = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x86.ActiveCfg = Release|Any CPU + {31B05493-104F-437F-9FA7-CA5286CE697C}.Release|x86.Build.0 = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x64.Build.0 = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Debug|x86.Build.0 = Debug|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|Any CPU.Build.0 = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x64.ActiveCfg = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x64.Build.0 = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x86.ActiveCfg = Release|Any CPU + {937AF12E-D770-4534-8FF8-C59042609C2A}.Release|x86.Build.0 = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x64.ActiveCfg = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x64.Build.0 = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x86.ActiveCfg = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Debug|x86.Build.0 = Debug|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|Any CPU.Build.0 = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x64.ActiveCfg = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x64.Build.0 = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x86.ActiveCfg = Release|Any CPU + {5A028B04-9D76-470B-B5B3-766CE4CE860C}.Release|x86.Build.0 = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|Any CPU.Build.0 = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x64.ActiveCfg = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x64.Build.0 = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x86.ActiveCfg = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Debug|x86.Build.0 = Debug|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|Any CPU.ActiveCfg = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|Any CPU.Build.0 = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x64.ActiveCfg = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x64.Build.0 = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x86.ActiveCfg = Release|Any CPU + {749DE4C8-F733-43F8-B2A8-6649E71C7570}.Release|x86.Build.0 = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x64.ActiveCfg = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x64.Build.0 = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x86.ActiveCfg = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Debug|x86.Build.0 = Debug|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|Any CPU.Build.0 = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x64.ActiveCfg = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x64.Build.0 = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x86.ActiveCfg = Release|Any CPU + {56D2C79E-2737-4FF9-9D19-150065F568D5}.Release|x86.Build.0 = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x64.ActiveCfg = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x64.Build.0 = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x86.ActiveCfg = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Debug|x86.Build.0 = Debug|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|Any CPU.Build.0 = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x64.ActiveCfg = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x64.Build.0 = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x86.ActiveCfg = Release|Any CPU + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13}.Release|x86.Build.0 = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|Any CPU.Build.0 = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x64.ActiveCfg = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x64.Build.0 = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x86.ActiveCfg = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Debug|x86.Build.0 = Debug|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|Any CPU.ActiveCfg = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|Any CPU.Build.0 = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x64.ActiveCfg = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x64.Build.0 = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x86.ActiveCfg = Release|Any CPU + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798}.Release|x86.Build.0 = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|x64.ActiveCfg = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|x64.Build.0 = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|x86.ActiveCfg = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Debug|x86.Build.0 = Debug|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|Any CPU.Build.0 = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|x64.ActiveCfg = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|x64.Build.0 = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|x86.ActiveCfg = Release|Any CPU + {26055403-C7F5-4709-8813-0F7387102791}.Release|x86.Build.0 = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x64.Build.0 = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Debug|x86.Build.0 = Debug|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|Any CPU.Build.0 = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x64.ActiveCfg = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x64.Build.0 = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x86.ActiveCfg = Release|Any CPU + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF}.Release|x86.Build.0 = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|Any CPU.Build.0 = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|x64.ActiveCfg = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|x64.Build.0 = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|x86.ActiveCfg = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Debug|x86.Build.0 = Debug|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|Any CPU.ActiveCfg = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|Any CPU.Build.0 = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|x64.ActiveCfg = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|x64.Build.0 = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|x86.ActiveCfg = Release|Any CPU + {258327E9-431E-475C-933B-50893676E452}.Release|x86.Build.0 = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x64.ActiveCfg = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x64.Build.0 = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x86.ActiveCfg = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Debug|x86.Build.0 = Debug|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|Any CPU.Build.0 = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x64.ActiveCfg = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x64.Build.0 = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x86.ActiveCfg = Release|Any CPU + {42AF60C8-A5E1-40E0-86F8-98256364AF6F}.Release|x86.Build.0 = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x64.Build.0 = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Debug|x86.Build.0 = Debug|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|Any CPU.Build.0 = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x64.ActiveCfg = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x64.Build.0 = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x86.ActiveCfg = Release|Any CPU + {88C6A9C3-B433-4C36-8767-429C8C2396F8}.Release|x86.Build.0 = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x64.Build.0 = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Debug|x86.Build.0 = Debug|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|Any CPU.Build.0 = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x64.ActiveCfg = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x64.Build.0 = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x86.ActiveCfg = Release|Any CPU + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE}.Release|x86.Build.0 = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x64.ActiveCfg = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x64.Build.0 = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x86.ActiveCfg = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Debug|x86.Build.0 = Debug|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|Any CPU.Build.0 = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x64.ActiveCfg = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x64.Build.0 = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x86.ActiveCfg = Release|Any CPU + {14C918EA-693E-41FE-ACAE-2E82DF077BEA}.Release|x86.Build.0 = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|x64.ActiveCfg = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|x64.Build.0 = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|x86.ActiveCfg = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Debug|x86.Build.0 = Debug|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|Any CPU.Build.0 = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|x64.ActiveCfg = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|x64.Build.0 = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|x86.ActiveCfg = Release|Any CPU + {81111B26-74F6-4912-9084-7115FD119945}.Release|x86.Build.0 = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x64.ActiveCfg = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x64.Build.0 = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x86.ActiveCfg = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Debug|x86.Build.0 = Debug|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|Any CPU.Build.0 = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x64.ActiveCfg = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x64.Build.0 = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x86.ActiveCfg = Release|Any CPU + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE}.Release|x86.Build.0 = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x64.ActiveCfg = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x64.Build.0 = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x86.ActiveCfg = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Debug|x86.Build.0 = Debug|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|Any CPU.Build.0 = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x64.ActiveCfg = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x64.Build.0 = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x86.ActiveCfg = Release|Any CPU + {8D0F501D-01B1-4E24-958B-FAF35B267705}.Release|x86.Build.0 = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x64.ActiveCfg = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x64.Build.0 = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x86.ActiveCfg = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Debug|x86.Build.0 = Debug|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|Any CPU.Build.0 = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x64.ActiveCfg = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x64.Build.0 = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x86.ActiveCfg = Release|Any CPU + {5BA91095-7F10-4717-B296-49DFBFC1C9C2}.Release|x86.Build.0 = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x64.ActiveCfg = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x64.Build.0 = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x86.ActiveCfg = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Debug|x86.Build.0 = Debug|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|Any CPU.Build.0 = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x64.ActiveCfg = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x64.Build.0 = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x86.ActiveCfg = Release|Any CPU + {99616566-4EF1-4DC7-B655-825FE43D203D}.Release|x86.Build.0 = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x64.ActiveCfg = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x64.Build.0 = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Debug|x86.Build.0 = Debug|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|Any CPU.Build.0 = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x64.ActiveCfg = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x64.Build.0 = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x86.ActiveCfg = Release|Any CPU + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0}.Release|x86.Build.0 = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x64.ActiveCfg = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x64.Build.0 = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Debug|x86.Build.0 = Debug|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|Any CPU.Build.0 = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x64.ActiveCfg = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x64.Build.0 = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x86.ActiveCfg = Release|Any CPU + {A3B19095-2D95-4B09-B07E-2C082C72394B}.Release|x86.Build.0 = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x64.ActiveCfg = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x64.Build.0 = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x86.ActiveCfg = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Debug|x86.Build.0 = Debug|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|Any CPU.Build.0 = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x64.ActiveCfg = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x64.Build.0 = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x86.ActiveCfg = Release|Any CPU + {807837AF-B392-4589-ADF1-3FDB34D6C5BF}.Release|x86.Build.0 = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x64.ActiveCfg = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x64.Build.0 = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x86.ActiveCfg = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Debug|x86.Build.0 = Debug|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|Any CPU.Build.0 = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x64.ActiveCfg = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x64.Build.0 = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x86.ActiveCfg = Release|Any CPU + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A}.Release|x86.Build.0 = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x64.ActiveCfg = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x64.Build.0 = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Debug|x86.Build.0 = Debug|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|Any CPU.Build.0 = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x64.ActiveCfg = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x64.Build.0 = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x86.ActiveCfg = Release|Any CPU + {68F4D8A1-E32F-487A-B460-325F36989BE3}.Release|x86.Build.0 = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x64.Build.0 = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Debug|x86.Build.0 = Debug|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|Any CPU.Build.0 = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x64.ActiveCfg = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x64.Build.0 = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x86.ActiveCfg = Release|Any CPU + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2}.Release|x86.Build.0 = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x64.ActiveCfg = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x64.Build.0 = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x86.ActiveCfg = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Debug|x86.Build.0 = Debug|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|Any CPU.Build.0 = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x64.ActiveCfg = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x64.Build.0 = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x86.ActiveCfg = Release|Any CPU + {606C751B-7CF1-47CF-A25C-9248A55C814F}.Release|x86.Build.0 = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x64.ActiveCfg = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x64.Build.0 = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x86.ActiveCfg = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Debug|x86.Build.0 = Debug|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x64.ActiveCfg = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x64.Build.0 = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x86.ActiveCfg = Release|Any CPU + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431}.Release|x86.Build.0 = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x64.Build.0 = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Debug|x86.Build.0 = Debug|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|Any CPU.Build.0 = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x64.ActiveCfg = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x64.Build.0 = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x86.ActiveCfg = Release|Any CPU + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC}.Release|x86.Build.0 = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x64.ActiveCfg = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x64.Build.0 = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x86.ActiveCfg = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Debug|x86.Build.0 = Debug|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|Any CPU.Build.0 = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x64.ActiveCfg = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x64.Build.0 = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x86.ActiveCfg = Release|Any CPU + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853}.Release|x86.Build.0 = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x64.Build.0 = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x86.ActiveCfg = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Debug|x86.Build.0 = Debug|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|Any CPU.Build.0 = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x64.ActiveCfg = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x64.Build.0 = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x86.ActiveCfg = Release|Any CPU + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E}.Release|x86.Build.0 = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x64.ActiveCfg = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x64.Build.0 = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x86.ActiveCfg = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Debug|x86.Build.0 = Debug|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|Any CPU.Build.0 = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|x64.ActiveCfg = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|x64.Build.0 = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|x86.ActiveCfg = Release|Any CPU + {06DC817F-A936-4F83-8929-E00622B32245}.Release|x86.Build.0 = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x64.Build.0 = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Debug|x86.Build.0 = Debug|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|Any CPU.Build.0 = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x64.ActiveCfg = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x64.Build.0 = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x86.ActiveCfg = Release|Any CPU + {2C999476-0291-4161-B3E9-1AA99A3B1139}.Release|x86.Build.0 = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x64.Build.0 = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x86.ActiveCfg = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Debug|x86.Build.0 = Debug|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|Any CPU.Build.0 = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x64.ActiveCfg = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x64.Build.0 = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x86.ActiveCfg = Release|Any CPU + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2}.Release|x86.Build.0 = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x64.ActiveCfg = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x64.Build.0 = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x86.ActiveCfg = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Debug|x86.Build.0 = Debug|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|Any CPU.Build.0 = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x64.ActiveCfg = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x64.Build.0 = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x86.ActiveCfg = Release|Any CPU + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98}.Release|x86.Build.0 = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x64.Build.0 = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Debug|x86.Build.0 = Debug|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|Any CPU.Build.0 = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x64.ActiveCfg = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x64.Build.0 = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x86.ActiveCfg = Release|Any CPU + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D}.Release|x86.Build.0 = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x64.Build.0 = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Debug|x86.Build.0 = Debug|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|Any CPU.Build.0 = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x64.ActiveCfg = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x64.Build.0 = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x86.ActiveCfg = Release|Any CPU + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE}.Release|x86.Build.0 = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x64.ActiveCfg = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x64.Build.0 = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x86.ActiveCfg = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Debug|x86.Build.0 = Debug|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|Any CPU.Build.0 = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x64.ActiveCfg = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x64.Build.0 = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x86.ActiveCfg = Release|Any CPU + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92}.Release|x86.Build.0 = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x64.ActiveCfg = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x64.Build.0 = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x86.ActiveCfg = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Debug|x86.Build.0 = Debug|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|Any CPU.Build.0 = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x64.ActiveCfg = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x64.Build.0 = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x86.ActiveCfg = Release|Any CPU + {50140A32-6D3C-47DB-983A-7166CBA51845}.Release|x86.Build.0 = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x64.Build.0 = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Debug|x86.Build.0 = Debug|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|Any CPU.Build.0 = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x64.ActiveCfg = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x64.Build.0 = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x86.ActiveCfg = Release|Any CPU + {031979F2-6ABA-444F-A6A4-80115DC487CE}.Release|x86.Build.0 = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x64.ActiveCfg = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x64.Build.0 = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x86.ActiveCfg = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Debug|x86.Build.0 = Debug|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|Any CPU.Build.0 = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x64.ActiveCfg = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x64.Build.0 = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x86.ActiveCfg = Release|Any CPU + {D71B0DA5-80A3-419E-898D-40E77A9A7F19}.Release|x86.Build.0 = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x64.Build.0 = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Debug|x86.Build.0 = Debug|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|Any CPU.Build.0 = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x64.ActiveCfg = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x64.Build.0 = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x86.ActiveCfg = Release|Any CPU + {B2C877D9-B521-4901-8817-76B5DAA62FCE}.Release|x86.Build.0 = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x64.ActiveCfg = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x64.Build.0 = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x86.ActiveCfg = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Debug|x86.Build.0 = Debug|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|Any CPU.Build.0 = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x64.ActiveCfg = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x64.Build.0 = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x86.ActiveCfg = Release|Any CPU + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278}.Release|x86.Build.0 = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x64.ActiveCfg = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x64.Build.0 = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x86.ActiveCfg = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Debug|x86.Build.0 = Debug|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|Any CPU.Build.0 = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x64.ActiveCfg = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x64.Build.0 = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x86.ActiveCfg = Release|Any CPU + {7116DD6B-2491-49E1-AB27-5210E949F753}.Release|x86.Build.0 = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x64.Build.0 = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Debug|x86.Build.0 = Debug|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|Any CPU.Build.0 = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x64.ActiveCfg = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x64.Build.0 = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x86.ActiveCfg = Release|Any CPU + {7DBE31A6-D2FD-499E-B675-4092723175AD}.Release|x86.Build.0 = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x64.ActiveCfg = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x64.Build.0 = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x86.ActiveCfg = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Debug|x86.Build.0 = Debug|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|Any CPU.Build.0 = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x64.ActiveCfg = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x64.Build.0 = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x86.ActiveCfg = Release|Any CPU + {D99E6EAE-D278-4480-AA67-85F025383E47}.Release|x86.Build.0 = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x64.ActiveCfg = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x64.Build.0 = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x86.ActiveCfg = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Debug|x86.Build.0 = Debug|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|Any CPU.Build.0 = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x64.ActiveCfg = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x64.Build.0 = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x86.ActiveCfg = Release|Any CPU + {D3825714-3DDA-44B7-A99C-5F3E65716691}.Release|x86.Build.0 = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x64.ActiveCfg = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x64.Build.0 = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x86.ActiveCfg = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Debug|x86.Build.0 = Debug|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|Any CPU.Build.0 = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x64.ActiveCfg = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x64.Build.0 = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x86.ActiveCfg = Release|Any CPU + {FAB78D21-7372-48FE-B2C3-DE1807F1157D}.Release|x86.Build.0 = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|x64.ActiveCfg = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|x64.Build.0 = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|x86.ActiveCfg = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Debug|x86.Build.0 = Debug|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|Any CPU.Build.0 = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|x64.ActiveCfg = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|x64.Build.0 = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|x86.ActiveCfg = Release|Any CPU + {EADFA337-B0FA-4712-A24A-7C08235BDF98}.Release|x86.Build.0 = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|x64.ActiveCfg = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|x64.Build.0 = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Debug|x86.Build.0 = Debug|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|Any CPU.Build.0 = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|x64.ActiveCfg = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|x64.Build.0 = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|x86.ActiveCfg = Release|Any CPU + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {361838C4-72E2-1C48-5D76-CA6D1A861242} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {D9F91EA0-8AF5-452A-86D8-52BACB2E39CB} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {5DBE2E9E-9905-47CE-B8DC-B25409AF1EF2} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {8BCEAAFC-9168-4CC0-AFDB-177E5F7C15C6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {46D35B4F-6A04-47FF-958B-5E6A73FCC059} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {44A1241B-8ECF-4AFA-9972-452C39AD43D6} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {85AB3BB7-C493-4387-B39A-EB299AC37312} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {5C5E91CA-3F98-4E9A-922B-F6415EABD1A3} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {93DB06DC-B254-48A9-8F2C-6130A5658F27} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {03CA315C-8AA1-4CEA-A28B-5EB35C586F4A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {40094279-250C-42AE-992A-856718FEFBAC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {B2967228-F8F7-4931-B257-1C63CB58CE1D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {6D52EC2B-0A1A-4693-A8EE-5AB32A4A3ED9} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {37F203A3-624E-4794-9C99-16CAC22C17DF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {3FF93987-A30A-4D50-8815-7CF3BB7CAE05} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {AACE8717-0760-42F2-A225-8FCCE876FB65} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {4AAD6965-E879-44AD-A8ED-E1D713A3CD6D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {85D82A87-1F4A-4B1B-8422-5B7A7B7704E3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {FE227DF2-875D-4BEA-A4E0-14EA7F3EC1D0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {D0FB54BA-4D14-4A32-B09F-7EC94F369460} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {69C9E010-CBDD-4B89-84CF-7AB56D6A078A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {E471176A-E1F3-4DE5-8D30-0865903A217A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {FA013511-DF20-45F7-8077-EBA2D6224D64} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {B9F84697-54FE-4648-B173-EE3D904FFA4D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {6751A76C-8ED8-40F4-AE2B-069DB31395FE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {DDBFA2EF-9CAE-473F-A438-369CAC25C66A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {063DE5E1-C8FE-47D0-A12A-22A25CDF2C22} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {35350FAB-FC51-4FE8-81FB-011003134C37} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {1BFC95B4-4C8A-44B2-903A-11FBCAAB9519} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {C4A65377-22F7-4D15-92A3-4F05847D167E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {BDDE59E1-C643-4C87-8608-0F9A7A54DE09} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0CC116C8-A7E5-4B94-9688-32920177FF97} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {E8862F6E-85C1-4FDB-AA92-0BB489B7EA1E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {84DEDF05-A5BD-4644-86B9-6B7918FE3F31} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {9DEB1F54-94B5-40C4-AC44-220E680B016D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {7C3E87F2-93D8-4968-95E3-52C46947D46C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {C0504D97-9BCD-4AE4-B0DC-B31C17B150F2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {31B05493-104F-437F-9FA7-CA5286CE697C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {937AF12E-D770-4534-8FF8-C59042609C2A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {5A028B04-9D76-470B-B5B3-766CE4CE860C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {749DE4C8-F733-43F8-B2A8-6649E71C7570} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {56D2C79E-2737-4FF9-9D19-150065F568D5} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {E41F6DC4-68B5-4EE3-97AE-801D725A2C13} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {285F1D0F-501F-4E2E-8FA0-F2CF28AE3798} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {26055403-C7F5-4709-8813-0F7387102791} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0C00D0DA-C4C3-4B23-941F-A3DB2DBF33AF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {258327E9-431E-475C-933B-50893676E452} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {42AF60C8-A5E1-40E0-86F8-98256364AF6F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {88C6A9C3-B433-4C36-8767-429C8C2396F8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {6B7099AB-01BF-4EC4-87D0-5C9C032266DE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {14C918EA-693E-41FE-ACAE-2E82DF077BEA} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {81111B26-74F6-4912-9084-7115FD119945} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {80E2D661-FF3E-4A10-A2DF-AFD4F3D433FE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {8D0F501D-01B1-4E24-958B-FAF35B267705} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {5BA91095-7F10-4717-B296-49DFBFC1C9C2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {99616566-4EF1-4DC7-B655-825FE43D203D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {EE3C03AD-E604-4C57-9B78-CF7F49FBFCB0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {A3B19095-2D95-4B09-B07E-2C082C72394B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {807837AF-B392-4589-ADF1-3FDB34D6C5BF} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {64EAFDCF-8283-4D5C-AC78-7969D5FE926A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {68F4D8A1-E32F-487A-B460-325F36989BE3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {4A3DA4AE-7B88-4674-A7E2-F5D42B8256F2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {606C751B-7CF1-47CF-A25C-9248A55C814F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0BE44D0A-CC4B-4E84-8AF3-D8D99551C431} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {CC4CCE5F-55BC-4745-A204-4FA92BC1BADC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {5CCE0DB7-C115-4B21-A7AE-C8488C22A853} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {A09C9E66-5496-47EC-8B23-9EEB7CBDC75E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {06DC817F-A936-4F83-8929-E00622B32245} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {2C999476-0291-4161-B3E9-1AA99A3B1139} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {476EAADA-1B39-4049-ABE4-CCAC21FFE9E2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0EF56124-E6E8-4E89-95DD-5A5D5FF05A98} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0DBB9FC4-2E46-4C3E-BE88-2A8DCB59DB7D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {8A40142F-E8C8-4E86-BE70-7DD4AB1FFDEE} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {C9D20F74-EE5F-4C9E-9AB1-C03E90B34F92} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {50140A32-6D3C-47DB-983A-7166CBA51845} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {031979F2-6ABA-444F-A6A4-80115DC487CE} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {D71B0DA5-80A3-419E-898D-40E77A9A7F19} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {B2C877D9-B521-4901-8817-76B5DAA62FCE} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {08D3B6D0-3CE8-4F24-A6F1-BCAB01AD6278} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {7116DD6B-2491-49E1-AB27-5210E949F753} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {7DBE31A6-D2FD-499E-B675-4092723175AD} = {361838C4-72E2-1C48-5D76-CA6D1A861242} + {D99E6EAE-D278-4480-AA67-85F025383E47} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {D3825714-3DDA-44B7-A99C-5F3E65716691} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {FAB78D21-7372-48FE-B2C3-DE1807F1157D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {EADFA337-B0FA-4712-A24A-7C08235BDF98} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {110F7EC2-3149-4D1B-A972-E69E79F1EBF5} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal diff --git a/global.json b/src/global.json similarity index 94% rename from global.json rename to src/global.json index 5a25782e..56e246dd 100644 --- a/global.json +++ b/src/global.json @@ -1,6 +1,6 @@ -{ - "sdk": { - "version": "10.0.100-preview.7.25380.108", - "rollForward": "latestMinor" - } -} +{ + "sdk": { + "version": "10.0.100-preview.7.25380.108", + "rollForward": "latestMinor" + } +}