212 lines
8.0 KiB
TypeScript
212 lines
8.0 KiB
TypeScript
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
|
import {
|
||
|
|
RadiusAttributes,
|
||
|
|
ERadiusAttributeType,
|
||
|
|
} from '../../ts_server/index.js';
|
||
|
|
|
||
|
|
tap.test('should get attribute definitions by type', async () => {
|
||
|
|
const userNameDef = RadiusAttributes.getDefinition(ERadiusAttributeType.UserName);
|
||
|
|
expect(userNameDef).toBeDefined();
|
||
|
|
expect(userNameDef!.name).toEqual('User-Name');
|
||
|
|
expect(userNameDef!.valueType).toEqual('text');
|
||
|
|
|
||
|
|
const nasIpDef = RadiusAttributes.getDefinition(ERadiusAttributeType.NasIpAddress);
|
||
|
|
expect(nasIpDef).toBeDefined();
|
||
|
|
expect(nasIpDef!.name).toEqual('NAS-IP-Address');
|
||
|
|
expect(nasIpDef!.valueType).toEqual('address');
|
||
|
|
|
||
|
|
const nasPortDef = RadiusAttributes.getDefinition(ERadiusAttributeType.NasPort);
|
||
|
|
expect(nasPortDef).toBeDefined();
|
||
|
|
expect(nasPortDef!.name).toEqual('NAS-Port');
|
||
|
|
expect(nasPortDef!.valueType).toEqual('integer');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should get attribute type by name', async () => {
|
||
|
|
expect(RadiusAttributes.getTypeByName('User-Name')).toEqual(ERadiusAttributeType.UserName);
|
||
|
|
expect(RadiusAttributes.getTypeByName('NAS-IP-Address')).toEqual(ERadiusAttributeType.NasIpAddress);
|
||
|
|
expect(RadiusAttributes.getTypeByName('NAS-Port')).toEqual(ERadiusAttributeType.NasPort);
|
||
|
|
expect(RadiusAttributes.getTypeByName('Unknown-Attribute')).toBeUndefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should get attribute name by type', async () => {
|
||
|
|
expect(RadiusAttributes.getNameByType(ERadiusAttributeType.UserName)).toEqual('User-Name');
|
||
|
|
expect(RadiusAttributes.getNameByType(ERadiusAttributeType.UserPassword)).toEqual('User-Password');
|
||
|
|
expect(RadiusAttributes.getNameByType(255)).toInclude('Unknown-Attribute');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should parse text attributes', async () => {
|
||
|
|
const textBuffer = Buffer.from('testuser', 'utf8');
|
||
|
|
const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.UserName, textBuffer);
|
||
|
|
expect(parsed).toEqual('testuser');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should parse address attributes', async () => {
|
||
|
|
const addressBuffer = Buffer.from([192, 168, 1, 100]);
|
||
|
|
const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.NasIpAddress, addressBuffer);
|
||
|
|
expect(parsed).toEqual('192.168.1.100');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should parse integer attributes', async () => {
|
||
|
|
const intBuffer = Buffer.allocUnsafe(4);
|
||
|
|
intBuffer.writeUInt32BE(12345, 0);
|
||
|
|
const parsed = RadiusAttributes.parseValue(ERadiusAttributeType.NasPort, intBuffer);
|
||
|
|
expect(parsed).toEqual(12345);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should encode text attributes', async () => {
|
||
|
|
const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.UserName, 'testuser');
|
||
|
|
expect(encoded.toString('utf8')).toEqual('testuser');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should encode address attributes', async () => {
|
||
|
|
const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.NasIpAddress, '10.20.30.40');
|
||
|
|
expect(encoded.length).toEqual(4);
|
||
|
|
expect(encoded[0]).toEqual(10);
|
||
|
|
expect(encoded[1]).toEqual(20);
|
||
|
|
expect(encoded[2]).toEqual(30);
|
||
|
|
expect(encoded[3]).toEqual(40);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should encode integer attributes', async () => {
|
||
|
|
const encoded = RadiusAttributes.encodeValue(ERadiusAttributeType.NasPort, 65535);
|
||
|
|
expect(encoded.length).toEqual(4);
|
||
|
|
expect(encoded.readUInt32BE(0)).toEqual(65535);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should encode complete attribute with TLV format', async () => {
|
||
|
|
const encoded = RadiusAttributes.encodeAttribute(ERadiusAttributeType.UserName, 'test');
|
||
|
|
expect(encoded[0]).toEqual(ERadiusAttributeType.UserName); // Type
|
||
|
|
expect(encoded[1]).toEqual(6); // Length (2 + 4)
|
||
|
|
expect(encoded.subarray(2).toString('utf8')).toEqual('test');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle attribute value too long', async () => {
|
||
|
|
const longValue = Buffer.alloc(254); // Max is 253 bytes
|
||
|
|
let error: Error | undefined;
|
||
|
|
try {
|
||
|
|
RadiusAttributes.encodeAttribute(ERadiusAttributeType.UserName, longValue);
|
||
|
|
} catch (e) {
|
||
|
|
error = e as Error;
|
||
|
|
}
|
||
|
|
expect(error).toBeDefined();
|
||
|
|
expect(error!.message).toInclude('too long');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should parse raw attribute', async () => {
|
||
|
|
const rawAttr = {
|
||
|
|
type: ERadiusAttributeType.UserName,
|
||
|
|
value: Buffer.from('john.doe', 'utf8'),
|
||
|
|
};
|
||
|
|
const parsed = RadiusAttributes.parseAttribute(rawAttr);
|
||
|
|
expect(parsed.type).toEqual(ERadiusAttributeType.UserName);
|
||
|
|
expect(parsed.name).toEqual('User-Name');
|
||
|
|
expect(parsed.value).toEqual('john.doe');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle Vendor-Specific Attributes', async () => {
|
||
|
|
// Vendor-Id: 9 (Cisco), Vendor-Type: 1, Value: 'test'
|
||
|
|
const vendorId = 9;
|
||
|
|
const vendorType = 1;
|
||
|
|
const vendorValue = Buffer.from('cisco-av-pair', 'utf8');
|
||
|
|
|
||
|
|
const vsaBuffer = RadiusAttributes.encodeVSA({ vendorId, vendorType, vendorValue });
|
||
|
|
|
||
|
|
// Parse it back
|
||
|
|
const parsed = RadiusAttributes.parseVSA(vsaBuffer);
|
||
|
|
expect(parsed).toBeDefined();
|
||
|
|
expect(parsed!.vendorId).toEqual(vendorId);
|
||
|
|
expect(parsed!.vendorType).toEqual(vendorType);
|
||
|
|
expect(parsed!.vendorValue.toString('utf8')).toEqual('cisco-av-pair');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should create complete Vendor-Specific attribute', async () => {
|
||
|
|
const vendorId = 311; // Microsoft
|
||
|
|
const vendorType = 1;
|
||
|
|
const vendorValue = Buffer.from('test-value', 'utf8');
|
||
|
|
|
||
|
|
const attr = RadiusAttributes.createVendorAttribute(vendorId, vendorType, vendorValue);
|
||
|
|
|
||
|
|
// First byte should be type 26 (Vendor-Specific)
|
||
|
|
expect(attr[0]).toEqual(ERadiusAttributeType.VendorSpecific);
|
||
|
|
|
||
|
|
// Second byte is total length
|
||
|
|
expect(attr[1]).toBeGreaterThan(6);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should check if attribute is encrypted', async () => {
|
||
|
|
expect(RadiusAttributes.isEncrypted(ERadiusAttributeType.UserPassword)).toBeTruthy();
|
||
|
|
expect(RadiusAttributes.isEncrypted(ERadiusAttributeType.UserName)).toBeFalsy();
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle all standard RFC 2865 attributes', async () => {
|
||
|
|
// Test that all standard attributes have definitions
|
||
|
|
const standardAttributes = [
|
||
|
|
ERadiusAttributeType.UserName,
|
||
|
|
ERadiusAttributeType.UserPassword,
|
||
|
|
ERadiusAttributeType.ChapPassword,
|
||
|
|
ERadiusAttributeType.NasIpAddress,
|
||
|
|
ERadiusAttributeType.NasPort,
|
||
|
|
ERadiusAttributeType.ServiceType,
|
||
|
|
ERadiusAttributeType.FramedProtocol,
|
||
|
|
ERadiusAttributeType.FramedIpAddress,
|
||
|
|
ERadiusAttributeType.FramedIpNetmask,
|
||
|
|
ERadiusAttributeType.FramedRouting,
|
||
|
|
ERadiusAttributeType.FilterId,
|
||
|
|
ERadiusAttributeType.FramedMtu,
|
||
|
|
ERadiusAttributeType.FramedCompression,
|
||
|
|
ERadiusAttributeType.LoginIpHost,
|
||
|
|
ERadiusAttributeType.LoginService,
|
||
|
|
ERadiusAttributeType.LoginTcpPort,
|
||
|
|
ERadiusAttributeType.ReplyMessage,
|
||
|
|
ERadiusAttributeType.CallbackNumber,
|
||
|
|
ERadiusAttributeType.CallbackId,
|
||
|
|
ERadiusAttributeType.FramedRoute,
|
||
|
|
ERadiusAttributeType.FramedIpxNetwork,
|
||
|
|
ERadiusAttributeType.State,
|
||
|
|
ERadiusAttributeType.Class,
|
||
|
|
ERadiusAttributeType.VendorSpecific,
|
||
|
|
ERadiusAttributeType.SessionTimeout,
|
||
|
|
ERadiusAttributeType.IdleTimeout,
|
||
|
|
ERadiusAttributeType.TerminationAction,
|
||
|
|
ERadiusAttributeType.CalledStationId,
|
||
|
|
ERadiusAttributeType.CallingStationId,
|
||
|
|
ERadiusAttributeType.NasIdentifier,
|
||
|
|
ERadiusAttributeType.ProxyState,
|
||
|
|
ERadiusAttributeType.ChapChallenge,
|
||
|
|
ERadiusAttributeType.NasPortType,
|
||
|
|
ERadiusAttributeType.PortLimit,
|
||
|
|
];
|
||
|
|
|
||
|
|
for (const attrType of standardAttributes) {
|
||
|
|
const def = RadiusAttributes.getDefinition(attrType);
|
||
|
|
expect(def).toBeDefined();
|
||
|
|
expect(def!.name).toBeDefined();
|
||
|
|
expect(def!.valueType).toBeDefined();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle all RFC 2866 accounting attributes', async () => {
|
||
|
|
const accountingAttributes = [
|
||
|
|
ERadiusAttributeType.AcctStatusType,
|
||
|
|
ERadiusAttributeType.AcctDelayTime,
|
||
|
|
ERadiusAttributeType.AcctInputOctets,
|
||
|
|
ERadiusAttributeType.AcctOutputOctets,
|
||
|
|
ERadiusAttributeType.AcctSessionId,
|
||
|
|
ERadiusAttributeType.AcctAuthentic,
|
||
|
|
ERadiusAttributeType.AcctSessionTime,
|
||
|
|
ERadiusAttributeType.AcctInputPackets,
|
||
|
|
ERadiusAttributeType.AcctOutputPackets,
|
||
|
|
ERadiusAttributeType.AcctTerminateCause,
|
||
|
|
ERadiusAttributeType.AcctMultiSessionId,
|
||
|
|
ERadiusAttributeType.AcctLinkCount,
|
||
|
|
];
|
||
|
|
|
||
|
|
for (const attrType of accountingAttributes) {
|
||
|
|
const def = RadiusAttributes.getDefinition(attrType);
|
||
|
|
expect(def).toBeDefined();
|
||
|
|
expect(def!.name).toBeDefined();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|