diff --git a/src/developer/web-api/overview.md b/src/developer/web-api/overview.md index c921189eb..918e16623 100644 --- a/src/developer/web-api/overview.md +++ b/src/developer/web-api/overview.md @@ -569,7 +569,7 @@ by making a GET request to the `/api/relativePeriods/{RELATIVE_PERIOD_KEYWORD}` The endpoint supports the following parameters: - `startDate`: Represents the start date to calculate the relative period from. Format: `yyyy-MM-dd`. Default is today if not provided. -- `financialYearStart`: Should be one of FINANCIAL_YEAR_APRIL, FINANCIAL_YEAR_JULY, FINANCIAL_YEAR_OCTOBER. Default is FINANCIAL_YEAR_OCTOBER if not provided. +- `financialYearStart`: Should be one of FINANCIAL_YEAR_FEBRUARY, FINANCIAL_YEAR_APRIL, FINANCIAL_YEAR_JULY, FINANCIAL_YEAR_AUGUST, FINANCIAL_YEAR_SEPTEMBER, FINANCIAL_YEAR_OCTOBER. Default is FINANCIAL_YEAR_OCTOBER if not provided. As an example, to get the ISO representation of the relative period `LAST_3_MONTHS` diff --git a/src/developer/web-api/route.md b/src/developer/web-api/route.md index 621052b9c..8cecd7700 100644 --- a/src/developer/web-api/route.md +++ b/src/developer/web-api/route.md @@ -93,7 +93,7 @@ A number of authentication modes are supported when running routes. These authen } } ``` - Note that this request configures the route with static headers so that these headers are included in the request sent from DHIS2. Keep in mind that DHIS2 does not store these static headers as encrypted. + Note that this request configures the route with static headers so that these headers are included in the request sent from DHIS2. Keep in mind that DHIS2 does not store these static headers as encrypted. * `api-headers`: adds user-defined headers for API authentication. Here is an example creating a route configured with `api-headers` authentication: @@ -182,7 +182,7 @@ For performance reasons, the maximum transfer time for a route response is 5 min } ``` -The minimum permitted response timeout is 1 second while the maximum permitted timeout is 60 seconds. The `responseTimeoutSeconds` setting should be used with caution since concurrent, long-running routes could degrade DHIS2's overall performance. +The minimum permitted response timeout is 1 second while the maximum permitted timeout is 60 seconds. The `responseTimeoutSeconds` setting should be used with caution since concurrent, long-running routes could degrade DHIS2's overall performance. >IMPORTANT: The response timeout can be customised starting from DHIS2 v40.10 and v41.6. Earlier DHIS2 versions have a response timeout of 10 seconds, except for: >* 40.8 ≤ v < 40.10 @@ -223,5 +223,3 @@ The [Route Manager app](https://apps.dhis2.org/app/5dbe9ab8-46bd-411e-b22f-905f0 ![Routes Manager app](./resources/images/route-manager/route-manager-list.png) Visit the [DHIS2 system maintenance guide](https://docs.dhis2.org/en/use/user-guides/dhis-core-version-master/maintaining-the-system/route-manager.html) to learn more about using Route Manager. - - diff --git a/src/developer/web-api/scheduling.md b/src/developer/web-api/scheduling.md index 208bb9ae0..c04397534 100644 --- a/src/developer/web-api/scheduling.md +++ b/src/developer/web-api/scheduling.md @@ -61,7 +61,7 @@ Table: `ANALYTICS_TABLE` job parameters | Name | Type | Default | Description | |---------------|---------------|---------|--------------------------------------------------| -| `lastYears` | int | empty | Number of years back to include. No value means all years. | +| `lastYears` | int | empty | Number of years back to include. No value means all years. | | `skipTableTypes` | array of enum | `[]` | Skip generation of tables; Possible values: `DATA_VALUE`, `COMPLETENESS`, `COMPLETENESS_TARGET`, `ORG_UNIT_TARGET`, `EVENT`, `ENROLLMENT`, `VALIDATION_RESULT` | | `skipResourceTables` | boolean | `false` | Skip generation of resource tables | | `skipPrograms` | array of string | `[]` | Optional list of programs (IDs) that should be skipped | diff --git a/src/developer/web-api/settings-and-configuration.md b/src/developer/web-api/settings-and-configuration.md index 0952883d7..7fbb6a765 100644 --- a/src/developer/web-api/settings-and-configuration.md +++ b/src/developer/web-api/settings-and-configuration.md @@ -147,7 +147,8 @@ Table: System settings | keyCustomTopMenuLogo | Logo for custom top menu | No | | globalShellEnabled | When this property is enabled (set to true), apps will be displayed as iframes within a global shell. This global shell provides a consistent header bar across the system which has expanded functionalities compared to the original header bar. Default: true. | No | | keyCacheAnalyticsDataYearThreshold | Analytics data older than this value (in years) will always be cached. "0" disabled this setting. Default: 0 | No | -| analyticsFinancialYearStart | Set financial year start. Default: October | No | +| analyticsFinancialYearStart | Set financial year start. Options: FINANCIAL_YEAR_FEBRUARY, FINANCIAL_YEAR_APRIL, FINANCIAL_YEAR_JULY, INANCIAL_YEAR_AUGUST, FINANCIAL_YEAR_SEPTEMBER, FINANCIAL_YEAR_OCTOBER. Default: FINANCIAL_YEAR_OCTOBER | No | +| analyticsWeeklyStart | Set weekly relative period start day. Options: WEEKLY (Monday), WEEKLY_WEDNESDAY, WEEKLY_THURSDAY, WEEKLY_FRIDAY, WEEKLY_SATURDAY, WEEKLY_SUNDAY. Default: WEEKLY (Monday) | No | | keyIgnoreAnalyticsApprovalYearThreshold | "0" check approval for all data. "-1" disable approval checking. "1" or higher checks approval for all data that is newer than "1" year. | No | | keyAnalyticsMaxLimit | Maximum number of analytics records. Default: "50000" | No | | KeyTrackedEntityMaxLimit | Maximum number of tracked entities that are returned by `/tracker/trackedEntities`. More info [here](tracker.md#tracked-entities-collection-limits). Default: "50000" | No | diff --git a/src/developer/web-api/tracker.md b/src/developer/web-api/tracker.md index ef8bc4f04..5414e088e 100644 --- a/src/developer/web-api/tracker.md +++ b/src/developer/web-api/tracker.md @@ -125,7 +125,7 @@ The table below will point out any exceptional cases between these two. | event | The identifier of the event. Generated if not supplied. | No | Yes | String:Uid | ABCDEF12345 | | programStage | The program stage the event represents. | Yes | No | String:Uid | ABCDEF12345 | | enrollment | A reference to the enrollment which owns the event. Not applicable for `EVENT PROGRAM`. | Yes | Yes | String:Uid | ABCDEF12345 | -| program | The program that contains the event. | No | Yes | String:Uid | ABCDEF12345 | +| program | Only for reading data. The type of program the enrollment which owns the event has. | No | Yes | String:Uid | ABCDEF12345 | | trackedEntity | Only for reading data. The tracked entity which owns the event. Not applicable for `EVENT PROGRAM`. | No | No | String:Uid | ABCDEF12345 | | status | Status of the event. Default is `ACTIVE`. For `EVENT PROGRAM` only `ACTIVE` and `COMPLETED` statuses are allowed. | No | No | Enum | ACTIVE, COMPLETED, VISITED, SCHEDULE, OVERDUE, SKIPPED | | orgUnit | The organisation unit where the user registered the event. | Yes | No | String:Uid | ABCDEF12345 | @@ -3153,18 +3153,18 @@ Table: Entity query criteria definition | Property | Description | Example | | --- |---|---| -|attributeValueFilters|A list of attributeValueFilters. This is used to specify filters for attribute values when listing tracked entities|`"attributeValueFilters"=[{"attribute": "abcAttributeUid","le": "20","ge": "10","lt": "20","gt": "10","in": ["India", "Norway"],"like": "abc","sw": "abc","ew": "abc","dateFilter": {"startDate": "2014-05-01","endDate": "2019-03-20","startBuffer": -5,"endBuffer": 5,"period": "LAST_WEEK","type": "RELATIVE"}}]`| -|enrollmentStatus|The tracked entities enrollment status. Can be none(any enrollmentstatus) or ACTIVE, COMPLETED, CANCELLED|| +|attributeValueFilters|A list of attributeValueFilters. This is used to specify filters for attribute values when listing tracked entities|`"attributeValueFilters":[{"attribute": "abcAttributeUid","le": "20","ge": "10","lt": "20","gt": "10","in": ["India", "Norway"],"like": "abc","sw": "abc","ew": "abc","dateFilter": {"startDate": "2014-05-01","endDate": "2019-03-20","startBuffer": -5,"endBuffer": 5,"period": "LAST_WEEK","type": "RELATIVE"}}]`| +|enrollmentStatus|The tracked entities enrollment status. Can be none(any enrollmentstatus) or `ACTIVE`, `COMPLETED`, `CANCELLED`|| |followUp|When this parameter is true, the working list only returns tracked entities that have an enrollment with `followUp=true`.|| |organisationUnit|To specify the uid of the organisation unit|`{"organisationUnit": "a3kGcGDCuk7"}`| |ouMode|To specify the organisation unit selection mode. Options are `SELECTED`, `CHILDREN`, `DESCENDANTS`, `ACCESSIBLE`, `CAPTURE`, `ALL`|`"ouMode": "SELECTED"`| -|assignedUserMode|To specify the assigned user selection mode for events. Options are CURRENT, PROVIDED, NONE , ANY. See table below to understand what each value indicates. If `PROVIDED` (or null), non-empty assignedUsers in the payload will be considered.|"assignedUserMode": "PROVIDED"| -|assignedUsers|To specify a list of assigned users for events. To be used along with PROVIDED assignedUserMode above.|`"assignedUsers": ["a3kGcGDCuk7", "a3kGcGDCuk8"]`| +|assignedUserMode|To specify the assigned user selection mode for events. Options are `CURRENT`, `PROVIDED`, `NONE`, `ANY`. See table below to understand what each value indicates. If `PROVIDED` (or null), non-empty assignedUsers in the payload will be considered.|`"assignedUserMode": "PROVIDED"`| +|assignedUsers|To specify a list of assigned users for events. To be used along with `PROVIDED` assignedUserMode above.|`"assignedUsers": ["a3kGcGDCuk7", "a3kGcGDCuk8"]`| |displayColumnOrder|To specify the output ordering of columns|`"displayOrderColumns": ["enrollmentDate", "program"]`| -|order|To specify ordering/sorting of fields and its directions in comma separated values. A single item in order is of the form "orderDimension:direction". Note: Supported orderDimensions are trackedEntity, created, createdAt, createdAtClient, updatedAt, updatedAtClient, enrolledAt, inactive and the tracked entity attributes|`"order"="a3kGcGDCuk6:desc"`| -|programStage|To specify a programStage uid to filter on. tracked entities will be filtered based on presence of enrollment in the specified program stage.|`"programStage"="a3kGcGDCuk6"`| -|trackedEntityType|To specify a trackedEntityType filter tracked entities on.|`{"trackedEntityType"="a3kGcGDCuk6"}`| -|trackedEntities|To specify a list of tracked entities to use when querying tracked entities.|`"trackedEntities"=["a3kGcGDCuk6","b4jGcGDCuk7"]`| +|order|To specify ordering/sorting of fields and its directions in comma separated values. A single item in order is of the form "orderDimension:direction". Note: Supported orderDimensions are trackedEntity, created, createdAt, createdAtClient, updatedAt, updatedAtClient, enrolledAt, inactive and the tracked entity attributes|`"order":"a3kGcGDCuk6:desc"`| +|programStage|To specify a programStage uid to filter on. tracked entities will be filtered based on presence of enrollment in the specified program stage.|`"programStage":"a3kGcGDCuk6"`| +|trackedEntityType|To specify a trackedEntityType filter tracked entities on.|`{"trackedEntityType":"a3kGcGDCuk6"}`| +|trackedEntities|To specify a list of tracked entities to use when querying tracked entities.|`"trackedEntities":["a3kGcGDCuk6","b4jGcGDCuk7"]`| |enrollmentCreatedDate|[DateFilterPeriod](#webapi_tracker_workinglists_common_objects) object date filtering based on enrollment created date.|`"enrollmentCreatedDate": { "period": "LAST_WEEK", "type": "RELATIVE" }`| |enrollmentIncidentDate|[DateFilterPeriod](#webapi_tracker_workinglists_common_objects) object date filtering based on enrollment incident date.|`"enrollmentIncidentDate": { "startDate": "2014-05-01", "endDate": "2019-03-20", "startBuffer": -5, "endBuffer": 5, "period": "LAST_WEEK", "type": "RELATIVE" }`| |eventStatus|The event status. Options are `ACTIVE`, `COMPLETED`, `VISITED`, `SCHEDULE`, `OVERDUE`, `SKIPPED` and `VISITED`|`"status":"VISITED"`| @@ -3685,6 +3685,16 @@ GET /api/messages To retrieve a specific message. +``` +GET /api/messages/scheduled/sent?enrollment={uid} +``` + +``` +GET /api/messages/scheduled/sent?event={uid} +``` + +To retrieve a specific message. + ``` GET /api/messages/{uid} ``` @@ -3735,6 +3745,10 @@ tracker export endpoints. Further performance optimizations require knowledge of patterns and data distribution. If your implementation can share this information, it will help prioritize improvements. +For tracker performance improvements relative to previous releases, see the corresponding release +note, e.g. +[2.43](https://dhis2.github.io/dhis2-releases/releases/2.43/ReleaseNote-2.43.html#tracker). + ### Import #### Notifications diff --git a/src/sysadmin/reference/dhis.conf.md b/src/sysadmin/reference/dhis.conf.md index 984409473..d6048d6b0 100644 --- a/src/sysadmin/reference/dhis.conf.md +++ b/src/sysadmin/reference/dhis.conf.md @@ -264,6 +264,9 @@ analytics.connection.pool.min_size = 5 # Number of connections a pool will try to acquire upon startup. Should be between minPoolSize and maxPoolSize. (default: 5). analytics.connection.pool.initial_size = 5 +# If present, overrides the analytics.connection.url value - useful when running Apache Doris in a container (so it is not able to access Postgres via `localhost`). +doris.catalog.connection.url = https://doris.example.org + # ---------------------------------------------------------------------- # System telemetry [Optional] diff --git a/src/sysadmin/reference/oauth.md b/src/sysadmin/reference/oauth.md index e8e2ba01d..acbe68654 100644 --- a/src/sysadmin/reference/oauth.md +++ b/src/sysadmin/reference/oauth.md @@ -1,290 +1,972 @@ -# OpenID Connect (OIDC) configuration { #install_oidc_configuration } +# OAuth2 and OpenID Connect (OIDC) { #install_oauth2_oidc_configuration } -DHIS2 supports the OpenID Connect (OIDC) identity layer for single sign-in (SSO). OIDC is a standard authentication protocol that lets users sign in with an identity provider (IdP) such as for example Google. After users have successfully signed in to their IdP, they will be automatically signed in to DHIS2. +DHIS2's OAuth2 / OIDC stack has two sides: -This section provides general information about using DHIS2 with an OIDC provider, as well as complete configuration examples. +1. **DHIS2 as an Authorization Server**. DHIS2 can issue its own OAuth2 + access tokens and OpenID Connect ID tokens. Web apps, server-to-server + integrations, and the DHIS2 Android Capture app authenticate against + it. This is built on top of + [Spring Authorization Server](https://docs.spring.io/spring-authorization-server/reference/). +2. **DHIS2 as a Relying Party (OIDC login)**. Users can sign in to DHIS2 + with an external identity provider such as Google, Microsoft Entra ID + (Azure AD), WSO2, Okta, or any standards-compliant OIDC provider. + DHIS2 validates the ID token and matches it to a local user account. -The DHIS2 OIDC 'authorization code' authentication flow: +Both sides can be enabled at the same time. This chapter covers both, +plus how client applications use the resulting tokens to call the DHIS2 +API (JWT bearer authentication). -1. A user attempts to log in to DHIS2 and clicks the OIDC provider button on the login page. -2. DHIS2 redirects the browser to the IdP's login page. +## Terminology -3. If not already logged in, the user is prompted for credentials. When successfully authenticated, the IdP responds with a redirect back to the DHIS2 server. The redirect includes a unique authorization code generated for the user. +| Term | Meaning in this chapter | +|------|-------------------------| +| Authorization Server (AS) | The component that issues tokens. In DHIS2 this is Spring Authorization Server, enabled via `oauth2.server.enabled=on`. | +| Resource Server | The component that validates tokens and protects APIs. In DHIS2 this is the same process, the DHIS2 web API. | +| Identity Provider (IdP) | The OIDC provider that authenticates end users. Can be DHIS2 itself (internal) or an external provider (Google, Azure AD, …). | +| Relying Party (RP) | The OIDC client. When DHIS2 is logging users in via an external IdP, DHIS2 is the RP. | +| Registered client (OAuth2 client) | A database record describing an application allowed to request tokens from the AS. See [OAuth2 clients](#oauth2_clients). | +| DCR | Dynamic Client Registration (RFC 7591). Lets a client register itself at runtime instead of an admin creating it up front. Used by the Android Capture app. | +| IAT | Initial Access Token. A short-lived JWT that authorizes exactly one DCR registration call. | +| `private_key_jwt` | Client authentication method in which the client proves its identity by signing a JWT with its private key, rather than sending a shared secret (RFC 7523). | +| JWKS | JSON Web Key Set. The public-key document used to verify JWT signatures. | -4. The DHIS2 server internally sends the user's authorization code back to the IdP server along with its own client id and client secret credentials. +--- -5. The IdP returns an ID token back to the DHIS2 server. DHIS2 server performs validation of the token. +## Enabling the authorization server { #enabling_the_authorization_server } -6. The DHIS2 server looks up the internal DHIS2 user with the mapping claims found in the ID token (defaults to email), authorizes the user and completes the login process. +The authorization server is off by default. To turn it on, set in +`dhis.conf`: -## Requirements for using OIDC with DHIS2: +```properties +# Public HTTPS base URL of this DHIS2 instance. +# Used as the OAuth2 issuer URI (the `iss` claim in issued tokens). +# Must be set; the authorization server refuses to start otherwise. +server.base.url = https://dhis2.example.org -### IdP server account +# Turn on Spring Authorization Server. +oauth2.server.enabled = on +``` -You must have an admin account on an online identity provider (IdP) or on a standalone server that are supported by DHIS2. +When `oauth2.server.enabled = on`, DHIS2 exposes the following endpoints +(paths are the Spring Authorization Server defaults): -The following IdPs are currently supported and tested: +| Endpoint | Path | Purpose | +|----------|------|---------| +| Authorization | `/oauth2/authorize` | User-facing authorization endpoint (authorization_code flow). | +| Token | `/oauth2/token` | Token exchange (authorization_code, refresh_token, client_credentials, `private_key_jwt` client assertion). | +| JWKS | `/oauth2/jwks` | Public keys used to verify issued JWTs. | +| Revocation | `/oauth2/revoke` | Revoke an access or refresh token (RFC 7009). | +| Introspection | `/oauth2/introspect` | Token introspection (RFC 7662). | +| OIDC userinfo | `/oauth2/userinfo` | Standard OIDC userinfo endpoint. | +| OIDC discovery | `/.well-known/openid-configuration` | OIDC discovery document. | +| Dynamic Client Registration | `/connect/register` | RFC 7591 client registration (used by Android DCR). | +| Device enrollment (DHIS2-specific) | `/api/auth/enrollDevice` | Mints a one-time Initial Access Token for DCR. See [DCR](#dynamic_client_registration). | -* Google -* Azure AD -* WSO2 -* Okta (See separate tutorial: [here](#configure-openid-connect-with-okta)) +### Issuer URI and `server.base.url` { #oauth2_issuer_uri } -There is also a **generic provider** config which can support "any" OIDC compatible provider. +The OAuth2 issuer URI (the `iss` claim in every issued JWT, and the base +for all OIDC discovery metadata) is derived from `server.base.url`. Set +it to the **public** URL clients use to reach DHIS2. If DHIS2 sits +behind a TLS-terminating reverse proxy, use the external `https://` URL, +not the internal `http://` URL Tomcat sees. -### DHIS2 user accounts +The authorization server reads the issuer URI directly from +`server.base.url` rather than inferring it from the incoming HTTP +request. -For Open ID Connect (OIDC) authentication in DHIS2, user accounts must be created in DHIS2 and mapped to the corresponding entries in the Identity Provider (IdP) platform. This mapping is achieved by configuring the *OIDC mapping value* property for each DHIS2 user account. +`server.base.url` is normalized so that it +is treated the same with or without a trailing slash. -Note that the mapping values are case sensitive. For example, if email addresses are used as claims in the IdP and the email addresses use upper and lower-case letters, make sure to take the capitalization into account when entering the DHIS2 user OIDC mapping value. +The authorization server throws `IllegalStateException` at startup if +`server.base.url` is empty when `oauth2.server.enabled=on`. +DHIS2 also logs a warning when `server.base.url` is missing in any configuration. -Importing users from an external directory such as Active Directory is currently not supported. Provisioning and management of users with an external identity store is not supported by the OIDC standard. +### Persistent signing keystore { #oauth2_keystore } -### IdP claims and mapping of users +The authorization server signs every issued JWT with an RSA private +key. DHIS2 loads the signing key from one of two sources, chosen at +startup: -To sign in to DHIS2 with OIDC, a given user must be provisioned in the IdP and then mapped to a user account in DHIS2. OIDC uses a method that relies on claims to share user account attributes with other applications. Claims include user account attributes such as email, phone number, name, etc. DHIS2 relies on a IdP claim to map user accounts from the IdP to those in the DHIS2 server. By default, DHIS2 expects the IdP to pass the _email_ claim. Depending on your IdP, you may need to configure DHIS2 to use a different IdP claim. +1. **From a keystore file** (recommended for production): a Java + KeyStore (`.jks` / `.p12`) on disk. +2. **Ephemeral** (default): a fresh RSA-2048 keypair is generated in + memory at startup. **Every restart invalidates every previously + issued token**, because the public key used to sign them is gone. + This mode is only appropriate for development or first-boot. -If you are using Google or Azure AD as an IdP, the default behavior is to use the _email_ claim to map IdP identities to DHIS2 user accounts. +Configuration keys (all in `dhis.conf`): -> **Note** -> -> In order for a DHIS2 user to be able to log in with an IdP, the user profile checkbox: *External authentication only OpenID or LDAP* must be checked and *OpenID* field must match the claim (mapping claim) returned by the IdP. Email is the default claim used by both Google and Azure AD. +```properties +# Path to the Java keystore containing the signing key. +# If empty, DHIS2 falls back to the ephemeral mode below. +oauth2.server.jwt.keystore.path = /etc/dhis2/oauth2-signing.p12 + +# Password for the keystore file itself. +oauth2.server.jwt.keystore.password = + +# Alias of the key entry inside the keystore. REQUIRED when keystore.path is set. +oauth2.server.jwt.keystore.alias = dhis2-oauth2-signing + +# Optional: password protecting the private-key entry, if different from +# the keystore password. +oauth2.server.jwt.keystore.key-password = + +# If no keystore.path is configured, generate an ephemeral RSA-2048 +# keypair at startup. Default: true. Set to false in production to make +# a missing keystore a hard error instead of silently falling back to +# an ephemeral key. +oauth2.server.jwt.keystore.generate-if-missing = false +``` -## Configure the Identity Provider for OIDC +Only **RSA** keys are supported for the authorization server signing +key (no EC, no HMAC). -This topic provides general information about configuring an identity provider (IdP) to use OIDC with DHIS2. This is one step in a multi-step process. Each IdP has slightly different ways to configure it. Check your IdP's own documentation for how to create and configure an OIDC application. Here we refer to the DHIS2 server as the OIDC "application". +### Creating a signing keystore -### Redirect URL +A simple way to create a PKCS12 keystore containing a 2048-bit RSA key: -All IdPs will require a redirect URL to your DHIS2 server. -You can construct it using the following pattern: +```bash +keytool -genkeypair \ + -alias dhis2-oauth2-signing \ + -keyalg RSA -keysize 2048 \ + -validity 3650 \ + -storetype PKCS12 \ + -keystore /etc/dhis2/oauth2-signing.p12 \ + -storepass "" \ + -keypass "" \ + -dname "CN=dhis2-oauth2-signing" +chmod 600 /etc/dhis2/oauth2-signing.p12 +chown tomcat:tomcat /etc/dhis2/oauth2-signing.p12 ``` -(protocol):/(your DHIS2 host)/oauth2/code/PROVIDER_KEY + +Then point `dhis.conf` at the file with the alias and password. + +After starting DHIS2, the corresponding public key is published at +`https://dhis2.example.org/oauth2/jwks`. Clients and resource servers +fetch it from there to verify tokens. + +### Key rotation + +The keystore is read once at startup. To rotate the signing key, add a +new key entry to the keystore (or swap keystores), update +`oauth2.server.jwt.keystore.alias`, and restart DHIS2. All tokens +signed by the previous key become unverifiable as soon as the new +public key replaces the old one at `/oauth2/jwks`. Plan rotations to +coincide with short-lived token expiry. + +> There is currently no on-the-fly key rotation; all token-validity +> coverage comes from access-token TTL. + +--- + +## OAuth2 clients { #oauth2_clients } + +Every application that asks DHIS2 for a token must be registered as an +**OAuth2 client**. Clients are persisted in the `oauth2_client` table. + +### Client concepts + +| Concept | Values in DHIS2 | +|---------|-----------------| +| Client authentication methods | `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, `private_key_jwt`, `none`. Stored as a comma-separated list on the client. | +| Authorization grant types | `authorization_code`, `refresh_token` (**allowed via the admin API**). `client_credentials` is reserved for the internal DCR system registrar only. | +| Scopes | Any string; standard `openid`, `profile`, `email` are honored by the token customizer. | +| Redirect URIs | `http://` and `https://` always accepted. Custom schemes (e.g. the Android deep-link `dhis2oauth://oauth`) must appear verbatim in the `deviceEnrollmentRedirectAllowlist` system setting. | + +> **Only `authorization_code` and `refresh_token` are accepted when +> creating clients via the admin API or metadata import.** Trying to +> create a client with `client_credentials` returns HTTP 409 with the +> `E4000` error. The internal `system-dcr-registrar-client` is the +> only client allowed to use `client_credentials` and is managed by +> DHIS2 itself. + +### Managing clients via the API { #oauth2_clients_rest } + +CRUD endpoint: `/api/oAuth2Clients` (standard DHIS2 metadata CRUD). +Requires the `F_OAUTH2_CLIENT_MANAGE` authority. + +#### Create a confidential web-app client + +```bash +curl -u admin:district -X POST \ + -H 'Content-Type: application/json' \ + https://dhis2.example.org/api/oAuth2Clients \ + -d '{ + "clientId": "my-web-app", + "clientSecret": "REPLACE-WITH-STRONG-RANDOM", + "name": "My Web App", + "clientAuthenticationMethods": "client_secret_basic", + "authorizationGrantTypes": "authorization_code,refresh_token", + "redirectUris": "https://my-web-app.example.org/callback", + "scopes": "openid,profile,email", + "clientSettings": "{\"requireAuthorizationConsent\":true}", + "tokenSettings": "{}" + }' ``` -Example when using Google IdP: +#### List clients -``` -https://mydhis2-server.org/oauth2/code/google +```bash +curl -u admin:district \ + 'https://dhis2.example.org/api/oAuth2Clients?fields=id,clientId,name,authorizationGrantTypes' ``` -External links to instructions for configuring your IdP: +The internal `system-dcr-registrar-client` is filtered out of list +responses. Attempts to create, update, delete, or rename a client to +`system-dcr-registrar-client` are rejected with HTTP 409. -* [Google](https://developers.google.com/identity/protocols/oauth2/openid-connect) -* [Azure AD tutorial](https://medium.com/xebia-engineering/authentication-and-authorization-using-azure-active-directory-266980586ab8) +#### Update a client +```bash +curl -u admin:district -X PUT \ + -H 'Content-Type: application/json' \ + https://dhis2.example.org/api/oAuth2Clients/ \ + -d '{ "clientId":"my-web-app", "redirectUris":"https://my-web-app.example.org/callback,https://my-web-app.example.org/callback-v2" }' +``` -## Example setup for Google +If you PUT without a `name`, the previously-persisted name is +preserved (the Settings UI POSTs without `name`). -1. Register an account and sign in. For example, for Google, you can go to the Google [developer console](https://console.developers.google.com). -2. In the Google developer dashboard, click 'create a new project'. -3. Follow the instructions for creating an OAuth 2.0 client ID and client secret. -4. Set your "Authorized redirect URL" to: `https://mydhis2-server.org/oauth2/code/google` -5. Copy and keep the "client id" and "client secret" in a secure place. +#### Delete -> **Tip** -> -> When testing on a local DHIS2 instance running for example on your laptop, you can use localhost as the redirect URL, like this: `https://localhost:8080/oauth2/code/google` -> *Remember to also add the redirect URL in the Google developer console* +```bash +curl -u admin:district -X DELETE \ + https://dhis2.example.org/api/oAuth2Clients/ +``` + +### Authorizations and consents (read-only) + +Issued authorizations and user consents are stored in +`oauth2_authorization` and `oauth2_authorization_consent`. Two +superuser-only read-only controllers surface them for debugging: + +- `GET /api/oAuth2Authorizations`: issued tokens and codes per user/client. +- `GET /api/oAuth2AuthorizationConsents`: granted consents per user/client. + +These cannot be imported via `/api/metadata` and cannot be mutated via +the REST API; they are managed by the authorization server itself. + +### Client secret lifecycle + +When a client is created with `client_secret_basic` or +`client_secret_post` authentication, the `clientSecret` supplied in +the POST body is stored and compared verbatim at the token endpoint. +Treat the value like any other long-lived credential: + +- Use strong random values. Do not reuse across environments. +- Rotate by PUTting a new `clientSecret` value. The old secret stops + working immediately. +- Grant the `F_OAUTH2_CLIENT_MANAGE` authority only to users who need + it. + + + +--- + +## Dynamic Client Registration (DCR) { #dynamic_client_registration } + +DHIS2 supports [RFC 7591 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591). +DCR is implicitly enabled whenever the authorization server is enabled +(`oauth2.server.enabled=on`). There is no separate `oauth2.dcr.enabled` +key. + +The primary driver for DCR is the DHIS2 Android Capture app: every +enrolled device becomes its own first-party OAuth2 client, authenticated +via `private_key_jwt` instead of a shared secret. See +[Android device enrollment walkthrough](#android_dcr_walkthrough). + +### Flow + +1. A user of the Android app (or any DCR-aware client) hits + `GET /api/auth/enrollDevice?redirectUri=&state=`. +2. DHIS2 validates that `redirectUri` matches the + `deviceEnrollmentRedirectAllowlist` system setting and that the user + belongs to a group in `deviceEnrollmentAllowedUserGroups` (if the + setting is non-empty). +3. DHIS2 mints a one-time, short-lived JWT **Initial Access Token + (IAT)** and 302-redirects to + `?iat=&state=`. +4. The client POSTs a standard RFC 7591 registration payload to + `/connect/register`, presenting the IAT as its authorization. The + payload **must include inline `jwks`** (the client's public keys). + `jwks_uri` is not accepted. +5. DHIS2 validates the IAT, persists a new `oauth2_client` row with + `ClientAuthenticationMethod.PRIVATE_KEY_JWT`, and returns the + standard RFC 7591 registration response. +6. The client subsequently authenticates at `/oauth2/token` by signing + a JWT assertion with the private key that matches the registered + public JWKS. + +IATs are single-use: after one successful `/connect/register`, the +underlying authorization row is consumed and the IAT cannot be +replayed. + +### Relevant system settings + +DCR behavior is configured via the standard **System Settings** API +(database-backed, not `dhis.conf`). + +| System setting | Default | Controls | +|----------------|---------|----------| +| `deviceEnrollmentRedirectAllowlist` | `dhis2oauth://oauth` | Comma-separated glob allow-list of `redirect_uri` values that `GET /api/auth/enrollDevice` and DCR clients may use. Custom schemes (like the Android deep-link `dhis2oauth://oauth`) **must** appear here to be accepted. | +| `deviceEnrollmentAllowedUserGroups` | *(empty)* | CSV of user-group UIDs. If non-empty, only members of those groups may call `/api/auth/enrollDevice`. Empty means any authenticated user. | +| `deviceEnrollmentIATTtlSeconds` | `60` | Lifetime of the Initial Access Token. | + +### Consent prompt behavior + +Spring Authorization Server's default for DCR-registered clients is +`requireAuthorizationConsent=true`, which would pop a consent screen +on every authorization_code flow. DHIS2 overrides that default for DCR +clients, every DCR-registered client is saved with `requireAuthorizationConsent=false` +so that first-party device flows don't interrupt the user with a +self-grant prompt. + +REST-created (non-DCR) clients still default to +`requireAuthorizationConsent=true`, so the consent screen is shown as +expected for third-party web apps. + +### Android device enrollment walkthrough { #android_dcr_walkthrough } + +**Server-side setup (one time):** + +1. Set `server.base.url` to the public HTTPS URL of the DHIS2 + instance. +2. Set `oauth2.server.enabled = on` in `dhis.conf`. +3. Configure a persistent keystore (see + [Persistent signing keystore](#oauth2_keystore)); without it, + every server restart invalidates every device's tokens, but not the clients. +4. (Optional) Restrict device enrollment to a specific user group: + set `deviceEnrollmentAllowedUserGroups` in System Settings. +5. Confirm the default redirect allow-list value + `dhis2oauth://oauth` is present in + `deviceEnrollmentRedirectAllowlist`. This is what the Android app + uses as its deep-link. + +**Per-device flow (automated by the Android app):** + +1. The user signs in to DHIS2 via the Android app's embedded browser + (OIDC authorization_code flow against the internal DHIS2 IdP; see + [Internal DHIS2 OIDC provider](#internal_dhis2_oidc_provider)). +2. The app generates an RSA keypair on device and stores the private + key in the Android Keystore. +3. The app calls `GET /api/auth/enrollDevice?redirectUri=dhis2oauth://oauth&state=`. +4. DHIS2 validates the redirect URI against the allow-list, + mints an IAT, and 302-redirects the browser to + `dhis2oauth://oauth?iat=&state=`. +5. Android unwraps the deep-link and hands the IAT back to the app. +6. The app POSTs to `/connect/register` with payload: + ```json + { + "redirect_uris": ["dhis2oauth://oauth"], + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "token_endpoint_auth_method": "private_key_jwt", + "token_endpoint_auth_signing_alg": "RS256", + "jwks": { + "keys": [ { ...device public key... } ] + } + } + ``` + Authorization header: `Bearer `. +7. DHIS2 stores the inline JWKS against the new client + (ClientSettings key `client.inline.jwks`) and responds with the + generated `client_id`. +8. Going forward, the app completes authorization_code flows against + `/oauth2/authorize` and signs JWT client assertions (RFC 7523) + with its private key when calling `/oauth2/token`. +9. When the user signs out, the post-logout redirect routes back to + the same `dhis2oauth://oauth` deep-link, + so the Android app stays in control of the UX. + + +--- + +## OIDC login: DHIS2 as a Relying Party { #oidc_login } + +DHIS2 supports OpenID Connect for single sign-in. After users +authenticate at their identity provider (IdP), they are signed in to +DHIS2 automatically. + +The OIDC 'authorization code' authentication flow: + +1. A user opens the DHIS2 login page and clicks the OIDC provider + button. +2. DHIS2 redirects the browser to the IdP's login page. +3. If not already signed in, the user enters credentials. The IdP + responds with a redirect back to DHIS2 carrying an authorization + code. +4. DHIS2 exchanges the authorization code (plus its client id + + secret, or a `private_key_jwt` assertion) at the IdP's token + endpoint and receives an ID token. +5. DHIS2 validates the ID token signature against the IdP's JWKS. +6. DHIS2 looks up the internal user by the configured + `mapping_claim`, authorizes the user, and completes the login. + +### Requirements + +1. **An IdP.** You must control an account on an external IdP or run + a standalone one. Tested providers: + - Google + - Microsoft Entra ID (Azure AD) + - WSO2 + - Okta (via the generic provider; see + [tutorial](../../../topics/tutorials/configure-oidc-with-okta.md)) + - Any OIDC-compliant provider via the **generic** provider config. + - DHIS2 itself, via the [internal DHIS2 OIDC provider](#internal_dhis2_oidc_provider). +2. **DHIS2 user accounts.** Each user that should log in via OIDC + must have a matching DHIS2 user row with: + - `External authentication only (OpenID or LDAP)` checked in the + user profile, and + - an `OpenID` value equal to the expected value of the IdP's + mapping claim (case-sensitive). + Importing users from an external directory is not supported by the + OIDC standard and is not provided by DHIS2. +3. **Redirect URL.** Every IdP needs the DHIS2 redirect URL + registered as an authorized redirect. Pattern: + ``` + /oauth2/code/ + ``` + Example for Google: + ``` + https://dhis2.example.org/oauth2/code/google + ``` + +### Claims and user mapping + +OIDC uses **claims** to carry user attributes (email, name, preferred +username, phone, etc.). DHIS2 maps an IdP account to a DHIS2 account +by looking up the DHIS2 user whose `OpenID` field equals the IdP's +mapping-claim value. + +The mapping claim is `email` by default for all external providers +(Google, Azure AD, WSO2, generic). The **internal DHIS2 provider** +uses `username` by default. + +If your IdP presents a different claim (for example `preferred_username` +or a custom claim), set `oidc.provider..mapping_claim` to that +claim name. + +### Enabling OIDC login -### Google dhis.conf example: ```properties -# Enables OIDC login +# Global switch for OIDC login (oauth2Login filter chain + provider repo). oidc.oauth2.login.enabled = on -# Client id, given to you in the Google developer console -oidc.provider.google.client_id = +# Optional: where to land the user after they log out of the IdP. +oidc.logout.redirect_url = https://dhis2.example.org +``` -# Client secret, given to you in the Google developer console -oidc.provider.google.client_secret = +Then configure at least one provider. Examples follow. -# [Optional] Authorized redirect URI, the same as set in the Google developer console -# If your public hostname is different from what the server sees internally, -# you need to provide your full public url, like the example below. -oidc.provider.google.redirect_url = https://mydhis2-server.org/oauth2/code/google +### Google { #oidc_google } -# [Optional] Where to redirect after logging out. -# If your public hostname is different from what the server sees internally, -# you need to provide your full public url, like the example below. -oidc.logout.redirect_url = https://mydhis2-server.org -``` +1. In the [Google developer console](https://console.developers.google.com), create a + project and an OAuth 2.0 client ID/secret. +2. Add the DHIS2 redirect URL: + `https://dhis2.example.org/oauth2/code/google`. +3. Configure DHIS2: + +```properties +oidc.oauth2.login.enabled = on -## Example setup for Azure AD +oidc.provider.google.client_id = +oidc.provider.google.client_secret = -Make sure your Azure AD account in the Azure portal is configured with a redirect URL like: `(protocol):/(host)/oauth2/code/PROVIDER_KEY`. -To register your DHIS2 server as an "application" in the Azure portal, follow these steps: +# Optional overrides +oidc.provider.google.redirect_url = https://dhis2.example.org/oauth2/code/google +oidc.logout.redirect_url = https://dhis2.example.org +``` -> **Note** +> **Tip** > -> PROVIDER_KEY is the "name" part of the configuration key, example: "oidc.provider.PROVIDER_KEY.tenant = My Azure SSO" -> If you have multiple Azure providers you want to configure, you can use this name form: (azure.0), (azure.1) etc. -> Redirect URL example: https://mydhis2-server.org/oauth2/code/azure.0 +> When testing locally, use `https://localhost:8080/oauth2/code/google` +> and add the same URL to the Google console. + +### Microsoft Entra ID (Azure AD) { #oidc_azure } -1. Search for and select *App registrations*. -2. Click *New registration*. -3. In the *Name* field, enter a descriptive name for your DHIS2 instance. -4. In the *Redirect URI* field, enter the redirect URL as specified above. -5. Click *Register*. +1. In the Azure portal, go to **App registrations → New registration** + and set the redirect URI to: + `https://dhis2.example.org/oauth2/code/azure.0` +2. Copy the tenant (directory) ID and the client ID/secret. +3. Configure DHIS2: -### Azure AD dhis.conf example: ```properties -# Enables OIDC login oidc.oauth2.login.enabled = on -# First provider (azure.0): - -# Tenant ID, also called Directory ID, in UUID format +# First Azure provider (azure.0): oidc.provider.azure.0.tenant = - -# Client id, given to you in the Azure portal, in UUID format oidc.provider.azure.0.client_id = +oidc.provider.azure.0.client_secret = +oidc.provider.azure.0.redirect_url = https://dhis2.example.org/oauth2/code/azure.0 -# Client secret, given to you in the Azure portal -oidc.provider.azure.0.client_secret = +# Optional: +oidc.provider.azure.0.mapping_claim = email # default is email +oidc.provider.azure.0.enable_logout = on # default is on -# [Optional] Authorized redirect URI, the as set in Azure portal -# If your public hostname is different from what the server sees internally, -# you need to provide your full public URL -oidc.provider.azure.0.redirect_url = https://mydhis2-server.org/oauth2/code/azure.0 +oidc.logout.redirect_url = https://dhis2.example.org +``` -# [Optional] Where to redirect after logging out. -# If your public hostname is different from what the server sees internally, -# you need to provide your full public URL -oidc.logout.redirect_url = https://mydhis2-server.org +Multiple Azure tenants are supported. Use `azure.0`, `azure.1`, … +blocks; each becomes its own login-page button. -# [Optional], defaults to 'email' -oidc.provider.azure.0.mapping_claim = email -# [Optional], defaults to 'on' -oidc.provider.azure.0.support_logout = on +### Generic providers { #oidc_generic } -# Second provider (azure.1): +The generic provider can be used for "any" standards-compliant OIDC +IdP. It appears as a button on the login page with the provider key +as the default name (or the value of `display_alias` if defined). The +provider key can be any alphanumeric string except the reserved names +`google`, `azure`, `wso2`, and `dhis2`. + +Example: configuring a fictional OIDC provider `myprovider`. + +```properties +oidc.oauth2.login.enabled = on -# Tenant ID, also called Directory ID, in UUID format -oidc.provider.azure.1.tenant = -... +# Required properties: +oidc.provider.myprovider.client_id = +oidc.provider.myprovider.client_secret = +oidc.provider.myprovider.mapping_claim = email +oidc.provider.myprovider.authorization_uri = https://myprovider.example.org/connect/authorize +oidc.provider.myprovider.token_uri = https://myprovider.example.org/connect/token +oidc.provider.myprovider.user_info_uri = https://myprovider.example.org/connect/userinfo +oidc.provider.myprovider.jwk_uri = https://myprovider.example.org/.well-known/openid-configuration/jwks + +# Optional: +oidc.provider.myprovider.end_session_endpoint = https://myprovider.example.org/connect/endsession +oidc.provider.myprovider.scopes = openid,email,profile +oidc.provider.myprovider.redirect_url = https://dhis2.example.org/oauth2/code/myprovider +oidc.provider.myprovider.enable_logout = on +oidc.provider.myprovider.enable_pkce = on +oidc.provider.myprovider.display_alias = My Provider +oidc.provider.myprovider.login_image = ../security/btn_myprovider.svg +oidc.provider.myprovider.login_image_padding = 0px 1px + +# Optional extra request parameters appended to the authorization request +# (key/value pairs, comma-separated): +oidc.provider.myprovider.extra_request_parameters = acr_values lvl4,other_key value2 + +oidc.logout.redirect_url = https://dhis2.example.org ``` -## Generic providers +The full set of keys supported per generic provider: + +| Key | Required | Purpose | +|-----|----------|---------| +| `client_id` | yes | Client ID issued by the IdP. | +| `client_secret` | yes (unless `private_key_jwt`) | Client secret issued by the IdP. | +| `authorization_uri` | yes | IdP authorization endpoint. | +| `token_uri` | yes | IdP token endpoint. | +| `user_info_uri` | yes | IdP userinfo endpoint. | +| `jwk_uri` | yes | IdP JWKS endpoint (public keys). | +| `mapping_claim` | no (default `email`) | Claim used to map to the DHIS2 user. | +| `redirect_url` | no | Override the default redirect URL. | +| `issuer_uri` | no | Expected `iss` claim. | +| `end_session_endpoint` | no | IdP logout endpoint. | +| `scopes` | no (default `openid,email`) | Scopes requested in the authorization request. | +| `display_alias` | no | Label for the login-page button. | +| `login_image` | no | Relative path to a logo for the button. | +| `login_image_padding` | no | CSS padding around the logo. | +| `enable_logout` | no (default `on`) | Use `end_session_endpoint` on logout. | +| `enable_pkce` | no (default `off`) | Enable PKCE (RFC 7636). | +| `authorization_grant_type` | no (default `authorization_code`) | Grant type. | +| `client_authentication_method` | no | `client_secret_basic`, `client_secret_post`, or `private_key_jwt`. | +| `keystore_path` / `keystore_password` / `key_alias` / `key_password` | for `private_key_jwt` | See [private_key_jwt client auth](#oidc_private_key_jwt). | +| `jwk_set_url` | for `private_key_jwt` | URL at which the IdP can fetch DHIS2's public key. | +| `extra_request_parameters` | no | Extra params on the authorization request. | +| `user_info_response_type` | no (default `json`) | `json` (Spring's standard userinfo path) or `jwt` (signed-JWT userinfo, e.g. MOSIP eSignet). See [Signed-JWT userinfo](#oidc_signed_jwt_userinfo). | +| `user_info_jws_algorithm` | for `user_info_response_type = jwt` | JWS algorithm used to verify the signed userinfo JWT. Default `RS256`; allowed: `RS256/384/512`, `PS256/384/512`, `ES256/384/512`. | + +Unknown keys are logged at startup with a suggestion of the +closest valid key. + +### `private_key_jwt` client authentication to the IdP { #oidc_private_key_jwt } + +Some enterprise IdPs require clients to authenticate with a JWT +assertion signed by the client's private key (RFC 7523 / +`private_key_jwt`) instead of a shared secret. DHIS2 supports this +per-provider by loading a key from a dedicated per-provider +keystore: -The generic provider can be used to configure "any" standard OIDC provider which are compatible with "Spring Security". +```properties +oidc.provider.myprovider.client_id = +oidc.provider.myprovider.client_authentication_method = private_key_jwt + +# RSA key DHIS2 signs JWT client assertions with: +oidc.provider.myprovider.keystore_path = /etc/dhis2/myprovider-client.p12 +oidc.provider.myprovider.keystore_password = +oidc.provider.myprovider.key_alias = myprovider-client +oidc.provider.myprovider.key_password = + +# URL exposing the matching public JWK for the IdP to fetch. +# DHIS2 serves it at /api/publicKeys//jwks.json by default; +# set jwk_set_url so the JWT header `jku` points at that URL. +oidc.provider.myprovider.jwk_set_url = https://dhis2.example.org/api/publicKeys//jwks.json +``` -In the example below we will configure the Norwegian governmental _HelseID_ OIDC provider using the provider key `helseid`. +Register the public JWK (or `jwk_set_url`) with the IdP during client +setup there; the IdP uses it to verify the `private_key_jwt` +assertion. Rotation works the same way as the authorization-server +keystore: add a new key entry, update the alias, restart DHIS2. -The defined provider will appear as a button on the login page with the provider key as the default name, -or the value of the `display_alias` if defined. The provider key is arbitrary and can be any alphanumeric string, -except for the reserved names used by the specific providers (`google`, `azure.0,azure.1...`, `wso2`). +> **Two keystores, two purposes** +> +> - `oauth2.server.jwt.keystore.*` signs tokens **issued by** DHIS2 as + > an authorization server. Only one keystore per instance. +> - `oidc.provider..keystore_*` signs JWT assertions sent **from** + > DHIS2 to an external IdP during OIDC login. One keystore per + > provider, used only when that provider is configured with + > `private_key_jwt`. -> **Note** -> The generic provider uses the following hardcoded configuration defaults: -> **(These are not possible to change)** -> * Client Authentication, `ClientAuthenticationMethod.BASIC`: [rfc](https://tools.ietf.org/html/rfc6749#section-2.3) -> * Authenticated Requests, `AuthenticationMethod.HEADER`: [rfc](https://tools.ietf.org/html/rfc6750#section-2) +### Signed-JWT userinfo (eSignet) { #oidc_signed_jwt_userinfo } -### Generic (helseid) dhis.conf example: +The OIDC spec allows the userinfo endpoint to return either plain +`application/json` (the default behaviour every provider in this +document uses) or a signed JWT (`application/jwt`). Some IdPs — +notably **MOSIP eSignet** — return only the signed-JWT form. Enable +the JWT path per provider by setting `user_info_response_type = jwt`: ```properties -# Enables OIDC login -oidc.oauth2.login.enabled = on - -# Required variables: -oidc.provider.helseid.client_id = -oidc.provider.helseid.client_secret = -oidc.provider.helseid.mapping_claim = helseid://claims/identity/email -oidc.provider.helseid.authorization_uri = https://helseid.no/connect/authorize -oidc.provider.helseid.token_uri = https://helseid.no/connect/token -oidc.provider.helseid.user_info_uri = https://helseid.no/connect/userinfo -oidc.provider.helseid.jwk_uri = https://helseid.no/.well-known/openid-configuration/jwks -oidc.provider.helseid.end_session_endpoint = https://helseid.no/connect/endsession -oidc.provider.helseid.scopes = helseid://scopes/identity/email - -# [Optional] Authorized redirect URI, the as set in Azure portal -# If your public hostname is different from what the server sees internally, -# you need to provide your full public url, like the example below. -oidc.provider.helseid.redirect_url = https://mydhis2-server.org/oauth2/code/helseid - -# [Optional], defaults to 'on' -oidc.provider.helseid.enable_logout = on - -# [Optional] Where to redirect after logging out. -# If your public hostname is different from what the server sees internally, -# you need to provide your full public URL, like the example below. -oidc.logout.redirect_url = https://mydhis2-server.org - -# [Optional] PKCE support, see: https://oauth.net/2/pkce/), default is 'false' -oidc.provider.helseid.enable_pkce = on - -# [Optional] Extra variables appended to the request. -# Must be key/value pairs like: "KEY1 VALUE1,KEY2 VALUE2,..." -oidc.provider.helseid.extra_request_parameters = acr_values lvl4,other_key value2 - -# [Optional] This is the alias/name displayed on the login button in the DHIS2 login page -oidc.provider.helseid.display_alias = HelseID - -# [Optional] Link to a url for a logo. (Only relative paths are supported) -oidc.provider.helseid.login_image = ../security/btn_helseid.svg - -# [Optional] CSS padding for the logo image -oidc.provider.helseid.login_image_padding = 0px 1px +oidc.provider.esignet.client_id = +oidc.provider.esignet.client_secret = +oidc.provider.esignet.mapping_claim = sub +oidc.provider.esignet.authorization_uri = https://esignet.example.org/authorize +oidc.provider.esignet.token_uri = https://esignet.example.org/oauth/token +oidc.provider.esignet.user_info_uri = https://esignet.example.org/oidc/userinfo +oidc.provider.esignet.jwk_uri = https://esignet.example.org/oidc/.well-known/jwks.json + +# Signed-JWT userinfo +oidc.provider.esignet.user_info_response_type = jwt +oidc.provider.esignet.user_info_jws_algorithm = RS256 ``` -## JWT bearer token authentication +What changes when this flag is set: + +- DHIS2 calls the userinfo endpoint with `Accept: application/jwt`. +- The response body is treated as a signed JWT. The signature is + verified against the IdP's JWKS — fetched from the existing + `jwk_uri` — using the algorithm named in `user_info_jws_algorithm`. +- The `mapping_claim` lookup then proceeds exactly as on the JSON + path (`mapping_claim = sub` is typical for eSignet). +- The same DHIS2 user requirements apply: account must exist, be + flagged for external authentication, and not be disabled or + expired. + +`user_info_jws_algorithm` is validated at startup against an +allow-list of asymmetric algorithms: + +| Family | Algorithms | +|--------|------------| +| RSA-PKCS#1 v1.5 | `RS256`, `RS384`, `RS512` | +| RSA-PSS | `PS256`, `PS384`, `PS512` | +| ECDSA | `ES256`, `ES384`, `ES512` | + +HMAC algorithms (`HS256`, `HS384`, `HS512`) and `none` are +deliberately **not** accepted — the signing key for userinfo must +come from the IdP's published JWKS, never from a shared secret. +Unknown or unsupported values cause the provider to be rejected at +startup with an explicit log line. + +`user_info_response_type` is also case-insensitively validated; +the only accepted values are `json` (default) and `jwt`. Any other +value fails the provider at startup. + +> **Notes** +> +> - Combining `user_info_response_type = jwt` with +> `client_authentication_method = private_key_jwt` is supported and +> common (eSignet typically requires both). +> - The JWKS document is fetched lazily on first login and cached +> per provider with Nimbus's standard remote-key cache and refresh +> policy, so a normal IdP key rotation requires no DHIS2 restart. -Authentication with *JWT bearer tokens* can be enabled for clients which API-based when OIDC is configured. -The DHIS2 Android client is such a type of client and have to use JWT authentication if OIDC login is enabled. +### Internal DHIS2 OIDC provider { #internal_dhis2_oidc_provider } -> **Note** -> -> DHIS2 currently only supports the OAuth2 authorization code grant flow for authentication with JWT, (also known as "three-legged OAuth") -> DHIS2 currently only supports using Google as an OIDC provider when using JWT tokens +When `oauth2.server.enabled = on`, DHIS2 automatically registers +itself as an OIDC provider with the registration ID `dhis2-internal`. +This provider is **not** shown on the web login page (it is used only +for the Android app's authorization_code flow against the internal +authorization server and for resource-server JWT validation). +The internal provider is +fully auto-configured from `server.base.url`. The following minimum +config is enough to enable DHIS2-as-IdP for the Android app: -## Requirements -* Configure your Google OIDC provider as described above -* Disable the config parameter ```oauth2.authorization.server.enabled``` by setting it to 'off' -* Enable the config parameter ```oidc.jwt.token.authentication.enabled``` by setting it to 'on' -* Generate an Android OAuth2 client_id as described [here](https://developers.google.com/identity/protocols/oauth2/native-app#creatingcred) +```properties +server.base.url = https://dhis2.example.org +oauth2.server.enabled = on +``` -## JWT authentication example +All of the `oidc.provider.dhis2.*` endpoint URIs are derived from +`server.base.url` at startup. The separate `oidc.oauth2.login.enabled` +key is **not** needed for the internal provider (it controls only the +web-facing external-IdP login buttons). -The following `dhis.conf` section shows an example of how to enable JWT authentication for an API-based client. +If you need to override the internal provider's client credentials +(rare, usually for tests), the following keys exist: ```properties -# Enables OIDC login -oidc.oauth2.login.enabled = on +oidc.provider.dhis2.client_id = dhis2-internal # default +oidc.provider.dhis2.client_secret = secret # default +oidc.provider.dhis2.mapping_claim = username # default +oidc.provider.dhis2.server_url = +``` -# Minimum required config variables: -oidc.provider.google.client_id = -oidc.provider.google.client_secret = +### Linked accounts { #connect_single_identity_to_multiple_accounts } -# Enable JWT support -oauth2.authorization.server.enabled = off -oidc.jwt.token.authentication.enabled = on +DHIS2 can map a single IdP identity to multiple DHIS2 accounts. Users +can list their linked accounts and switch between them via API. -# Define client 1 using JWT tokens -oidc.provider.google.ext_client.0.client_id = +When enabled, the `openid` column in `userinfo` is no longer unique: +on a successful IdP sign-in, DHIS2 logs in the account that most +recently signed in. -# Define client 2 using JWT tokens -oidc.provider.google.ext_client.1.client_id = +```properties +linked_accounts.enabled = on +# Optional: override where the user is redirected during account switch. +linked_accounts.logout_url = https://dhis2.example.org/dhis-web-login/logout +linked_accounts.relogin_url = https://dhis2.example.org/ ``` -> **Note** -> -> [Check out our tutorial for setting up Okta as a generic OIDC provider.](../../../topics/tutorials/configure-oidc-with-okta.md) +See [Switching between user accounts connected to the same identity +provider account](../../../develop/using-the-api/dhis-core-version-master/users.html#switching-between-user-accounts-connected-to-the-same-identity-provider-account) for the user-switching API. + +--- + +## JWT bearer token authentication { #jwt_bearer_authentication } -## Connecting a single identity provider account to multiple DHIS2 accounts { #connect_single_identity_to_multiple_accounts } +After obtaining an access token (from DHIS2's own authorization +server, the internal DHIS2 IdP, or any registered external IdP), +clients authenticate subsequent API calls with: -DHIS2 has the ability to map a single identity provider account to multiple DHIS2 accounts. API calls are available to list the linked accounts and also switch between then. +``` +Authorization: Bearer +``` + +### When JWT bearer authentication is active + +Two flags in `dhis.conf` enable inbound JWT bearer authentication: + +| Key | What it does | +|-----|--------------| +| `oauth2.server.enabled` | Enables the Spring Authorization Server **and** the JWT bearer filter. Also registers the internal `dhis2-internal` OIDC provider so DHIS2 accepts its own issued tokens. | +| `oidc.jwt.token.authentication.enabled` | Enables **only** the JWT bearer filter (for accepting tokens issued by an external IdP). Does not expose an authorization server. | -When this option is selected, the `openid` database field in the `userinfo` table does not need to be unique. When presented with an `openid` value from the identity provider, DHIS2 will log in the user that most recently logged in. +Either flag adds a bearer-token filter after HTTP basic auth. If both +are on, the filter is still registered once; the difference is only +whether the internal provider is wired. -The following `dhis.conf` section shows how to enable linked accounts. +### Token validation + +Tokens are validated per request by +`Dhis2JwtAuthenticationManagerResolver`. Behavior: + +- Extract `iss` from the JWT header. Look up a registered provider + (internal or external) by matching issuer URI. +- Verify the signature against the provider's JWKS. +- Match the token audience: + - If `iss` is the internal `dhis2-internal` provider, **every** `aud` + must match a registered `Dhis2OAuth2Client.clientId`. + - Otherwise, the provider's registered client IDs must include at + least one audience. +- Map the token to a DHIS2 user via the provider's + `mapping_claim` (`username` or `email` are supported; any other + value fails the request with `InvalidBearerTokenException`). + +### Minimal configuration for Android clients + +For the DHIS2 Android Capture app to authenticate against a DHIS2 +instance using JWT bearer tokens issued by the internal DHIS2 IdP, +only two config keys are required: ```properties -# Enable a single OIDC account to log in as one of several DHIS2 accounts -linked_accounts.enabled = on +server.base.url = https://dhis2.example.org +oauth2.server.enabled = on ``` -For instructions on how to list linked accounts and switch between them, see [*Switching between user accounts connected to the same identity provider account* in the Users chapter of the developer documentation.](../../../develop/using-the-api/dhis-core-version-master/users.html#switching-between-user-accounts-connected-to-the-same-identity-provider-account) +(Plus a persistent keystore; see +[Persistent signing keystore](#oauth2_keystore).) + +### Minimal configuration for third-party JWT-issued tokens + +To accept tokens issued by an external IdP (Google, Azure AD, custom) +**without** running DHIS2's own authorization server, configure the +IdP as a generic OIDC provider and enable the bearer filter: + +```properties +# Global OIDC login switch (also controls the bearer filter) +oidc.jwt.token.authentication.enabled = on + +# External IdP as a generic provider +oidc.provider.myprovider.client_id = +oidc.provider.myprovider.client_secret = +oidc.provider.myprovider.authorization_uri = ... +oidc.provider.myprovider.token_uri = ... +oidc.provider.myprovider.user_info_uri = ... +oidc.provider.myprovider.jwk_uri = ... +oidc.provider.myprovider.mapping_claim = email +``` + +Clients then obtain tokens from the IdP and call DHIS2 with the +resulting bearer token. DHIS2 verifies the signature against the +provider's JWKS and maps the token to a DHIS2 user via +`mapping_claim`. + + +--- + +## Configuration reference { #oauth2_config_reference } + +All keys live in `dhis.conf`. + +### Authorization server + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `oauth2.server.enabled` | `on`/`off` | `off` | Enable the Spring Authorization Server, DCR endpoints, internal DHIS2 OIDC provider, and the JWT bearer filter. | +| `oauth2.server.jwt.keystore.path` | path | `` | Keystore file containing the authorization-server signing key. | +| `oauth2.server.jwt.keystore.password` | string | `` | Keystore password. | +| `oauth2.server.jwt.keystore.alias` | string | `` | Alias of the key entry inside the keystore. Required when `keystore.path` is set. | +| `oauth2.server.jwt.keystore.key-password` | string | `` | Password of the private-key entry. Optional; defaults to the keystore password. | +| `oauth2.server.jwt.keystore.generate-if-missing` | `true`/`false` | `true` | Generate an ephemeral RSA-2048 keypair at startup if `keystore.path` is empty. Set to `false` in production. | +| `server.base.url` | URL | `` | Public HTTPS base URL; used as OAuth2 issuer URI when the authorization server is enabled. | + +### OIDC login (DHIS2 as Relying Party) + +| Key | Type | Default | Purpose | +|-----|------|---------|---------| +| `oidc.oauth2.login.enabled` | `on`/`off` | `off` | Enable the `oauth2Login` filter chain for web login with external IdPs. | +| `oidc.logout.redirect_url` | URL | `` | Where the user lands after logging out of the IdP. | +| `oidc.jwt.token.authentication.enabled` | `on`/`off` | `off` | Enable inbound JWT bearer token authentication (without running an authorization server). | +| `oidc.provider.google.client_id` | string | `` | Google client ID. | +| `oidc.provider.google.client_secret` | string | `` | Google client secret. | +| `oidc.provider.google.mapping_claim` | string | `email` | Claim used to map Google identities to DHIS2 users. | +| `oidc.provider.google.redirect_url` | URL | `` | Optional override for the redirect URL. | +| `oidc.provider.azure..tenant` | string | `` | Azure tenant (directory) ID for provider `n`. | +| `oidc.provider.azure..client_id` | string | `` | Azure client ID. | +| `oidc.provider.azure..client_secret` | string | `` | Azure client secret. | +| `oidc.provider.azure..mapping_claim` | string | `email` | Mapping claim. | +| `oidc.provider.azure..enable_logout` | `on`/`off` | `on` | Enable logout via the Azure `end_session_endpoint`. | +| `oidc.provider.wso2.*` | - | - | WSO2 provider (see WSO2 section). | +| `oidc.provider..*` | - | - | Generic OIDC provider. Full key set documented under [Generic providers](#oidc_generic). | +| `oidc.provider.dhis2.client_id` | string | `dhis2-internal` | Override the internal-provider client ID (rare; usually unset). | +| `oidc.provider.dhis2.client_secret` | string | `secret` | Override the internal-provider client secret. | +| `oidc.provider.dhis2.mapping_claim` | string | `username` | Mapping claim for the internal provider. | +| `oidc.provider.dhis2.server_url` | URL | `` | Override base URL; defaults to `server.base.url`. | +| `linked_accounts.enabled` | `on`/`off` | `off` | Allow one IdP identity to map to multiple DHIS2 accounts. | +| `linked_accounts.logout_url` | URL | `` | Logout URL used by the account-switch flow. | +| `linked_accounts.relogin_url` | URL | `` | Re-login URL used by the account-switch flow. | + +### System settings (database-backed) + +| Setting | Default | Purpose | +|---------|---------|---------| +| `deviceEnrollmentRedirectAllowlist` | `dhis2oauth://oauth` | Allow-list of `redirect_uri` values for DCR and for custom-scheme clients. | +| `deviceEnrollmentAllowedUserGroups` | *(empty)* | CSV of user-group UIDs allowed to enroll devices. Empty = any authenticated user. | +| `deviceEnrollmentIATTtlSeconds` | `60` | Lifetime of Initial Access Tokens. | + +--- + +## Troubleshooting { #oauth2_troubleshooting } + +**The authorization server does not start (`IllegalStateException: server.base.url is required`).** +Set `server.base.url` in `dhis.conf` to the public HTTPS URL of the instance. + +**Issued tokens have `iss: http://...` instead of `https://...`.** +Set `server.base.url` to the full public HTTPS URL. The issuer URI +is derived from `server.base.url` directly; your app server does +not need to know it is behind a proxy. + +**Tokens issued before a restart stop working after restart.** +You are running with the default ephemeral-keystore mode. Configure +a persistent keystore (see +[Persistent signing keystore](#oauth2_keystore)). + +**`POST /api/oAuth2Clients` with `"authorizationGrantTypes":"client_credentials"` returns 409 (`E4000`).** +Only `authorization_code` and `refresh_token` are permitted via the +admin API. `client_credentials` is reserved for the internal DCR +system registrar. + +**Creating a client with a custom-scheme redirect URI (`dhis2oauth://oauth`) is rejected.** +Add the exact URI to the `deviceEnrollmentRedirectAllowlist` system +setting. `http://` and `https://` URIs are always accepted. + +**OIDC login button does not appear.** +Check that `oidc.oauth2.login.enabled = on` and that at least one +`oidc.provider.*.client_id` is configured with matching +`client_secret`. Unknown keys are logged at startup with a +suggestion of the closest valid key. + +**JWT bearer request fails with HTTP 401 and `Invalid mapping claim`.** +The provider's `mapping_claim` is neither `username` nor `email`; only +these two are supported. Set `oidc.provider..mapping_claim` to +one of them. + + +**`/connect/register` rejects the payload for a missing `jwks`.** +DHIS2 DCR requires an inline JWKS object in the registration body; +`jwks_uri` is not accepted. Include the full `jwks` object with the +client's public keys. + +--- + +## Upgrade notes: 2.41 to 2.42 { #oauth2_upgrade_2_42 } + +2.42 replaces the authorization server implementation. 2.41 shipped +an authorization server built on the deprecated +`spring-security-oauth2` project; 2.42 uses the actively maintained +Spring Authorization Server. This is a rewrite, not a drop-in +upgrade: the configuration key, endpoint paths, token format, and +client schema all change. + +### Breaking changes + +| Area | 2.41 | 2.42 | +|------|------|------| +| Implementation | `spring-security-oauth2` (deprecated) | Spring Authorization Server | +| Enable key | `oauth2.authorization.server.enabled` | `oauth2.server.enabled` | +| Token endpoint | `/oauth/token` | `/oauth2/token` | +| Authorize endpoint | `/oauth/authorize` | `/oauth2/authorize` | +| Access token format | Opaque, server-stored | JWT, RSA-signed | +| JWKS endpoint | none | `/oauth2/jwks` | +| Signing key | not applicable | RSA keypair from keystore | +| `OAuth2Client` entity | `cid`, `secret`, `redirectUris`, `grantTypes` | Spring AS `RegisteredClient` with full OAuth2/OIDC properties | +| Client tables | `oauth2client`, `oauth2clientgranttypes`, `oauth2clientredirecturis` | `oauth2_client` | +| Authorization tables | `oauth_access_token`, `oauth_code` | `oauth2_authorization`, `oauth2_authorization_consent` | + +The `/api/oAuth2Clients` endpoint path and the +`F_OAUTH2_CLIENT_MANAGE` authority that gates it are unchanged, but +the request and response JSON schemas are different. + +### What is unchanged from 2.41 + +- OIDC login as a Relying Party (`oidc.oauth2.login.enabled`) with + Google, Microsoft Entra ID, WSO2, and generic providers. +- Inbound JWT bearer authentication against external IdPs + (`oidc.jwt.token.authentication.enabled`). +- Linked accounts (`linked_accounts.*`). + +### Upgrade actions + +If 2.41 had the authorization server enabled: + +1. Rename `oauth2.authorization.server.enabled` to + `oauth2.server.enabled` in `dhis.conf`. +2. Configure a persistent signing keystore + (`oauth2.server.jwt.keystore.*`). Without it, tokens are signed + with an ephemeral keypair that is regenerated on every restart. + See [Persistent signing keystore](#oauth2_keystore). +3. Recreate every registered OAuth2 client against the new schema + via `POST /api/oAuth2Clients`. Rows in the legacy `oauth2client*` + tables are not migrated. +4. Update client applications for the new endpoint paths (`/oauth/*` + becomes `/oauth2/*`) and the new token format (opaque becomes + JWT, validated against `/oauth2/jwks`). +5. Existing access tokens and authorization codes do not survive the + upgrade; clients must re-authenticate. + +If 2.41 did not have the authorization server enabled, the upgrade +is a no-op: the new authorization server is disabled by default. + diff --git a/src/user/configure-metadata.md b/src/user/configure-metadata.md index d10084b27..258abe052 100644 --- a/src/user/configure-metadata.md +++ b/src/user/configure-metadata.md @@ -56,10 +56,10 @@ You may customize which columns are shown in the list for the current object. Th 1. Click the ![](resources/images/maintenance/icon_settings.png) icon to the top right of the list of objects you want to configure. 2. A dropdown-menu will appear, select **Manage columns**. 3. A dialog will appear, with the default columns selected. -3. Click any column-name in the list of **Available columns** to add them to the list of selected columns. -4. You may reorder the selected columns by drag-and-dropping the ![](resources/images/maintenance/icon_reorder.png) icon. -5. You may also remove any column from the view by clicking the X-icon next to the name. -6. Click **Save** once you are satisified with your changes. +4. Click any column-name in the list of **Available columns** to add them to the list of selected columns. +5. You may reorder the selected columns by drag-and-dropping the ![](resources/images/maintenance/icon_reorder.png) icon. +6. You may also remove any column from the view by clicking the X-icon next to the name. +7. Click **Save** once you are satisfied with your changes. You may easily reset to the default values by clicking the **Reset to default** button. @@ -165,7 +165,7 @@ option could be reused. This is important if particular category options > > You can automatically select all organisation units that belong to > an organisation unit level or organisation unit group, for example - > "Chiefdom" or "Urban. To do this: + > "Chiefdom" or "Urban". To do this: > > Select an **Organisation unit level** or **Organisation unit > group** and click **Select**. @@ -211,7 +211,7 @@ you can create that category. ### Create or edit a category combination { #create_category_combination } -Category combinations lets you combine multiple categories into a +Category combinations let you combine multiple categories into a related set. You can disaggregate the data element "Number of new HIV infections" @@ -222,7 +222,7 @@ into the following categories: - Gender: "Male", "Female" In this example, there are two levels of disaggregation that consist of -two separate data element categories. Each data element category consist +two separate data element categories. Each data element category consists of several data element category options. In DHIS2, different data elements are disaggregated according to a @@ -263,8 +263,8 @@ which donor supports the project. In this case, create a category option group set called "Donor". Each donor can be created as a category option group, where each category option / project is put in the appropriate group. In the data analysis applications, the "Donor" group -set will appear as a data dimension, while each donor appear as -dimension items, ready to be included in reports. +set will appear as a data dimension, while each donor appears as a +dimension item, ready to be included in reports. To create a category option group: @@ -322,10 +322,10 @@ dimensionality to your captured data for analysis in for example the When categories and category combinations have the data dimension type "Attribute", they can apply a common set of attributes to a related set of data values contained in a data set. When category combinations are -used as a attribute, they serve as another dimension (similar to +used as an attribute, they serve as another dimension (similar to "Period" and "Organisation unit") which you can use in your analysis. -Suppose that a NGO is providing ART services in a given facility. They +Suppose that an NGO is providing ART services in a given facility. They would need to report each month on the "ART monthly summary", which would contain a number of data elements. The NGO and project could potentially change over time. In order to attribute data to a given NGO @@ -344,7 +344,7 @@ with each data value at the time of data entry. "Implementing partners and projects" category combination. When you enter data in the **Data entry** app, you can select an -"Implementing partner" and a "Project". Each recorded data value, is +"Implementing partner" and a "Project". Each recorded data value is assigned a specific combination of these categories as an attribute. These attributes (when specified as a dimension) can be used in the analysis applications similar to other dimensions, for example the @@ -390,7 +390,7 @@ example organisation units and tracked entity attributes. These sharing settings control which users and users groups that can view or edit a metadata object. -Some metadata objects also allows you to change the sharing setting of +Some metadata objects also allow you to change the sharing setting of data entry for the object. These additional settings control who can view or enter data in form fields using the metadata. @@ -792,7 +792,7 @@ many similar objects. You can assign different sharing settings to metadata objects, for example organisation units and tracked entity attributes. These sharing -settings control which users and users groups that can view or edit a +settings control which users and user groups can view or edit a metadata object. Some metadata objects also allows you to change the sharing setting of @@ -950,9 +950,9 @@ Table: Data entry form types | Data entry form type | Description | |---|---| -| Default form | Once you have assigned a data set to an organisation unit, a default form is created automatically. The default form is then available in the **Data entry** app for the organisation units you have assigned it to.

A default form consist of a list of the data elements belonging to the data set together with a column for inputting the values. If your data set contains data elements with a non-default category combination, for example age groups or gender, additional columns are automatically created in the default form based on the different categories.

If you use more than one category combination you get multiple columns in the default form with different column headings for the options. | +| Default form | Once you have assigned a data set to an organisation unit, a default form is created automatically. The default form is then available in the **Data entry** app for the organisation units you have assigned it to.

A default form consists of a list of the data elements belonging to the data set together with a column for inputting the values. If your data set contains data elements with a non-default category combination, for example age groups or gender, additional columns are automatically created in the default form based on the different categories.

If you use more than one category combination you get multiple columns in the default form with different column headings for the options. | | Section form | If the default form doesn't meet your needs, you can modify it to create a section form. Section forms give you more flexibility when it comes to using tabular forms.

In a section form you can, for example, create multiple tables with subheadings and disable (grey out) cells in a table.

When you have added a section form to a data set, the section form is available in the **Data entry** app. | -| Custom form | If the form you want to design is too complicated for default or section forms, you can create a custom form. A custom form takes more time to create than a section form, but you have full control over the design.

You can, for example, mimic an existing paper aggregation form with a custom form. This makes data entry easier, and should reduce the number incorrectly entered data elements.

When you have added a custom form to a data set, the custom form is available in the **Data entry** app. | +| Custom form | If the form you want to design is too complicated for default or section forms, you can create a custom form. A custom form takes more time to create than a section form, but you have full control over the design.

You can, for example, mimic an existing paper aggregation form with a custom form. This makes data entry easier, and should reduce the number of incorrectly entered data elements.

When you have added a custom form to a data set, the custom form is available in the **Data entry** app. | > **Note** > @@ -1404,7 +1404,7 @@ data entry forms for data sets. #### Manage grey fields in a section form You can disable data elements and category options for data entry. That -means it won’t be possible to enter data into these fields during data +means it won't be possible to enter data into these fields during data entry. ![](resources/images/datasets/section_form_grey_fields.png) @@ -2550,7 +2550,7 @@ easily filter, organise or aggregate data by groups within a group set. 5. (Optional) Select **Data dimension**. - If you select **Data dimension**, the group set will be available to the analytics as another dimension, in addition to the standard dimensions of “Period” and “Organisation unit”. + If you select **Data dimension**, the group set will be available to the analytics as another dimension, in addition to the standard dimensions of "Period" and "Organisation unit". 6. (Optional) Select **Include subhierarchy in analytics**. @@ -3612,7 +3612,7 @@ The main purpose of the option group set is to add more dimensionality to your c 4. **Option set** 5. **Data dimension** - If you select **Data dimension**, the group set will be available to the analytics as another dimension, in addition to the standard dimensions of “Period” and “Organisation unit”. + If you select **Data dimension**, the group set will be available to the analytics as another dimension, in addition to the standard dimensions of "Period" and "Organisation unit". 4. Select option groups and assign them. diff --git a/src/user/configure-programs-in-the-maintenance-app.md b/src/user/configure-programs-in-the-maintenance-app.md index 265e5ccc5..3008e7cd1 100644 --- a/src/user/configure-programs-in-the-maintenance-app.md +++ b/src/user/configure-programs-in-the-maintenance-app.md @@ -3,10 +3,10 @@ ## About programs { #about_program_maintenance_app } Traditionally, public health information systems have been reporting -aggregated data of service provision across its health programs. This +aggregated data of service provision across their health programs. This does not allow you to trace the people provided with these services. In DHIS2, you can define your own programs with stages. These programs are -a essential part of the "tracker" functionality which lets you track +an essential part of the "tracker" functionality which lets you track individual records. You can also track other ‘entities’ such as wells or insurances. You can create two types of programs: @@ -64,13 +64,13 @@ Table: Types of data entry forms for event programs > **Note** > -> - Custom forms takes precedence over section forms if both are +> - Custom forms take precedence over section forms if both are > present. > -> - If no custom or section form are defined, the basic form will be +> - If no custom or section form is defined, the basic form will be > used. > -> - The Android apps only supports section forms. +> - The Android apps only support section forms. You can create *program notifications* for event programs. The notifications are sent either via the internal DHIS2 messaging system, @@ -169,12 +169,12 @@ the user in the **Event Capture** app. 3. Add data elements by clicking the plus sign next to the data elements' names. - 4. Repeat above steps until you've all the sections you need. + 4. Repeat the above steps until you have all the sections you need. 5. Change the section order: click the options menu, then drag the section to the place you want. -5. To create a **Custom** data entry from: Use the WYSIWYG editor to +5. To create a **Custom** data entry form: Use the WYSIWYG editor to create a completely customized form. If you select **Source**, you can paste HTML code directly in the editing area. You can also insert images for example flags or logos. @@ -192,7 +192,7 @@ available in the **Access** tab. Assign organization units: 1. In the organisation tree, double-click the organisation units you - want to add to the program to. + want to add to the program. You can locate an organisation unit in the tree by expanding the branches (click on the arrow symbol), or by searching for it by @@ -292,7 +292,7 @@ Change roles and access: > - Click **Run now** to send the program notifications immediately. > > - Select a time and click **Start** to schedule the program -> notifications to be send at a specific +> notifications to be sent at a specific time. ### Reference information: Program notification parameters { #reference_information_event_program_notification_parameters } @@ -387,7 +387,7 @@ program. A program needs several types of metadata that you create in the **Main 1. In the list of **Available program tracked entity attributes**, double-click the attributes you want to assign to the program. - 2. (Opptional) For each assigned attribute, add additional settings: + 2. (Optional) For each assigned attribute, add additional settings: | Setting | Description | |---|---| @@ -420,12 +420,12 @@ program. A program needs several types of metadata that you create in the **Main #### Create program stages { #create_program_stages } -A program consist of program stages. A program stage defines which +A program consists of program stages. A program stage defines which actions should be taken at each stage. > **Note** > -> Changes to a program stage is not saved until you save the program. +> Changes to a program stage are not saved until you save the program. 1. Click the plus sign to create a program stage. 2. Enter program stage details: @@ -433,7 +433,7 @@ actions should be taken at each stage. 2. (Optional) select a **Color** and an **Icon** that will be used by the data capture apps to identify this program stage. 3. Enter a **Description**. - 4. Enter the required number of days into the **Scheduled days from start** field: The first event in this program stage will be scheduled this many days after the enrollment or the incident date, depending on the configuration. If **Show incident date** in **Enrollment details** is configured, the system will use incident date as start. If **Genereate events based on enrollment date** in **Program stage details** is configured the system will use enrollment date as start. + 4. Enter the required number of days into the **Scheduled days from start** field: The first event in this program stage will be scheduled this many days after the enrollment or the incident date, depending on the configuration. If **Show incident date** in **Enrollment details** is configured, the system will use incident date as start. If **Generate events based on enrollment date** in **Program stage details** is configured the system will use enrollment date as start. 3. Enter repeatable program stage details. 1. Specify if the program stage is **Repeatable** or not. 2. Select a **Period type**. @@ -505,12 +505,12 @@ by the data capture apps to identify this program stage. 3. Add data elements by clicking the plus sign next to the data elements' names. - 4. Repeat above steps until you've all the sections you need. + 4. Repeat the above steps until you have all the sections you need. 5. Change the section order: click the options menu, then drag the section to the place you want. - 5. To create a **Custom** data entry from: Use the WYSIWYG editor to + 5. To create a **Custom** data entry form: Use the WYSIWYG editor to create a completely customized form. If you select **Source**, you can paste HTML code directly in the editing area. You can also insert images for example flags or logos. @@ -529,7 +529,7 @@ available in the **Access** tab. Assign organization units: 1. In the organisation tree, double-click the organisation units you - want to add to the program to. + want to add to the program. You can locate an organisation unit in the tree by expanding the branches (click on the arrow symbol), or by searching for it by @@ -615,9 +615,9 @@ template. |---|---|---| | Program enrollment | The program notification is sent when the TEI enrols in the program. | - | | Program completion | The program notification is sent when the program of TEI is completed | - | - | Days scheduled (incident date) | The program notification is sent XX number of days before or after the incident date | You need to enter the number of days before or after the scheduled date that the notification will be send. | - | Days scheduled (enrollment date) | The program notification is sent XX number of days before or after the enrollment date | You need to enter the number of days before or after the scheduled date that the notification will be send. | - | Program Rule | Notification will be triggered as a result of program rule exeuction. | Program rule with ProgramRuleActionType.SENDMESSAGE need to be in place to make this trigger successful. | + | Days scheduled (incident date) | The program notification is sent XX number of days before or after the incident date | You need to enter the number of days before or after the scheduled date that the notification will be sent. | + | Days scheduled (enrollment date) | The program notification is sent XX number of days before or after the enrollment date | You need to enter the number of days before or after the scheduled date that the notification will be sent. | + | Program Rule | Notification will be triggered as a result of program rule execution. | Program rule with ProgramRuleActionType.SENDMESSAGE need to be in place to make this trigger successful. | 7. In the **Who-to-send-it** field, select who should receive the @@ -678,7 +678,7 @@ template. | Trigger | Description | Note | |---|---|---| | Program stage completion | The program stage notification is sent when the program stage is completed | - | - | Days scheduled (due date) | The program stage notification is sent XX number of days before or after the due date | You need to enter the number of days before or after the scheduled date that the notification will be send. | + | Days scheduled (due date) | The program stage notification is sent XX number of days before or after the due date | You need to enter the number of days before or after the scheduled date that the notification will be sent. | | Program Rule | Notification will be triggered as a result of program rule execution. | Program rule with ProgramRuleActionType.SENDMESSAGE need to be in place to make this trigger successful. | 1. **Allow notification to be sent multiple times** @@ -699,7 +699,7 @@ template. | Parent OrgUnit Only | Send notification only to those users who belong to parent organisation unit. | - | | Data Element | Data Element associated with ProgramStage can be selected as recipient. | Data Element will only be effective if DataElement has value type PHONE_NUMBER/EMAIL. | | Tracked Entity Attribute | Tracked Entity Attribute associated with ProgramInstance/Enrollment can be selected as recipient. | Attribute will only be effective if it has value type PHONE_NUMBER/EMAIL. | - | Web Hook | Web hooks are automated HTTP messages sent to an external URL configured in web hook URL field. Notificaiton template variables will be sent as key-value pairs in the HTTP request. | - | + | Web Hook | Web hooks are automated HTTP messages sent to an external URL configured in web hook URL field. Notification template variables will be sent as key-value pairs in the HTTP request. | - | 10. Click **Save**. @@ -804,7 +804,7 @@ objects: 5. Select an **Aggregation type**. -6. Select a if you want to **Display in form**. +6. Select if you want to **Display in form**. 7. Assign one or multiple **Legend**s. @@ -1053,8 +1053,6 @@ A filter that uses both attributes and data elements looks like this: ## Setting up new Program disaggregation Mappings { #program_disaggregation_mapping } -## The Concept - DHIS2 v42 introduces the ability to assign Disaggregation Category Combinations to a Program Indicator and create a mapping between the program data and each category option contained in the combination. This creates a relationship between the tracker and aggregate data models which allows for analysing individual data in the same way and alongside aggregated data. ![Program links to Category](resources/images/program/Program_to_category.png){ width=60% } @@ -1089,12 +1087,12 @@ The Program Indicator Disaggregation mappings, defined at the Program level, pro Loading this category combination will display the Mapping selection drop down for each of the categories defined, as this is the first time these categories have been selected there are no mappings currently available. -7. Under the Disaggregation categories section you should see both of the categories from the combination added as suggestion. Click **Add category** for both both **Gender** and **U5y** +7. Under the Disaggregation categories section you should see both of the categories from the combination added as suggestion. Click **Add category** for both **Gender** and **U5y** ![](resources/images/program/Disaggregation_Mappings.png) -## Create the category mappings +### Create the category mappings 8. In the text field under each Category enter an expression using the Program Data Elements and Attributes that defines the category. The expression uses the same syntax as the Filter section of the Program Indicator creation screen. It is recommended to open a Program Indicator within the Program you are mapping, use the Filter screen to construct the expression and then copy it into this field. This allows you to use the inbuilt expression validation of the Program Indicator filter builder. [Program Indicator functions and Variale operators](https://docs.dhis2.org/en/use/user-guides/dhis-core-version-242/configuring-the-system/programs.html#program_indicator_functions_variables_operators) @@ -1129,7 +1127,7 @@ The Program Indicator Disaggregation mappings, defined at the Program level, pro You can now compare the data from the two separate program indicators and the single program indicator that has been disaggregated. -# Transferring Program Indicator data via the Aggregate data exchange app +### Transferring Program Indicator data via the Aggregate data exchange app In addition to viewing a disaggregated Program Indicator in the Data Visualiser you can now transfer the Program Data, via the Disaggregated Program indicator, into a Data Element that shares the same Category Combination. @@ -1139,11 +1137,12 @@ By adding the ID of a Data Element in the **Data element for aggregate data expo ![](resources/images/program/PI_Disaggregation_DE_for_Data_Exchange.png) + ## Configure program rules { #configure_program_rule } ### About program rules { #about_program_rules } -Program rules allows you to create and control dynamic behaviour of the +Program rules allow you to create and control dynamic behaviour of the user interface in the **Tracker Capture** and **Event Capture** apps. During data entry, the program rules expressions are evaluated each time the user interface is displayed, and each time a data element is @@ -1195,7 +1194,7 @@ objects: 3. Select a **Program** and enter a **Name** - Please note that the name of the program may not contain any of the following exlcuded keywords: + Please note that the name of the program may not contain any of the following excluded keywords: - `and` - `or` - `not` @@ -1223,7 +1222,7 @@ objects: | **Data element from the newest event in the current program** | This source type is used when a program rule variable needs to reflect the newest known value of a data element, regardless of what event the user currently has open.

This source type is populated slightly differently in **Tracker Capture** and **Event Capture** apps:

**Tracker Capture**: the program rule variable will be populated with the newest data value collected for the given data element within the enrollment.

**Event Capture**: the program rule variable will be populated with the current events data.
**NB** Future dates are "newer" than current or past dates.

In order to know what event is the newest, the report date (event date) is used. If you have many events with the same report date, the system choose the one with the latest createdAt property of the event.| | **Data element in current event** | Program rule variables with this source type will contain the data value from the same event that the user currently has open.

This is the most commonly used source type, especially for skip logic (hide actions) and warning/error rules. | | **Data element from previous event** | Program rule variables with this source type will contain the value from a specified data element from a previous event. Only older events is evaluated, not including the event that the user currently has open.

This source type is commonly used when a data element only should be collected once during an enrollment, and should be hidden in subsequent events.

Another use case is making rules for validating input where there is an expected progression from one event to the next - a rule can evaluate whether the previous value is higher/lower and give a warning if an unexpected value is entered. | - | **Calculated value** | Program rule variable with this source type is not connected directly to any form data - but will be populated as a result of some other program rules **ASSIGN** action.

This variable will be used for making preliminary calculations, having a **ASSIGN** program rule action and assigning a value, this value can be used by other program rules - potentially making the expressions simpler and more maintanable.

These variables will not be persisted and will stay in memory only during the execution of the set of program rules. Any program rule that assigns a data value to a preliminary calculated value would normally also have a **priority** assigned - to make sure that the preliminary calculation is done before the rule that consumes the calculated value. | + | **Calculated value** | Program rule variable with this source type is not connected directly to any form data - but will be populated as a result of some other program rules **ASSIGN** action.

This variable will be used for making preliminary calculations, having a **ASSIGN** program rule action and assigning a value, this value can be used by other program rules - potentially making the expressions simpler and more maintainable.

These variables will not be persisted and will stay in memory only during the execution of the set of program rules. Any program rule that assigns a data value to a preliminary calculated value would normally also have a **priority** assigned - to make sure that the preliminary calculation is done before the rule that consumes the calculated value. | | **Tracked entity attribute** | Populates the program rule variable with a specified tracked entity attribute for the current enrollment.

Use this is the source type to create program rules that evaluate data values entered during registration.

This source type is also useful when you create program rules that compare data in events to data entered during registration.

This source type is only used for tracker programs (programs with registration). | 6. Click **Save**. @@ -1304,7 +1303,8 @@ objects: | **Warning on complete** | **Data element to display warning next to**

**Tracked entity attribute to display warning next to**

**Static text**

**Expression to evaluate and display after static text** | Used to give the user a warning if he/she tries to complete inconsistent data, but at the same time to allow the user to continue. The warning is shown in a dialog when the user completes the form.

**Static text** defines the message shown to the user when the expression is true and the action is triggered. This field is mandatory.

You can select which data element or tracked entity attribute to link the error to. This will help the user to fix the error.

If you don't select a data element or a tracked entity attribute to display the error next to, make sure you write a comprehensive error message that helps the user to fix the error. | | **Send Message** | **Message template to send** | Send Message triggers a notification based on provided message template. This action will be taken immediately. The message template will be parsed and variables will be substituted with actual values. | | **Schedule Message** | **Message template to send**

**Data field which contains expression to evaluate the date which notification should be sent at. If this expression results in any value other than Date, then resultant will be discarded and notification will not get scheduled.** | Schedule Message will schedule notification at date provided by Expression in the data field. Sample expression is given below
d2:addDays( '2018-04-20', '2' )
Message template will be parsed and variables will be substituted with actual values. | - | **Hide option** | **Data element to hide option for**

**Tracked entity attribute to hide option for**

**Option that should be hidden** | Used to selectively hide a single option for an option set in a given data element/tracked entity attribute.

When combined with **show option group** the **hide option** takes presedence. | + | **Schedule event** | **Program stage to schedule event for**

**Expression to evaluate the scheduled date** | Automatically schedules a new event for the specified program stage on the date returned by the expression. The expression must evaluate to a valid date; if it does not, no event will be scheduled.

The event is only scheduled once — if the rule condition evaluates to true again, no duplicate event is created.

Useful for programs where the timing of the next visit or follow-up can be derived from existing data, for example scheduling a second vaccination dose a fixed number of days after the first.

Example expression that schedules an event 28 days after a recorded date:
`d2:addDays(#{dateOfFirstDose}, 28)` | + | **Hide option** | **Data element to hide option for**

**Tracked entity attribute to hide option for**

**Option that should be hidden** | Used to selectively hide a single option for an option set in a given data element/tracked entity attribute.

When combined with **show option group** the **hide option** takes precedence. | | **Hide option group** | **Data element to hide option group for**

**Tracked entity attribute to hide option group for**

**Option group that should be hidden** | Used to hide all options in a given option group and data element/tracked entity attribute.

When combined with **show option group** the **hide option group** takes precedence. | | **Show option group** | **Data element to show option group for**

**Tracked entity attribute to show option group for**

**Option group that should be shown** | Used to show only options from a given option group in a given data element/tracked entity attribute. To show an option group implicitly hides all options that is not part of the group(s) that is shown. | @@ -1321,7 +1321,7 @@ objects: > You can view all examples on the demo server: > -This example shows how to configure a program rule which calculate +This example shows how to configure a program rule which calculates number of weeks and days in a pregnancy and display the result in the format the clinician is used to see it in. The calculation is based on previous recorded @@ -1691,9 +1691,9 @@ Table: Standard variables to use in program rule expressions | V{event_count} | (number) | Contains the total number of events in the enrollment. | | V{enrollment_date} | (date) | Contains the enrollment date of the current enrollment. Will not have a value for single event programs. | | V{incident_date} | (date) | Contains the incident date of the current enrollment. Will not have a value for single event programs. | -| V{enrollment_id} | (string) | Universial identifier string(UID) of the current enrollment. Will not have a value for single event programs. | +| V{enrollment_id} | (string) | Universal identifier string (UID) of the current enrollment. Will not have a value for single event programs. | | V{enrollment_status} | (string) | Contains status of the current enrollment.
It can be ACTIVE, COMPLETED or CANCELLED. Example expression to check status is:
`V{enrollment_status} == 'COMPLETED'` | -| V{event_id} | (string) | Universial identifier string(UID) of the current event context. Will not have a value at the moment the rule is executed as part of the registration form. | +| V{event_id} | (string) | Universal identifier string (UID) of the current event context. Will not have a value at the moment the rule is executed as part of the registration form. | | V{orgunit_code} | (string) | Contains the code of the orgunit that is linked to the current enrollment. For single event programs the code from the current event orgunit will be used instead.
Example expression to check whether orgunit code starts with WB_:
`d2:left(V{orgunit_code},3) == 'WB_'` | | V{environment} | (string) | Contains a code representing the current runtime environment for the rules. The possible values is "WebClient", "AndroidClient" and "Server". Can be used when a program rule is only supposed to run in one or more of the client types. | | V{program_stage_id} | (string) | Contains the ID of the current program stage that triggered the rules. This can be used to run rules in specific program stages, or avoid execution in certain stages. When executing the rules in the context of a TEI registration form the variable will be empty. | @@ -1733,13 +1733,13 @@ For more information about configuration and the meaning of 'From constraint' an 6. (Optional) Select whether the relationship should be bidirectional -7. Provide **Relationship name seen from inititating entity**. This is the name of the relationship that will be shown in the Data Entry app at the 'left' side of the relationship. E.g. in a Mother-child relationship this could be 'Mother of'. +7. Provide **Relationship name seen from initiating entity**. This is the name of the relationship that will be shown in the Data Entry app at the 'left' side of the relationship. E.g. in a Mother-child relationship this could be 'Mother of'. 8. (Optional) Provide **Relationship name seen from receiving entity**. This is the name of the relationship that will be shown at the 'right' side of the relationship in the Data Entry app. E.g. in a Mother-child relationship this could be 'Mother'. 9. Select a 'From constraint'. This limits what kind of entities can be included in the relationship. [Relationship model](#relationship_model_relationship_type). After selecting a 'From constraint', you have the option to choose which attributes or data elements should be shown in the relationship widget in Tracker Capture and Capture for the "From constraint". The list will vary based on the constraint: * When selecting “Tracked Entity Instance”, then a Tracked Entity Type only, choose between the configured Tracked Entity Type Attributes - * When selecting “Tracked Entity Instance”, then a Tracked Entity Type and a Program, choose between the the attributes that have been configured for both the Tracked Entity Type and for the Program + * When selecting “Tracked Entity Instance”, then a Tracked Entity Type and a Program, choose between the attributes that have been configured for both the Tracked Entity Type and for the Program * When selecting “Enrollment in program”, choose between the attributes that have been configured for the Program * When selecting “Event in program or program stage”, choose between the data elements that have been configured for that Event program or Program stage @@ -1751,13 +1751,13 @@ For more information about configuration and the meaning of 'From constraint' an ### About tracked entity types { #about_tracked_entity } -A tracked entity is a types of entities which can be tracked through the +A tracked entity is a type of entity which can be tracked through the system. It can be anything from persons to commodities, for example a medicine or a person. A program must have one tracked entity. To enroll a tracked entity -instance into a program, the tracked entity type and tracked -entity type of a program must be the same. +instance into a program, the tracked entity type of the entity and +the tracked entity type of the program must be the same. Tracked entity attributes are used to register extra information for a tracked entity. Tracked entity attributes can be shared between @@ -1846,7 +1846,7 @@ programs. | Sum | Sum of data values in the period and organisation unit dimension. | | Standard deviation | Standard deviation (population-based) of data values. | | Variance | Variance (population-based) of data values. | - +program 12. Select **Unique** to specify that the values of the tracked entity attribute is unique. @@ -1933,7 +1933,7 @@ search for tracked entity instances outside their data capture organisation units. Searching can be done either in the context of a program, or in the -context of a tracked entity type. To be give users the option of +context of a tracked entity type. To give users the option of searching in the context of a program, it is necessary to configure which of the programs tracked entity attributes is searchable. To give users the option of searching in the context of a tracked entity type, @@ -1956,7 +1956,7 @@ searchable. 5. Set the attribute searchable -Searchable program attributes will assigned to a search group. +Searchable program attributes will be assigned to a search group. - Unique group. One group per unique program attribute. Unique attributes cannot be combined with other program attributes in a @@ -2006,7 +2006,7 @@ searchable. 4. Set the attribute searchable -Searchable TET attributes will assigned to a search group. +Searchable TET attributes will be assigned to a search group. - Unique group. One group per unique TET attribute. Unique attributes cannot be combined with other TET attributes in a search. The result @@ -2034,7 +2034,7 @@ There are two limits that can be set for a TET search To be able to search in other organisation units than the users data capture organisation units, the user must be assigned with search organisation units. Giving a user a search organisation unit will also -give it access to search in all children of that organisation unit. +give them access to search in all children of that organisation unit. 1. Open **Users app** @@ -2089,7 +2089,7 @@ example organisation units and tracked entity attributes. These sharing settings control which users and users groups that can view or edit a metadata object. -Some metadata objects also allows you to change the sharing setting of +Some metadata objects also allow you to change the sharing setting of data entry for the object. These additional settings control who can view or enter data in form fields using the metadata. diff --git a/src/user/system-settings.md b/src/user/system-settings.md index 81a667ee2..54c97c2d0 100644 --- a/src/user/system-settings.md +++ b/src/user/system-settings.md @@ -35,11 +35,10 @@ Table: Analytics settings | **Default relative period for analysis** | Defines the relative period to use by default in analytics apps such as the **Data Visualizer app** and **Maps app**. The relative period will be automatically selected when you open these apps.

Recommended setting: the most commonly used relative period among your users. | | **Property to display in analysis modules** | Sets whether you want to display the metadata objects' names or short names in analytics apps such as the **Data Visualizer app**, **Maps app** and **Line Listing app**.

The user can override this setting in the **Settings** app: **User settings** \> **Property to display in analysis modules**. | | **Default digit group separator to display in analysis modules** | Sets the default digit group separator in analytics apps such as the **Data Visualizer app** and **Line Listing app**. | -| **Hide daily periods** | Hide daily periods in the analysis tools | -| **Hide weekly periods** | Hide weekly periods in the analysis tools | -| **Hide monthly periods** | Hide monthly periods in the analysis tools | -| **Hide bimonthly periods** | Hide bimonthly periods in the analysis tools | -| **Financial year relative start month** | Defines which month (April, July or October) the the relative financial year in the analytics apps should begin on. | +| **Hide daily/weekly/biweekly/monthly/bimonthly periods** | Hide daily/weekly/biweekly/monthly/bimonthly periods in the analysis tools.

**Note**

This setting was removed from DHIS2 version 2.43. Use **Period types available in analytics apps** instead. | +| **Period types available in analytics apps** | Controls which period types are available for data output in analytics apps. Use the checkboxes to enable or disable individual period types; disabled types will not appear in Analytics apps. Period types are grouped by frequency: daily, weekly, bi-weekly, monthly, bi-monthly, quarterly, six-monthly and yearly (including financial year variants). The yearly period type is always enabled and cannot be disabled.

**Note**

This setting is available from DHIS2 version 2.43. In earlier versions, use the individual **Hide daily periods**, **Hide weekly periods**, **Hide biweekly periods**, **Hide monthly periods** and **Hide bimonthly periods** settings. | +| **Weekly relative period start day** | Defines which day of the week the relative weekly period starts on in analytics apps. Available options are Monday (default), Wednesday, Thursday, Friday, Saturday and Sunday.

**Note**

This setting is available from DHIS2 version 2.43. | +| **Financial year relative start month** | Defines which month the relative financial year in the analytics apps should begin on. Available options are April, July and October. From DHIS2 version 2.43, February, August and September are also available. | | **Cache strategy** | Decides for how long reports analytics responses should be cached.

If you use the scheduled, nightly analytics update, you may want to select **Cache until 6 AM tomorrow**. This is because data in reports change at that time, and you can safely cache data up to the moment when the analytics tables are updated.

If you are loading data continuously into the analytics tables, select **No cache**.

For other cases select the amount of time you want the data to be cached. | | **Cacheability** | Sets whether analytics data responses should be served with public or private visibility.

**Private**: Any node or server between the DHIS2 server and the end user which has the ability to cache can NOT cache the web page. This is useful if the page served can or do contain sensitive information. This means that each time you want a web page, either you get a new page from the DHIS2 server, or the DHIS2 server caches the page. No other server than the DHIS2 server are allowed to cache the page.

**Public**: Any node or server between the DHIS2 server and the end user which has the ability to cache can cache the web page. This relives the traffic to the DHIS2 server and potentially speeds up the subsequent page loading speed. | | **Analytics cache mode** | Support two different modes:

**Progressive**: this relates to the new progressive caching feature for analytics. When enabled, it OVERRIDES the global caching strategy for analytics requests. This mode will trigger HTTP and data layer caching for all analytics requests. When enabling this mode, the *caching factor* is MANDATORY.

**Fixed**: the requests will be cached based on the period of time defined in *cache strategy.* | @@ -47,11 +46,12 @@ Table: Analytics settings | **Max number of years to hide unapproved data in analytics** | Sets whether and for how long back in time analytics should respect the approval level of the data. Typically, data which is several years old would be considered to be approved by default. In order to speed up analytics requests, you can choose to ignore the actual approval level of historical data.

**Never check approval**: no data will be hidden, irrespective of its data approval status.

**Check approval for all data**: approval status will always be checked.

Other options, for example **Last 3 years**: approval status will be checked for data which is newer than 3 years old; older data will not be checked. | | **Respect category option start and end date in analytics table export** | This setting controls whether analytics should filter data which is associated with a category option with a start and end date, but which is not associated with a period within the category options interval of validity. | | **Include zero data values in analytics tables** | This setting allows for including zero values in analytics tables. This only applies to data elements where the **Store zero data values** property is enabled. Note that setting **Store zero data values** on large numbers of data elements is strongly discouraged, as it can fill the analytics tables with zeros and cause unnecessary performance overhead.| -| **Enable embedded dashboards** | If enabled, users are presented with two modes of dashboard creation when creating a new dashboard: 1) Internal: the existing dashboard creation flow, based on data from the current instance. or 2) External: Embed a dashboard built from data external to the instance. -| **Allow users to switch dashboard item view type** | Allows users to switch dashboard items' view between charts, pivot tables and maps, using the dashboard item menu. | -| **Allow users to open dashboard item in relevant app** | Allows users to open dashboard items in the app for that type of item, using the dashboard item menu. | -| **Allow users to show dashboard item interpretations and details** | Allows users to see dashboard items' interpretations and details, using the dashboard item menu. | -| **Allow users to view dashboard item in fullscreen** | Allows users to view dashboard item in fullscreen, using the dashboard item menu. | +| **Enable embedded dashboards** | If enabled, users are presented with two modes of dashboard creation when creating a new dashboard: 1) Internal: the existing dashboard creation flow, based on data from the current instance. or 2) External: Embed a dashboard built from data external to the instance. | +| **Allow users to switch dashboard items view type** | Allows users to switch dashboard items' view between charts, pivot tables and maps, using the dashboard item menu. | +| **Allow users to open dashboard items in relevant app** | Allows users to open dashboard items in the app for that type of item, using the dashboard item menu. | +| **Allow users to show dashboard items interpretations and details** | Allows users to see dashboard items' interpretations and details, using the dashboard item menu. | +| **Allow users to view dashboard items in fullscreen** | Allows users to view dashboard items in fullscreen, using the dashboard item menu. | +| **Use centroids for organisation unit polygons in event analytics** | When enabled, analytics event tables are created using the centroid (centre point) of each organisation unit polygon, rather than the full polygon geometry. This applies to data elements and tracked entity attributes of type organisation unit or organisation unit geometry. | | **Org unit group set in facility map layers** | Defines the default organisation unit group set which can be used to style facilities, with icons, when using the maps application. | | **Org unit level in facility map layers** | Defines the default level for Facility layers when using the maps application. Organisation units for the default level will be displayed, unless a user selects a different level for a given layer. | | **Default basemap** | Select which basemap will be selected by default in the **Maps** app. If no value is selected, then **OSM Light** will be used.| @@ -85,7 +85,7 @@ Table: Appearance settings | **Application notification** | Sets a notification which will be visible on the front page under the login area. | | **Application left-side footer** | Sets a text in the left-side footer area of the login page. (When using a language written in a right-to-left script, such as Arabic, this will be in the right-footer area of the login page.) | | **Application right-side footer** | Sets a text in the right-side footer area of the login page. ((When using a language written in a right-to-left script, such as Arabic, this will be in the left-footer area of the login page.) | -| **Style (Android)** | This setting influences the style (look and feel) of the DHIS2 android app. This style in general does not apply to web apps. | +| **Theme color** | This color picker sets the color used in the DHIS2 header bar in all web apps and for the theme in the Android app. Upon setting a color, refresh the page to see the change. The Global Shell must be enabled to see the custom color in the header bar on web. If a color is set, click the Remove color button to return to the default color. | | **Start page** | Sets the page or app which the user will be redirected to after log in.

Recommended setting: the **Dashboard** app. | | **Enable light-weight start page** | Instructs apps to render a light-weight and fast landing page. Recommended in low-bandwidth environments. | | **Help page link** | Defines the URL which users will see when they click **Profile** \>**Help**. | @@ -97,6 +97,7 @@ Table: Appearance settings | **Login page theme** | This lets you select between the default layout, the sidebar layout, or a custom layout for the login app. If you select a custom layout, you need to provide a custom template in the "Login page template" section. | | **Login page template** | Here you can paste the HTML to define the layout and style of the login page. More details for how to define the template are available in the developer documentation. | | **Enable Global Shell** | When this property is enabled (set to true, the default), the Global Shell provides a common interface and navigation tools across all DHIS2 web applications.

More technical details about the Global Shell can be found [in the Developer Portal](https://developers.dhis2.org/docs/references/global-shell). | +| **Enable custom translations** | With this option enabled, apps can take advantage of custom translations that are set in the datastore. Read more in the [Localization of DHIS2](#custom_translations_using_the_datastore) page. | ## Email settings { #system_email_settings }