uanetstandard-test-suite · master
Docs · Data features

Access control

Fifty variables organised to exercise every combination of access-level flag, user role, and data type. The systematic test surface for access-attribute logic.

Path: TestServer / AccessControl

A test surface for the OPC UA access-level model. 50 variables split across 5 groups: access levels, AdminOnly, OperatorLevel, ViewerLevel, and AllCombinations.

1. Access levels

Path: AccessControl / AccessLevels

5 variables, one per access-level flavour:

BrowseName accessLevel userAccessLevel Initial Notes
CurrentRead_Only CurrentRead CurrentRead 42 Write → Bad_NotWritable
CurrentWrite_Only CurrentWrite CurrentWrite 0 Read → Bad_NotReadable
ReadWrite CurrentRead+Write CurrentRead+Write 100 Full RW
HistoryRead_Only CurrentRead+HistoryRead same 200 History-read enabled
FullAccess CurrentRead+Write+HistoryRead same 300 Everything works

CurrentWrite_Only uses only the CurrentWrite bit in both accessLevel and userAccessLevel — the server cannot read either. The "server-could-but-user-cannot" framing below applies to the OPC UA model in general, not to this specific node.

accessLevel vs userAccessLevel

The OPC UA spec gives each variable two access-level attributes:

  • accessLevel (id 17): server-level capabilities — what the variable can do.
  • userAccessLevel (id 18): per-user capabilities — what the current session is allowed to do.

For CurrentWrite_Only, the server could read (the variable holds a value internally), but the user cannot. accessLevel includes CurrentRead; userAccessLevel is only CurrentWrite.

Reading id 17 vs id 18 separately is the standard test for "did your client expose both attributes correctly?".

2. AdminOnly

Path: AccessControl / AdminOnly

Variables intended for admin-only access. OPC-level access is RW for all; role enforcement is server-side.

BrowseName Type Initial Purpose
SecretConfig String "secret-value-123" Sensitive config
SystemParameter Int32 9999 System-level parameter
CalibrationFactor Double 1.0 Calibration coefficient
MaintenanceMode Boolean false Maintenance flag

Behaviour:

Connected as Read Write
admin OK OK
operator OK OK
viewer OK OK
anonymous OK OK

Despite the folder name, the AdminOnly nodes do not carry role-protected write hooks in the current build — they're plain RW variables. Role enforcement is only wired for the variables under OperatorLevel/ (see below). Treat AdminOnly/ as "tagged for future hardening" rather than an authoritative gate.

3. OperatorLevel

Path: AccessControl / OperatorLevel

Variables with role-based write protection. admin and operator can write; viewer cannot.

BrowseName Type Initial Purpose
Setpoint Double 50.0 Process setpoint
MotorSpeed Int32 1500 Motor speed (RPM)
ProcessEnabled Boolean true Process enable flag
RecipeName String "Recipe_A" Active recipe name

Behaviour matrix:

Connected as Read Write
admin OK OK
operator OK OK
viewer OK Bad_UserAccessDenied

This is the canonical test for "role-aware writes" in your client.

4. ViewerLevel

Path: AccessControl / ViewerLevel

Read-only by design. All five roles can read; none can write.

BrowseName Type Value Notes
ProductionCount UInt32 12345 Static
MachineName String "Machine-001" Static
IsRunning Boolean true Static
CurrentTemperature Double 45.2 Static — no per-read noise
UptimeSeconds UInt32 86400 Static — does not increment

All five ViewerLevel variables hold static values set once at server start. They are read-only via the access-level bits, but none of them are updated by a timer or by a per-read hook — use the variables under Dynamic/ if you need genuinely changing data.

5. AllCombinations

Path: AccessControl / AllCombinations

Every combination of 8 data types × 4 access modes = 32 variables. Systematic test surface.

Access mode suffix legend

Suffix Access (accessLevel + userAccessLevel)
_RO CurrentRead
_RW CurrentRead + CurrentWrite
_WO userAccessLevel = CurrentWrite (server can read)
_HR CurrentRead + HistoryRead

Type matrix

Type _RO value _RW initial _WO initial _HR initial
Boolean true false false true
Int32 42 0 0 100
UInt32 42 0 0 100
Double 3.14 0.0 0.0 2.71
String "readonly" "readwrite" "writeonly" "history"
DateTime DateTime.UtcNow at startup same same same
Byte 0xFF (255) 0 0 0xAB (171)
Float 1.5 0.0 0.0 2.5

Variable list

AllCombinations/Boolean_RO     Boolean_RW     Boolean_WO     Boolean_HR
AllCombinations/Int32_RO       Int32_RW       Int32_WO       Int32_HR
AllCombinations/UInt32_RO      UInt32_RW      UInt32_WO      UInt32_HR
AllCombinations/Double_RO      Double_RW      Double_WO      Double_HR
AllCombinations/String_RO      String_RW      String_WO      String_HR
AllCombinations/DateTime_RO    DateTime_RW    DateTime_WO    DateTime_HR
AllCombinations/Byte_RO        Byte_RW        Byte_WO        Byte_HR
AllCombinations/Float_RO       Float_RW       Float_WO       Float_HR

Test patterns

Read-only rejection

for type in [Boolean, Int32, ..., Float]:
    status = write(AllCombinations/{type}_RO, sample_value)
    assert status == Bad_NotWritable

Write-only rejection on read

for type:
    dv = read(AllCombinations/{type}_WO)
    assert dv.statusCode == Bad_NotReadable

Full RW happy path

for type:
    write(AllCombinations/{type}_RW, sample_value)
    assert read(AllCombinations/{type}_RW).value == sample_value

HistoryRead flag

for type:
    access = read_attribute(AllCombinations/{type}_HR, AccessLevel)
    assert (access & HistoryRead) != 0

Role-based write

Connect to opcua-userpass (4841) as viewer:

status = write(AccessControl/OperatorLevel/Setpoint, 60.0)
assert status == Bad_UserAccessDenied

Same write as operatorGood.