Skip to content

Commit

Permalink
add tests to ensureAuthorizationRules
Browse files Browse the repository at this point in the history
  • Loading branch information
ggazzo committed Dec 24, 2024
1 parent 8bf2e4c commit 947ec22
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 17 deletions.
2 changes: 1 addition & 1 deletion packages/fake/src/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const fakeEndpoints = new Elysia({ prefix: "/fake" })
}

const lastEventId = events[events.length - 1]._id;
const lastEvent = events[events.length - 1].event as any; //TODO: fix typing
const lastEvent = events[events.length - 1].event;

const inviteEvent = await signEvent(
roomMemberEvent({
Expand Down
9 changes: 6 additions & 3 deletions packages/homeserver/src/fixtures/ContextBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export function createMediaId(length: number) {
}

class MockedRoom {
private events: EventStore[] = [];
public events: EventStore[] = [];
constructor(
private roomId: string,
public roomId: string,
events: EventStore[],
) {
for (const event of events) {
Expand Down Expand Up @@ -191,7 +191,10 @@ export class ContextBuilder {
makeRequest,
createRoom: async (sender: string, ...members: string[]) => {
const { roomId, events } = await createRoom(
[sender, ...members],
[
`@${sender}:${config.name}`,
...members.map((member) => `@${member}:${config.name}`),
],
createSignedEvent(config.signingKey[0], config.name),
`!${createMediaId(18)}:${config.name}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ const getMissingEvents = (a: string[]) => [];

// https://spec.matrix.org/v1.2/rooms/v9/#authorization-rules

export const ensureAuthorizationRulesBatch = function* (
events: EventBase[],
roomId: string,
size = 100,
) {
const array = [...events];
while (array.length) {
yield ensureAuthorizationRules(array.splice(0, size), roomId);
}
};

export const ensureAuthorizationRules = async (
events: EventBase[],
roomId: string,
Expand Down Expand Up @@ -60,15 +71,22 @@ export const ensureAuthorizationRules = async (
}
}

const eventsToBeStored = new Set<string>();
for await (const event of sortedAuthEvents) {
console.log("event ->", event);
console.log("event.auth_events ->", event.auth_events);
console.log("authMap ->", authMap);
try {
if (await prep(event, authMap)) {
eventsToBeStored.add(generateId(event));
}
} catch (e) {
console.log("error", e);
}
}

return eventsToBeStored;
};

async function prep(event: EventBase, authMap: Map<string, EventBase>) {
const auth: EventBase[] = [];
const authEvents = new Map<string, EventBase>();
for (const authEventId of event.auth_events) {
const ae = authMap.get(authEventId);
if (!ae) {
Expand All @@ -80,7 +98,7 @@ async function prep(event: EventBase, authMap: Map<string, EventBase>) {
);
return;
}
auth.push(ae);
authEvents.set(authEventId, ae);
}
// We're not bothering about room state, so flag the event as an outlier.
// event.internalMetadata.outlier = true;
Expand All @@ -89,12 +107,12 @@ async function prep(event: EventBase, authMap: Map<string, EventBase>) {
// validateEventForRoomVersion(event);
switch (true) {
case isRoomCreateEvent(event): {
await validateRoomCreateEvent(event, authMap);
break;
await validateRoomCreateEvent(event, authEvents);
return true;
}
case isRoomMemberEvent(event): {
await validateRoomMemberEvent(event, authMap);
break;
await validateRoomMemberEvent(event, authEvents);
return true;
}
}

Expand All @@ -107,9 +125,9 @@ async function prep(event: EventBase, authMap: Map<string, EventBase>) {

const callerInRoom = caller?.content.membership === "join";

const callerPowerLevel = getUserPowerLevel(event.sender, authMap);
const callerPowerLevel = getUserPowerLevel(event.sender, authEvents);

const inviteLevel = getNamedPowerLevel("invite", authMap) ?? 0;
const inviteLevel = getNamedPowerLevel("invite", authEvents) ?? 0;

// 5 If the sender’s current membership state is not join, reject.
if (!callerInRoom) {
Expand Down Expand Up @@ -138,7 +156,7 @@ async function prep(event: EventBase, authMap: Map<string, EventBase>) {

// 9 If type is m.room.power_levels
if (isRoomPowerLevelsEvent(event)) {
await validateRoomPowerLevelsEvent(event, authMap);
await validateRoomPowerLevelsEvent(event, authEvents);
}
// 10 otherwise, allow.
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { describe, expect, test } from "bun:test";
import { ensureAuthorizationRules } from "./ensureAuthorizationRules";
import { hs1 } from "../../fixtures/ContextBuilder";

describe("ensureAuthorizationRules", () => {
test("it should pass - real case from synapse", async () => {
const promise = ensureAuthorizationRules(
[
{
auth_events: [],
prev_events: [],
type: "m.room.create",
room_id: "!YrzBTuXYuWmkPFNPvP:hs1",
sender: "@admin:hs1",
content: {
room_version: "10",
creator: "@admin:hs1",
},
depth: 1,
state_key: "",
origin: "hs1",
origin_server_ts: 1734749094984,
// @ts-expect-error
hashes: {
sha256: "4eNVJv9TsqtwoOObcFcVlBzPngedlhHFMJzC6XlXO48",
},
signatures: {
hs1: {
"ed25519:a_HDhg":
"bU3jArtCF6n3j1cJ6uvybisyX8vC9m9pTphN6VG1ju5MhpiVjtBlgHoVOM2ofGZ6JK01kuE6By6PwaV7qwj/Dg",
},
},
unsigned: {
age: 1193,
},
},
{
auth_events: [
"$CUQnskjHihUMzzxepn2fvkAybKdmUglnbUyV4sw4UnE",
"$xuZGslwbSTKYjCDAqlDsdvRy963ijx5lBIia2c2BOQw",
],
prev_events: ["$xuZGslwbSTKYjCDAqlDsdvRy963ijx5lBIia2c2BOQw"],
type: "m.room.power_levels",
room_id: "!YrzBTuXYuWmkPFNPvP:hs1",
sender: "@admin:hs1",
content: {
users: {
"@admin:hs1": 100,
"@g21:rc1": 100,
},
users_default: 0,
events: {
"m.room.name": 50,
"m.room.power_levels": 100,
"m.room.history_visibility": 100,
"m.room.canonical_alias": 50,
"m.room.avatar": 50,
"m.room.tombstone": 100,
"m.room.server_acl": 100,
"m.room.encryption": 100,
},
events_default: 0,
state_default: 50,
ban: 50,
kick: 50,
redact: 50,
invite: 0,
historical: 100,
},
depth: 3,
state_key: "",
origin: "hs1",
origin_server_ts: 1734749095033,
// @ts-expect-error
hashes: {
sha256: "caoUhtqs/bob6I4duim8flOAf9/qQP5t/aZ0g2ZJn2U",
},
signatures: {
hs1: {
"ed25519:a_HDhg":
"dwH6+IoS1N+3c+PdMi8K21G6R4ndMJ7oK4ds3Ny9gUlh5i9qJpflE5YgSmY7tXJJaFxgu/ahHESMsmUZ1clJBQ",
},
},
unsigned: {
age: 1144,
},
},
{
auth_events: ["$CUQnskjHihUMzzxepn2fvkAybKdmUglnbUyV4sw4UnE"],
prev_events: ["$CUQnskjHihUMzzxepn2fvkAybKdmUglnbUyV4sw4UnE"],
type: "m.room.member",
room_id: "!YrzBTuXYuWmkPFNPvP:hs1",
sender: "@admin:hs1",
content: {
displayname: "admin",
membership: "join",
},
depth: 2,
state_key: "@admin:hs1",
origin: "hs1",
origin_server_ts: 1734749095014,
// @ts-expect-error
hashes: {
sha256: "HseVbe6ngHdu2bJRVG9TfIy3HyQ+ZftmgYettfV0Kwc",
},
signatures: {
hs1: {
"ed25519:a_HDhg":
"hwRDAopG7oSlEDt+V00hzGstHQixwdm86vexS9yT9eG13qXpDo7IQUmFxBVtGEPKnHs19yBCYMdBcrqBPK9eAA",
},
},
unsigned: {
age: 1163,
},
},
{
auth_events: [
"$CUQnskjHihUMzzxepn2fvkAybKdmUglnbUyV4sw4UnE",
"$xuZGslwbSTKYjCDAqlDsdvRy963ijx5lBIia2c2BOQw",
"$pLzcU_5Kn3ir4HpYYee1XHVUTO3IvIqPmR2xDLxAL8I",
],
prev_events: ["$pLzcU_5Kn3ir4HpYYee1XHVUTO3IvIqPmR2xDLxAL8I"],
type: "m.room.join_rules",
room_id: "!YrzBTuXYuWmkPFNPvP:hs1",
sender: "@admin:hs1",
content: {
join_rule: "invite",
},
depth: 4,
state_key: "",
origin: "hs1",
origin_server_ts: 1734749095039,
// @ts-expect-error
hashes: {
sha256: "A5zAjIbuy4uoPN8wasdasukqViu8Ox7WjTmUSjXgVFPTTsEY",
},
signatures: {
hs1: {
"ed25519:a_HDhg":
"zwScOvaLrjAYAalWDHit6IB7O00xJ1zNy5/Q39H8aDcUt85pwzvEntNPxtDcIr3bq91p3wca6FXV9egjZ//sDQ",
},
},
unsigned: {
age: 1138,
},
},
],
"roomId",
);
expect(async () => promise).not.toThrow();
const data = await promise;
expect(data.size).toEqual(4);
});

test("it should pass - fake scenario made using createRoom", async () => {
const hs1Context = await hs1.build();

const room = await hs1Context.createRoom("@g21:hs1");

const result = await ensureAuthorizationRules(
room.events.map((event) => event.event),
room.roomId,
);

expect(result.size).toEqual(6);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ import {
import { getUserPowerLevel } from "./ensureAuthorizationRules";
import { env } from "bun";

const isValidPowerLevel = (
obj: unknown,
): obj is {
[key: string]: number;
} => {
if (typeof obj !== "object" || obj === null) {
return false;
}

for (const key of Object.keys(obj)) {
if (typeof key !== "string") {
return false;
}

const powerLevel = key in obj && obj[key as keyof typeof obj];

if (typeof powerLevel !== "number") {
return false;
}
}

return true;
};
const isValidUserPowerLevel = (
obj: unknown,
): obj is {
Expand All @@ -20,6 +43,7 @@ const isValidUserPowerLevel = (
return false;
}
const [user, server] = key.split(":");

if (user.length === 0 || server.length === 0) {
return false;
}
Expand All @@ -41,6 +65,7 @@ export const validateRoomPowerLevelsEvent = async (
// 9.1 If users key in content is not a dictionary with keys that are valid user IDs with values that are integers (or a string that is an integer), reject.

const users = event.content.users ?? {};

if (!isValidUserPowerLevel(users)) {
throw new Error("Invalid users");
}
Expand All @@ -65,7 +90,7 @@ export const validateRoomPowerLevelsEvent = async (
if (
typeof value !== "object" ||
value === null ||
!isValidUserPowerLevel(value)
!isValidPowerLevel(value)
) {
throw new Error("Invalid keys");
}
Expand Down

0 comments on commit 947ec22

Please sign in to comment.