Remove static Users auth, use shared QualityMapper for historian, simplify LDAP permission checks
- Remove ConfigUserAuthenticationProvider and Users property — LDAP is the only auth mechanism - Fix historian quality mapping to use existing QualityMapper (OPC DA quality bytes, not custom mapping) - Add AppRoles constants, unify HasWritePermission/HasAlarmAckPermission into shared HasRole helper - Hoist write permission check out of per-item loop, eliminate redundant _ldapRolesEnabled field - Update docs (Configuration.md, Security.md, OpcUaServer.md, HistoricalDataAccess.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -114,13 +114,10 @@ Controls user authentication and write authorization for the OPC UA server. Defi
|
||||
|----------|------|---------|-------------|
|
||||
| `AllowAnonymous` | `bool` | `true` | Accepts anonymous client connections when `true` |
|
||||
| `AnonymousCanWrite` | `bool` | `true` | Permits anonymous users to write when `true` |
|
||||
| `Users` | `List<UserCredential>` | `[]` | List of username/password credentials for `UserName` token authentication |
|
||||
|
||||
Each entry in the `Users` list has two properties: `Username` (string) and `Password` (string). The `Users` list is ignored when `Ldap.Enabled` is `true`.
|
||||
|
||||
#### LDAP Authentication
|
||||
|
||||
When `Ldap.Enabled` is `true`, credentials are validated against the configured LDAP server and group membership determines OPC UA permissions. The `Users` list is ignored.
|
||||
When `Ldap.Enabled` is `true`, credentials are validated against the configured LDAP server and group membership determines OPC UA permissions.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
@@ -148,13 +145,12 @@ When LDAP is enabled, authenticated users receive permissions based on their LDA
|
||||
|
||||
Users can belong to multiple groups. The `admin` user in the default GLAuth configuration belongs to all three groups.
|
||||
|
||||
Example with LDAP authentication:
|
||||
Example configuration:
|
||||
|
||||
```json
|
||||
"Authentication": {
|
||||
"AllowAnonymous": true,
|
||||
"AnonymousCanWrite": false,
|
||||
"Users": [],
|
||||
"Ldap": {
|
||||
"Enabled": true,
|
||||
"Host": "localhost",
|
||||
@@ -171,19 +167,6 @@ Example with LDAP authentication:
|
||||
}
|
||||
```
|
||||
|
||||
Example with static user list (no LDAP):
|
||||
|
||||
```json
|
||||
"Authentication": {
|
||||
"AllowAnonymous": true,
|
||||
"AnonymousCanWrite": false,
|
||||
"Users": [
|
||||
{ "Username": "operator", "Password": "op123" },
|
||||
{ "Username": "engineer", "Password": "eng456" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
Controls OPC UA transport security profiles and certificate handling. Defined in `SecurityProfileConfiguration`. See [Security Guide](security.md) for detailed usage.
|
||||
@@ -321,7 +304,9 @@ Integration tests use this constructor to inject substitute implementations of `
|
||||
"Authentication": {
|
||||
"AllowAnonymous": true,
|
||||
"AnonymousCanWrite": true,
|
||||
"Users": []
|
||||
"Ldap": {
|
||||
"Enabled": false
|
||||
}
|
||||
},
|
||||
"Security": {
|
||||
"Profiles": ["None"],
|
||||
|
||||
@@ -44,7 +44,7 @@ The `TOP` clause is included only when `maxValues > 0` (the OPC UA client specif
|
||||
|
||||
- `Value` column (double) takes priority over `vValue` (string). If both are null, the value is null.
|
||||
- `SourceTimestamp` and `ServerTimestamp` are both set to the `DateTime` column.
|
||||
- `StatusCode` is mapped from the Historian `Quality` byte via `MapQuality`.
|
||||
- `StatusCode` is mapped from the Historian `Quality` byte via `QualityMapper` (the same OPC DA quality byte mapping used for live MXAccess data).
|
||||
|
||||
## Aggregate Reads
|
||||
|
||||
@@ -65,16 +65,15 @@ Null aggregate values return `BadNoData` status rather than `Good` with a null v
|
||||
|
||||
## Quality Mapping
|
||||
|
||||
`MapQuality` converts Wonderware Historian quality bytes to OPC UA status codes:
|
||||
The Historian stores standard OPC DA quality bytes, the same format used by MXAccess at runtime. The quality byte is passed through the shared `QualityMapper` pipeline (`MapFromMxAccessQuality` → `MapToOpcUaStatusCode`), which maps the OPC DA quality families to OPC UA status codes:
|
||||
|
||||
| Historian Quality | OPC UA StatusCode |
|
||||
|---|---|
|
||||
| 0 | `Good` |
|
||||
| 1 | `Bad` |
|
||||
| 2-127 | `Bad` |
|
||||
| 128+ | `Uncertain` |
|
||||
| Historian Quality Byte | OPC DA Family | OPC UA StatusCode |
|
||||
|---|---|---|
|
||||
| 0-63 | Bad | `Bad` (with sub-code when an exact enum match exists) |
|
||||
| 64-191 | Uncertain | `Uncertain` (with sub-code when an exact enum match exists) |
|
||||
| 192+ | Good | `Good` (with sub-code when an exact enum match exists) |
|
||||
|
||||
This follows the Wonderware convention where quality 0 indicates a good sample, 1 indicates explicitly bad data, and values at or above 128 represent uncertain quality (e.g., interpolated or suspect values).
|
||||
See `Domain/QualityMapper.cs` and `Domain/Quality.cs` for the full mapping table and sub-code definitions.
|
||||
|
||||
## Aggregate Function Mapping
|
||||
|
||||
|
||||
@@ -63,15 +63,15 @@ The `ServiceLevel` is updated whenever MXAccess connection state changes or Gala
|
||||
`UserTokenPolicies` are dynamically configured based on the `Authentication` settings in `appsettings.json`:
|
||||
|
||||
- An `Anonymous` user token policy is added when `AllowAnonymous` is `true` (the default).
|
||||
- A `UserName` user token policy is added when the `Users` list contains at least one entry.
|
||||
- A `UserName` user token policy is added when `Ldap.Enabled` is `true`.
|
||||
|
||||
Both policies can be active simultaneously, allowing clients to connect with or without credentials.
|
||||
|
||||
### Session impersonation
|
||||
|
||||
When a client presents `UserName` credentials, the server validates them through `IUserAuthenticationProvider`. If the credentials do not match any entry in the configured `Users` list, the session is rejected.
|
||||
When a client presents `UserName` credentials, the server validates them through `IUserAuthenticationProvider`. If LDAP authentication is enabled, credentials are validated via LDAP bind and group membership determines the user's application-level roles (`ReadOnly`, `ReadWrite`, `AlarmAck`). If validation fails, the session is rejected.
|
||||
|
||||
On successful validation, the session identity is set to a `RoleBasedIdentity` that carries the user's granted role IDs. Authenticated users receive the `WellKnownRole_AuthenticatedUser` role. Anonymous connections receive the `WellKnownRole_Anonymous` role. These roles are used downstream by the write override to enforce `AnonymousCanWrite` restrictions.
|
||||
On successful validation, the session identity is set to a `RoleBasedIdentity` that carries the user's granted role IDs. Authenticated users receive the `WellKnownRole_AuthenticatedUser` role. Anonymous connections receive the `WellKnownRole_Anonymous` role. When LDAP is enabled, application-level roles from group membership control write and alarm-ack permissions. Without LDAP, `AnonymousCanWrite` controls whether anonymous users can write.
|
||||
|
||||
## Certificate handling
|
||||
|
||||
|
||||
@@ -147,19 +147,23 @@ Remove `None` from the `Profiles` list to prevent unencrypted connections:
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Configure named users
|
||||
### 3. Configure LDAP authentication
|
||||
|
||||
Disable anonymous access and define named users in the `Authentication` section. Use `AnonymousCanWrite` to control whether anonymous clients (if still allowed) can write:
|
||||
Enable LDAP authentication to validate credentials against the GLAuth server. LDAP group membership controls what each user can do (read, write, alarm acknowledgment). See [Configuration Guide](Configuration.md) for the full LDAP property reference.
|
||||
|
||||
```json
|
||||
{
|
||||
"Authentication": {
|
||||
"AllowAnonymous": false,
|
||||
"AnonymousCanWrite": false,
|
||||
"Users": [
|
||||
{ "Username": "operator", "Password": "secure-password" },
|
||||
{ "Username": "viewer", "Password": "read-only-password" }
|
||||
]
|
||||
"Ldap": {
|
||||
"Enabled": true,
|
||||
"Host": "localhost",
|
||||
"Port": 3893,
|
||||
"BaseDN": "dc=lmxopcua,dc=local",
|
||||
"ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local",
|
||||
"ServiceAccountPassword": "serviceaccount123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user