diff --git a/.vscode/settings.json b/.vscode/settings.json index 8721e71f7..643ed9ae0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -80,7 +80,7 @@ "style": "underscore" }, "ul-style": { - "style": "asterisk" + "style": "consistent" } }, "[markdown]": { diff --git a/Application/EdFi.Ods.AdminApi.V3/E2E Tests/Bruno Admin API E2E 3.0/v3/Tenants/GET - Tenants EdOrgs by Tenant Name - Multitenant.bru b/Application/EdFi.Ods.AdminApi.V3/E2E Tests/Bruno Admin API E2E 3.0/v3/Tenants/GET - Tenants EdOrgs by Tenant Name - Multitenant.bru index afe9e2f39..22317aa0a 100644 --- a/Application/EdFi.Ods.AdminApi.V3/E2E Tests/Bruno Admin API E2E 3.0/v3/Tenants/GET - Tenants EdOrgs by Tenant Name - Multitenant.bru +++ b/Application/EdFi.Ods.AdminApi.V3/E2E Tests/Bruno Admin API E2E 3.0/v3/Tenants/GET - Tenants EdOrgs by Tenant Name - Multitenant.bru @@ -171,7 +171,7 @@ script:post-response { const objectName = "DataStore"; const response = await axios.delete(`${API_URL}/v3/dataStores/${bru.getVar("created-DataStoreId")}`, axiosConfig); - if (response.status === 200) { + if (response.status === 200 || response.status === 204) { console.log(`✅ ${objectName} with ID ${bru.getVar("created-DataStoreId")} removed successfully`); bru.deleteVar("created-DataStoreId"); } else { diff --git a/docs/Healthcheck-Worker-Removal.md b/docs/Healthcheck-Worker-Removal.md deleted file mode 100644 index 479c0f36c..000000000 --- a/docs/Healthcheck-Worker-Removal.md +++ /dev/null @@ -1,50 +0,0 @@ -# Healthcheck Worker Removal Documentation - -## Overview - -This document outlines the changes made to remove the Healthcheck Worker endpoints from the Admin API as part of ticket ADMINAPI-1295. These changes were implemented to streamline the API and remove features that were no longer needed. - -## Components Removed - -The following components were removed from the codebase: - -1. **Healthcheck Worker Endpoints**: - * Healthcheck worker-specific endpoints in the Admin API - * Associated route registrations and handlers - -2. **Scopes**: - * Any specialized scopes related to healthcheck worker functionality - * Authorization policies specific to healthcheck workers - -## Restoring Functionality (If Needed) - -If the Healthcheck Worker functionality needs to be restored in the future, follow these steps: - -1. **Revert the Git Commit**: - - ```bash - git revert - ``` - - Where `` is the commit hash for the ADMINAPI-1295 changes. - -2. **Alternative: Cherry-Pick Previous Implementation**: - If you need to selectively restore parts of the healthcheck worker functionality: - - ```bash - git checkout -- Application/EdFi.Ods.AdminApi.AdminConsole/Features/Healthcheck/ - ``` - -## Testing After Restoration - -1. **API Functionality**: - * Test healthcheck worker endpoints - * Verify they respond with expected data - -2. **Integration Testing**: - * Verify that healthcheck worker functionality works properly with client applications - -## References - -* Ticket: ADMINAPI-1295 -* Commit: 5e3d484d2672c77b3ca459befbc490b140b43b41 diff --git a/docs/Instance-Worker-Removal.md b/docs/Instance-Worker-Removal.md deleted file mode 100644 index b1db83802..000000000 --- a/docs/Instance-Worker-Removal.md +++ /dev/null @@ -1,80 +0,0 @@ -# Instance Management Worker Removal - -## Overview - -This document outlines the changes made to remove the Instance Worker endpoints from the Admin API as part of ticket ADMINAPI-1294. These changes were implemented to streamline the API and remove features that were no longer needed. - -## Components Removed - -The following components were removed from the codebase: - -1. **Instance Worker Endpoints**: - * Instance worker-specific endpoints in the Admin API - * Associated route registrations and handlers - * Endpoints included: - * `/adminconsole/odsInstances` (GET) - Used to list available ODS instances - * `/adminconsole/odsInstances/{id}` (GET) - Used to get specific ODS instance metadata - * `/adminconsole/odsInstances` (POST) - Used to create a new ODS instance request - * `/adminconsole/odsInstances` (PUT) - Used to update an existing ODS instance - * `/adminconsole/odsInstances` (DELETE) - Used to request deletion of an ODS instanceemoval - * `/adminconsole/instances` (GET) - Used to retrieve instances for worker - * `/adminconsole/instances/{id}` (GET) - Used to get specific instance details - * `/adminconsole/instances` (POST) - Used to create new instance - * `/adminconsole/instances` (PUT) - Used to update instance - * `/adminconsole/instances` (DELETE) - Used to delete instance - * `/adminconsole/instances/{instanceId}/completed` - Used to mark instance creation as completed - * `/adminconsole/instances/{instanceId}/deletefailed` - Used to mark instance deletion as failed - * `/adminconsole/instances/{instanceId}/renameFailed` - Used to mark instance rename as failed - * `/adminconsole/instances/{instanceId}/renamed` - Used to mark instance rename as completed - * `/adminconsole/instances/{instanceId}/deleted` - Used to mark instance deletion as completed - -2. **Supporting Code**: - * Instance worker models and DTOs (e.g., `InstanceWorkerModel`) - * Feature handlers (e.g., `WorkerInstanceRenamed`, `WorkerInstanceDeleted`, `WorkerInstanceRenameFailed`) - * Instance management services and commands: - * Instance creation/completion commands - * Instance deletion commands - * Instance rename commands - * Status transition handlers for instance lifecycle management - -3. **Database Components**: - * Tables: - * `adminconsole.Instances` - Stored instance management information and status tracking for the worker queue - * `adminconsole.OdsInstanceContexts` - Instance context mapping that duplicated information already in `dbo.OdsInstanceContext` - * `adminconsole.OdsInstanceDerivatives` - Instance derivative information that duplicated data in `dbo.OdsInstanceDerivative` - -These database tables primarily functioned as a job queue system for the Instance Management Worker, tracking the status of instance operations (creation, deletion, renaming). This information was largely redundant with the core data already maintained in the standard `dbo` schema tables, which ultimately contained the authoritative instance data used by the ODS/API. - -## Restoring Functionality (If Needed) - -If the Instance Worker functionality needs to be restored in the future, follow these steps: - -1. **Revert the Git Commit**: - - ```bash - git revert - ``` - - Where `` is the commit hash for the ADMINAPI-1294 changes. - -2. **Alternative: Cherry-Pick Previous Implementation**: - If you need to selectively restore parts of the instance worker functionality: - - ```bash - git checkout -- Application/EdFi.Ods.AdminApi.AdminConsole/Features/WorkerInstances/ - ``` - -## Testing After Restoration - -1. **API Functionality**: - * Test instance worker endpoints - * Verify they respond with expected data - -2. **Integration Testing**: - * Verify that instance worker functionality works properly with client applications - * Test the full lifecycle of instance creation, renaming, and deletion - -## References - -* Ticket: ADMINAPI-1294 -* Commit: [Include the commit hash once the changes are merged] diff --git a/docs/PRD-ODS-Admin-API-2.3.md b/docs/PRD-ODS-Admin-API-2.3.md new file mode 100644 index 000000000..2ff7a3aa6 --- /dev/null +++ b/docs/PRD-ODS-Admin-API-2.3.md @@ -0,0 +1,396 @@ +# Product Requirements Document: ODS Admin API 2.3 + +> **Status**: completed \ +> **Version**: 2.3 \ +> **Owner**: Stephen Fuqua \ +> **Product**: Ed-Fi ODS Admin API \ +> **Repository**: `Ed-Fi-Alliance-OSS/ODS-Admin-API` \ +> **Jira**: `ADMINAPI` + +## 1. Product Overview + +The ODS Admin API exists because platform hosts need a programmatic, automatable way to administer Ed-Fi ODS/API configuration without directly manipulating `EdFi_Admin` and `EdFi_Security` database tables. The product serves as a headless administrative control plane by exposing REST endpoints for managing vendors, applications, API clients, profiles, claim sets, ODS instances, ODS instance contexts, and authorization metadata used by the Ed-Fi ODS/API. + +The REST API implemented by this PRD is called the Ed-Fi Management API. As such, this PRD - in combination with the Ed-Fi Alliance API Design Guidelines - implicitly serves as the requirements for the Ed-Fi Management API. + +### 1.1 Strategic Alignment + +ODS Admin API serves the Ed-Fi community by making ODS/API administration repeatable, auditable, and scriptable. This matters because education agencies and platform hosts operate ODS/API installations across environments, database engines, vendors, and tenants. A documented API is safer than ad hoc SQL changes and better aligned with automation, CI/CD, and least-privilege operational practices. + +Current-state alignment: + +- The API supports ODS/API 7.x and greater as the primary target for the 2.x line. +- Legacy support is preserved through the v1 code path and compatibility documentation for ODS/API 6.x. +- Docker documentation supports local development and testing for PostgreSQL and SQL Server-oriented deployments, including single-tenant and multi-tenant compose variants. + +### 1.2 Target Users and Personas + +Admin API is a backend service. Most of the personas below reach it indirectly, through Admin App or through their own automation, rather than through a UI of its own. Persona definitions for administrator, operator, and vendor-side roles are adopted from the Admin App PRD (`Ed-Fi-AdminApp/docs/PRD-AdminApp-v4.0.md`) so the two products describe the same people consistently. A few personas below are specific to direct use of Admin API and have no Admin App equivalent. + +#### Platform Host System Administrator + +This administrator is likely in a hybrid IT role, serving both as a programmer and an IT administrator, responsible for deployment and maintenance of the Ed-Fi Technology Suite. Deploys Admin API into Docker, IIS, or hosted environments; configures database engines, connection strings, signing keys, path bases, logging, rate limiting, Swagger, and tenant settings. + +In addition, configures vendors, applications, client credentials, claim sets, profiles, and ODS instances needed to operate a deployment. Alternatively, builds integration points to the ODS Admin API, allowing other personas to interact with the application indirectly. + +#### Security Administrator + +Reviews and modifies claim sets, resource claim actions, and authorization strategy overrides directly through ODS Admin API, or indirectly through an interface provided by the System Administrator. + +#### Developer + +Uses Swagger, HTTP examples, E2E tests, and DB tests to validate API behavior during extension or upgrade work. Uses ODS Admin API as a backend service for UI-driven administration, especially around tenants, applications, credentials, and health-oriented operational views. + +### 1.3 Jobs to Be Done + +JTBD 1 and JTBD 5 describe jobs shared with Admin App; the language is aligned with the Admin App PRD so both documents describe the same underlying job consistently. The remaining entries are specific to direct or automated use of Admin API. + +#### JTBD 1: Issue Credentials + +**Personas**: all + +**When** supporting a new application integration to an existing Ed-Fi API deployment, \ +**I want** to perform basic CRUD operations for Vendors, Applications, API Clients, and Credentials, \ +**so that** I can distribute OAuth credentials ("key and secret") to the vendor. + +**How Admin API Helps**: Exposes REST endpoints for the full CRUD lifecycle of vendors, applications, and API clients, generating a `client_id`/`client_secret` pair on creation (FR-VENDOR-1, FR-APP-1, FR-APP-5, FR-CLIENT-1). + +**Variations**: Resetting (rotating) credentials for an existing application or API client preserves the key and generates a new secret, so access can continue without re-onboarding (FR-APP-10, FR-CLIENT-6). + +#### JTBD 2: Register Administrative Automation Clients + +**Personas**: Platform host system administrator, Developer + +**When** standing up an Ed-Fi ODS/API deployment, \ +**I want** to register an administrative API client and obtain a token, \ +**so that** automation — including Admin App — can call protected Admin API endpoints. + +**How Admin API Helps**: Supports OAuth 2.0 client-credentials token issuance (`POST /connect/token`) and self-contained client registration (`POST /connect/register`) when enabled (FR-AUTH-1, FR-AUTH-2). + +#### JTBD 3: Configure ODS Instances + +**Personas**: Platform host system administrator, Developer + +**When** launching a school year database ("instance") for a tenant deployment, \ +**I want** to create and manage ODS instance, context, and derivative records, \ +**so that** applications can be associated with the correct database instance. + +**How Admin API Helps**: Provides CRUD endpoints for ODS instances, ODS instance contexts, and ODS instance derivatives (FR-ODS-1 through FR-ODS-10). + +> [!NOTE] +> Unlike Admin App's broader "configure new environments/instances" job, Admin API does not create environments or tenants — tenant configuration is static (see OUT-6). This job is limited to ODS instance records within an already-configured tenant. + +#### JTBD 4: Manage Authorization + +**Personas**: Security administrator, Platform host system administrator + +**When** I manage authorization for a deployment, \ +**I want** to view and edit resource claims, actions, authorization strategies, claim sets, and claim-set resource claim actions, \ +**so that** access rules are visible and editable. + +**How Admin API Helps**: Exposes read/write endpoints for claim sets and read-only endpoints for resource claims, actions, and authorization strategies (FR-CLAIM-1 through FR-CLAIM-8). Admin App currently only supports viewing, exporting, and importing claim sets, so detailed editing depends on Admin API directly until Admin App's in-app claim set editor ships. + +#### JTBD 5: Transfer Claim Sets Between Environments + +**Personas**: Platform host system administrator + +**When** I have a claim set that is configured correctly in one environment, \ +**I want** to copy, export, and import that claim set to another environment or tenant, \ +**so that** I can avoid manual claim set configuration and reduce the risk of errors when setting up a new environment. + +**How Admin API Helps**: Provides copy, export, and import endpoints for claim sets that preserve enough structure to move authorization configuration between environments (FR-CLAIM-1, FR-CLAIM-8). + +**Examples**: + +1. Configure a claim set in a staging environment, then export and import it to production. +2. Export a claim set used in one tenant and copy it to another tenant. + +#### JTBD 6: Isolate Multi-Tenant Administration + +**Personas**: Platform host system administrator + +**When** I operate a multi-tenant deployment, \ +**I want** tenant-aware requests to resolve the correct `EdFi_Admin` and `EdFi_Security` connection strings, \ +**so that** each tenant's administration remains isolated. + +**How Admin API Helps**: Resolves tenant context from the `tenant` request header and routes to tenant-specific connection strings (FR-TENANT-1 through FR-TENANT-10). + +#### JTBD 7: Troubleshoot Operational Issues + +**Personas**: Platform host system administrator, Developer + +**When** I troubleshoot the service, \ +**I want** health checks, request logging, trace IDs, and structured error responses, \ +**so that** operational failures can be diagnosed quickly. + +**How Admin API Helps**: Exposes a `/health` endpoint, correlates logs with trace identifiers, and returns structured error responses (FR-ERROR-1 through FR-ERROR-8, NFR-REL-1 through NFR-REL-3). + +#### JTBD 8: Upgrade Between Admin API Versions + +**Personas**: Platform host system administrator, Developer + +**When** I migrate between Admin API versions, \ +**I want** predictable binary replacement and deployment-specific instructions, \ +**so that** upgrades do not require undocumented manual steps. + +**How Admin API Helps**: Documents migration guidance for Docker and IIS installation patterns and accepts a release-supplied API version in build automation (NFR-COMPAT-3, NFR-COMPAT-4). + +## 2. Enterprise Architecture + +Admin API sits between administrators, automation clients, optional UI clients, and the ODS/API administrative data stores. It writes to `EdFi_Admin` and `EdFi_Security`, and the ODS/API reads those same stores to enforce API access. + +External systems and dependencies: + +- Ed-Fi ODS/API: The downstream API whose vendors, applications, credentials, claim sets, profiles, and authorization rules are administered. Supports both Ed-Fi ODS/API v6 and Ed-Fi ODS/API v7. +- `EdFi_Admin` database: Stores administrative entities such as vendors, applications, API clients, ODS instances, profile data, and OpenIddict token server tables. All database objects under the schema `dbo` are owned by the Ed-Fi ODS/API. +- `EdFi_Security` database: Stores claim sets, resource claims, resource claim actions, and authorization strategies used by ODS/API authorization. All database objects under the schema `dbo` are owned by the Ed-Fi ODS/API. + +```plaintext ++----------------------+ Bearer JWT +-------------+ +| Client application | -------------------------------> | Ed-Fi ODS | +| (e.g. Admin App) | GET | POST | PUT | DELETE | Admin API | ++----------------------+ +-------------+ + | r/w + ------------------------- + | | + v v + +--------------+ +-----------------+ + | EdFi_Admin | | EdFi_Security | + | (database) | | (database) | + +--------------+ +-----------------+ +``` + +> [!WARNING] +> The ODS Admin API does _not_ support Ed-Fi API v8 (aka DMS). Ed-Fi API v8 has its own implementation of the Management API, called Configuration Management Service. + +## 3. Functional Requirements + +### 3.1 API mode, versioning, and endpoint registration + +- **FR-VERSION-1** The application SHALL support an `AdminApiMode` configuration value that selects v1 (supporting ODS/API v6) or v2 (supporting ODS/API v7) behavior at startup +- **FR-VERSION-2** The application SHALL reject requests whose version prefix conflicts with the configured API mode. +- **FR-VERSION-3** The application SHALL expose v2 functionality under `/v2/*` routes when running in v2 mode. +- **FR-VERSION-4** The application SHALL expose an anonymous `GET /` endpoint that returns version and build metadata for the active API mode ("Discovery endpoint"). +- **FR-VERSION-5** The application SHALL generate Swagger/OpenAPI descriptions for all configured API versions when Swagger is enabled. + +### 3.2 Authentication, registration, and tokens + +- **FR-AUTH-1** The application SHALL support OAuth 2.0 client credentials token issuance through `POST /connect/token`. +- **FR-AUTH-2** The application SHALL support self-contained client registration through `POST /connect/register` when registration is enabled. +- **FR-AUTH-3** The registration endpoint SHALL reject duplicate client IDs. +- **FR-AUTH-4** The registration endpoint SHALL require client ID, client secret, and display name. +- **FR-AUTH-5** The registration endpoint SHALL require client secrets to contain lowercase, uppercase, numeric, and special characters and be 32 to 128 characters long. +- **FR-AUTH-6** Access tokens SHALL use the configured issuer URL and require the `edfi_admin_api/full_access` scope. +- **FR-AUTH-7** Protected endpoints SHALL require authenticated bearer tokens with the implemented full-access scope unless explicitly marked anonymous. +- **FR-AUTH-8** Production deployments SHALL provide a signing key when not running in development mode. +- **FR-AUTH-9** The application SHALL support built-in authentication without requiring an external identity provider. +- **FR-AUTH-10** The product MAY allow use of an external OIDC provider, but the authorization model still has one scope. + +### 3.3 Vendor Management + +- **FR-VENDOR-1** The API SHALL allow authorized clients to list, retrieve, create, update, and delete vendors. +- **FR-VENDOR-2** Vendor records SHALL include company name, namespace prefixes, contact name, and contact email address. +- **FR-VENDOR-3** Vendor namespace prefixes SHALL support multiple comma-separated values. +- **FR-VENDOR-4** Vendor namespace prefix handling SHALL trim whitespace and ignore empty prefixes. +- **FR-VENDOR-5** Deleting a vendor SHALL remove associated user/contact records and applications through the existing command behavior. +- **FR-VENDOR-6** Vendor endpoints SHALL support pagination, filtering, ordering, and direction parameters where documented in OpenAPI. + +### 3.4 Application Management + +- **FR-APP-1** The API SHALL allow authorized clients to list, retrieve, create, update, delete, and reset credentials for applications. +- **FR-APP-2** Creating an application SHALL require application name, vendor ID, claim set name, education organization IDs, and ODS instance IDs. +- **FR-APP-3** Creating an application SHALL validate that the vendor ID exists. +- **FR-APP-4** Creating or updating an application SHALL validate profile IDs and ODS instance IDs when provided. +- **FR-APP-5** Creating an application SHALL create a related API client and return generated key and secret material in the creation result. +- **FR-APP-6** Application names SHALL be constrained by the maximum length enforced by validation constants. +- **FR-APP-7** When duplicate prevention is enabled, the API SHALL reject duplicate applications based on the configured composite comparison of name, vendor, claim set, profiles, and ODS instances. +- **FR-APP-8** Updating an application SHALL support changes to name, claim set, vendor, profile IDs, education organization IDs, ODS instance IDs, and enabled state. +- **FR-APP-9** Deleting an application SHALL remove related API clients, ODS instance associations, education organization associations, and client access tokens while preserving profile records. +- **FR-APP-10** Application credential reset behavior SHALL preserve the key and generate a new secret according to the implemented command behavior. + +### 3.5 API client Management + +- **FR-CLIENT-1** The API SHALL allow authorized clients to list, retrieve, create, update, delete, and reset credentials for API clients. +- **FR-CLIENT-2** Creating an API client SHALL require name, approval state, application ID, and at least one ODS instance ID. +- **FR-CLIENT-3** Creating an API client SHALL validate that the application ID exists. +- **FR-CLIENT-4** Creating or updating an API client SHALL validate ODS instance IDs when provided. +- **FR-CLIENT-5** API client names SHALL be limited to the maximum length enforced by validation constants. +- **FR-CLIENT-6** API client reset SHALL keep the existing key and generate a new secret. +- **FR-CLIENT-7** API client reset SHALL return a not-found error when the API client does not exist. +- **FR-CLIENT-8** Deleting an API client SHALL delete related API client ODS instance associations. +- **FR-CLIENT-9** API client models SHALL default to approved, active credentials unless request or persistence state says otherwise. + +### 3.6 ODS Instance, Context, and Derivative Management + +- **FR-ODS-1** The API SHALL allow authorized clients to list, retrieve, create, update, and delete ODS instance records. +- **FR-ODS-2** ODS instances SHALL include name, optional instance type, and connection string. +- **FR-ODS-3** ODS instance create and update behavior SHALL reject invalid connection strings when validation identifies them. +- **FR-ODS-4** The API SHALL allow authorized clients to manage ODS instance contexts. +- **FR-ODS-5** ODS instance contexts SHALL require ODS instance ID, context key, and context value. +- **FR-ODS-6** ODS instance context uniqueness SHALL be enforced by ODS instance ID and context key. +- **FR-ODS-7** The API SHALL allow authorized clients to manage ODS instance derivatives. +- **FR-ODS-8** ODS instance derivatives SHALL require ODS instance ID and derivative type. +- **FR-ODS-9** ODS instance derivative type SHALL be restricted to the allowed derivative types documented in validation constants. +- **FR-ODS-10** ODS instance derivative uniqueness SHALL be enforced by ODS instance ID and derivative type. + +### 3.7 Profiles + +- **FR-PROFILE-1** The API SHALL allow authorized clients to list, retrieve, create, update, and delete profiles. +- **FR-PROFILE-2** Profile records SHALL include a name and profile definition. +- **FR-PROFILE-3** Profile creation and update SHALL prevent duplicate names where enforced by current validators and commands. +- **FR-PROFILE-4** Profile definitions SHALL support the XML content-filter format used by ODS/API profiles. +- **FR-PROFILE-5** Deleting an application SHALL NOT delete referenced profile records. + +### 3.8 Claim Sets and Authorization Rules + +- **FR-CLAIM-1** The API SHALL allow authorized clients to list, retrieve, create, update, delete, copy, export, and import claim sets. +- **FR-CLAIM-2** Claim set creation SHALL store claim set name and default flags for application-only and Ed-Fi preset usage. +- **FR-CLAIM-3** Claim set names SHALL be unique where enforced by current validation and command behavior. +- **FR-CLAIM-4** The API SHALL allow authorized clients to add, update, and delete resource claim action associations on claim sets. +- **FR-CLAIM-5** The API SHALL allow authorized clients to override and reset authorization strategies for resource claim actions. +- **FR-CLAIM-6** The API SHALL expose read-only endpoints for resource claims, resource claim actions, and authorization strategies. +- **FR-CLAIM-7** A resource claim action association SHALL include at least one action when required by validation. +- **FR-CLAIM-8** Claim set import and export SHALL preserve enough claim set structure to move authorization configuration between environments. + +### 3.9 Tenants and Multi-Tenancy + +- **FR-TENANT-1** The API SHALL support operating in either a single-tenant or multi-tenant mode. +- **FR-TENANT-2** The application SHALL treat single-tenant mode as the default mode. +- **FR-TENANT-3** In multi-tenant v2 mode, the API SHALL resolve tenant context from the `tenant` request header. +- **FR-TENANT-4** Tenant IDs SHALL contain only alphanumeric characters and hyphens and SHALL be limited to the implemented maximum length. +- **FR-TENANT-5** The API SHALL reject write requests that require a tenant when the tenant header is missing in multi-tenant mode. +- **FR-TENANT-6** The tenant service SHALL return configured tenants when multi-tenancy is enabled. +- **FR-TENANT-7** Tenant configuration SHALL include separate `EdFi_Admin` and `EdFi_Security` connection strings per tenant. +- **FR-TENANT-8** Tenant connection strings SHALL NOT be exposed as plain API response details in tenant listing behavior. +- **FR-TENANT-9** In multi-tenant mode, all client credentials SHALL be able to access all tenants. + +> [!TIP] +> Multi-tenancy _in this context_ is an _administrative_ feature, not a _security_ feature. + +### 3.10 Sorting, filtering, and pagination + +- **FR-QUERY-1** Collection endpoints SHALL support `offset` and `limit` query parameters where documented. +- **FR-QUERY-2** Default offset and limit SHALL be configurable. +- **FR-QUERY-3** Collection endpoints SHALL support `orderBy` and `direction` parameters where documented. +- **FR-QUERY-4** Direction values SHALL support ascending and descending semantics as documented in the OpenAPI markdown. +- **FR-QUERY-5** Resource-specific filter parameters SHALL match documented model fields such as ID, name, company, namespace prefix, claim set name, ODS instance name, and other entity-specific fields. + +### 3.11 Error handling and responses + +- **FR-ERROR-1** Validation failures SHALL return HTTP 400 with structured validation details. +- **FR-ERROR-2** Missing resources SHALL return HTTP 404 where the feature uses not-found exception behavior. +- **FR-ERROR-3** Unauthorized requests SHALL return HTTP 401. +- **FR-ERROR-4** Authenticated but unauthorized requests SHALL return HTTP 403. +- **FR-ERROR-5** Business conflicts SHALL return HTTP 409 where endpoint metadata and command behavior support conflict responses. +- **FR-ERROR-6** Unhandled server failures SHALL return HTTP 500 with a structured error response. +- **FR-ERROR-7** Invalid token scopes SHALL return OAuth-compatible invalid-scope error details. +- **FR-ERROR-8** Error responses and logs SHALL include enough trace information to correlate a request with server logs. + +### 3.12 Rate Limiting + +- **FR-RATE-1** The API SHALL enforce a rate limit on client requests and return HTTP 429 (Too Many Requests) when a client application exceeds the limit within the configured time frame. +- **FR-RATE-2** Rate limit specifications — including request count and time frame — SHALL be configurable per deployment. + +## 4. Non-functional requirements + +### 4.1 Compatibility and upgradeability + +- **NFR-COMPAT-1** The 2.x line SHALL support ODS/API 7.0 and greater as the primary compatibility target. +- **NFR-COMPAT-2** The repository SHALL retain v1 compatibility code for older ODS/API administration scenarios where still supported. +- **NFR-COMPAT-3** Migration guidance SHALL support Docker and IIS installation patterns. +- **NFR-COMPAT-4** Build automation SHALL accept an API version supplied by release automation. + +### 4.2 Security and Privacy + +- **NFR-SEC-1** Protected endpoints SHALL require JWT bearer authentication. +- **NFR-SEC-2** Authorization SHALL require the implemented `edfi_admin_api/full_access` scope unless endpoint behavior explicitly allows anonymous access. +- **NFR-SEC-3** Production deployments SHALL require configured signing material rather than relying on development-only ephemeral keys. +- **NFR-SEC-4** Client secrets SHALL meet configured complexity and length requirements at registration. +- **NFR-SEC-5** Credential reset SHALL generate new secret material instead of reusing previous secrets. +- **NFR-SEC-6** Tenant connection strings and encryption keys SHALL be treated as sensitive configuration. +- **NFR-SEC-7** Swagger SHALL be disabled in production unless intentionally enabled and protected by deployment controls. +- **NFR-SEC-8** Registration SHALL be disabled after bootstrap in deployments that do not require open client self-registration. + +### 4.3 Reliability and operations + +- **NFR-REL-1** The application SHALL expose a `/health` endpoint. +- **NFR-REL-2** The health endpoint SHALL return HTTP 200 when dependencies are healthy and HTTP 503 when any dependency is unhealthy. +- **NFR-REL-3** Health output SHALL include grouped dependency status details. +- **NFR-REL-4** Database provider selection SHALL fail fast when an unsupported database engine is configured. +- **NFR-REL-5** Invalid API mode configuration SHALL fail explicitly rather than silently choosing behavior. +- **NFR-REL-6** Multi-tenant database resolution SHALL fail explicitly when tenant configuration is missing or incomplete. +- **NFR-REL-7** The application SHALL use a distinct schema for managing database objects that support the application. For example, the credentials for accessing ODS Admin API are stored in a table under ODS Admin API's unique schema. + +### 4.4 Observability + +- **NFR-OBS-1** The application SHALL use log4net for application logging. Although this is an architectural detail, it has been elevated to a product requirement in order to avoid breaking changes the log output formatting. +- **NFR-OBS-2** Request must be logged even when they fail before reaching an endpoint. +- **NFR-OBS-3** Logs SHALL include request path and trace identifier. +- **NFR-OBS-4** Error logs SHALL include structured error context. +- **NFR-OBS-5** The application SHALL support external log collection through deployment-specific log4net configuration. + +### 4.5 Performance and Scalability + +- **NFR-PERF-1** Collection endpoints SHALL support pagination to avoid unbounded result sets. +- **NFR-PERF-2** Query behavior SHALL support sorting and filtering for common administrative lookup scenarios. +- **NFR-PERF-3** Tenant configuration SHALL be cached where supported by tenant service behavior. + +### 4.6 Accessibility and Usability + +- **NFR-UX-1** The REST API SHALL provide Swagger/OpenAPI metadata when Swagger is enabled. +- **NFR-UX-2** Swagger SHALL include OAuth client-credentials configuration for interactive testing. +- **NFR-UX-3** Error responses SHALL be actionable for invalid requests, missing entities, malformed JSON, and authentication failures. +- **NFR-UX-4** HTTP example files SHALL remain aligned with current endpoints and authentication behavior. + +### 4.7 Software Development Lifecycle + +- **NFR-SDLC-1**: The application SHALL maintain consistent code quality through formatting and linting. +- **NFR-SDLC-2**: The application SHALL achieve 100% unit test coverage of business logic, exclusive of I/O operations at the API and query layers. This is the objective, not the current state: as of the 2.3 release, actual coverage is approximately 55-59% overall (with the v1 code path untested), tracked in `docs/design/tests-coverage/ADMINAPI-1448-coverage-gaps.md`. +- **NFR-SDLC-3**: The application SHALL cover all happy paths and common failure scenarios in integration tests. +- **NFR-SDLC-4**: The application SHALL have automated integration builds and push-button package management. +- **NFR-SDLC-5**: The application SHALL be shipped in native packaging format and as production-ready images (OCI-compliant). + +## 5. System architecture + +Data ownership: + +- Admin API owns the administrative mutation workflow exposed through its REST endpoints. +- `EdFi_Admin` and `EdFi_Security` remain the persistence stores shared with ODS/API. +- ODS/API consumes the resulting administrative and security configuration. +- Tenant configuration is currently static configuration, not a first-class mutable database-backed product entity in the observed source. + +Integration points: + +- OAuth token endpoint for automation clients. +- Optionally configure to use Keycloak as an OAuth2 identity provider (IdP) instead of using the built in provider. +- Database connections for `EdFi_Admin` and `EdFi_Security`. +- Swagger/OpenAPI for client discovery and manual testing. +- Docker and IIS deployment paths. + +## 6. Out of scope and known limitations + +- **OUT-1**: Admin API does not provision or mutate ODS/API business data; it manages administrative and security configuration. +- **OUT-2**: Admin API does not currently expose implemented fine-grained scopes beyond `edfi_admin_api/full_access`. +- **OUT-3**: Dynamic tenant provisioning through mutable API endpoints is not supported, consistent with the ODS/API; tenants are configured statically. +- **OUT-4**: Token lifetime is hardcoded to 30 minutes in source and is not shown as a configurable product setting. +- **OUT-5**: Key rotation and external key-management integration. +- **OUT-6**: Admin API returns generated client credentials directly in the API response. Secure one-time distribution mechanisms (e.g., Admin App's optional Yopass-based link) are an Admin App concern, not implemented in Admin API. + +## 7. Glossary + +- **Admin API**: REST API for administering ODS/API configuration, client credentials, and authorization metadata. +- **Admin App**: UI-oriented administrative application described in design documentation as using Admin API as a backend service. +- **API client**: Credentialed client record used to access ODS/API through key and secret material. +- **Application**: Administrative representation of an ODS/API client application, associated with vendor, claim set, education organizations, ODS instances, profiles, and API client credentials. +- **Claim set**: Named collection of authorization rules used by ODS/API security. +- **Ed-Org**: Education Organization. A school, district, or other educational entity whose data an application or API client is authorized to access. Admin API references Ed-Orgs by ID (education organization IDs); it does not create or manage Ed-Org records itself. +- **`EdFi_Admin`**: Database containing administrative configuration such as vendors, applications, API clients, profiles, and ODS instance metadata. +- **`EdFi_Security`**: Database containing ODS/API security metadata such as claim sets, resource claims, actions, and authorization strategies. +- **ODS/API**: Ed-Fi Operational Data Store and API. +- **ODS instance**: Administrative representation of an ODS/API database instance, including name, type, and connection string. +- **ODS instance context**: Key/value metadata associated with an ODS instance. +- **ODS instance derivative**: Related ODS instance connection such as a read replica or snapshot. +- **OpenIddict**: .NET library used by Admin API for self-contained OAuth/OpenID Connect server functionality. +- **Profile**: ODS/API profile definition that constrains API content through XML profile configuration. +- **Resource** claim: ODS/API authorization resource protected by claim-set rules. +- **Tenant**: Named deployment partition resolved by request header and mapped to tenant-specific `EdFi_Admin` and `EdFi_Security` connection strings. diff --git a/docs/api-specifications/openapi-yaml/admin-api-2.3.0-pre.yaml b/docs/api-specifications/openapi-yaml/admin-api-2.3.0-pre.yaml deleted file mode 100644 index dbbbb84b6..000000000 --- a/docs/api-specifications/openapi-yaml/admin-api-2.3.0-pre.yaml +++ /dev/null @@ -1,3053 +0,0 @@ -openapi: 3.0.1 -info: - title: Admin API Documentation - description: 'The Ed-Fi Admin API is a REST API-based administrative interface for managing vendors, applications, client credentials, and authorization rules for accessing an Ed-Fi API.' - version: v2 -paths: - /v2/resourceClaims: - get: - tags: - - ResourceClaims - summary: Retrieves all resourceClaims. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Resource Claim Id - schema: - type: integer - format: int32 - - name: name - in: query - description: Resource Claim Name - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/resourceClaimModel' - '/v2/resourceClaims/{id}': - get: - tags: - - ResourceClaims - summary: Retrieves a specific resourceClaim based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/resourceClaimModel' - /v2/resourceClaimActions: - get: - tags: - - ResourceClaimActions - summary: Retrieves all resourceClaimActions. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: resourceName - in: query - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/resourceClaimActionModel' - /v2/resourceClaimActionAuthStrategies: - get: - tags: - - ResourceClaimActionAuthStrategies - summary: Retrieves all resourceClaimActionAuthStrategies. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: resourceName - in: query - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/resourceClaimActionAuthStrategyModel' - /v2/vendors: - get: - tags: - - Vendors - summary: Retrieves all vendors. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Vendor/ company id - schema: - type: integer - format: int32 - - name: company - in: query - description: Vendor/ company name - schema: - type: string - - name: namespacePrefixes - in: query - description: Namespace prefix for the vendor. Multiple namespace prefixes can be provided as comma separated list if required. - schema: - type: string - - name: contactName - in: query - description: Vendor contact name - schema: - type: string - - name: contactEmailAddress - in: query - description: Vendor contact email id - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/vendorModel' - post: - tags: - - Vendors - summary: Creates vendor based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addVendorRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/vendors/{id}': - get: - tags: - - Vendors - summary: Retrieves a specific vendor based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/vendorModel' - put: - tags: - - Vendors - summary: Updates vendor based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editVendorRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - Vendors - summary: Deletes an existing vendor using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/profiles: - get: - tags: - - Profiles - summary: Retrieves all profiles. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Profile id - schema: - type: integer - format: int32 - - name: name - in: query - description: Profile name - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/profileModel' - post: - tags: - - Profiles - summary: Creates profile based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addProfileRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/profiles/{id}': - get: - tags: - - Profiles - summary: Retrieves a specific profile based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/profileDetailsModel' - put: - tags: - - Profiles - summary: Updates profile based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editProfileRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - Profiles - summary: Deletes an existing profile using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/odsInstances: - get: - tags: - - OdsInstances - summary: Retrieves all odsInstances. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: List of ODS instance id - schema: - type: integer - format: int32 - - name: name - in: query - description: Ods Instance name - schema: - type: string - - name: instanceType - in: query - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/odsInstanceModel' - post: - tags: - - OdsInstances - summary: Creates odsInstance based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addOdsInstanceRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/odsInstances/{id}': - get: - tags: - - OdsInstances - summary: Retrieves a specific odsInstance based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/odsInstanceDetailModel' - put: - tags: - - OdsInstances - summary: Updates odsInstance based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editOdsInstanceRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - OdsInstances - summary: Deletes an existing odsInstance using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/odsInstanceDerivatives: - get: - tags: - - OdsInstanceDerivatives - summary: Retrieves all odsInstanceDerivatives. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/odsInstanceDerivativeModel' - post: - tags: - - OdsInstanceDerivatives - summary: Creates odsInstanceDerivative based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addOdsInstanceDerivativeRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/odsInstanceDerivatives/{id}': - get: - tags: - - OdsInstanceDerivatives - summary: Retrieves a specific odsInstanceDerivative based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/odsInstanceDerivativeModel' - put: - tags: - - OdsInstanceDerivatives - summary: Updates odsInstanceDerivative based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editOdsInstanceDerivativeRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - OdsInstanceDerivatives - summary: Deletes an existing odsInstanceDerivative using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/odsInstanceContexts: - get: - tags: - - OdsInstanceContexts - summary: Retrieves all odsInstanceContexts. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/odsInstanceContextModel' - post: - tags: - - OdsInstanceContexts - summary: Creates odsInstanceContext based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addOdsInstanceContextRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/odsInstanceContexts/{id}': - get: - tags: - - OdsInstanceContexts - summary: Retrieves a specific odsInstanceContext based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/odsInstanceContextModel' - put: - tags: - - OdsInstanceContexts - summary: Updates odsInstanceContext based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editOdsInstanceContextRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - OdsInstanceContexts - summary: Deletes an existing odsInstanceContext using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - '/v2/claimSets/{id}/export': - get: - tags: - - ClaimSets - summary: Exports a specific claimset by id - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/claimSetDetailsModel' - /v2/claimSets: - get: - tags: - - ClaimSets - summary: Retrieves all claimSets. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Claim set id - schema: - type: integer - format: int32 - - name: name - in: query - description: Claim set name - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/claimSetModel' - post: - tags: - - ClaimSets - summary: Creates claimSet based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/claimSets/{id}': - get: - tags: - - ClaimSets - summary: Retrieves a specific claimSet based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/claimSetDetailsModel' - put: - tags: - - ClaimSets - summary: Updates claimSet based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - ClaimSets - summary: Deletes an existing claimSet using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/authorizationStrategies: - get: - tags: - - AuthorizationStrategies - summary: Retrieves all authorizationStrategies. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/authorizationStrategyModel' - /v2/applications: - get: - tags: - - Applications - summary: Retrieves all applications. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Application id - schema: - type: integer - format: int32 - - name: applicationName - in: query - description: Application name - schema: - type: string - - name: claimsetName - in: query - description: Claim set name - schema: - type: string - - name: ids - in: query - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/applicationModel' - post: - tags: - - Applications - summary: Creates application based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addApplicationRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/applicationResult' - '/v2/applications/{id}': - get: - tags: - - Applications - summary: Retrieves a specific application based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/applicationModel' - put: - tags: - - Applications - summary: Updates application based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editApplicationRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - Applications - summary: Deletes an existing application using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - '/v2/odsInstances/{id}/applications': - get: - tags: - - OdsInstances - summary: Retrieves applications assigned to a specific ODS instance based on the resource identifier. - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/applicationModel' - '/v2/vendors/{id}/applications': - get: - tags: - - Vendors - summary: Retrieves applications assigned to a specific vendor based on the resource identifier. - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/applicationModel' - /v2/apiclients: - get: - tags: - - Apiclients - summary: Retrieves all apiclients. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: applicationid - in: query - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/apiClientModel' - post: - tags: - - Apiclients - summary: Creates apiclient based on the supplied values. - description: 'The POST operation can be used to create or update resources. In database terms, this is often referred to as an "upsert" operation (insert + update). Clients should NOT include the resource "id" in the JSON body because it will result in an error. The web service will identify whether the resource already exists based on the natural key values provided, and update or create the resource appropriately. It is recommended to use POST for both create and update except while updating natural key of a resource in which case PUT operation must be used.' - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addApiClientRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/apiClientResult' - '/v2/apiclients/{id}': - get: - tags: - - Apiclients - summary: Retrieves a specific apiclient based on the identifier. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/apiClientModel' - put: - tags: - - Apiclients - summary: Updates apiclient based on the resource identifier. - description: 'The PUT operation is used to update a resource by identifier. If the resource identifier ("id") is provided in the JSON body, it will be ignored. Additionally, this API resource is not configured for cascading natural key updates. Natural key values for this resource cannot be changed using PUT operation, so the recommendation is to use POST as that supports upsert behavior.' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editApiClientRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - Apiclients - summary: Deletes an existing apiclient using the resource identifier. - description: 'The DELETE operation is used to delete an existing resource by identifier. If the resource doesn''t exist, an error will result (the resource will not be found).' - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: Resource was successfully deleted. - /v2/actions: - get: - tags: - - Actions - summary: Retrieves all actions. - description: This GET operation provides access to resources using the "Get" search pattern. The values of any properties of the resource that are specified will be used to return all matching results (if it exists). - parameters: - - name: offset - in: query - description: Indicates how many items should be skipped before returning results. - schema: - type: integer - format: int32 - default: '0' - - name: limit - in: query - description: Indicates the maximum number of items that should be returned in the results. - schema: - type: integer - format: int32 - default: '25' - - name: orderBy - in: query - description: Indicates the property name by which the results will be sorted. - schema: - type: string - default: '' - - name: direction - in: query - description: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - schema: - title: Indicates whether the result should be sorted in descending order (DESC) or ascending order (ASC). - enum: - - Ascending - - Descending - type: string - default: Descending - - name: id - in: query - description: Action id - schema: - type: integer - format: int32 - - name: name - in: query - description: Action name - schema: - type: string - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/actionModel' - /: - get: - tags: - - Information - summary: Retrieve API informational metadata - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - /v2/claimSets/copy: - post: - tags: - - ClaimSets - summary: Copies the existing claimset and create a new one. - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/copyClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - /v2/claimSets/import: - post: - tags: - - ClaimSets - summary: Imports a new claimset - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/importClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - '/v2/claimSets/{claimSetId}/resourceClaimActions/{resourceClaimId}/overrideAuthorizationStrategy': - post: - tags: - - ClaimSets - summary: Overrides the default authorization strategies on provided resource claim for a specific action. - description: "Override the default authorization strategies on provided resource claim for a specific action.\r\n\r\nex: actionName = read, authorizationStrategies= [ \"Ownershipbased\" ]" - parameters: - - name: claimSetId - in: path - required: true - schema: - type: integer - format: int32 - - name: resourceClaimId - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/overrideAuthStategyOnClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - '/v2/claimSets/{claimSetId}/resourceClaimActions/{resourceClaimId}/resetAuthorizationStrategies': - post: - tags: - - ClaimSets - summary: Resets to default authorization strategies on provided resource claim. - parameters: - - name: claimSetId - in: path - required: true - schema: - type: integer - format: int32 - - name: resourceClaimId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - '/v2/claimSets/{claimSetId}/resourceClaimActions': - post: - tags: - - ClaimSets - summary: Adds ResourceClaimAction association to a claim set. - description: "Add resourceClaimAction association to claim set. At least one action should be enabled. Valid actions are read, create, update, delete, readchanges.\r\nresouceclaimId is required fields." - parameters: - - name: claimSetId - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/addResourceClaimOnClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '201': - description: Created - /connect/register: - post: - tags: - - Connect - summary: Registers new client - description: Registers new client - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - ClientId: - type: string - description: Client id - ClientSecret: - type: string - description: Client secret - DisplayName: - type: string - description: Client display name - encoding: - ClientId: - style: form - ClientSecret: - style: form - DisplayName: - style: form - responses: - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Application registered successfully. - /connect/token: - post: - tags: - - Connect - summary: Retrieves bearer token - description: "\nTo authenticate Swagger requests, execute using \"Authorize\" above, not \"Try It Out\" here." - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - client_id: - type: 'string ' - client_secret: - type: 'string ' - grant_type: - type: 'string ' - scope: - type: string - responses: - '400': - description: 'Bad request, such as invalid scope.' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Sign-in successful. - '/v2/claimSets/{claimSetId}/resourceClaimActions/{resourceClaimId}': - put: - tags: - - ClaimSets - summary: Updates the ResourceClaimActions to a specific resource claim on a claimset. - description: 'Updates the resourceClaimActions to a specific resource claim on a claimset. At least one action should be enabled. Valid actions are read, create, update, delete, readchanges.' - parameters: - - name: claimSetId - in: path - required: true - schema: - type: integer - format: int32 - - name: resourceClaimId - in: path - required: true - schema: - type: integer - format: int32 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/editResourceClaimOnClaimSetRequest' - required: true - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - delete: - tags: - - ClaimSets - summary: Deletes a resource claims association from a claimset - parameters: - - name: claimSetId - in: path - required: true - schema: - type: integer - format: int32 - - name: resourceClaimId - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '200': - description: OK - '/v2/applications/{id}/reset-credential': - put: - tags: - - Applications - summary: Reset application credentials. Returns new key and secret. - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/applicationResult' - '/v2/apiclients/{id}/reset-credential': - put: - tags: - - Apiclients - summary: Reset apiclient credentials. Returns new key and secret. - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '404': - description: Not found. A resource with given identifier could not be found. - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/apiClientResult' -components: - schemas: - actionForResourceClaimModel: - type: object - properties: - name: - type: string - nullable: true - additionalProperties: false - actionModel: - title: Action - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - uri: - type: string - nullable: true - additionalProperties: false - actionWithAuthorizationStrategy: - type: object - properties: - actionId: - type: integer - format: int32 - actionName: - type: string - nullable: true - authorizationStrategies: - type: array - items: - $ref: '#/components/schemas/authorizationStrategyModelForAction' - nullable: true - additionalProperties: false - addApiClientRequest: - title: AddApiClientRequest - type: object - properties: - name: - type: string - description: Api client name - isApproved: - type: boolean - description: Is approved - applicationId: - type: integer - description: Application id - format: int32 - odsInstanceIds: - type: array - items: - type: integer - format: int32 - description: List of ODS instance id - additionalProperties: false - addApplicationRequest: - title: AddApplicationRequest - type: object - properties: - applicationName: - type: string - description: Application name - vendorId: - type: integer - description: Vendor/ company id - format: int32 - claimSetName: - type: string - description: Claim set name - profileIds: - type: array - items: - type: integer - format: int32 - description: Profile id - nullable: true - educationOrganizationIds: - type: array - items: - type: integer - format: int64 - description: Education organization ids - odsInstanceIds: - type: array - items: - type: integer - format: int32 - description: List of ODS instance id - enabled: - type: boolean - description: Indicates whether the ApiClient's credetials is enabled. Defaults to true if not provided. - nullable: true - additionalProperties: false - addClaimSetRequest: - title: AddClaimSetRequest - type: object - properties: - name: - type: string - description: Claim set name - additionalProperties: false - addOdsInstanceContextRequest: - title: AddOdsInstanceContextRequest - type: object - properties: - odsInstanceId: - type: integer - description: ODS instance context ODS instance id. - format: int32 - contextKey: - type: string - description: context key. - contextValue: - type: string - description: context value. - additionalProperties: false - addOdsInstanceDerivativeRequest: - title: AddOdsInstanceDerivativeRequest - type: object - properties: - odsInstanceId: - type: integer - description: ODS instance derivative ODS instance id. - format: int32 - derivativeType: - type: string - description: derivative type. - connectionString: - type: string - description: connection string. - additionalProperties: false - addOdsInstanceRequest: - title: AddOdsInstanceRequest - type: object - properties: - name: - type: string - description: Ods Instance name - instanceType: - type: string - description: Ods Instance type - nullable: true - connectionString: - type: string - description: Ods Instance connection string - additionalProperties: false - addProfileRequest: - title: AddProfileRequest - type: object - properties: - name: - type: string - description: Profile name - definition: - type: string - description: Profile definition - additionalProperties: false - example: "{\n \"name\": \"Test-Profile\",\n \"definition\": \"\"\n}" - addResourceClaimOnClaimSetRequest: - title: AddResourceClaimActionsOnClaimSetRequest - type: object - properties: - resourceClaimId: - type: integer - description: ResourceClaim id - format: int32 - resourceClaimActions: - type: array - items: - $ref: '#/components/schemas/resourceClaimAction' - additionalProperties: false - addVendorRequest: - title: AddVendorRequest - type: object - properties: - company: - type: string - description: Vendor/ company name - namespacePrefixes: - type: string - description: Namespace prefix for the vendor. Multiple namespace prefixes can be provided as comma separated list if required. - contactName: - type: string - description: Vendor contact name - contactEmailAddress: - type: string - description: Vendor contact email id - additionalProperties: false - adminApiError: - title: AdminApiError - type: object - additionalProperties: false - description: Wrapper schema for all error responses - apiClientModel: - title: ApiClient - type: object - properties: - id: - type: integer - format: int32 - key: - type: string - nullable: true - name: - type: string - nullable: true - isApproved: - type: boolean - useSandbox: - type: boolean - sandboxType: - type: integer - format: int32 - applicationId: - type: integer - format: int32 - keyStatus: - type: string - nullable: true - educationOrganizationIds: - type: array - items: - type: integer - format: int64 - nullable: true - odsInstanceIds: - type: array - items: - type: integer - format: int32 - nullable: true - additionalProperties: false - apiClientResult: - title: ApiClient - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - key: - type: string - nullable: true - secret: - type: string - nullable: true - applicationId: - type: integer - format: int32 - additionalProperties: false - applicationModel: - title: Application - type: object - properties: - id: - type: integer - format: int32 - applicationName: - type: string - nullable: true - claimSetName: - type: string - nullable: true - educationOrganizationIds: - type: array - items: - type: integer - format: int64 - nullable: true - vendorId: - type: integer - format: int32 - nullable: true - profileIds: - type: array - items: - type: integer - format: int32 - nullable: true - odsInstanceIds: - type: array - items: - type: integer - format: int32 - nullable: true - enabled: - type: boolean - additionalProperties: false - applicationResult: - title: ApplicationKeySecret - type: object - properties: - id: - type: integer - format: int32 - key: - type: string - nullable: true - secret: - type: string - nullable: true - additionalProperties: false - authorizationStrategy: - title: ResourceClaimAuthorizationStrategy - type: object - properties: - authStrategyId: - type: integer - format: int32 - authStrategyName: - type: string - nullable: true - isInheritedFromParent: - type: boolean - additionalProperties: false - authorizationStrategyModel: - title: AuthorizationStrategy - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - displayName: - type: string - nullable: true - additionalProperties: false - authorizationStrategyModelForAction: - type: object - properties: - authStrategyId: - type: integer - format: int32 - authStrategyName: - type: string - nullable: true - additionalProperties: false - claimSetDetailsModel: - title: ClaimSetWithResources - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - _isSystemReserved: - type: boolean - readOnly: true - _applications: - type: array - items: - $ref: '#/components/schemas/simpleApplicationModel' - nullable: true - readOnly: true - resourceClaims: - type: array - items: - $ref: '#/components/schemas/claimSetResourceClaimModel' - nullable: true - additionalProperties: false - claimSetModel: - title: ClaimSet - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - _isSystemReserved: - type: boolean - readOnly: true - _applications: - type: array - items: - $ref: '#/components/schemas/simpleApplicationModel' - nullable: true - readOnly: true - additionalProperties: false - claimSetResourceClaimActionAuthStrategies: - title: ClaimSetResourceClaimActionAuthorizationStrategies - type: object - properties: - actionId: - type: integer - format: int32 - nullable: true - actionName: - type: string - nullable: true - authorizationStrategies: - type: array - items: - $ref: '#/components/schemas/authorizationStrategy' - nullable: true - additionalProperties: false - claimSetResourceClaimModel: - title: ClaimSetResourceClaim - type: object - properties: - id: - type: integer - format: int32 - readOnly: true - name: - type: string - nullable: true - actions: - type: array - items: - $ref: '#/components/schemas/resourceClaimAction' - nullable: true - _defaultAuthorizationStrategiesForCRUD: - type: array - items: - $ref: '#/components/schemas/claimSetResourceClaimActionAuthStrategies' - nullable: true - readOnly: true - authorizationStrategyOverridesForCRUD: - type: array - items: - $ref: '#/components/schemas/claimSetResourceClaimActionAuthStrategies' - nullable: true - children: - type: array - items: - $ref: '#/components/schemas/claimSetResourceClaimModel' - description: Children are collection of ResourceClaim - nullable: true - additionalProperties: false - copyClaimSetRequest: - title: CopyClaimSetRequest - type: object - properties: - originalId: - type: integer - description: ClaimSet id to copy - format: int32 - name: - type: string - description: New claimset name - additionalProperties: false - editApiClientRequest: - title: EditApiClientRequest - type: object - properties: - name: - type: string - description: Api client name - isApproved: - type: boolean - description: Is approved - applicationId: - type: integer - description: Application id - format: int32 - odsInstanceIds: - type: array - items: - type: integer - format: int32 - description: List of ODS instance id - additionalProperties: false - editApplicationRequest: - title: EditApplicationRequest - type: object - properties: - applicationName: - type: string - description: Application name - vendorId: - type: integer - description: Vendor/ company id - format: int32 - claimSetName: - type: string - description: Claim set name - profileIds: - type: array - items: - type: integer - format: int32 - description: Profile id - nullable: true - educationOrganizationIds: - type: array - items: - type: integer - format: int64 - description: Education organization ids - odsInstanceIds: - type: array - items: - type: integer - format: int32 - description: List of ODS instance id - enabled: - type: boolean - description: Indicates whether the ApiClient's credetials is enabled. Defaults to true if not provided. - nullable: true - additionalProperties: false - editClaimSetRequest: - title: EditClaimSetRequest - type: object - properties: - name: - type: string - description: Claim set name - additionalProperties: false - editOdsInstanceContextRequest: - title: EditOdsInstanceContextRequest - type: object - properties: - odsInstanceId: - type: integer - description: ODS instance context ODS instance id. - format: int32 - contextKey: - type: string - description: context key. - contextValue: - type: string - description: context value. - additionalProperties: false - editOdsInstanceDerivativeRequest: - title: EditOdsInstanceDerivativeRequest - type: object - properties: - odsInstanceId: - type: integer - description: ODS instance derivative ODS instance id. - format: int32 - derivativeType: - type: string - description: derivative type. - connectionString: - type: string - description: connection string. - additionalProperties: false - editOdsInstanceRequest: - title: EditOdsInstanceRequest - type: object - properties: - name: - type: string - description: Ods Instance name - instanceType: - type: string - description: Ods Instance type - nullable: true - connectionString: - type: string - description: Ods Instance connection string - nullable: true - additionalProperties: false - editProfileRequest: - title: EditProfileRequest - type: object - properties: - name: - type: string - description: Profile name - definition: - type: string - description: Profile definition - additionalProperties: false - example: "{\n \"name\": \"Test-Profile\",\n \"definition\": \"\"\n}" - editResourceClaimOnClaimSetRequest: - title: EditResourceClaimActionsOnClaimSetRequest - type: object - properties: - resourceClaimActions: - type: array - items: - $ref: '#/components/schemas/resourceClaimAction' - additionalProperties: false - editVendorRequest: - title: EditVendorRequest - type: object - properties: - company: - type: string - description: Vendor/ company name - namespacePrefixes: - type: string - description: Namespace prefix for the vendor. Multiple namespace prefixes can be provided as comma separated list if required. - contactName: - type: string - description: Vendor contact name - contactEmailAddress: - type: string - description: Vendor contact email id - additionalProperties: false - importClaimSetRequest: - title: ImportClaimSetRequest - type: object - properties: - name: - type: string - description: Claim set name - resourceClaims: - type: array - items: - $ref: '#/components/schemas/claimSetResourceClaimModel' - description: Resource Claims - additionalProperties: false - informationResult: - title: Information - type: object - properties: - version: - type: string - description: Application version - build: - type: string - description: Build / release version - additionalProperties: false - odsInstanceContextModel: - title: OdsInstanceContext - type: object - properties: - id: - type: integer - format: int32 - odsInstanceId: - type: integer - format: int32 - contextKey: - type: string - nullable: true - contextValue: - type: string - nullable: true - additionalProperties: false - odsInstanceDerivativeModel: - title: OdsInstanceDerivative - type: object - properties: - id: - type: integer - format: int32 - odsInstanceId: - type: integer - format: int32 - nullable: true - derivativeType: - type: string - nullable: true - additionalProperties: false - odsInstanceDetailModel: - title: OdsInstanceDetail - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - instanceType: - type: string - nullable: true - odsInstanceContexts: - type: array - items: - $ref: '#/components/schemas/odsInstanceContextModel' - nullable: true - odsInstanceDerivatives: - type: array - items: - $ref: '#/components/schemas/odsInstanceDerivativeModel' - nullable: true - additionalProperties: false - odsInstanceModel: - title: OdsInstance - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - instanceType: - type: string - nullable: true - additionalProperties: false - overrideAuthStategyOnClaimSetRequest: - title: OverrideAuthStategyOnClaimSetRequest - type: object - properties: - actionName: - type: string - nullable: true - authorizationStrategies: - type: array - items: - type: string - description: AuthorizationStrategy Names - additionalProperties: false - profileDetailsModel: - title: ProfileDetails - type: object - properties: - id: - type: integer - format: int32 - nullable: true - name: - type: string - nullable: true - definition: - type: string - nullable: true - additionalProperties: false - profileModel: - title: Profile - type: object - properties: - id: - type: integer - format: int32 - nullable: true - name: - type: string - nullable: true - additionalProperties: false - registerClientRequest: - title: RegisterClientRequest - type: object - properties: - clientId: - type: string - description: Client id - clientSecret: - type: string - description: Client secret - displayName: - type: string - description: Client display name - additionalProperties: false - resourceClaimAction: - title: ResourceClaimAction - type: object - properties: - name: - type: string - nullable: true - enabled: - type: boolean - additionalProperties: false - resourceClaimActionAuthStrategyModel: - type: object - properties: - resourceClaimId: - type: integer - format: int32 - resourceName: - type: string - nullable: true - claimName: - type: string - nullable: true - authorizationStrategiesForActions: - type: array - items: - $ref: '#/components/schemas/actionWithAuthorizationStrategy' - nullable: true - additionalProperties: false - resourceClaimActionModel: - type: object - properties: - resourceClaimId: - type: integer - format: int32 - resourceName: - type: string - nullable: true - claimName: - type: string - nullable: true - actions: - type: array - items: - $ref: '#/components/schemas/actionForResourceClaimModel' - nullable: true - additionalProperties: false - resourceClaimModel: - title: ResourceClaimModel - type: object - properties: - id: - type: integer - format: int32 - name: - type: string - nullable: true - parentId: - type: integer - format: int32 - nullable: true - parentName: - type: string - nullable: true - children: - type: array - items: - $ref: '#/components/schemas/resourceClaimModel' - description: Children are collection of SimpleResourceClaimModel - nullable: true - additionalProperties: false - simpleApplicationModel: - title: Application - type: object - properties: - applicationName: - type: string - nullable: true - additionalProperties: false - vendorModel: - title: Vendor - type: object - properties: - id: - type: integer - format: int32 - nullable: true - company: - type: string - nullable: true - namespacePrefixes: - type: string - nullable: true - contactName: - type: string - nullable: true - contactEmailAddress: - type: string - nullable: true - additionalProperties: false - securitySchemes: - oauth: - type: oauth2 - flows: - clientCredentials: - tokenUrl: https://localhost/connect/token - scopes: - edfi_admin_api/full_access: Full access to the Admin API - edfi_admin_api/tenant_access: Access to a specific tenant - edfi_admin_api/worker: Worker access to the Admin API -security: - - oauth: - - api \ No newline at end of file diff --git a/docs/api-specifications/openapi-yaml/admin-api-console-2.3.0-pre.yaml b/docs/api-specifications/openapi-yaml/admin-api-console-2.3.0-pre.yaml deleted file mode 100644 index 702feb76a..000000000 --- a/docs/api-specifications/openapi-yaml/admin-api-console-2.3.0-pre.yaml +++ /dev/null @@ -1,131 +0,0 @@ -openapi: 3.0.1 -info: - title: Admin API Documentation - description: 'The Ed-Fi Admin API is a REST API-based administrative interface for managing vendors, applications, client credentials, and authorization rules for accessing an Ed-Fi API.' - version: adminconsole -paths: - /: - get: - tags: - - Information - summary: Retrieve API informational metadata - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - /connect/register: - post: - tags: - - Connect - summary: Registers new client - description: Registers new client - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - ClientId: - type: string - description: Client id - ClientSecret: - type: string - description: Client secret - DisplayName: - type: string - description: Client display name - encoding: - ClientId: - style: form - ClientSecret: - style: form - DisplayName: - style: form - responses: - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Application registered successfully. - /connect/token: - post: - tags: - - Connect - summary: Retrieves bearer token - description: "\nTo authenticate Swagger requests, execute using \"Authorize\" above, not \"Try It Out\" here." - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - client_id: - type: 'string ' - client_secret: - type: 'string ' - grant_type: - type: 'string ' - scope: - type: string - responses: - '400': - description: 'Bad request, such as invalid scope.' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Sign-in successful. -components: - schemas: - adminApiError: - title: AdminApiError - type: object - additionalProperties: false - description: Wrapper schema for all error responses - informationResult: - title: Information - type: object - properties: - version: - type: string - description: Application version - build: - type: string - description: Build / release version - additionalProperties: false - registerClientRequest: - title: RegisterClientRequest - type: object - properties: - clientId: - type: string - description: Client id - clientSecret: - type: string - description: Client secret - displayName: - type: string - description: Client display name - additionalProperties: false - securitySchemes: - oauth: - type: oauth2 - flows: - clientCredentials: - tokenUrl: https://localhost/connect/token - scopes: - edfi_admin_api/full_access: Full access to the Admin API - edfi_admin_api/tenant_access: Access to a specific tenant - edfi_admin_api/worker: Worker access to the Admin API -security: - - oauth: - - api \ No newline at end of file diff --git a/docs/design/ADMINAPI-1284-adminconsole-definition-removal.md b/docs/design/ADMINAPI-1284-adminconsole-definition-removal.md deleted file mode 100644 index 86a56c10e..000000000 --- a/docs/design/ADMINAPI-1284-adminconsole-definition-removal.md +++ /dev/null @@ -1,273 +0,0 @@ -# AdminConsole Endpoints Removal Analysis and Design - -## Overview - -This document outlines the findings from the analysis phase (Phase 1) of the ticket ADM### Proposed Endpoint Structure - -### New Endpoints - -| Current Endpoint | Proposed Endpoint | Purpose | -|------------------|-------------------|---------| -| `/adminconsole/tenants` | `/v2/tenants` | List all tenants | -| `/adminconsole/tenants/{tenantId}` | `/v2/tenants/{tenantId}` | Get tenant details | -| `/adminconsole/odsInstances/{id}` | `/v2/odsInstances/{id}/metadata` | Get instance details | - -## Current State Analysis - -### Existing Endpoints - -The following `/adminconsole` endpoints have been identified in the codebase: - -| Endpoint | HTTP Method | Purpose | -|----------|-------------|---------| -| `/tenants` | GET | List all tenants | -| `/tenants/{tenantId}` | GET | Get tenant details by ID | -| `/odsInstances` | GET | List all ODS instances | -| `/odsInstances/{id}` | GET | Get instance details by ID | -| `/instances` | GET | List all instances for worker use | -| `/instances/{id}` | GET | Get instance details for worker use | -| `/instances` | POST | Create a new instance | -| `/instances` | PUT | Update an existing instance | -| `/instances` | DELETE | Delete an instance | -| `/instances/{instanceId}/completed` | POST | Mark instance creation as completed | -| `/instances/{instanceId}/deletefailed` | POST | Mark instance deletion as failed | -| `/instances/{instanceId}/renameFailed` | POST | Mark instance rename as failed | -| `/instances/{instanceId}/renamed` | POST | Mark instance rename as completed | -| `/instances/{instanceId}/deleted` | POST | Mark instance deletion as completed | -| `/healthcheck` | GET | Get health check information | -| `/healthcheck` | POST | Create a health check entry | - -### Database Schema - -The admin console functionality currently uses the following tables in the `adminconsole` schema: - -1. `adminconsole.Instances` - Stores ODS instance information - * Maps to the `Instance` entity model - * Contains information about ODS instances including status, credentials, and metadata - * References `dbo.OdsInstances` via the `OdsInstanceId` foreign key - -2. `adminconsole.HealthChecks` - Stores health check information - * Maps to the `HealthCheck` entity model - * Used by the health check worker to track instance health - -3. `adminconsole.OdsInstanceContexts` - Stores context information for ODS instances - * Maps to the `OdsInstanceContext` entity model - * Contains context key-value pairs for ODS instances - -4. `adminconsole.OdsInstanceDerivatives` - Stores derivative information for ODS instances - * Maps to the `OdsInstanceDerivative` entity model - * Contains derivative type information for ODS instances - -### Code Dependencies - -#### Core Services - -1. `IAdminConsoleTenantsService` - Manages tenant operations - * Responsible for initializing and retrieving tenant information - * Used by the tenant endpoints - -2. `IAdminConsoleInstancesService` - Manages instance operations - * Responsible for initializing instance data - * Maps ODS instances to admin console instances - -3. `IAdminConsoleInitializationService` - Handles initialization of admin console data - * Initializes applications needed for the admin console - -#### Worker Functionality (To Be Removed) - -The codebase includes worker-specific functionality that will be removed as per the ticket requirements: - -1. **Instance Management Worker** - * Handles ODS instance creation, deletion, and renaming - * Transitions instance status (Pending → Completed, Pending_Delete → Deleted, etc.) - * Uses endpoints like `/instances/{instanceId}/completed` to report operation results - -2. **Health Check Worker** - * Monitors the health of ODS instances - * Records health check results in the `adminconsole.HealthChecks` table - * Uses health check endpoints to report monitoring results - -This worker functionality will be tagged in Git before removal to allow for future restoration if needed. - -## Endpoints Categorization - -### Core API Endpoints (to be preserved and migrated) - -1. **Tenant Management** - * `/tenants` (GET) - Retrieve all tenants - * `/tenants/{tenantId}` (GET) - Retrieve a specific tenant - -2. **ODS Instance Management** - * `/odsInstances` (GET) - Retrieve all ODS instances - * `/odsInstances/{id}` (GET) - Retrieve a specific ODS instance - -### Worker-Specific Endpoints (to be removed) - -1. **Instance Status Management** - * `/instances` (GET) - Get instances for worker use - * `/instances/{id}` (GET) - Get specific instance for worker use - * `/instances/{instanceId}/completed` (POST) - Used by worker processes - * `/instances/{instanceId}/deletefailed` (POST) - Used by worker processes - * `/instances/{instanceId}/renameFailed` (POST) - Used by worker processes - * `/instances/{instanceId}/renamed` (POST) - Used by worker processes - * `/instances/{instanceId}/deleted` (POST) - Used by worker processes - -2. **Health Check Management** - * `/healthcheck` (GET) - Used by health check worker - * `/healthcheck` (POST) - Used by health check worker - -## Tables to Be Migrated - -### Approach for Table Migration - -After analyzing the relationship between `[EdFi_Admin].[adminconsole].[Instances]` and `[EdFi_Admin].[dbo].[OdsInstances]`, we've determined that the additional information in the `adminconsole.Instances` table serves specific purposes not covered by the core `OdsInstances` table. - -Additionally, we've identified that there is already duplication between the following tables: - -* `adminconsole.OdsInstanceContexts` and `dbo.OdsInstanceContext` -* `adminconsole.OdsInstanceDerivatives` and `dbo.OdsInstanceDerivative` - -Since we're removing worker functionality, we should leverage the existing tables in the `dbo` schema rather than creating redundant tables in the `adminapi` schema. - -### Recommended Approach: Create a Single Extension Table - -Instead of migrating all three tables from the `adminconsole` schema, we will: - -1. Create a new extension table in the `adminapi` schema: - -```sql -[EdFi_Admin].[adminapi].[InstanceMetadata] -``` - -1. Continue using the existing tables in the `dbo` schema: - -```sql -[EdFi_Admin].[dbo].[OdsInstanceContext] -[EdFi_Admin].[dbo].[OdsInstanceDerivative] -``` - -The `InstanceMetadata` table will: - -* Have a foreign key to `dbo.OdsInstances` (OdsInstanceId) -* Store tenant association information (TenantId, TenantName) -* Store API access information (BaseUrl, ResourceUrl, OAuthUrl, Credentials) -* Remove worker-specific status tracking fields -* Reference the existing context and derivative information in `dbo` schema - -This approach provides a clean separation of concerns while eliminating redundancy in the database model. - -### Tables to Create - -1. `adminapi.InstanceMetadata` - * Extends the core `dbo.OdsInstances` table with additional API configuration - * Will not include worker-specific status fields - * Will maintain foreign key relationship to `dbo.OdsInstances` - -### Tables to Remove - -Tables exclusively used by worker processes should be removed: - -1. `adminconsole.Instances` - Will be replaced by `adminapi.InstanceMetadata` -2. `adminconsole.OdsInstanceContexts` - Redundant with `dbo.OdsInstanceContext` -3. `adminconsole.OdsInstanceDerivatives` - Redundant with `dbo.OdsInstanceDerivative` -4. `adminconsole.HealthChecks` - Specific to health check worker functionality - -## Proposed Endpoint Structure - -### Endpoints to Preserve - -| Current Endpoint | Proposed Endpoint | Purpose | -|------------------|-------------------|---------| -| `/adminconsole/tenants` | `/v2/tenants` | List all tenants | -| `/adminconsole/tenants/{tenantId}` | `/v2/tenants/{tenantId}` | Get tenant details | -| `/adminconsole/odsInstances/{id}` | `/v2/odsInstances/{id}/metadata` | Get instance details with data that previously was coming from `/adminconsole/odsInstances/{id}` endpoint| - -### Endpoints to Remove - -The following endpoints are exclusively used by worker processes and should be removed: - -1. `/adminconsole/instances` (GET) - Used by worker to get all instances -2. `/adminconsole/instances/{id}` (GET) - Used by worker to get instance details -3. `/adminconsole/instances` (POST) - Used to create new instance -4. `/adminconsole/instances` (PUT) - Used to update instance -5. `/adminconsole/instances` (DELETE) - Used to delete instance -6. `/adminconsole/instances/{instanceId}/completed` - Used to mark instance creation as completed -7. `/adminconsole/instances/{instanceId}/deletefailed` - Used to mark instance deletion as failed -8. `/adminconsole/instances/{instanceId}/renameFailed` - Used to mark instance rename as failed -9. `/adminconsole/instances/{instanceId}/renamed` - Used to mark instance rename as completed -10. `/adminconsole/instances/{instanceId}/deleted` - Used to mark instance deletion as completed -11. `/adminconsole/healthcheck` (GET) - Used to retrieve health check information -12. `/adminconsole/healthcheck` (POST) - Used to create health check entries - -## Implementation Plan - -### 1. Code Preservation Strategy - -To ensure we can easily restore the worker functionality in the future: - -1. Create separate, focused Pull Requests for each worker component: - * PR #1: Remove the Instance Management Worker functionality - * PR #2: Remove the Health Check Worker functionality - -2. This approach has several advantages: - * Each PR will represent a discrete, reversible change - * We can use `git revert` on specific PRs to restore functionality when needed - * Changes are more manageable and easier to review - * Documentation of the removed functionality is preserved in the PR history - -3. Document the PR numbers and purpose in the project documentation for future reference - -### 2. Database Migration - -1. Create the new extension table in the `adminapi` schema: - * `adminapi.InstanceMetadata` (replacing functionality from `adminconsole.Instances`) - * Ensure it has appropriate foreign key relationships to `dbo.OdsInstances` - -2. Remove redundant tables: - * Drop `adminconsole.Instances`, `adminconsole.OdsInstanceContexts`, and `adminconsole.OdsInstanceDerivatives` - * Remove the `adminconsole.HealthChecks` table and any other worker-specific tables - -### 3. Endpoint Updates - -1. Preserve the approved endpoints by updating their routes: - * `/tenants` → `/v2/tenants` - * `/tenants/{tenantId}` → `/v2/tenants/{tenantId}` - * `/odsInstances` → `/v2/odsInstances/{id}/metadata` - * `/odsInstances/{id}` → `/v2/odsInstances/{id}/metadata` - -2. Remove worker-specific endpoints: - * `/instances` (GET) - * `/instances/{id}` (GET) - * `/instances/{instanceId}/completed` - * `/instances/{instanceId}/deletefailed` - * `/instances/{instanceId}/renameFailed` - * `/instances/{instanceId}/renamed` - * `/instances/{instanceId}/deleted` - * `/healthcheck` - -### 4. Code Cleanup - -1. Remove worker-specific services and dependencies -2. Update entity models to reference the correct tables: - * Create new model for `adminapi.InstanceMetadata` - * Continue using existing models for `dbo.OdsInstanceContext` and `dbo.OdsInstanceDerivative` -3. Update AutoMapper profiles and other dependent code -4. Clean up any unused code related to worker functionality -5. Update dependency injection registrations - -### 5. Test Updates - -1. Update existing test projects: - * `EdFi.Ods.AdminConsole.DBTests` - Contains numerous tests for instance management commands - * Update command tests (CompleteInstance, AddInstance, DeleteInstance, etc.) - * Update query tests (GetInstanceById, etc.) - -2. Create new tests for the migrated endpoints: - * Test `/v2/odsInstances/{id}/metadata` endpoints - * Test `/v2/tenants` endpoints - * Verify database interactions with the new `adminapi.InstanceMetadata` table - -3. Update integration tests: - * Modify tests that rely on `adminconsole` schema tables - * Update tests to use the new endpoint paths - * Ensure proper integration with the existing `/v2/odsInstances` endpoints diff --git a/docs/design/INTEGRATE-HEALTHCHECK-SERVICE.md b/docs/design/INTEGRATE-HEALTHCHECK-SERVICE.md deleted file mode 100644 index 48e30b9ae..000000000 --- a/docs/design/INTEGRATE-HEALTHCHECK-SERVICE.md +++ /dev/null @@ -1,62 +0,0 @@ -# Integrating EdFi.Ods.AdminApi.HealthCheck into EdFi.Ods.AdminApi with Quartz.NET - -## Overview - -This document describes the design and process for integrating the -`EdFi.Ods.AdminApi.HealthCheck` into the `EdFi.Ods.AdminApi` application, -leveraging Quartz.NET for scheduled and on-demand execution of health checks. - ---- - -## Goals - -* Enable scheduled health checks of ODS API instances via Quartz.NET. -* Allow on-demand triggering of health checks via an API endpoint. -* Ensure only one health check job runs at a time to prevent data conflicts. -* Centralize health check logic in `EdFi.Ods.AdminApi.HealthCheck`. - ---- - -## Architecture - -### Components - -* **HealthCheckService**: Service class that performs health checks across tenants and instances. -* **HealthCheckJob**: Quartz.NET job that invokes `HealthCheckService.Run()`. -* **Quartz.NET Scheduler**: Manages scheduled and ad-hoc job execution. -* **HealthCheckTrigger Endpoint**: API endpoint to trigger health checks on demand. - ---- - -## Process Flow - -### 1. Service Registration - -* Register `HealthCheckService` and its dependencies in the DI container (typically as `scoped` or `transient`). -* Register `HealthCheckJob` with Quartz.NET using `AddQuartz` and `AddQuartzHostedService`. - -### 2. Scheduling with Quartz.NET - -* Configure Quartz.NET to schedule `HealthCheckJob` at a configurable interval (e.g., every 10 minutes, using `HealthCheckFrequencyInMinutes` from configuration). -* Use the `[DisallowConcurrentExecution]` attribute on `HealthCheckJob` to prevent overlapping executions. - -### 3. On-Demand Triggering - -* Implement an API endpoint (e.g., `/v2/healthcheck`) in `EdFi.Ods.AdminApi`. Note: Grouped with `v2` endpoints for consistency. -* The endpoint uses `ISchedulerFactory` to schedule an immediate, one-time execution of `HealthCheckJob`. - -### 4. Concurrency Control - -* `[DisallowConcurrentExecution]` ensures only one instance of `HealthCheckJob` runs at a time, regardless of trigger source (scheduled or on-demand). - ---- - -## Configuration - -* **appsettings.json**: - * `HealthCheck:HealthCheckFrequencyInMinutes`: Controls the schedule interval. - Set to 0 to disable scheduled health checks. - * `AppSettings:EnableAdminConsoleAPI`: Enables or disables the health check - API endpoint and scheduled health checks. - -Please refer to the [POC PR #323](https://github.com/Ed-Fi-Alliance-OSS/AdminAPI-2.x/pull/323) for implementation details and code examples. diff --git a/docs/design/Integrate-Instance-Management.md b/docs/design/Integrate-Instance-Management.md deleted file mode 100644 index 93b12daaa..000000000 --- a/docs/design/Integrate-Instance-Management.md +++ /dev/null @@ -1,141 +0,0 @@ -# Integrating EdFi.AdminConsole.InstanceManagement into EdFi.Ods.AdminApi with Quartz.NET - -This document describes the design and process for integrating the `Ed-Fi-Admin-Console-Instance-Management-Worker-Process` -into the `EdFi.Ods.AdminApi` application, leveraging Quartz.NET for on-demand execution of Instance-Management-Worker. - -## Overview of the InstanceManagement solution - -These are the 4 projects that create the Instance Management Worker solution. `EdFi.AdminConsole.InstanceMgrWorker.Configuration` is -the only project that would be copied over to Admin API. - -1. EdFi.AdminConsole.InstanceManagementWorker (Console Application, where process starts) -2. EdFi.AdminConsole.InstanceMgrWorker.Configuration (Manage ods database creation and deletion) -3. EdFi.AdminConsole.InstanceMgrWorker.Core (main core functionality, mainly call Admin API features) -4. EdFi.Ods.AdminConsole.InstanceMgrWorker.Core.UnitTests (17 Unit tests) - -### Restoring and deleting the instance database - -The `EdFi.AdminConsole.InstanceMgrWorker.Configuration` project is the responsible to do these tasks. -It should not change given that we still need these tasks. - -#### Restoring and deleting a mssql - -The process to create the mssql database is the following: - -1. Reads the logical file names from the backup using `RESTORE FILELISTONLY`. -2. Executes a `RESTORE DATABASE` command to create the new database from the backup, moving the data and log files to the correct locations. - -To delete an ods database instance it simply executes `DROP DATABASE` - -#### Restoring and deleting a pgsql - -To create the ods instance database on pgsql it simply executes the `CREATE DATABASE "new-database" TEMPLATE "template-database"` command. - -To delete it, the command is `DROP DATABASE IF EXISTS "database"`. - -### HTTP call to Admin API - -The `EdFi.AdminConsole.InstanceMgrWorker.Core` is the responsible to do these tasks. -This project can be removed, given that `Instance-Management-Worker` lives now with Admin API. -To get tenants and instances, and other other transactions we do in this project, we can use the -`Database/Commands` classes - -### Project execution - -The `EdFi.AdminConsole.InstanceManagementWorker` is the responsible to do these tasks. -This project can be removed as well. Its main tasks is to loop through tenants and instances -to process instances to be created and instances to be deleted. -The component that performs these tasks will be integrated as new **Features** (Features layer) in `EdFi.Ods.AdminApi` - -## New Architecture - -### Components - -#### 1. InstanceManagementCompleteService Feature - -Service that performs instance management creation for given instance -`InstanceManagementCompleteService` is triggered on every call to `POST /adminconsole/instances` - -##### InstanceManagementCompleteService will - -* Create new records on ApiClients, OdsInstances, potentially OdsInstanceContexts and OdsInstanceDerivatives as well, etc. -* Create the database itself, if it doesn't exist. -* Change instance status from `Pending` to `Completed` on `adminconsole.Instances`. -In this case `Completed` means that the instance is created, and it's fully functional. - -#### 2. InstanceManagementDeleteService Feature - -Service that performs instance management deletion for given instance -`InstanceManagementDeleteService` is triggered on every call to `DELETE /adminconsole/instances` - -##### InstanceManagementDeleteService will - -* Delete records on ApiClients, OdsInstances, OdsInstanceContexts, OdsInstanceDerivatives as well, etc. -* Delete the database itself. -* Change instance status from `Pending_Delete` to `Deleted` on `adminconsole.Instances`. - -#### 3. InstanceManagementRenameService Feature - -Service that performs instance management renaming for given instance -`InstanceManagementRenameService` is triggered on every call to `PUT /adminconsole/instances` - -##### InstanceManagementRenameService will - -* Update the instance information on OdsInstances, OdsInstanceContexts, OdsInstanceDerivatives, etc. -* Rename the database itself if the instance name actually changed. -* Change instance status from `Pending_Rename` to `Completed` on `adminconsole.Instances`. - -#### 4. InstanceManagementCompleteJob - -Quartz.NET job that invokes `InstanceManagementCompleteService.RunAsync()` - -#### 5. InstanceManagementDeleteJob - -Quartz.NET job that invokes `InstanceManagementDeleteService.RunAsync()` - -#### 6. InstanceManagementRenameJob - -Quartz.NET job that invokes `InstanceManagementRenameService.RunAsync()` - -### Configuration - -There are a number of application settings that need to be added to Admin API - -| Name | Description | -| --- | --- | -| AppSettings:OverrideExistingDatabase | When a creation of a new instance is requested, but the database already exists. | -| AppSettings:SqlServerBakFile | Backup Sql Server file to use as a template when creating a new ods instance database. | -| AppSettings:MaxRetryAttempts | When calling Ods API and Admin API, how many times to retry when connection is not successful | -| DatabaseProvider | Database engine | - -There are a number of application settings that we **DO NOT** need anymore in Admin API - -| Name | Description | -| --- | --- | -| AdminApiSettings:AdminConsoleTenantsURL | Call to get Tenants from Admin API | -| AdminApiSettings:AdminConsoleInstancesURL | Call to get Instances from Admin API | -| AdminApiSettings:AdminConsoleCompleteInstancesURL | Call to complete Instances from Admin API | -| AdminApiSettings:AdminConsoleInstanceDeletedURL | Call to delete Instances from Admin API | -| AdminApiSettings:AdminConsoleInstanceDeleteFailedURL | Call when a deletion of a instance database has failed | -| AdminApiSettings:AccessTokenUrl | Call to get access token | -| AdminApiSettings:ClientId | Client Id to get authenticated on Admin API | -| AdminApiSettings:ClientSecret | Client Secret Id to get authenticated on Admin API | -| AdminApiSettings:GrantType | Grant Type | -| AdminApiSettings:Scope | Scope | - -### Connection Strings - -Two new connection strings need to be added to Admin API - -| Name | Description | -| --- | --- | -| EdFi_Master | To get authenticated on Database Engine (mssql or pgsql) | -| EdFi_Ods | Where the new Instance database is created or deleted | - -### Cleanup - -Given the new architecture, it should be safe to remove these 3 endpoints - -1. /adminconsole/instances/{instanceId}/deletefailed -2. /adminconsole/instances/{instanceId}/renameFailed -3. /adminconsole/instances/{instanceId}/completed diff --git a/docs/design/adminconsole/APIS-FOR-ADMIN-CONSOLE.md b/docs/design/adminconsole/APIS-FOR-ADMIN-CONSOLE.md deleted file mode 100644 index 589c6ef87..000000000 --- a/docs/design/adminconsole/APIS-FOR-ADMIN-CONSOLE.md +++ /dev/null @@ -1,133 +0,0 @@ -# REST API Support for Admin Console - -This document describes the new interfaces and data storage requirements to be -fulfilled directly in the Ed-Fi ODS/API Admin API 2 application, in support of -the Ed-Fi Admin Console and the two worker processes (Instance Management, -Health Check). - -## System Context - -```mermaid -C4Container - title "Admin API Containers" - - System(AdminConsole, "Ed-Fi Admin Console", "A web application for managing ODS/API Deployments") - - System_Boundary(backend, "Backend Systems") { - - Boundary(b0, "Admin API") { - Container(AdminAPI, "Ed-Fi Admin API 2", "BFF for Admin Console") - - Container(HealthCheck, "Admin API Health
Check Worker", "Asynch ODS/API communication") - UpdateElementStyle(HealthCheck, $bgColor="silver") - Container(Instance, "Admin API Instance
Management Worker", "Asynch DB management") - UpdateElementStyle(Instance, $bgColor="silver") - } - - Boundary(b2, "Databases") { - ContainerDb(Security, "EdFi_Security.dbo.*", "Configuration for ODS/API") - - Container_Boundary(b3, "EdFi_Admin DB") { - ContainerDb(dbo, "dbo.*", "Configuration for ODS/API") - ContainerDb(adminapi, "adminapi.*", "Configuration for Admin API") - ContainerDb(adminconsole, "adminconsole.*", "End-user requests and config") - } - } - } - - Rel(AdminConsole, AdminAPI, "Issues HTTP requests") - - Rel(HealthCheck, AdminAPI, "Reads ODS/API connections,
Writes health info") - UpdateRelStyle(HealthCheck, AdminAPI, $offsetX="0", $offsetY="0") - - Rel(Instance, AdminAPI, "Reads instance requests,
Write instance status") - UpdateRelStyle(Instance, AdminAPI, $offsetY="90", $offsetX="-140") - - Rel(AdminAPI, Security, "Reads and writes") - UpdateRelStyle(AdminAPI, Security, $offsetX="-40", $offsetY="15") - - Rel(AdminAPI, dbo, "Reads and writes") - UpdateRelStyle(AdminAPI, dbo, $offsetX="-25", $offsetY="-10") - - Rel(AdminAPI, adminapi, "Reads and writes") - UpdateRelStyle(AdminAPI, adminapi, $offsetX="-25", $offsetY="0") - - Rel(AdminAPI, adminconsole, "Reads and writes") - UpdateRelStyle(AdminAPI, adminconsole, $offsetX="0", $offsetY="50") - - UpdateLayoutConfig($c4ShapeInRow="1", $c4BoundaryInRow="2") -``` - -## Solution Design - -We are going to expose the Admin Console endpoints required for the application -in Admin API 2. It will be hosted as a different definition in Swagger and the -base path will be `/adminconsole/{resource}`. - -The original Admin Console source code received by the Ed-Fi Alliance included -support for several functional areas that will be excluded in the version 1.0 -release of the Ed-Fi Admin Console. The following table lists the functional -areas and their status. Functions that are included in the release plan will be -described in more detail below. - -| Function | Status | Notes | -| ------------------- | ------ | -------------------------------------------------------------- | -| Tenant management | ✅ | Will still require manual updates to ODS/API's tenant settings | -| Instance management | ✅ | Asynchronously supported by the Instance Management Worker | -| Health check | ✅ | Asynchronously supported by the Health Check Worker | -| Vendors | ✅ | Directly supporting `dbo.Vendors` | -| Applications | ✅ | Directly managing `dbo.Applications` (and `dbo.ApiClients`?) | -| Claimsets | ✅ | (Read-only?) list of available claimsets | -| Onboarding Wizard | ❌ | May restore in the future | -| User Profile | ❌ | May restore in the future | -| Permissions | ❌ | May restore in the future | - -### Tenant Management - -Path segment: `/adminconsole/tenants`. - -Initially, the tenant API will follow the ODS/API's pattern: it will be stored -in the appsettings file. Hence only the `GET` request is supported; modification -support will be restored in the future when tenants move to a database table. - -See [Tenant Management Data](./TENANT-DATA.md) for information about the -structure and data of a `tenant` object. - -### ODS Instances - -**Path segment: `/adminconsole/odsInstances`.** - -Supports full create, read, and update operations from the administrative user's -point of view. Does not support delete. Some data that will be managed with -`instance` (described below) should not be available to an end-user and will not -be included in this endpoint. Notably, the `clientId` and `clientSecret` should -be empty strings when responding to the `odsinstances` endpoint requests. - -See [Instance Management Data](./INSTANCE-DATA.md) for more information about -this endpoint and its data. - -### Instances - -**Path segment: `/adminconsole/instances`.** - -Supports all CRUD operations, for use by the [Instance Management -Worker](./INSTANCE-MANAGEMENT.md), [Health Check -Worker](./HEALTH-CHECK-WORKER.md), or a system administrator directly accessing -the API with appropriate credentials. Must be secured by Role name in the JSON -Web Token (JWT). Define the role name in appSettings. For more about role -management, also see [Keycloak Configuration](./KEYCLOAK.md). - -**Path segment: `/adminconsole/instances/{id}/completed`** - -POST operation for the instance management worker only. Supports updating only -the status of an instance, without having to provide a body. For the given ID, -sets the following values on the instance: - -* `status`: "Completed" -* `completedAt` set to "now" - * Started is being set the same initially; in the future there may be a more - sophisticated process that updates the record as "in progress" with a start - date. - -See [Instance Management Data](./INSTANCE-DATA.md) for more information about -the structure and data of the two `instance` objects. diff --git a/docs/design/adminconsole/HEALTH-CHECK-WORKER.md b/docs/design/adminconsole/HEALTH-CHECK-WORKER.md deleted file mode 100644 index 353900283..000000000 --- a/docs/design/adminconsole/HEALTH-CHECK-WORKER.md +++ /dev/null @@ -1,183 +0,0 @@ -# Health Check Worker - -This document describes the work performed by the Admin API 2 application and -its associated Health Check Worker for retrieving and storing record counts from -the ODS/API. - -## Containers - -```mermaid -C4Container - title "Health Check" - - System(AdminConsole, "Ed-Fi Admin Console", "A web application for managing ODS/API Deployments") - UpdateElementStyle(AdminConsole, $bgColor="silver") - - System_Boundary(backend, "Backend Systems") { - - Boundary(b0, "Admin API") { - Container(AdminAPI, "Ed-Fi Admin API 2") - - Container(HealthCheck, "Admin API Health
Check Worker") - } - - Boundary(b1, "ODS/API") { - System(OdsApi, "Ed-Fi ODS/API", "A REST API system for
educational data interoperability") - UpdateElementStyle(OdsApi, $bgColor="silver") - } - - Boundary(b2, "Shared Databases") { - ContainerDb(Admin, "EdFi_Admin,
EdFi_Security") - UpdateElementStyle(Admin, $bgColor="silver") - } - } - - Rel(AdminConsole, AdminAPI, "Issues HTTP requests") - - Rel(HealthCheck, AdminAPI, "Reads ODS/API connections,
Writes health info") - UpdateRelStyle(HealthCheck, AdminAPI, $offsetY="50") - - Rel(HealthCheck, OdsApi, "Reads records counts") - UpdateRelStyle(HealthCheck, OdsApi, $offsetX="-60", $offsetY="20") - - Rel(AdminAPI, Admin, "Reads and writes") - UpdateRelStyle(AdminAPI, Admin, $offsetY="50", $offsetX="10") - - UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2") -``` - -## Solution Design - -### ODS/API Credentials - -The worker needs client credentials (also known as "key and secret") for -accessing each ODS Instance via the API. These credentials need to use their own -read only claimset providing access to all queries resources, using -`NoFurtherAuthorizationStrategy`. By implication then, the system must create -records in the following tables: - -1. `dbo.Vendor` creating an "Ed-Fi Administrative Tools" vendor with namespace - `uri://ed-fi.org`. -2. `dbo.Applications` creating an "Ed-Fi Health Check" application. -3. A new `dbo.ApiClients` and `dbo.ApiClientOdsInstances` record for each ODS - Instance. NOTE: while it is possible to associate a single `ApiClient` with - multiple `OdsInstances`, we prefer having a one-to-one relationship. - -#### Admin API's Responsibilities - -Managing these tables is Admin API 2's responsibility, not the health check -worker's responsibility. - -During _deployment_, Admin API 2 should: - -1. Create the vendor and application records. -2. Create the readonly claimset. - -The health check worker needs to retrieve client credentials from Admin API 2 -using `GET /adminconsole/instances`. A system administrator could modify the -`dbo.OdsInstance` and related tables outside of the Admin API 2 application. To -prevent errors, Admin API 2 should synchronize its own `adminconsole.Instances` -table with any new records in `dbo.OdsInstances` each time it starts. While -doing so, it can create client credentials for the Health Check Worker to access -that instance. - -The following diagram shows the startup synchronization process. When inserting -into `adminconsole.Instances`, set the status to "Completed". - -```mermaid -sequenceDiagram - AdminApi ->> EdFi_Admin: SELECT dbo.OdsInstances - AdminApi ->> EdFi_Admin: SELECT adminconsole.Instances - AdminApi ->> EdFi_Admin: SELECT dbo.Application WHERE name = Ed-Fi Health Check - - loop for each OdsInstance - AdminApi ->> AdminApi: Is OdsInstance in Instance list? - - opt no - AdminApi ->> EdFi_Admin: BEGIN TRANSACTION - AdminApi ->> EdFi_Admin: INSERT adminconsole.Instances - AdminApi ->> EdFi_Admin: INSERT dbo.ApiClients - AdminApi ->> EdFi_Admin: INSERT dbo.ApiClientOdsInstances - AdminApi ->> EdFi_Admin: UPDATE adminconsole.Instances (credentials) - AdminApi ->> EdFi_Admin: COMMIT - end - end -``` - -> [!TIP] -> Whenever the [Instance Management Worker](./INSTANCE-MANAGEMENT.md) creates a -> new ODS database instance, it must also create client credentials. This is shown -> in that document's sequence diagram. - -#### Encryption and Storage of Credentials - -The stored credentials must be encrypted at rest. When an Admin Console user -wants to review the status of the instances, the Admin Console application will -retrieve the instance information from Admin API 2 via `GET -/adminconsole/instances`. This request _must not_ contain the encrypted -credentials. The data used by the Admin Console are stored as a JSON object in -the `Document` column. The credentials should be stored in a new column: - -1. It is easier to avoid returning this information to the Admin Console since - it does not need to be removed from the `document` body. -2. Minimizing the amount of encrypted information will aid in application - debugging. - -Suggestion: add a new column `Credentials` of type string. Continue using JSON -storage, encrypting a string like the following: - -```json -{ - "client_id": "abcdedf", - "client_secret": "1232142" -} -``` - -The encryption can utilize the same key that Admin API 2 uses for encrypting -connection strings in `dbo.OdsInstances`. - -### Reading ODS/API Record Counts - -In this case, we will store the client/secret values in Admin API to call some -of the ODS/API endpoints to generate the returning payload to the Admin Console. -The payload contains a 'total-count' report of some of the resources, it takes -the value from the header in ODS/API. - -Example: - -```none -https://api.ed-fi.org:443/v7.1/api/data/v3/ed-fi/studentSchoolAssociations?limit=0&totalCount=true -``` - -The parameter `totalCount` is important to use because this will return us the -count in the header as `total-count`. With this value we can map it to our -payload in the field called `studentSchoolAssociations`. - -This process has to be called per field of the payload - -ODS/API resources to access: - -* studentSpecialEducationProgramAssociations -* studentDisciplineIncidentBehaviorAssociations -* studentSchoolAssociations -* studentSchoolAttendanceEvents -* studentSectionAssociations -* staffEducationOrganizationAssignmentAssociations -* staffSectionAssociations -* courseTranscripts -* sections - -> [!WARNING] Unknown fields -> -> * basicReportingPeriodAttendances -> * reportingPeriodExts -> * localEducationAgencyId: We are assuming this as Ed Org Id but we are not sure about this -> * healthy (boolean): We are asumming this as a flag that return true if the above data have been populated correctly and no error from ODS/API - -As we have to call multiple endpoints in this one, we are considering use a -caching approach (maybe the in-memory provided by .NET will be enough). If we -want to refresh the data we can send a flag to the endpoint to do so. - -### Storing Record Counts - -placeholder diff --git a/docs/design/adminconsole/INSTANCE-DATA.md b/docs/design/adminconsole/INSTANCE-DATA.md deleted file mode 100644 index 1314c1f9c..000000000 --- a/docs/design/adminconsole/INSTANCE-DATA.md +++ /dev/null @@ -1,418 +0,0 @@ -# Instance Management Data - -See [APIs for Admin Console: Tenant -Management](./APIS-FOR-ADMIN-CONSOLE.md#tenant-management) for context. - -## REST Interface - -> [!NOTE] -> Admin Console might not be transmitting Tenant ID in the `POST` request -> as of Jan 20. We may need to modify Admin Console to include this. - -### GET /adminconsole/odsInstances - -Also supports `GET /adminconsole/odsInstances/{id}` - -* **Purpose**: Provide instance list to Admin Console. -* **Description**: - * Reads from the `adminconsole.Instance` table. - * No additional authorization required. - * The `baseUrl` value comes from the tenant information. - * Respond with 200 -* **Response Format**: - - ```json - [ - { - "odsInstanceId": 1, - "tenantId": 1, - "name": "Instance1", - "instanceType": "enterprise", - "baseUrl": "http://localhost/api", - "odsInstanceContexts": [ - { - "id": 1, - "odsInstanceId": 1, - "contextKey": "schoolYearFromRoute", - "contextValue": "2024" - } - ], - "odsInstanceDerivatives": [ - { - "id": 1, - "odsInstanceId": 2, - "derivativeType": "Read" - } - ] - } - ] - ``` - -### POST /adminconsole/odsInstances - -* **Purpose**: Accept instance creation requests from the Admin Console. -* **Description**: - * Validate the incoming payload. - * Insert data into the `adminconsole.Instance` table. - * Respond with `202 Accepted` and include the new id created. -* **Validation**: - - | Property | Rules | - | ------------------------------------ | ---------------------------------------------------------- | - | name | Max length: 100. Must be unique for the tenant. | - | instanceType | Max length: 100. | - | odsInstanceContexts | Empty array is allowed | - | odsInstanceContext.contextKey | Max length: 50. Must be unique for the instance. | - | odsInstanceDerivatives | Empty array is allowed | - | odsInstanceContext.contextValue | Max length: 50. | - | odsInstanceDerivative.derivativeType | Max length: 50. Allowed values: "ReadReplica", "Snapshot". | - -* **Sample Payload**: - - ```json - { - "odsInstanceId": 1, - "tenantId": 1, - "name": "Instance #1 - 2024", - "instanceType": "enterprise", - "odsInstanceContexts": [ - { - "contextKey": "schoolYearFromRoute", - "contextValue": "2024" - } - ], - "odsInstanceDerivatives": [ - { - "derivativeType": "ReadReplica" - } - ] - } - ``` - -* **Response Format**: - - ```json - { - "instanceId": 1, - } - ``` - -### PUT /adminconsole/odsInstances/{id} - -* **Purpose**: Update an instance definition. -* **Description**: - * Validate the incoming payload. - * Updates both the `adminconsole.Instance` and the `dbo.OdsInstances` tables. - * Respond with `202 No Content` - * If the `name` has changed: - * Set the status to `PENDING_RENAME` in the `adminconsole.Instance` table. - * Delete records from `dbo.OdsInstances` tables, so that the ODS/API does - not try to use this database while it is being renamed. -* **Validation**: - - | Property | Rules | - | ------------------------------------ | ---------------------------------------------------------- | - | name | Max length: 100. Must be unique for the tenant. | - | instanceType | Max length: 100. | - | odsInstanceContexts | Empty array is allowed | - | odsInstanceContext.contextKey | Max length: 50. Must be unique for the instance. | - | odsInstanceDerivatives | Empty array is allowed | - | odsInstanceContext.contextValue | Max length: 50. | - | odsInstanceDerivative.derivativeType | Max length: 50. Allowed values: "ReadReplica", "Snapshot". | - -* **Sample Payload**: - - ```json - { - "odsInstanceId": 1, - "tenantId": 1, - "name": "Instance #1 - 2024", - "instanceType": "enterprise", - "odsInstanceContexts": [ - { - "contextKey": "schoolYearFromRoute", - "contextValue": "2024" - } - ], - "odsInstanceDerivatives": [ - { - "derivativeType": "ReadReplica" - } - ] - } - ``` - -
- -> [!IMPORTANT] -> The following diagram represents the possible values the Instances -> have during the Worker's process - -
- -```mermaid -stateDiagram-v2 - classDef progressStyle font-style:italic,font-weight:bold,fill:#7a8eff - classDef noteStyle font-style:italic,font-weight:bold,fill:#ff9e89 - [*] --> Pending - state if_state_create <> - Pending --> InProgress - InProgress --> if_state_create - if_state_create --> Completed : Success - if_state_create --> InProgress : Retry - if_state_create --> CreateFailed : Error - CreateFailed --> Pending : Manually process - state Rename { - state if_state_rename <> - PendingRename --> RenameInProgress - RenameInProgress --> if_state_rename - if_state_rename --> RenameInProgress : Retry - if_state_rename --> RenameFailed : Error - RenameFailed --> PendingRename : Manually process - } - state Delete { - state if_state_delete <> - PendingDelete --> DeleteInProgress - DeleteInProgress --> if_state_delete - if_state_delete --> Deleted : Success - if_state_delete --> DeleteInProgress : Retry - if_state_delete --> DeletedFailed : Error - DeletedFailed --> PendingDelete : Manually process - Deleted --> [*] - } - if_state_rename --> Completed : Success - Completed --> PendingDelete - Completed --> PendingRename - Completed --> [*] - noteRetry : The retry mechanism will use the Polly library, after 3 retries the instance will set with the Failed status depending on the process (CreateFailed, RenameFailed, DeleteFailed). Once the Instance is set the Failed status, Admin Console will display an indicator telling the admin to check the log for more details - noteInProgress : *InProgress, DeleteInProgress, and RenameInProgress would be relevant in the multiple workers scenario - - class InProgress progressStyle - class DeleteInProgress progressStyle - class RenameInProgress progressStyle - class noteRetry noteStyle - class noteInProgress noteStyle -``` - -### DELETE /adminconsole/odsInstances/{id} - -* **Purpose**: Mark an instance for deletion. -* **Description**: - * Updates the status of the instance to `PENDING_DELETE`. - * This operation performs a soft delete by updating the status field; the record remains in the table. - * Does not immediately remove the instance; it is scheduled for deletion in other related systems. - * The instance must have a status of `COMPLETED` before it can be marked as `PENDING_DELETE`. - * Responds with `202 Accepted`. - -* **Validation**: - * The instance must exist. - * The current status must be `COMPLETED`. If not, the request is rejected with `409 Conflict`. - -* **Response Codes**: - * `202 Accepted` – The instance was successfully marked for deletion. - * `404 Not Found` – The specified instance does not exist. - * `409 Conflict` – The instance cannot be deleted because it is not in a `COMPLETED` state. - -### GET /adminconsole/instances - -Also supports `GET /adminconsole/instances/{id}` - -* **Purpose**: Provide instance list for the worker applications. -* **Description**: - * Reads from the `adminconsole.Instance` table. - * Must be authorized with an appropriate Role name in the token: `clientId` - and `clientSecret` must be left blank when this endpoint is accessed by an - Admin Console user. - * Returns a separate object for each ODS Instance Context. - * The `resourceUrl` is constructed from the tenant's base URL plus instance - context information. - * `odsInstanceId` will be null if the `status` is not `COMPLETED`. - * Return all values without the need for paging. - * Respond with 200 -* **Query String Parameters**: - * `status` to search by Status. - * `tenantName` to search by Tenant -* **Response Format**: - - ```json - [ - { - "tenantId": 1, - "tenantName": "Tenant1", - "instanceId": 1, - "odsInstanceId": 1, - "instanceName": "Instance #1 - 2024", - "resourceUrl": "http://localhost/api/2024/data/v3", - "oauthUrl": "http://localhost/api/2024/oauth/token", - "clientId": "abc123", - "clientSecret": "d5rftyguht67gyhuijk", - "status": "Completed" - } - ] - ``` - -> [!NOTE] -> In the future the health check worker should use the read replica / snapshot -> if available. For now, it will use the primary database instance. - -### POST /adminconsole/instances/{id}/completed - -* **Purpose**: Updates the given `adminconsole.Instance` record by changing the - status to `COMPLETED`. -* **Description**: - * Responds with `204 No Content` if the record is _already complete_ or if the operations described below succeed. - * Responds with `404 Not Found` if the Id does not exist. - * As described in [Instance Management Worker](./INSTANCE-MANAGEMENT.md), this - action does the following work if the status is not already `COMPLETED`, - using a single database transaction: - * Insert into `dbo.OdsInstances`. - * If needed, insert into `dbo.OdsInstanceContexts` and `dbo.OdsInstanceDerivatives`. - * Insert into `dbo.ApiClients` to create credentials for the [Health Check Worker](./HEALTH-CHECK-WORKER.md). - * Insert into `dbo.ApiClientOdsInstances`. - * Update `adminconsole.Instance` to set: - * New credentials - * Status = `COMPLETED` -* **Validation**: - - | Property | Rules | - | ------------------------------------ | ---------------------------------------------------------- | - | connectionString | Valid mssql or pgsql connection string. | - -* **Sample Payload**: - - ```json - { - "connectionString": "host=localhost;port=5431;username=username;password=password;database=database;application name=AppName;" - } - ``` - -### PUT /adminconsole/instances/{id} - -* Not supported at this time. Respond with `405 Method Not Allowed`. - -### DELETE /adminconsole/instances/{id} - -* Not supported at this time. Respond with `405 Method Not Allowed`. - -### POST /adminconsole/instances/{id}/deleted - -* **Purpose**: Marks the given `adminconsole.Instance` record as `DELETED`. -* **Description**: - * Responds with `204 No Content` if the record is already marked as "Deleted" or if the operations described below succeed. - * Responds with `404 Not Found` if the specified ID does not exist. - * As described in [Instance Management Worker](./INSTANCE-MANAGEMENT.md), this action performs the following operations in a single database transaction if the status is not already `DELETED`: - * Delete the corresponding record from `dbo.OdsInstances`. - * If applicable, delete related records from `dbo.OdsInstanceContexts` and `dbo.OdsInstanceDerivatives`. - * Delete associated records from `dbo.ApiClients` and `dbo.ApiClientOdsInstances`. - * Update the `adminconsole.Instance` record to: - * **Status** = `DELETED`. - -### POST /adminconsole/instances/{id}/deleteFailed - -* **Purpose**: Marks the given `adminconsole.Instance` record as `DELETE_FAILED` if the database drop operation performed by the Instance Management Worker has failed. -* **Description**: - * Responds with `204 No Content` if the record is already marked as `DELETE_FAILED` or if the operation succeeds. - * Responds with `404 Not Found` if the specified ID does not exist. - * As described in [Instance Management Worker](./INSTANCE-MANAGEMENT.md), this action updates the `adminconsole.Instance` record to: - * **Status** = `DELETE_FAILED`. - -### POST /adminconsole/instances/{id}/renamed - -* **Purpose**: Marks the given `adminconsole.Instance` record as `COMPLETED`. -* **Description**: - * Responds with `204 No Content` if the record is already marked as `COMPLETED` or if the operations described below succeed. - * Responds with `404 Not Found` if the specified ID does not exist. - * As described in [Instance Management Worker](./INSTANCE-MANAGEMENT.md), this action performs the following operations in a single database transaction if the status is not already `COMPLETED`: - * Update the name and connection string - * Re-insert into the `dbo.OdsInstances` tables and create new Health Check Worker credentials following the procedure used for `POST /adminconsole/instances/{id}/completed`. - * Set `adminconsole.Instance.Status='COMPLETED'`. - -### POST /adminconsole/instances/{id}/renameFailed - -* **Purpose**: Marks the given `adminconsole.Instance` record as `RENAME_FAILED` if the database drop operation performed by the Instance Management Worker has failed. -* **Description**: - * Responds with `204 No Content` if the record is already marked as `RENAME_FAILED` or if the operation succeeds. - * Responds with `404 Not Found` if the specified ID does not exist. - * As described in [Instance Management Worker](./INSTANCE-MANAGEMENT.md), this action sets `adminconsole.Instance.Status='RENAME_FAILED'`. - -## Future - -These endpoints will not be supported in Admin API 2.3, but are under consideration for a future version. - -### POST /adminconsole/instances/jobs/start - -* **Purpose**: Start processing jobs from the `adminconsole.Instance` table. -* **Description**: - * Select rows with any of these conditions: - * Status is "Pending" and `lockDateTime is null`. - * Status is "In Progress" and `lockDateTime` is expired (expiration timeout - value to be set in appsettings, for example 60 minutes). _This provides an - automated retry process_ - * Lock rows in the `adminconsole.Instance` table for processing by setting a - `jobId` value (UUID) and setting column `lockDateTime` to "now". - * Changes the status to `In Progress` - * Responds with `200 OK`. -* **Response Format**: - - ```json - { - "jobId": "", - "instances": [ - { - "odsInstanceId": 1, - "tenantId": 1, - "name": "Instance #1 - 2024", - "instanceType": "enterprise", - "odsInstanceContexts": [ - { - "contextKey": "schoolYearFromRoute", - "contextValue": "2024" - } - ], - "odsInstanceDerivatives": [ - { - "derivativeType": "ReadReplica" - } - ] - } - ] - } - ``` - -### POST /adminconsole/instances/jobs/{id}/completed - -* **Purpose**: Mark a job as complete and perform transactional updates. -* **Enhancements**: - * Accept a job completion payload. - * Add resultant data to tables `OdsInstances`, `OdsInstanceContext`, `OdsInstanceDerivatives` and update `adminconsole.Instance` status column to mark job as `Compelete` within a single transaction. - * Roll back on failure. - * Respond with `200 Ok`. - -## Data Storage - -No modifications will be made in the `dbo.*` tables. - -### adminconsole.Instance - -In the normal flow of work, this table will be populated by Admin Console / -Admin API _before_ a matching record exists in the `dbo.OdsInstances` table. The -`Document` column shown below is a flexible JSON object to whatever information -is necessary to support both the user interface and the creation of records in -`dbo.OdsInstances`, `dbo.OdsInstanceContext`, and `dbo.OdsInstanceDerivatives`. -The JSON structure gives the team flexibility for rapid iteration. - -Columns that need to be indexed (e.g. `status`) or updated by worker processes -should be real columns, instead of embedding them in the JSON data. - -| Column Name | Type | Nullable | Purpose | -| ------------- | -------------- | -------- | ------------------------------------------------------------ | -| InstanceId | int | no | Auto-incrementing identifier | -| OdsInstanceId | int | yes | Matching value from `dbo.OdsInstances` | -| TenantId | int | no | Tenant identifier | -| Document | JSON / string | yes | JSON document containing all but credentials information | -| Credentials | varbinary(500) | no | Encrypted JSON document with `client_id` and `client_secret` | -| Status | string | no | Pending, Completed, InProgress, or Error | -| CompletedAt | datetime | yes | Set this value when completed | - -> [!NOTE] -> Is `varbinary(500)` sufficient to hold encrypted credentials? diff --git a/docs/design/adminconsole/SQL-SERVER-SUPPORT.md b/docs/design/adminconsole/SQL-SERVER-SUPPORT.md deleted file mode 100644 index 7b377c220..000000000 --- a/docs/design/adminconsole/SQL-SERVER-SUPPORT.md +++ /dev/null @@ -1,231 +0,0 @@ -# Adding MS SQL Support to Instance Management - -## Summary - -The Instance Management worker allows system administrators to manage instances of their ODS. This worker is currently configured to support PostgreSQL and needs support added for SQL Server. - -## Background - -The Instance Management worker was designed to execute ODS administration tasks without tying up a handler's main process. This asynchronous nature requires the Instance management worker to manage other aspects of its environment, including persistence and compatibility with other services. The data layer for the Instance Management worker was implemented using PostgreSQL, with the idea of using this dialect of SQL to start, verifying its implementation, then including support for SQL Server after. - -The Postgres implementation of the Instance Manager includes support for Creating, Renaming, Deleting, and Restoring ODS Instances. - -There is a Postgres DB dump of sample student data that serves as a starting point for minimal and populated templates. No such sample exists for SQL Server so this will need to be solved in order to remain in parity with the Postgres provisioning features. Additionally, SQL Server utilizes a slightly different dialect from server so these actions must be converted. Lastly, SQL Server licensing requires that images / containers including the Azure SQL Server base image not be distributed. This does not prohibit providing instructions on how to build these images. - -Azure SQL Server support is an additional consideration but will remain out of the scope of this implementation. - -## Design Overview - -Adding support for SQL Server has been divided into the following four distinct areas of focus below. - -### 1. Configuring and connecting to the database - -The first step involves adding a connection and corresponding configuration to the application. This step is to ensure the application is communicating with the desired SQL Server instance (Platform hosted, docker hosted). There is a `CreateConnection()` method in the [`SqlServerSandboxProvisioner.cs`](https://github.com/Ed-Fi-Alliance-OSS/Ed-Fi-ODS/blob/main/Application/EdFi.Ods.Sandbox/Provisioners/SqlServerSandboxProvisioner.cs) example from ODS Sandbox, which demonstrates how to connect properly. - -### 2. Translating the actions to MS SQL dialect for each of the admin functions - -The current Postgres implementation of the Instance Management Worker borrows from the provisioner patterns seen in the `EdFi.Ods.Sandbox` project. - -The `PostgresSandboxProvisioner.cs` class contains methods for creating connections, renaming, deleting, managing file paths, retrieving DB Status among others. These actions can be used to inform the implementation details for the Instance Management worker and the corresponding SQL Server actions. These actions will need to be translated to MS SQL and added to a new`SqlServerInstanceProvisioner.cs` file located in the `Provisioners/` directory. - -Note, for restoring the database, the SqlServerSandboxProvisioner.cs from the API Sandbox uses a `CopySandboxAsync` command which sets up internal functions to backup a target and restore to a new destination. An internal function in particular, `GetBackupDirectoryAsync` uses the windows registry to locate the backup directory of the server. This will need to change, as SQL Server can run on platforms outside of Windows. The design details will configures a backup location the host and the application can read. - -Other supporting configuration files, e.g. `Provisioners/DatabaseEngineProvider.cs` will also need to be updated to reflect added support for SQL Server, resulting in a seamless experience between selecting the PostgreSQL engine and SQL Server. - -### 3. Low-friction SQL Server connection setup - -The next task is providing a low-friction environment for users to spin up and connect to the desired SQL Server instance. Historically, Ed-Fi has provided guidance to users on how to provision SQL Server configurations for various environments. This is done to avoid hosting images containing the distribution itself, which would create conflict with the Apache 2.0 license that accompanies the Ed-Fi Alliance source code and tools. - -SQL Server Options that Ed-Fi provides guidance: - -1. Installation included with official binaries -2. Experimental, bare MSSQL install scripts -3. Docker compose with local sample data (Users SQL Server Express Edition) - -A quick reference for setting up SQL Server runtime can be found at the following [tech docs guide](https://docs.ed-fi.org/reference/docker/#step-2-setup-runtime-environment). Because the Instance Management worker already takes advantage of Docker compose, option three appears to provide the most benefit for little effort. This will still allow users to get up and running quickly without spending much time provisioning a host machine. - -### 4. Seeding data for restoration - -Lastly, the Instance Management worker should be able to restore an ODS instance. This by extension means that the worker must support exporting the data, creating the instance, and importing the data to a created instance. - -The SQL Server database will need to be populated in order to provide the necessary data for export. Two viable options to consider for migrating the data include: - -1. Creating the Sample data and tables directly using synthesized data. -2. Transforming Sample data from PostgreSQL backups - -It appears that the MSSQL Sandbox compose files include a populated template that connects to a predefined volume. An approach can be to connect to this instance then export the to a BACPAC file, which can be used for restoration. - -Once this is done successfully, the next step is to implement the restoration of this BACPAC template data. An action can be added to the interface that runs the necessary steps within the transaction that results in creating an instance and subsequently reading the BACPAC. - -## Design Details - -The following adds additional implementation details on the design overview provided above. - -### 1. Configuring and connecting to the Database - -This step ensure that a Database connection can be established using the provided connection string settings. - -There is an `InstanceProvisionerBase.cs` class that is responsible for retrieving the connection strings and configuration settings when the provisioner is instantiated. This class also set up abstractions for creating a DbConnection, which can be overwritten for use in the SQL Engine specific Provisioner. The following is used to create a PostgreSQL DB connection: - -```csharp -protected override DbConnection CreateConnection() => new NpgsqlConnection(ConnectionString); -``` - -The `CreateConnection()` method is then used throughout the Provisioner to create a connection object for executing queries against. - -```csharp -using (var conn = CreateConnection()) -//...remaining implementation with conn. -``` - -The _databaseNameBuilder.SandboxNameForKey(clientKey) will likely need to be modified with multi-tenancy under consideration. - -### 2. Translate actions to MS SQL Dialect - -The next lift is to translate each of the DB Provisioning methods to the appropriate MS SQL Dialect - -#### Create - -The PostgreSQL provisioner implementation of `CopyDbInstanceAsync` container includes a `CREATE` statement that uses a `TEMPLATE` database. The SQL Server implementation does not support Db templates, so this will need to user a bak file to configure the populated template. This bak file will be restored when a Create DB instance command is issued. - -The Admin API Sandbox has SqlServerSandboxProvisioner.cs which implements a method `CopySandboxAsync`. This method illustrates the restoration method used with SQL Server and the backup file. This excerpt summarizes the process by calling internal functions near the top, making it easier to follow along. - -```csharp - using (var conn = CreateConnection()) - { - try - { - await conn.OpenAsync(); - - // This points to where the template should be saved - string backupDirectory = await GetBackupDirectoryAsync() - .ConfigureAwait(false); - - _logger.Debug($"backup directory = {backupDirectory}"); - - string backup = await PathCombine(backupDirectory, originalDatabaseName + ".bak"); - _logger.Debug($"backup file = {backup}"); - - var sqlFileInfo = await GetDatabaseFilesAsync(originalDatabaseName, newDatabaseName) - .ConfigureAwait(false); - - await BackUpAndRestoreSandbox() - .ConfigureAwait(false); - //.......... - } - // ........ - } -``` - -Note that the `GetBackupDirectoryAsync` will need to use a value other than the `HKEY_LOCAL_MACHINE` for retrieving the backup directory location. This could be replaced with an AppSettings configuration and will need to be configurable when using the RESTORE functionality. - -#### Delete - -Deleting instances from the Management Worker context requires removing the Database and tables associated with the client key. For reference, the Admin API Sandbox does the following: - -```csharp - using (var conn = CreateConnection()) - { - foreach (string key in deletedClientKeys) - { - await conn.ExecuteAsync($@" - IF EXISTS (SELECT name from sys.databases WHERE (name = '{_databaseNameBuilder.SandboxNameForKey(key)}')) - BEGIN - ALTER DATABASE [{_databaseNameBuilder.SandboxNameForKey(key)}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; - DROP DATABASE [{_databaseNameBuilder.SandboxNameForKey(key)}]; - END; - ", commandTimeout: CommandTimeout) - .ConfigureAwait(false); - } - } -``` - -The above is used to iterate through DB associated with the client key to perform the drop. - -#### Rename - -Renaming an instance is a two part process. The first is renaming the instances and tables themselves. This can be done with an ALTER command similar to this: - -```csharp -await conn.ExecuteAsync($"ALTER DATABASE {oldName} MODIFY Name = {newName};") - .ConfigureAwait(false); -``` - -The next step is updating data to reference this new instance. The client credentials and secrets table will need to be updated - care must be exercised to ensure renaming a table doest not impact the effectiveness of these credentials. - -Lastly, the system will need a fallback for handling potential reads while renaming the system. One approach is to spin up the new instance while the old is still running, then switch the configuration once the new instance has been set up. This will help mitigate downtime, but coordinating this will add complexity and potentially require more resources. - -Another approach is to simple take down the existing instance, rename and wait until the system is available again. This requires less complexity to manage but does increase downtime. - -Deciding on a best approach will depending on what level of unavailability is acceptable. - -#### Restore - -The Restoration will behave similar to the create process. The main difference is the restore will target a custom .bak file provided by a user, while the create will use a predefined .bak that will scaffold necessary tables and data. - -The configuration used to set the backup path in AppSettings can be used to locate the .bak file the user would like restored. The restoration then becomes similar to the create, but with a targeted file to restore. - -### 3. Low-friction SQL Server Setup - -We will explore using the Docker Compose environment for configuring and setting up the SQL Server and connecting with the Admin API. This will require us to configure the SQL Server container and network settings in a way that works with the Admin API but also portable for easy for developers. - -#### Build SQL Server compatible image - -Ensure we can configure and run an SQL Server image with the right requirements. - -#### Start up compose network - -Reference the build file to create an image and configure compose network. This allows compose to con3figure the runtime settings of the SQL Server container. - -#### Ensure running and connect directly via Docker host - -Once the container is running using compose, confirm the services are behaving as expected by connecting via the Docker host. This might involve using the host machine to connect to the shell of the SQL Server container. - -#### Pass connection settings to connect via application - -Once the container has been verified, derive the connection string and pass to the application to ensure connection to SQL Server container is valid. - -### 4. Seeding Data for restoration - -This last section involves provisioning the Template data that will be used during the CREATE instance action. We will need the schema, tables, roles and DB set up so that the necessary configuration is provided when a user requests a new instance. - -## Test Strategy - -The following user journeys represent areas critical to instance management using SQL Server. - -An admin can connect to a SQL Server. - -* Provision the server -* Create connection string -* Ensure that the application connect to server successfully - -An admin can add a new SQL Server ODS instance - -* Connect to the SQL Server Instance -* Execute command to create a new DB Instance and tables -* Demonstrate new instance and tables are available - -An admin can rename an existing SQL Server ODS instance - -* Connect to the SQL Server Instance -* Execute command to rename ODS instance -* Ensure DB and corresponding tables and connection strings are updated -* Add additional path checking duplicate name for rename does not exist - -An admin delete a new SQL Server ODS instance - -* Connect to the SQL Server instance -* Execute command to delete instance -* Ensure instance and corresponding tables are properly marked for deletion. - -An admin can restore a new SQL Server ODS instance - -* Connect to the SQL Server instance -* Execute the create command providing a name and source of the restoration -* Ensure a new instance exists with provided restoration data (dbs, tables, and rows) - -## Outstanding Questions - -When renaming and instance, what is the expectation around down time? - -When searching for an instance to rename or delete by key, is it possible for a client key to reference more than one ODS instance? diff --git a/docs/design/adminconsole/TENANT-DATA.md b/docs/design/adminconsole/TENANT-DATA.md deleted file mode 100644 index d37b3b3ed..000000000 --- a/docs/design/adminconsole/TENANT-DATA.md +++ /dev/null @@ -1,37 +0,0 @@ -# Tenant Management Data - -See [APIs for Admin Console: Tenant -Management](./APIS-FOR-ADMIN-CONSOLE.md#tenant-management) for context. - -> [!NOTE] -> Full CRUD support with database backend will be covered in a future version. - -## REST Interface - -### GET /adminconsole/tenants - -Also supports `GET /adminconsole/tenants/{id}` - -* **Purpose**: Provide tenant list to Admin Console. -* **Description**: - * Reads from the appsettings file. - * No additional authorization required. - * Respond with 200 -* **Response Format**: - - ```json - [ - { - "tenantId": 1, - "document": { - "name": "Tenant1", - "discoveryApiUrl": "http://localhost/api" - } - } - ] - ``` - -## Data Storage - -The required values will initially be stored in the appSettings file instead of -a database table. diff --git a/docs/design/adminconsole/WORKER-CRON-PROCEES.md b/docs/design/adminconsole/WORKER-CRON-PROCEES.md deleted file mode 100644 index 45472781b..000000000 --- a/docs/design/adminconsole/WORKER-CRON-PROCEES.md +++ /dev/null @@ -1,202 +0,0 @@ -# Running Cron process inside of Docker container - -___ - -## Summary - -This technical design proposes a solution for running the Health worker and Instance worker on a schedule using the `cron` process. The schedule is configurable using a `crontab` file and allows the workers to log and execute in specified intervals, all from within a container. - -## Background - -The Admin Console is a web client that provides an interface for users and admins to manage instances. In order to provide a quality experience in the console app, instance health and status collection is delegated to specialized service workers. - -The [Health Check Worker](./HEALTH-CHECK-WORKER.md) requires a periodic update on the health status, to alert the user if an instance is unreachable or in a failure state. - -The [Instance Management Worker](./INSTANCE-MANAGEMENT.md) registers and manages instances from a connected ODS/API - -For the purpose of the design, we assume that each of these workers can publish a production ready executable that, once running, calls a series endpoints defined in the execution environment. - -## Design Overview - -This design starts by stating clear assumptions and considerations about the problem statement and environment when determining the solution. - -A recommendation follows, outlining suggested steps for running the workers on a schedule in a container environment using crontab. This involves modifying a Docker file to support cron, as well as adding logs and environment variables to inject into the container. - -Following the recommendation is a testing strategy for ensuring the crontab task is running and logging. - -## Design Details - -### Assumptions - -In order to scope the investigation, the following assumptions about the user's environment were made: - -* The parameters needed by the executable are configured to be read from the execution environment. -* The user has access to the infrastructure needed to support execution of the application using the configured address. -* We assume the application is meant to deploy in a container environment, levering docker-compose for debugging and development. - -### Considerations - -When determining a solution, the following approaches were considered. - -* Create the schedule in the executable itself and copy into image -* Control schedule using external host / orchestrator -* Managing the schedule within the Docker Image itself - -Below are quick notes on each approach and the recommended approach. - -#### Schedule within the executable - -Scheduling within the executable means managing the schedule from within the application itself. While this makes setup locally simple, this approach that has some immediate obvious downsides. The application owner must manage the scheduler and must do so within the execution context of the application. Also, the container would have to run continuously, so the user would need to exercise care managing resources for a long running application process. - -#### Schedule controlled by the host system / orchestrator - -This approach is more common in container orchestration environments where a control plane can manage execution schedules. The ephemeral nature of containers make them a great environment for tasks that need to short execution times. The downside is that the configuration for this requires a bit more overhead and configuration beyond the host system and executable, which might not be ideal in scenarios where simplicity and speed of deployment are highly valued. - -#### Schedule controlled within container - -This approach is a hybrid of the two previous approaches, taking advantages and benefits from both. The advantage here is that the Docker image running in a container environment, can be built using a multi-stage docker file, which allows the entry point of the image to be defined at build time. This allows us to take advantage of the `crontab` process, which provides a configuration for 1) defining a process and 2) scheduling execution of that process. In this case, this process is the published console application, and can be configured to run on a schedule using a `crontab` file. - -### Recommendation - -After careful consideration, the approach of managing Cron within the Docker Image itself appears to be the best fit for our use case. This will provide customizable scheduling via a configurable file while allowing the execution to remain in the container environment, increasing security and testability. - -The steps for implementing the recommendation are provided below: - -#### Prepare the App Context - -The Docker file uses `WORKDIR /source` to reference the root of the application. That is to say `/source/Application/EdFi.AdminConsole.HealthCheck.Service` points to `/[your_local_repo_dir]/Application/EdFi.AdminConsole.HealthCheck.Service` - -The Docker file will also need to support exposing the configuration for the following environment variables: - -``` -{ - "AdminApiSettings": { - "ApiUrl": "https://localhost:7218/", - "AdminConsoleInstancesURI": "adminconsole/instances", - "AdminConsoleHealthCheckURI": "adminconsole/healthcheck", - "AccessTokenUrl": "https://localhost:7218/connect/token" - } -} -``` - -These can be exposed either by using the `AppSettingVariable__NestedKey=VALUE` accessor pattern to expose the values to the container. - -The `ClientId` and `ClientSecret` values will need to be retrieved from upon instance creation. - -#### Create a Cron file - -A cron file will need to be created at the root of your working dir. The file should contain the following: - -```c -* * * * * root dotnet /app/EdFi.AdminConsole.HealthCheckService.dll >> /var/log/achealthsvc.log - -``` - -This configuration is set to run every 1-min for testing. - -**Ensure that a new line is added to the end of the crontab file or the job will not run!** - -#### Update the Docker File - -The Docker File is updated to do the following: - -* Install `cron` -* Copy over the `crontab` file to `/etc/cron.d/container_cronjob -* Change permissions on the `container_cronjob` file -* Run `crontab` application - -An example of the Health Check Worker Dockerfile: - -```Dockerfile - -# Image based on .NET SDK to compile and publish the application -FROM mcr.microsoft.com/dotnet/sdk:8.0.401-alpine3.20@sha256:658c93223111638f9bb54746679e554b2cf0453d8fb7b9fed32c3c0726c210fe AS build - -WORKDIR /source - -# Copy source code and compile the application -COPY Application/EdFi.AdminConsole.HealthCheckService/. ./EdFi.AdminConsole.HealthCheckService/ - -WORKDIR /source/EdFi.AdminConsole.HealthCheckService - -# Restore dependencies, Then build and publish release -RUN dotnet restore &&\ -  dotnet publish -c Release -o /app - -# .NET Runtime image to execute the application -FROM mcr.microsoft.com/dotnet/runtime:8.0.11 AS runtime - -# Install Cron. -RUN apt-get update && apt-get install -y cron - -# Add cron from file and adjust permissions -COPY crontab /etc/cron.d/container_cronjob -RUN chmod 0644 /etc/cron.d/container_cronjob -RUN crontab /etc/cron.d/container_cronjob -WORKDIR /app - -# Add Published executable -COPY --from=build /app . - -# Execute the app via chron -CMD ["cron", "-f"] -``` - -### Add Docker compose for Development - -The `health-check-svs.yml` docker compose file provides a simple way to test building the image and logging the output. - -This might be moved or modified in the future to better support a multi-container environment. - -```yml -version: "3.9" - -services: - health-check-service: - build: - context: ../ - dockerfile: Docker/Dockerfile - volumes: - - service-logs:/var/log/achealthsvc.log - env_file: - - .env - -volumes: - service-logs: - name: health-check-service-logs - -``` - -## Testing Strategy - -The Docker file takes advantage of crontab file by piping the output of th cron executable to a log file. To ensure the executable is running, this log file is inspected to ensure the output from running the application is piped to the `achealthsvc.log` log file. - -To verify the schedule, a user can build the image using the Dockerfile and docker-compose included in the `/Docker/` directory. - -`docker compose -f .\Docker\health-check-svc.yml up` - -Can check that the job is in the table of jobs to run -`$ crontab -l` - -To verify the environment variables can be seen, in the container, once can use the `env` command from a connected shell, and inspect the values returned. - -## Additional Notes - -This holds any additional questions or comments that do not fit into the design spec above. - -### Outstanding Questions - -Will The Instance management worker need to be executed on a cron schedule? What would be executed? - -What is the difference between Admin API Client and Admin API Caller? - -How will the container image call the other services when hosted on localhost in other execution contexts? - -What is the expected deployment / execution environment for these services? - -* Docker compose okay when developing -* How will the workers be incorporated into the deployment - -Is Multi-tenancy something that needs to be accounted for? - -Can the crontab file read an environment variable? diff --git a/docs/design/adminconsole/admin-api-adminconsole-2.3-summary.md b/docs/design/adminconsole/admin-api-adminconsole-2.3-summary.md deleted file mode 100644 index 0debc9cbb..000000000 --- a/docs/design/adminconsole/admin-api-adminconsole-2.3-summary.md +++ /dev/null @@ -1,345 +0,0 @@ - - -

Admin API Documentation adminconsole

- -> Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. - -The Ed-Fi Admin API is a REST API-based administrative interface for managing vendors, applications, client credentials, and authorization rules for accessing an Ed-Fi API. - -# Authentication - -- oAuth2 authentication. - - - Flow: clientCredentials - - - Token URL = [https://localhost/connect/token](https://localhost/connect/token) - -|Scope|Scope Description| -|---|---| -|edfi_admin_api/full_access|Unrestricted access to all Admin API endpoints| - -

Information

- -## Retrieve API informational metadata - -`GET /` - -> Example responses - -> 200 Response - -```json -{ - "version": "string", - "build": "string" -} -``` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|OK|[informationResult](#schemainformationresult)| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error. An unhandled error occurred on the server. See the response body for details.|[informationResult](#schemainformationresult)| - - - -

Adminconsole

- -## get__adminconsole_odsinstances - -`GET /adminconsole/odsinstances` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_permissions - -`GET /adminconsole/permissions` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_steps - -`GET /adminconsole/steps` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_step - -`GET /adminconsole/step` - -

Parameters

- -|Name|In|Type|Required|Description| -|---|---|---|---|---| -|id|query|integer(int32)|true|none| - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_tenants - -`GET /adminconsole/tenants` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_tenant - -`GET /adminconsole/tenant` - -

Parameters

- -|Name|In|Type|Required|Description| -|---|---|---|---|---| -|id|query|integer(int32)|true|none| - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -## get__adminconsole_userprofile - -`GET /adminconsole/userprofile` - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. The request requires authentication|None| -|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The request is authenticated, but not authorized to access this resource|None| -|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request is authenticated, but it has a conflict with an existing element|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|FeatureConstants.InternalServerErrorResponseDescription|None| - - - -

Connect

- -## Registers new client - -`POST /connect/register` - -Registers new client - -> Body parameter - -```yaml -ClientId: string -ClientSecret: string -DisplayName: string - -``` - -

Parameters

- -|Name|In|Type|Required|Description| -|---|---|---|---|---| -|body|body|object|false|none| -|» ClientId|body|string|false|Client id| -|» ClientSecret|body|string|false|Client secret| -|» DisplayName|body|string|false|Client display name| - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application registered successfully.|None| -|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. The request was invalid and cannot be completed. See the response body for details.|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error. An unhandled error occurred on the server. See the response body for details.|None| - - - -## Retrieves bearer token - -`POST /connect/token` - -To authenticate Swagger requests, execute using "Authorize" above, not "Try It Out" here. - -> Body parameter - -```yaml -client_id: null -client_secret: null -grant_type: null -scope: string - -``` - -

Parameters

- -|Name|In|Type|Required|Description| -|---|---|---|---|---| -|body|body|object|false|none| -|» client_id|body|string |false|none| -|» client_secret|body|string |false|none| -|» grant_type|body|string |false|none| -|» scope|body|string|false|none| - -

Responses

- -|Status|Meaning|Description|Schema| -|---|---|---|---| -|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Sign-in successful.|None| -|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. The request was invalid and cannot be completed. See the response body for details.|None| -|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error. An unhandled error occurred on the server. See the response body for details.|None| - - - -# Schemas - -

adminApiError

- - - - - - -```json -{ - "title": "string", - "errors": [ - "string" - ] -} - -``` - -AdminApiError - -### Properties - -|Name|Type|Required|Restrictions|Description| -|---|---|---|---|---| -|title|string¦null|false|read-only|none| -|errors|[string]¦null|false|read-only|none| - -

informationResult

- - - - - - -```json -{ - "version": "string", - "build": "string" -} - -``` - -Information - -### Properties - -|Name|Type|Required|Restrictions|Description| -|---|---|---|---|---| -|version|string|false|none|Application version| -|build|string|false|none|Build / release version| - -

registerClientRequest

- - - - - - -```json -{ - "clientId": "string", - "clientSecret": "string", - "displayName": "string" -} - -``` - -RegisterClientRequest - -### Properties - -|Name|Type|Required|Restrictions|Description| -|---|---|---|---|---| -|clientId|string|false|none|Client id| -|clientSecret|string|false|none|Client secret| -|displayName|string|false|none|Client display name| - diff --git a/docs/design/adminconsole/admin-api-adminconsole-2.3.yaml b/docs/design/adminconsole/admin-api-adminconsole-2.3.yaml deleted file mode 100644 index dab723ae1..000000000 --- a/docs/design/adminconsole/admin-api-adminconsole-2.3.yaml +++ /dev/null @@ -1,245 +0,0 @@ -openapi: 3.0.1 -info: - title: Admin API Documentation - description: 'The Ed-Fi Admin API is a REST API-based administrative interface for managing vendors, applications, client credentials, and authorization rules for accessing an Ed-Fi API.' - version: adminconsole -paths: - /: - get: - tags: - - Information - summary: Retrieve API informational metadata - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - content: - application/json: - schema: - $ref: '#/components/schemas/informationResult' - /adminconsole/odsinstances: - get: - tags: - - Adminconsole - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/permissions: - get: - tags: - - Adminconsole - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/steps: - get: - tags: - - Adminconsole - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/step: - get: - tags: - - Adminconsole - parameters: - - name: id - in: query - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/tenants: - get: - tags: - - Adminconsole - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/tenant: - get: - tags: - - Adminconsole - parameters: - - name: id - in: query - required: true - schema: - type: integer - format: int32 - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /adminconsole/userprofile: - get: - tags: - - Adminconsole - responses: - '401': - description: Unauthorized. The request requires authentication - '403': - description: 'Forbidden. The request is authenticated, but not authorized to access this resource' - '409': - description: 'Conflict. The request is authenticated, but it has a conflict with an existing element' - '500': - description: FeatureConstants.InternalServerErrorResponseDescription - /connect/register: - post: - tags: - - Connect - summary: Registers new client - description: Registers new client - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - ClientId: - type: string - description: Client id - ClientSecret: - type: string - description: Client secret - DisplayName: - type: string - description: Client display name - encoding: - ClientId: - style: form - ClientSecret: - style: form - DisplayName: - style: form - responses: - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Application registered successfully. - /connect/token: - post: - tags: - - Connect - summary: Retrieves bearer token - description: "\nTo authenticate Swagger requests, execute using \"Authorize\" above, not \"Try It Out\" here." - requestBody: - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - client_id: - type: 'string' - client_secret: - type: 'string' - grant_type: - type: 'string' - scope: - type: string - responses: - '400': - description: Bad Request. The request was invalid and cannot be completed. See the response body for details. - '500': - description: Internal server error. An unhandled error occurred on the server. See the response body for details. - '200': - description: Sign-in successful. -components: - schemas: - adminApiError: - title: AdminApiError - type: object - properties: - title: - type: string - nullable: true - readOnly: true - errors: - type: array - items: - type: string - nullable: true - readOnly: true - additionalProperties: false - description: Wrapper schema for all error responses - informationResult: - title: Information - type: object - properties: - version: - type: string - description: Application version - build: - type: string - description: Build / release version - additionalProperties: false - registerClientRequest: - title: RegisterClientRequest - type: object - properties: - clientId: - type: string - description: Client id - clientSecret: - type: string - description: Client secret - displayName: - type: string - description: Client display name - additionalProperties: false - securitySchemes: - oauth: - type: oauth2 - flows: - clientCredentials: - tokenUrl: https://localhost/connect/token - scopes: - edfi_admin_api/full_access: Unrestricted access to all Admin API endpoints -security: - - oauth: - - api diff --git a/docs/design/adminconsole/developer.md b/docs/design/adminconsole/developer.md deleted file mode 100644 index d11e5a1cd..000000000 --- a/docs/design/adminconsole/developer.md +++ /dev/null @@ -1,66 +0,0 @@ - -# EF Core Migration and Database Update Commands - -This document provides step-by-step guidance for generating and applying migrations in EF Core. Commands for both Command Line (`cmd`) and Package Manager Console (`pmc`) are included. - -### 1. Add a Migration - -#### Command Line (`cmd`) - -Use the following command to add a migration in the command line: - -```bash -dotnet ef migrations add --context --output-dir Infrastructure/DataAccess/Artifacts/ --project EdFi.Ods.AdminApi.AdminConsole -``` - -#### Package Manager Console (`pmc`) - -In the Package Manager Console within Visual Studio, run: - -```powershell -Add-Migration -Context -Project EdFi.Ods.AdminApi.AdminConsole -OutputDir Infrastructure/DataAccess/Artifacts// -``` - -- `MigrationName`: Name of the migration (e.g., `InitialCreate`). -- `ContextName`: Name of the context (options: `AdminConsolePgSqlContext`,`AdminConsoleMsSqlContext`) -- `Database`: Name of the context (options: `Admin`,`Security`) -- `DbProvider`: The database provider, this will create a folder or add the migration in the specific db provider (options: `MsSql`,`PgSql` ). - ---- - -### 2. Update the Database - -#### Command Line (`cmd`) - -To update the database using the command line, run: - -```bash -dotnet ef database update --context --project EdFi.Ods.AdminApi.AdminConsole -``` - -#### Package Manager Console (`pmc`) - -For updating the database from the Package Manager Console, use: - -```powershell -Update-Database -Context -Project EdFi.Ods.AdminApi.AdminConsole -``` -- `ContextName`: Name of the context (options: `AdminConsolePgSqlContext`,`AdminConsoleMsSqlContext`) ---- - -### Summary - -| Action | Command (cmd) | Command (pmc) | -|---------------------|--------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| -| **Add Migration** | `dotnet ef migrations add --context --output-dir Infrastructure/DataAccess/Artifacts// --project EdFi.Ods.AdminApi.AdminConsole` | `Add-Migration -Context -Project EdFi.Ods.AdminApi.AdminConsole -OutputDir Infrastructure/DataAccess/Artifacts//` | -| **Update Database** | `dotnet ef database update --context --project EdFi.Ods.AdminApi.AdminConsole` | `Update-Database -Context -Project EdFi.Ods.AdminApi.AdminConsole` | - ---- - ->**Note:** Before running any commands, verify that the `appsettings.json` file has the right `DatabaseEngine` and the corresponding `connection string` correctly configured. - -> **Note:** Before running commands in cmd, make sure to navigate to the root folder of your project by using: -> -> ```bash -> cd path/to/your/project -> ``` diff --git a/docs/design/adminconsole/image-datahealth-ods.png b/docs/design/adminconsole/image-datahealth-ods.png deleted file mode 100644 index 9aa5c6d31..000000000 Binary files a/docs/design/adminconsole/image-datahealth-ods.png and /dev/null differ diff --git a/docs/design/adminconsole/image-db.png b/docs/design/adminconsole/image-db.png deleted file mode 100644 index 3e86de6c4..000000000 Binary files a/docs/design/adminconsole/image-db.png and /dev/null differ diff --git a/docs/design/adminconsole/notes.md b/docs/design/adminconsole/notes.md deleted file mode 100644 index d98912503..000000000 --- a/docs/design/adminconsole/notes.md +++ /dev/null @@ -1,103 +0,0 @@ -# Admin Console - -We are going to expose the Admin Console endpoints required for the application in Admin API. It will be hosted as a different definition in Swagger and the base path
-```http://{domain}/adminconsole/{endpoint}``` - -## Endpoints - -[See documentation](./admin-api-adminconsole-2.3-summary.md)
-[See OpenAPI definition](./admin-api-adminconsole-2.3.yaml) - -The endpoints implementation will return a ExpandoObject type or a list of ExpandoObject in order to keep it dynamically when we need to adjust something in the payloads. - -Code reference using mock data: -> This example ilustrates using mock data but the final result will use a persistence using the EdFi_Admin database and other sources. See section [Persistence](#persistence) for more details. - -``` -internal Task GetSteps() -{ - using (StreamReader r = new StreamReader("Mockdata/data-steps.json")) - { - string json = r.ReadToEnd(); - List result = JsonConvert.DeserializeObject>(json); - return Task.FromResult(Results.Ok(result)); - } -} - -internal Task GetStep(int id) -{ - using (StreamReader r = new StreamReader("Mockdata/data-step.json")) - { - string json = r.ReadToEnd(); - ExpandoObject result = JsonConvert.DeserializeObject(json); - return Task.FromResult(Results.Ok(result)); - } -} -``` - -## Persistence - -Tables will be created under the new schema called 'adminconsole' in the EdFi_Admin database. -Tables will have the following design: - -* Doc ID - integer/PK -* OdsInstanceId - integer or UUID -* EdOrgID - integer (optional) -* UserID - integer (optional) -* Document - JSONB - -We identify the following tables to be created: - -* OdsInstances [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-odsinstances.json) -* Permissions [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-permissions.json) -* Steps [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-steps.json) -* Tenants [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-tenants.json) -* UserProfile [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-userprofile.json) - -> Some of these examples contain sensitive data such as keys and secrets values, so we are thinking of using an encryption/decryption mechanism to store the JSONB data by means of an AES256 alghoritm same as we use in Data Import application. - -![alt text](image-db.png) - -### Health check endpoint - -The healthcheck endpoint has informaction that comes from ODS/API - -* Healt check [See JSONB example](../../../Application/EdFi.Ods.AdminApi/Mockdata/data-healthcheck.json) - -In this case, we will store the client/secret values in Admin API to call some of the ODS/API endpoints to generate the returning payload to the Admin Console. The payload contains a 'total-count' report of some of the resources, it takes the value from the header in ODS/API. - -Example
-```https://api.ed-fi.org:443/v7.1/api/data/v3/ed-fi/studentSchoolAssociations?offset=0&limit=25&totalCount=true``` - -The parameter 'totalCount' is important to use because this will return us the count in the header as 'total-count'. With this value we can map it to our payload in the field called 'studentSchoolAssociations' - -![alt text](image-datahealth-ods.png) - -This process has to be called per field of the payload - -#### ODS/API services to call - -* studentSpecialEducationProgramAssociations -* studentDisciplineIncidentBehaviorAssociations -* studentSchoolAssociations -* studentSchoolAttendanceEvents -* studentSectionAssociations -* staffEducationOrganizationAssignmentAssociations -* staffSectionAssociations -* courseTranscripts -* sections - -> Unknown fields -> -> * basicReportingPeriodAttendances -> * reportingPeriodExts -> * localEducationAgencyId: We are assuming this as Ed Org Id but we are not sure about this -> * healthy (boolean): We are asumming this as a flag that return true if the above data have been populated correctly and no error from ODS/API - -As we have to call multiple endpoints in this one, we are considering use a caching approach (maybe the in-memory provided by .NET will be enough). If we want to refresh the data we can send a flag to the endpoint to do so. - -## Authentication and Authorization - -The endpoints will use the same Admin API authentication and authorization mechanism provided. Admin API uses [openiddict](https://github.com/openiddict/openiddict-core) library which supports different identity providers (besides its own) like Keycloak so we will have to integrate it in Admin API at some point. - -Reference: [OpenIdDict with Keycloak](https://damienbod.com/2022/05/02/implement-an-openiddict-identity-provider-using-asp-net-core-identity-with-keycloak-federation/) diff --git a/docs/design/adminconsole/readme.md b/docs/design/adminconsole/readme.md deleted file mode 100644 index 094fb3a20..000000000 --- a/docs/design/adminconsole/readme.md +++ /dev/null @@ -1,137 +0,0 @@ -# Admin API Design to Support the Admin App - -## Overview - -The Ed-Fi Admin App is a web-based user interface tool for managing Ed-Fi -ODS/API Platform installations. User can perform actions that include: - -* Manage tenants and database instances -* Manage Client credentials ("keys and secrets") -* Review application and database health - -The Ed-Fi Admin App application is a front-end only. The Ed-Fi Admin API 2 -application will act as the backend-for-frontend (BFF), serving all of the -interaction needs for Admin App. The Ed-Fi Admin API 2 will in turn rely on -other services and "worker" applications as needed to achieve some of its goals. - -The purpose of this document and related documents is to describe the -architecture of the related applications, the interfaces that sustain -communication, and the storage layers requirements. - -> [!TIP] -> The following sections utilize the [C4 model](https://c4model.com/) for -> describing the System Context and decomposing Containers within that Context. -> The notes further refine the architecture with detailed [UML sequence -> diagrams](https://en.wikipedia.org/wiki/Sequence_diagram). - -## System Context - -```mermaid -C4Context - title System Context for Ed-Fi Admin App - - Enterprise_Boundary(edorg, "Education Organization") { - Person(User, "Admin App User", "A system administrator") - } - - Enterprise_Boundary(other, "Other Services") { - System(Keycloak, "Keycloak", "OpenID Connect authorization provider") - } - - Enterprise_Boundary(edfi, "Ed-Fi ODS/API Platform") { - System(AdminConsole, "Ed-Fi Admin App", "A web application for managing ODS/API Deployments") - - - System_Boundary(backend, "Backend Systems") { - System(AdminAPI, "Ed-Fi Admin API 2 and Workers", "A REST API system for managing
administrative data and deployments,
plus background worker apps") - System(OdsApi, "Ed-Fi ODS/API", "A REST API system for
educational data interoperability") - } - } - - Rel(User, AdminConsole, "Manages instances,
Manages client credentials,
Checks API health") - UpdateRelStyle(User, AdminConsole, $offsetX="0", $offsetY="-10") - - Rel(AdminConsole, AdminAPI, "Issues HTTP requests") - UpdateRelStyle(AdminConsole, AdminAPI, $offsetY="-40", $offsetX="20") - - Rel(AdminAPI, OdsApi, "Reads and
configures") - UpdateRelStyle(AdminAPI, OdsApi, $offsetY="-20", $offsetX="-30") - - Rel(User, Keycloak, "Authentication") - UpdateRelStyle(User, Keycloak, $offsetX="-20", $offsetY="10") - - UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2") -``` - -## Containers - -Two of the functions needed by Admin API 2 will benefit from background -execution, so that the Admin App end user can experience a quick response -time in the web browser. Two worker applications will provide the background -processing: - -1. Instance Management Worker - creates new database instances. -2. Health Check Worker - polls the ODS/API to find record counts and records - them in Admin API 2. - -```mermaid -C4Container - title "Admin API Containers" - - System(AdminConsole, "Ed-Fi Admin App", "A web application for managing ODS/API Deployments") - - System_Boundary(backend, "Backend Systems") { - - Boundary(b0, "Admin API") { - Container(AdminAPI, "Ed-Fi Admin API 2") - Container(HealthCheck, "Admin API Health
Check Worker") - Container(Instance, "Admin API Instance
Management Worker") - } - - Boundary(b1, "ODS/API") { - System(OdsApi, "Ed-Fi ODS/API", "A REST API system for
educational data interoperability") - SystemDb(ods3, "EdFi_ODS_") - } - - Boundary(b2, "Shared Databases") { - ContainerDb(Admin, "EdFi_Admin,
EdFi_Security") - } - } - - Rel(AdminConsole, AdminAPI, "Issues HTTP requests") - - Rel(HealthCheck, AdminAPI, "Reads ODS/API connections,
Writes health info") - UpdateRelStyle(HealthCheck, AdminAPI, $offsetY="50") - - Rel(HealthCheck, OdsApi, "Reads records counts") - UpdateRelStyle(HealthCheck, OdsApi, $offsetX="-60", $offsetY="20") - - Rel(Instance, AdminAPI, "Reads instance requests,
Write instance status") - UpdateRelStyle(Instance, AdminAPI, $offsetY="20", $offsetX="10") - - Rel(Instance, ods3, "Creates new ODS instances") - UpdateRelStyle(Instance, ods3, $offsetY="20", $offsetX="-50") - - Rel(OdsApi, ods3, "Reads and writes") - UpdateRelStyle(OdsApi, ods3, $offsetX="10") - - Rel(AdminAPI, Admin, "Reads and writes") - UpdateRelStyle(AdminAPI, Admin, $offsetY="50", $offsetX="10") - - Rel(OdsApi, Admin, "Reads") - UpdateRelStyle(OdsApi, Admin, $offsetY="20", $offsetX="-10") - - UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2") -``` - -## Interfaces and Interactions - -The API interfaces and the interactions between specific containers are -described in detail in the following documents: - -* [REST API Support for Admin App](./APIS-FOR-ADMIN-CONSOLE.md) -* [Instance Management Worker](./INSTANCE-MANAGEMENT.md) -* [Health Check Worker](./HEALTH-CHECK-WORKER.md) - -Also see [Keycloak Configuration](./KEYCLOAK.md) for more information on using -Keycloak as the Open ID Connect provider. diff --git a/docs/design/auth/KEYCLOAK.md b/docs/design/auth/KEYCLOAK.md index bb2b46fed..c63f7b407 100644 --- a/docs/design/auth/KEYCLOAK.md +++ b/docs/design/auth/KEYCLOAK.md @@ -2,7 +2,7 @@ ## How to Add Scopes in Keycloak -This guide will walk you through the steps to add three scopes (edfi_admin_api/full_access, edfi_admin_api/tenant_access, and edfi_admin_api/worker) in Keycloak. +This guide will walk you through the steps to add scope `edfi_admin_api/full_access` in Keycloak. ### Prerequisites @@ -33,79 +33,9 @@ This guide will walk you through the steps to add three scopes (edfi_admin_api/f * **Description**: (Optional) Provide a description for the scope. 3. Click the `Save` button to create the scope `edfi_admin_api/full_access`. -#### Step 5: Create Scope edfi_admin_api/tenant_access - -1. Click the `Create` button again to add another client scope. -2. Fill in the following details: - * **Name**: Enter `edfi_admin_api/tenant_access` as the name for the scope. - * **Description**: (Optional) Provide a description for the scope. -3. Click the `Save` button to create the scope `edfi_admin_api/tenant_access`. - -#### Step 6: Create Scope edfi_admin_api/worker - -1. Click the `Create` button one more time to add a third client scope. -2. Fill in the following details: - * **Name**: Enter `edfi_admin_api/worker` as the name for the scope. - * **Description**: (Optional) Provide a description for the scope. -3. Click the `Save` button to create the scope `edfi_admin_api/worker`. - -## How to Add a Realm Role to a Realm - -### Prerequisites - -* Access to the Keycloak administration console. - -### Steps - -#### Step 1: Log in to the Keycloak Admin Console - -1. Open your web browser and navigate to the Keycloak admin console. -2. Enter your admin username and password, then click `Sign In`. - -#### Step 2: Select the Realm - -1. In the top-left corner of the console, click on the dropdown menu to select the desired realm. - -#### Step 3: Navigate to Roles - -1. In the left-hand menu, click on `Realm Roles` under the `Manage` section. -2. This will open the roles management page for the selected realm. - -#### Step 4: Add an adminapi-client - -1. Click the `Create Role` button at the top right corner of the roles management page. -2. Fill in the following details: - * **Role Name**: adminapi-client. - * **Description**: (Optional) Provide a description for the role. -3. Click the `Save` button to create the new role. - -#### Step 4: Add an adminconsole-user - -1. Click the `Create Role` button at the top right corner of the roles management page. -2. Fill in the following details: - * **Role Name**: adminconsole-user. - * **Description**: (Optional) Provide a description for the role. -3. Click the `Save` button to create the new role. - -#### Step 5: Configure adminconsole-user Role Settings - -1. Select the role adminconsole-user -2. Click the `Associated Roles` tab -3. Click the `Assign Role` button -4. Filter by realm roles -5. Check adminapi-client - -#### Step 6: Assign the Role to Users - -1. To assign the new realm role to users, navigate to the `Users` section in the left-hand menu. -2. Select the user you want to assign the role to. -3. Go to the `Role Mappings` tab. -4. In the `Available Roles` section, find and select the new realm role. -5. Click the `Add selected` button to assign the role to the user. - ## How to Add a Mapper to Realm Roles -To add the claim `` to store the list of roles. +To add the claim `http://schemas.microsoft.com/ws/2008/06/identity/claims/role` to store the list of roles. ### Prerequisites @@ -143,7 +73,7 @@ To add the claim ` 5. Fill in the following details: * **Name**: Enter a name for the new mapper. * **Mapper Type**: Select `Role Name Mapper` from the dropdown. - * **Token Claim Name**: Enter the name "". + * **Token Claim Name**: Enter the name `http://schemas.microsoft.com/ws/2008/06/identity/claims/role`. * **Claim JSON Type**: Select `String` or `Array` depending on your needs. * **Add to ID token**: Check this box if you want to add the claim to the ID token. * **Add to access token**: Check this box if you want to add the claim to the access token. @@ -152,18 +82,4 @@ To add the claim ` #### Step 6: Explain the Purpose of the Mapper -The mapper created in the previous step is used to add a label "" to the tokens. By adding this label, you can ensure that the tokens contain the necessary information for your application's security and access control requirements. - -> [!IMPORTANT] -> Detailed notes to be filled out, replacing these bullet points. -> -> * Should make sure we can configure Keycloak once for use both by Admin API -> and by Admin Console. -> * Review the configuration script used by the Data Management Service. The -> configuration script approach is not required; Keycloak may have a better -> way of handling this. We just need to get both projects to use the same -> settings. Maybe create a Docker container for Ed-Fi Keycloak with shared -> settings already in place. -> * Need to describe how to create the role for the worker processes, which -> allows them to access client credentials via the `/adminconsole/instances` -> endpoint. +The mapper created in the previous step is used to add a label `http://schemas.microsoft.com/ws/2008/06/identity/claims/role` to the tokens. By adding this label, you can ensure that the tokens contain the necessary information for your application's security and access control requirements. diff --git a/docs/design/auth/README.md b/docs/design/auth/README.md index 35e4b6083..a69b0fb90 100644 --- a/docs/design/auth/README.md +++ b/docs/design/auth/README.md @@ -71,52 +71,20 @@ The [self-contained authentication](./SELF-CONTAINED.md) in Admin API 2.0 through 2.2 was adequate for the application's needs. This support provided only the `client_credentials` grant flow. OpenIdDict _can_ support users, not just clients, but the support was unnecessary at the time. With the introduction -of [Admin App](../adminconsole/readme.md), a user interface that is backed +of Ed-Fi Admin App, a user interface that is backed by Admin API, there is a need for additional capabilities for managing users, supporting other flows, and providing a user sign-in page. -Therefore, when supporting Admin App, Admin API will need to rely on a token +Therefore, when supporting Admin App, Admin API should be able to rely on a token generated by an Open ID Connect compatible IdP. The development team will use Keycloak as their test implementation. However, by using Open ID Connect, it -should be possible to utilize other IdP's. +should be possible to utilize other IdPs. For backwards compatibility, existing Admin API deployments will be able to continue utilizing the self-contained authentication without the need to connect a third party IdP. However, they will not be able to support Admin App in this mode. -### Differential Access Rights - -#### Client Authorization - -The Admin API now supports multiple applications, and the generated tokens will -need to reflect the client's role: - -* `adminapi-client`: all clients who access Admin API should have this role. -* `adminconsole-user`: user accounts that access the Admin App will _also_ have this role. - -Thus, the Admin API should reject any token that does not have the -`adminapi-client` role, and the Admin App should similarly reject a token -that does not have the `adminconsole-user` role. - -Roles will be described in the JWT with a new claim called -`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`. This claim name -is for role-based access control in ASP.NET Identity. - -#### Resource Authorization - -Resources (endpoints) will be protected using OAuth Scopes. Admin API already -has a Scope of `edfi_admin_api/full_access` that provides access to all -reosurces. New scopes are needed for more limited access: - -* Admin App will have two user types: system administrators with full - access, and tenant administrators with more limited access. Details about this - more limited access are yet to be determined. **New scope: - `edfi_admin_api/tenant_access`**. -* The instance management and health check workers do not require full access to - the API. Their access can be limited with a **new scope: - `edfi_admin_api/worker`**. - ### System Context 2.3+ ```mermaid