removed serialization, was a security risk

This commit is contained in:
nitowa
2023-01-10 06:33:25 +01:00
parent ec158e6090
commit c138a2b9af
5 changed files with 7 additions and 205 deletions
-1
View File
@@ -3,4 +3,3 @@ export * from './src/Frontend';
export * from './src/Interfaces'
export * from './src/Types';
export * from './src/Utils'
export * from './src/Decorator'
-67
View File
@@ -1,67 +0,0 @@
import { CLASSNAME_ATTRIBUTE } from "./Strings"
export abstract class DeserializerFactory {
static entityClasses = {}
static from(object: any) {
if(!object){
return
}
if(typeof object !== 'object'){ //definitely not a class object
return object
}
if(Array.isArray(object)){
return object.map(DeserializerFactory.from)
}
const clazz = DeserializerFactory.entityClasses[object[CLASSNAME_ATTRIBUTE]]
delete object[CLASSNAME_ATTRIBUTE]
if(!clazz){ //anonymous object or class not registered as @Serializable
Object.keys(object).forEach(key => {
object[key] = DeserializerFactory.from(object[key])
})
return object
}
const obj = new clazz()
Object.keys(object).forEach(key => {
obj[key] = DeserializerFactory.from(object[key])
})
return obj
}
static makeDeserializable(object: any){
if(!object)
return
if(typeof object !== 'object')
return object
if(object.constructor.name === 'Object'){
Object.keys(object).forEach(key => {
object[key] = DeserializerFactory.makeDeserializable(object[key])
})
return object
}
object[CLASSNAME_ATTRIBUTE] = object.constructor.name
Object.keys(object).forEach(key => {
object[key] = DeserializerFactory.makeDeserializable(object[key])
})
return object
}
}
export function Serializable(attr?: any) {
return function _Serializable<T extends { new(...args: any[]): {} }>(clazz: T) {
DeserializerFactory.entityClasses[clazz.name] = clazz
return clazz
}
}
+3 -7
View File
@@ -5,8 +5,6 @@ import * as T from './Types';
import * as I from './Interfaces';
import { stripAfterEquals, appendComma } from './Utils';
import { CALLBACK_NAME, DESTROY_PREFIX, SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
import { DeserializerFactory } from './Decorator';
DeserializerFactory
/**
@@ -209,10 +207,9 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
const headerArgs = fnArgs.join(",")
const argParams = fnArgs.map(stripAfterEquals).join(",")
sesame = appendComma(sesame)
const deserializer = DeserializerFactory
return eval(`async (${headerArgs}) => {
const returnvalue = await this.call("${fnName}", ${sesame} ${argParams})
return deserializer.from(returnvalue)
return returnvalue
}`)
}
@@ -230,7 +227,6 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
const argParams = fnArgs.map(stripAfterEquals).join(",")
sesame = appendComma(sesame, true)
headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
const deserializer = DeserializerFactory
const destroy_prefix = DESTROY_PREFIX
const frontendHookStr = `
@@ -248,10 +244,10 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
})
this.socket.hook(r.uuid, (...args) => {
${CALLBACK_NAME}.apply(${CALLBACK_NAME}, args.map(deserializer.from))
${CALLBACK_NAME}.apply(${CALLBACK_NAME}, args)
})
}
return deserializer.from(r.return)
return r.return
}else{
throw new Error("Empty response")
}
+1 -5
View File
@@ -4,7 +4,6 @@ import * as T from "./Types";
import * as I from "./Interfaces";
import { Socket } from "socket.io"
import { CALL_NOT_FOUND, DESTROY_PREFIX, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
import { DeserializerFactory } from "./Decorator";
/**
* Translate an RPC to RPCInfo for serialization.
@@ -80,12 +79,11 @@ const callGenerator = (rpcName: string, $__socket__$: I.Socket, rpcFunction: T.G
const argsArr = extractArgs(rpcFunction)
const args = argsArr.join(',')
const argsStr = argsArr.map(stripAfterEquals).join(',')
const deserializer = DeserializerFactory
const callStr = `async (${args}) => {
try{
const res = await rpcFunction(${argsStr})
return deserializer.makeDeserializable(res)
return res
}catch(e){
errorHandler($__socket__$, e, rpcName, [${args}])
}
@@ -108,7 +106,6 @@ export function stripAfterEquals(str: string): string {
* @returns A {@link HookFunction}
*/
const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
const deserializer = DeserializerFactory
let argsArr = extractArgs(rpc.hook)
argsArr.shift()//remove callback param
@@ -125,7 +122,6 @@ const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, s
const uuid = uuidv4()
const res = await rpc.hook((...cbargs) => {
${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
cbargs = cbargs.map(deserializer.makeDeserializable)
$__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
},${callArgs})
${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
+1 -123
View File
@@ -1,5 +1,4 @@
import { describe, it } from "mocha";
import { RPCServer, RPCSocket, Serializable } from '../Index'
import { RPCExporter, Socket } from "../src/Interfaces";
import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
import * as log from 'why-is-node-running';
@@ -10,6 +9,7 @@ import { PromiseIO } from "../src/PromiseIO/Server";
import { PromiseIOClient } from "../src/PromiseIO/Client";
import { assert, expect } from 'chai';
import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
import { RPCServer, RPCSocket } from "../Index";
var should = require('chai').should();
var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");
@@ -1011,125 +1011,3 @@ describe("attaching handlers before connecting", () => {
})
})
})
describe("class (de-)serialization", () => {
@Serializable()
class SubClass {
fString = "F"
}
@Serializable()
class TestClass {
aString = "A"
aNumber = 46
aObject = {
x: "x",
y: undefined,
sub: new SubClass()
}
aClassObject = new SubClass()
public returnOK() {
return "OK"
}
}
const verifyObject = (obj: any) => {
expect(obj).to.be.an.instanceOf(TestClass)
expect(obj.aString).to.be.a('string')
expect(obj.aNumber).to.be.a('number')
expect(obj.aObject).to.be.a('object')
expect(obj.aObject.x).to.be.a('string')
expect(obj.aObject.y).to.be.undefined
expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
expect(obj).to.not.have.key(CLASSNAME_ATTRIBUTE)
expect(obj.aObject.sub).to.not.have.key(CLASSNAME_ATTRIBUTE)
expect(obj.aClassObject).to.not.have.key(CLASSNAME_ATTRIBUTE)
expect(obj.returnOK()).to.be.equal('OK')
}
describe("Responses", () => {
type TestIfc = {
Test: {
returnClass: () => Promise<TestClass>
classCallback: (callback: Callback<[TestClass]>) => Promise<TestClass>
}
}
let myServer: RPCServer<TestIfc>;
let mySocket: ConnectedSocket<TestIfc>;
before(function (done) {
myServer = new RPCServer<TestIfc>([{
name: "Test",
RPCs: [
async function returnClass() {
return new TestClass()
}, {
name: "classCallback",
hook: async function (callback) {
setTimeout(_ => callback(new TestClass()), 250)
return new TestClass()
}
}
]
}])
myServer.listen(8084)
new RPCSocket<TestIfc>(8084, 'localhost').connect().then(connsock => {
mySocket = connsock
done()
})
})
after(function (done) {
mySocket.close()
myServer.close()
done()
})
it("receives class object in call response", async () => {
const obj: TestClass = await mySocket['Test'].returnClass()
verifyObject(obj)
})
it("receives class object in hook response", async function () {
const obj: TestClass = await mySocket.Test.classCallback(function(x){
x
})
verifyObject(obj)
})
it("receives class object in callback", function (done) {
mySocket.Test.classCallback(function (cbValue) {
verifyObject(cbValue)
done()
}).then(verifyObject)
})
})
describe("Parameters", () => {
it("Class object in call", function(done){
const server = new RPCServer([
{
name: "Test",
RPCs: [
function callWithClass(testObj: TestClass){
verifyObject(testObj)
done()
}
]
}
]).listen(8086)
new RPCSocket(8086, 'localhost').connect().then(sock => {
sock['Test'].callWithClass(new TestClass()).then(_ => {
sock.close()
server.close()
})
})
})
})
})