Add device security tests and enhance device capabilities
- Introduced comprehensive tests for device security features including GetRemoteUser, SetRemoteUser, GetIPAddressFilter, SetIPAddressFilter, and more. - Implemented mock server responses for various ONVIF device security SOAP actions. - Added new types and constants for device services, capabilities, and network protocols in types.go. - Enhanced existing tests for device services, discovery modes, and network configurations. - Ensured proper handling of service capabilities and network protocols in the client.
This commit is contained in:
@@ -0,0 +1,523 @@
|
||||
package onvif
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newMockDeviceSecurityServer() *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := xml.NewDecoder(r.Body)
|
||||
var envelope struct {
|
||||
Body struct {
|
||||
Content []byte `xml:",innerxml"`
|
||||
} `xml:"Body"`
|
||||
}
|
||||
decoder.Decode(&envelope)
|
||||
bodyContent := string(envelope.Body.Content)
|
||||
|
||||
w.Header().Set("Content-Type", "application/soap+xml")
|
||||
|
||||
switch {
|
||||
case strings.Contains(bodyContent, "GetRemoteUser"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetRemoteUserResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:RemoteUser>
|
||||
<tt:Username>remote_admin</tt:Username>
|
||||
<tt:Password></tt:Password>
|
||||
<tt:UseDerivedPassword>true</tt:UseDerivedPassword>
|
||||
</tds:RemoteUser>
|
||||
</tds:GetRemoteUserResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetRemoteUser"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetRemoteUserResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "GetIPAddressFilter"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetIPAddressFilterResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:IPAddressFilter>
|
||||
<tt:Type>Allow</tt:Type>
|
||||
<tt:IPv4Address>
|
||||
<tt:Address>192.168.1.0</tt:Address>
|
||||
<tt:PrefixLength>24</tt:PrefixLength>
|
||||
</tt:IPv4Address>
|
||||
</tds:IPAddressFilter>
|
||||
</tds:GetIPAddressFilterResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetIPAddressFilter"),
|
||||
strings.Contains(bodyContent, "AddIPAddressFilter"),
|
||||
strings.Contains(bodyContent, "RemoveIPAddressFilter"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetIPAddressFilterResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "GetZeroConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetZeroConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:ZeroConfiguration>
|
||||
<tt:InterfaceToken>eth0</tt:InterfaceToken>
|
||||
<tt:Enabled>true</tt:Enabled>
|
||||
<tt:Addresses>169.254.1.100</tt:Addresses>
|
||||
</tds:ZeroConfiguration>
|
||||
</tds:GetZeroConfigurationResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetZeroConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetZeroConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "GetPasswordComplexityConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetPasswordComplexityConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:MinLen>8</tds:MinLen>
|
||||
<tds:Uppercase>1</tds:Uppercase>
|
||||
<tds:Number>1</tds:Number>
|
||||
<tds:SpecialChars>1</tds:SpecialChars>
|
||||
<tds:BlockUsernameOccurrence>true</tds:BlockUsernameOccurrence>
|
||||
<tds:PolicyConfigurationLocked>false</tds:PolicyConfigurationLocked>
|
||||
</tds:GetPasswordComplexityConfigurationResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetPasswordComplexityConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetPasswordComplexityConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "GetPasswordHistoryConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetPasswordHistoryConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:Enabled>true</tds:Enabled>
|
||||
<tds:Length>5</tds:Length>
|
||||
</tds:GetPasswordHistoryConfigurationResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetPasswordHistoryConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetPasswordHistoryConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "GetAuthFailureWarningConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:GetAuthFailureWarningConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
|
||||
<tds:Enabled>true</tds:Enabled>
|
||||
<tds:MonitorPeriod>60</tds:MonitorPeriod>
|
||||
<tds:MaxAuthFailures>5</tds:MaxAuthFailures>
|
||||
</tds:GetAuthFailureWarningConfigurationResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
case strings.Contains(bodyContent, "SetAuthFailureWarningConfiguration"):
|
||||
w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
|
||||
<s:Body>
|
||||
<tds:SetAuthFailureWarningConfigurationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>
|
||||
</s:Body>
|
||||
</s:Envelope>`))
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func TestGetRemoteUser(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
remoteUser, err := client.GetRemoteUser(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRemoteUser failed: %v", err)
|
||||
}
|
||||
|
||||
if remoteUser.Username != "remote_admin" {
|
||||
t.Errorf("Expected username 'remote_admin', got %s", remoteUser.Username)
|
||||
}
|
||||
|
||||
if !remoteUser.UseDerivedPassword {
|
||||
t.Error("UseDerivedPassword should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRemoteUser(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
remoteUser := &RemoteUser{
|
||||
Username: "new_remote",
|
||||
Password: "password123",
|
||||
UseDerivedPassword: true,
|
||||
}
|
||||
|
||||
err = client.SetRemoteUser(ctx, remoteUser)
|
||||
if err != nil {
|
||||
t.Fatalf("SetRemoteUser failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIPAddressFilter(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
filter, err := client.GetIPAddressFilter(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetIPAddressFilter failed: %v", err)
|
||||
}
|
||||
|
||||
if filter.Type != IPAddressFilterAllow {
|
||||
t.Errorf("Expected Allow filter type, got %s", filter.Type)
|
||||
}
|
||||
|
||||
if len(filter.IPv4Address) != 1 {
|
||||
t.Fatalf("Expected 1 IPv4 address, got %d", len(filter.IPv4Address))
|
||||
}
|
||||
|
||||
if filter.IPv4Address[0].Address != "192.168.1.0" {
|
||||
t.Errorf("Expected address 192.168.1.0, got %s", filter.IPv4Address[0].Address)
|
||||
}
|
||||
|
||||
if filter.IPv4Address[0].PrefixLength != 24 {
|
||||
t.Errorf("Expected prefix length 24, got %d", filter.IPv4Address[0].PrefixLength)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetIPAddressFilter(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
filter := &IPAddressFilter{
|
||||
Type: IPAddressFilterAllow,
|
||||
IPv4Address: []PrefixedIPv4Address{
|
||||
{Address: "10.0.0.0", PrefixLength: 8},
|
||||
},
|
||||
}
|
||||
|
||||
err = client.SetIPAddressFilter(ctx, filter)
|
||||
if err != nil {
|
||||
t.Fatalf("SetIPAddressFilter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddIPAddressFilter(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
filter := &IPAddressFilter{
|
||||
Type: IPAddressFilterAllow,
|
||||
IPv4Address: []PrefixedIPv4Address{
|
||||
{Address: "172.16.0.0", PrefixLength: 12},
|
||||
},
|
||||
}
|
||||
|
||||
err = client.AddIPAddressFilter(ctx, filter)
|
||||
if err != nil {
|
||||
t.Fatalf("AddIPAddressFilter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveIPAddressFilter(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
filter := &IPAddressFilter{
|
||||
Type: IPAddressFilterAllow,
|
||||
IPv4Address: []PrefixedIPv4Address{
|
||||
{Address: "172.16.0.0", PrefixLength: 12},
|
||||
},
|
||||
}
|
||||
|
||||
err = client.RemoveIPAddressFilter(ctx, filter)
|
||||
if err != nil {
|
||||
t.Fatalf("RemoveIPAddressFilter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetZeroConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
zeroConf, err := client.GetZeroConfiguration(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetZeroConfiguration failed: %v", err)
|
||||
}
|
||||
|
||||
if zeroConf.InterfaceToken != "eth0" {
|
||||
t.Errorf("Expected interface token 'eth0', got %s", zeroConf.InterfaceToken)
|
||||
}
|
||||
|
||||
if !zeroConf.Enabled {
|
||||
t.Error("Zero configuration should be enabled")
|
||||
}
|
||||
|
||||
if len(zeroConf.Addresses) != 1 || zeroConf.Addresses[0] != "169.254.1.100" {
|
||||
t.Errorf("Expected address 169.254.1.100, got %v", zeroConf.Addresses)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetZeroConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = client.SetZeroConfiguration(ctx, "eth0", true)
|
||||
if err != nil {
|
||||
t.Fatalf("SetZeroConfiguration failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPasswordComplexityConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config, err := client.GetPasswordComplexityConfiguration(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPasswordComplexityConfiguration failed: %v", err)
|
||||
}
|
||||
|
||||
if config.MinLen != 8 {
|
||||
t.Errorf("Expected MinLen 8, got %d", config.MinLen)
|
||||
}
|
||||
|
||||
if config.Uppercase != 1 {
|
||||
t.Errorf("Expected Uppercase 1, got %d", config.Uppercase)
|
||||
}
|
||||
|
||||
if config.Number != 1 {
|
||||
t.Errorf("Expected Number 1, got %d", config.Number)
|
||||
}
|
||||
|
||||
if config.SpecialChars != 1 {
|
||||
t.Errorf("Expected SpecialChars 1, got %d", config.SpecialChars)
|
||||
}
|
||||
|
||||
if !config.BlockUsernameOccurrence {
|
||||
t.Error("BlockUsernameOccurrence should be true")
|
||||
}
|
||||
|
||||
if config.PolicyConfigurationLocked {
|
||||
t.Error("PolicyConfigurationLocked should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPasswordComplexityConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config := &PasswordComplexityConfiguration{
|
||||
MinLen: 10,
|
||||
Uppercase: 2,
|
||||
Number: 2,
|
||||
SpecialChars: 1,
|
||||
BlockUsernameOccurrence: true,
|
||||
PolicyConfigurationLocked: false,
|
||||
}
|
||||
|
||||
err = client.SetPasswordComplexityConfiguration(ctx, config)
|
||||
if err != nil {
|
||||
t.Fatalf("SetPasswordComplexityConfiguration failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPasswordHistoryConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config, err := client.GetPasswordHistoryConfiguration(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPasswordHistoryConfiguration failed: %v", err)
|
||||
}
|
||||
|
||||
if !config.Enabled {
|
||||
t.Error("Password history should be enabled")
|
||||
}
|
||||
|
||||
if config.Length != 5 {
|
||||
t.Errorf("Expected Length 5, got %d", config.Length)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPasswordHistoryConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config := &PasswordHistoryConfiguration{
|
||||
Enabled: true,
|
||||
Length: 10,
|
||||
}
|
||||
|
||||
err = client.SetPasswordHistoryConfiguration(ctx, config)
|
||||
if err != nil {
|
||||
t.Fatalf("SetPasswordHistoryConfiguration failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAuthFailureWarningConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config, err := client.GetAuthFailureWarningConfiguration(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetAuthFailureWarningConfiguration failed: %v", err)
|
||||
}
|
||||
|
||||
if !config.Enabled {
|
||||
t.Error("Auth failure warning should be enabled")
|
||||
}
|
||||
|
||||
if config.MonitorPeriod != 60 {
|
||||
t.Errorf("Expected MonitorPeriod 60, got %d", config.MonitorPeriod)
|
||||
}
|
||||
|
||||
if config.MaxAuthFailures != 5 {
|
||||
t.Errorf("Expected MaxAuthFailures 5, got %d", config.MaxAuthFailures)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAuthFailureWarningConfiguration(t *testing.T) {
|
||||
server := newMockDeviceSecurityServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
config := &AuthFailureWarningConfiguration{
|
||||
Enabled: true,
|
||||
MonitorPeriod: 120,
|
||||
MaxAuthFailures: 3,
|
||||
}
|
||||
|
||||
err = client.SetAuthFailureWarningConfiguration(ctx, config)
|
||||
if err != nil {
|
||||
t.Fatalf("SetAuthFailureWarningConfiguration failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddressFilterTypeConstants(t *testing.T) {
|
||||
if IPAddressFilterAllow != "Allow" {
|
||||
t.Errorf("IPAddressFilterAllow should be 'Allow', got %s", IPAddressFilterAllow)
|
||||
}
|
||||
|
||||
if IPAddressFilterDeny != "Deny" {
|
||||
t.Errorf("IPAddressFilterDeny should be 'Deny', got %s", IPAddressFilterDeny)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user