Skip to content

Commit 4024948

Browse files
committed
wip: more ddp
1 parent 6948f26 commit 4024948

File tree

9 files changed

+71
-116
lines changed

9 files changed

+71
-116
lines changed

meteor/server/api/ExternalMessageQueue.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
} from '@sofie-automation/meteor-lib/dist/api/ExternalMessageQueue'
1010
import { StatusObject, setSystemStatus } from '../systemStatus/systemStatus'
1111
import { MethodContextAPI, MethodContext } from './methodContext'
12-
import { StudioContentWriteAccess } from '../security/studio'
1312
import { ExternalMessageQueueObjId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1413
import { ExternalMessageQueue } from '../collections'
1514
import { ExternalMessageQueueObj } from '@sofie-automation/corelib/dist/dataModel/ExternalMessageQueue'
1615
import { MongoQuery } from '@sofie-automation/corelib/dist/mongo'
16+
import { UserPermissions } from '@sofie-automation/meteor-lib/dist/userPermissions'
17+
import { assertConnectionHasOneOfPermissions } from '../security/auth'
18+
19+
const USER_PERMISSIONS_FOR_EXTERNAL_MESSAGES: Array<keyof UserPermissions> = ['configure', 'studio', 'service']
1720

1821
let updateExternalMessageQueueStatusTimeout = 0
1922
function updateExternalMessageQueueStatus(): void {
@@ -68,36 +71,44 @@ Meteor.startup(() => {
6871

6972
async function removeExternalMessage(context: MethodContext, messageId: ExternalMessageQueueObjId): Promise<void> {
7073
check(messageId, String)
71-
await StudioContentWriteAccess.externalMessage(context, messageId)
74+
75+
assertConnectionHasOneOfPermissions(context.connection, ...USER_PERMISSIONS_FOR_EXTERNAL_MESSAGES)
7276

7377
// TODO - is this safe? what if it is in the middle of execution?
7478
await ExternalMessageQueue.removeAsync(messageId)
7579
}
7680
async function toggleHold(context: MethodContext, messageId: ExternalMessageQueueObjId): Promise<void> {
7781
check(messageId, String)
78-
const access = await StudioContentWriteAccess.externalMessage(context, messageId)
79-
const m = access.message
80-
if (!m) throw new Meteor.Error(404, `ExternalMessage "${messageId}" not found!`)
82+
83+
assertConnectionHasOneOfPermissions(context.connection, ...USER_PERMISSIONS_FOR_EXTERNAL_MESSAGES)
84+
85+
const existingMessage = await ExternalMessageQueue.findOneAsync(messageId)
86+
if (!existingMessage) throw new Meteor.Error(404, `ExternalMessage "${messageId}" not found!`)
8187

8288
await ExternalMessageQueue.updateAsync(messageId, {
8389
$set: {
84-
hold: !m.hold,
90+
hold: !existingMessage.hold,
8591
},
8692
})
8793
}
8894
async function retry(context: MethodContext, messageId: ExternalMessageQueueObjId): Promise<void> {
8995
check(messageId, String)
90-
const access = await StudioContentWriteAccess.externalMessage(context, messageId)
91-
const m = access.message
92-
if (!m) throw new Meteor.Error(404, `ExternalMessage "${messageId}" not found!`)
96+
97+
assertConnectionHasOneOfPermissions(context.connection, ...USER_PERMISSIONS_FOR_EXTERNAL_MESSAGES)
98+
99+
const existingMessage = await ExternalMessageQueue.findOneAsync(messageId)
100+
if (!existingMessage) throw new Meteor.Error(404, `ExternalMessage "${messageId}" not found!`)
93101

94102
const tryGap = getCurrentTime() - 1 * 60 * 1000
95103
await ExternalMessageQueue.updateAsync(messageId, {
96104
$set: {
97105
manualRetry: true,
98106
hold: false,
99107
errorFatal: false,
100-
lastTry: m.lastTry !== undefined && m.lastTry > tryGap ? tryGap : m.lastTry,
108+
lastTry:
109+
existingMessage.lastTry !== undefined && existingMessage.lastTry > tryGap
110+
? tryGap
111+
: existingMessage.lastTry,
101112
},
102113
})
103114
// triggerdoMessageQueue(1000)

meteor/server/api/ingest/lib.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyE
55
import { PeripheralDevice, PeripheralDeviceCategory } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice'
66
import { Rundown, RundownSourceNrcs } from '@sofie-automation/corelib/dist/dataModel/Rundown'
77
import { logger } from '../../logging'
8-
import { PeripheralDeviceContentWriteAccess } from '../../security/peripheralDevice'
98
import { MethodContext } from '../methodContext'
10-
import { Credentials } from '../../security/lib/credentials'
119
import { profiler } from '../profiler'
1210
import { IngestJobFunc } from '@sofie-automation/corelib/dist/worker/ingest'
1311
import { QueueIngestJob } from '../../worker/worker'
@@ -21,6 +19,7 @@ import {
2119
} from '@sofie-automation/corelib/dist/dataModel/Ids'
2220
import { PeripheralDevices } from '../../collections'
2321
import { getStudioIdFromDevice } from '../studio/lib'
22+
import { assertConnectionHasOneOfPermissions } from '../../security/auth'
2423

2524
/**
2625
* Run an ingest operation via the worker.
@@ -68,20 +67,37 @@ export async function runIngestOperation<T extends keyof IngestJobFunc>(
6867
export async function checkAccessAndGetPeripheralDevice(
6968
deviceId: PeripheralDeviceId,
7069
token: string | undefined,
71-
context: Credentials | MethodContext
70+
context: MethodContext
7271
): Promise<PeripheralDevice> {
7372
const span = profiler.startSpan('lib.checkAccessAndGetPeripheralDevice')
7473

75-
const { device: peripheralDevice } = await PeripheralDeviceContentWriteAccess.peripheralDevice(
76-
{ userId: context.userId, token },
77-
deviceId
78-
)
79-
if (!peripheralDevice) {
80-
throw new Meteor.Error(404, `PeripheralDevice "${deviceId}" not found`)
74+
assertConnectionHasOneOfPermissions(context.connection, 'gateway')
75+
76+
// If no token, we will never match
77+
if (!token) throw new Meteor.Error(401, `Not allowed access to peripheralDevice`)
78+
79+
const device = await PeripheralDevices.findOneAsync({ _id: deviceId })
80+
if (!device) throw new Meteor.Error(404, `PeripheralDevice "${deviceId}" not found`)
81+
82+
// Check if the device has a token, and if it matches:
83+
if (device.token && device.token === token) {
84+
span?.end()
85+
return device
86+
}
87+
88+
// If the device has a parent, try that for access control:
89+
const parentDevice = device.parentDeviceId ? await PeripheralDevices.findOneAsync(device.parentDeviceId) : device
90+
if (!parentDevice) throw new Meteor.Error(404, `PeripheralDevice parentDevice "${device.parentDeviceId}" not found`)
91+
92+
// Check if the parent device has a token, and if it matches:
93+
if (parentDevice.token && parentDevice.token === token) {
94+
span?.end()
95+
return device
8196
}
8297

98+
// No match for token found
8399
span?.end()
84-
return peripheralDevice
100+
throw new Meteor.Error(401, `Not allowed access to peripheralDevice`)
85101
}
86102

87103
export function getRundownId(studioId: StudioId, rundownExternalId: string): RundownId {

meteor/server/api/organizations.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { MethodContextAPI, MethodContext } from './methodContext'
44
import { NewOrganizationAPI, OrganizationAPIMethods } from '@sofie-automation/meteor-lib/dist/api/organization'
55
import { registerClassToMeteorMethods } from '../methods'
66
import { DBOrganization, DBOrganizationBase } from '@sofie-automation/meteor-lib/dist/collections/Organization'
7-
import { OrganizationContentWriteAccess } from '../security/organization'
8-
import { triggerWriteAccessBecauseNoCheckNecessary } from '../security/lib/securityVerify'
97
import { insertStudioInner } from './studio/api'
108
import { insertShowStyleBaseInner } from './showStyles'
119
import { BlueprintId, OrganizationId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1210
import { Blueprints, CoreSystem, Organizations, ShowStyleBases, Studios } from '../collections'
1311
import { getCoreSystemAsync } from '../coreSystem/collection'
12+
import { UserPermissions } from '@sofie-automation/meteor-lib/dist/userPermissions'
13+
import { assertConnectionHasOneOfPermissions } from '../security/auth'
14+
15+
const PERMISSIONS_FOR_MANAGE_ORGANIZATIONS: Array<keyof UserPermissions> = ['configure']
1416

1517
async function createDefaultEnvironmentForOrg(orgId: OrganizationId) {
1618
let systemBlueprintId: BlueprintId | undefined
@@ -42,8 +44,11 @@ async function createDefaultEnvironmentForOrg(orgId: OrganizationId) {
4244
await ShowStyleBases.updateAsync(showStyleId, { $set: { blueprintId: showStyleBlueprintId } })
4345
}
4446

45-
export async function createOrganization(organization: DBOrganizationBase): Promise<OrganizationId> {
46-
triggerWriteAccessBecauseNoCheckNecessary()
47+
export async function createOrganization(
48+
context: MethodContext,
49+
organization: DBOrganizationBase
50+
): Promise<OrganizationId> {
51+
assertConnectionHasOneOfPermissions(context.connection, ...PERMISSIONS_FOR_MANAGE_ORGANIZATIONS)
4752

4853
const orgId = await Organizations.insertAsync(
4954
literal<DBOrganization>({
@@ -60,7 +65,7 @@ export async function createOrganization(organization: DBOrganizationBase): Prom
6065
}
6166

6267
async function removeOrganization(context: MethodContext, organizationId: OrganizationId) {
63-
await OrganizationContentWriteAccess.organization(context, organizationId)
68+
assertConnectionHasOneOfPermissions(context.connection, ...PERMISSIONS_FOR_MANAGE_ORGANIZATIONS)
6469

6570
await Organizations.removeAsync(organizationId)
6671
}

meteor/server/api/peripheralDevice.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { MediaWorkFlowStep } from '@sofie-automation/shared-lib/dist/core/model/
2626
import { MOS } from '@sofie-automation/corelib'
2727
import { determineDiffTime } from './systemTime/systemTime'
2828
import { getTimeDiff } from './systemTime/api'
29-
import { PeripheralDeviceContentWriteAccess } from '../security/peripheralDevice'
3029
import { MethodContextAPI, MethodContext } from './methodContext'
3130
import { triggerWriteAccess, triggerWriteAccessBecauseNoCheckNecessary } from '../security/lib/securityVerify'
3231
import { checkAccessAndGetPeripheralDevice } from './ingest/lib'
@@ -81,7 +80,9 @@ export namespace ServerPeripheralDeviceAPI {
8180
check(deviceId, String)
8281
const existingDevice = await PeripheralDevices.findOneAsync(deviceId)
8382
if (existingDevice) {
84-
await PeripheralDeviceContentWriteAccess.peripheralDevice({ userId: context.userId, token }, deviceId)
83+
await checkAccessAndGetPeripheralDevice(deviceId, token, context)
84+
} else {
85+
triggerWriteAccessBecauseNoCheckNecessary()
8586
}
8687

8788
check(token, String)

meteor/server/api/rundown.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import {
1212
TriggerReloadDataResponse,
1313
} from '@sofie-automation/meteor-lib/dist/api/userActions'
1414
import { MethodContextAPI, MethodContext } from './methodContext'
15-
import { StudioContentWriteAccess } from '../security/studio'
1615
import { runIngestOperation } from './ingest/lib'
1716
import { IngestJobs } from '@sofie-automation/corelib/dist/worker/ingest'
1817
import { VerifiedRundownForUserAction, VerifiedRundownPlaylistForUserAction } from './lib'
1918
import { Blueprint } from '@sofie-automation/corelib/dist/dataModel/Blueprint'
2019
import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio'
2120
import { RundownPlaylistId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2221
import { Blueprints, Rundowns, ShowStyleBases, ShowStyleVariants, Studios } from '../collections'
22+
import { triggerWriteAccessBecauseNoCheckNecessary } from '../security/lib/securityVerify'
2323

2424
export namespace ServerRundownAPI {
2525
/** Remove an individual rundown */
@@ -63,16 +63,15 @@ export namespace ServerRundownAPI {
6363

6464
export namespace ClientRundownAPI {
6565
export async function rundownPlaylistNeedsResync(
66-
context: MethodContext,
66+
_context: MethodContext,
6767
playlistId: RundownPlaylistId
6868
): Promise<string[]> {
6969
check(playlistId, String)
70-
const access = await StudioContentWriteAccess.rundownPlaylist(context, playlistId)
71-
const playlist = access.playlist
70+
triggerWriteAccessBecauseNoCheckNecessary()
7271

7372
const rundowns = await Rundowns.findFetchAsync(
7473
{
75-
playlistId: playlist._id,
74+
playlistId: playlistId,
7675
},
7776
{
7877
sort: { _id: 1 },

meteor/server/security/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function parseConnectionPermissions(conn: RequestCredentials): UserPermis
1919
developer: true,
2020
testing: true,
2121
service: true,
22+
gateway: true,
2223
}
2324
}
2425

meteor/server/security/organization.ts

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { Meteor } from 'meteor/meteor'
22
import { logNotAllowed } from './lib/lib'
33
import { MongoQueryKey } from '@sofie-automation/corelib/dist/mongo'
44
import { allowAccessToOrganization } from './lib/security'
5-
import { Credentials, ResolvedCredentials, resolveCredentials } from './lib/credentials'
5+
import { Credentials, ResolvedCredentials } from './lib/credentials'
66
import { Settings } from '../Settings'
7-
import { MethodContext } from '../api/methodContext'
8-
import { triggerWriteAccess } from './lib/securityVerify'
97
import { isProtectedString } from '../lib/tempLib'
108
import { OrganizationId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
119

@@ -46,39 +44,3 @@ export namespace OrganizationReadAccess {
4644
return organizationContent(organizationId, cred)
4745
}
4846
}
49-
export namespace OrganizationContentWriteAccess {
50-
// These functions throws if access is not allowed.
51-
52-
export async function organization(
53-
cred0: Credentials,
54-
organizationId: OrganizationId
55-
): Promise<OrganizationContentAccess> {
56-
return anyContent(cred0, { organizationId })
57-
}
58-
59-
/** Return credentials if writing is allowed, throw otherwise */
60-
async function anyContent(
61-
cred0: Credentials | MethodContext,
62-
existingObj?: { organizationId: OrganizationId | null }
63-
): Promise<OrganizationContentAccess> {
64-
triggerWriteAccess()
65-
if (!Settings.enableUserAccounts) {
66-
return { userId: null, organizationId: null, cred: cred0 }
67-
}
68-
const cred = await resolveCredentials(cred0)
69-
if (!cred.user) throw new Meteor.Error(403, `Not logged in`)
70-
if (!cred.organizationId) throw new Meteor.Error(500, `User has no organization`)
71-
72-
const access = await allowAccessToOrganization(
73-
cred,
74-
existingObj ? existingObj.organizationId : cred.organizationId
75-
)
76-
if (!access.update) throw new Meteor.Error(403, `Not allowed: ${access.reason}`)
77-
78-
return {
79-
userId: cred.user._id,
80-
organizationId: cred.organizationId,
81-
cred: cred,
82-
}
83-
}
84-
}

meteor/server/security/studio.ts

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,11 @@ import { MongoQueryKey } from '@sofie-automation/corelib/dist/mongo'
44
import { logNotAllowed } from './lib/lib'
55
import { ExternalMessageQueueObj } from '@sofie-automation/corelib/dist/dataModel/ExternalMessageQueue'
66
import { Credentials, ResolvedCredentials, resolveCredentials } from './lib/credentials'
7-
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
87
import { Settings } from '../Settings'
98
import { triggerWriteAccess } from './lib/securityVerify'
109
import { isProtectedString } from '../lib/tempLib'
1110
import { fetchStudioLight } from '../optimizations'
12-
import {
13-
ExternalMessageQueueObjId,
14-
OrganizationId,
15-
RundownPlaylistId,
16-
StudioId,
17-
UserId,
18-
} from '@sofie-automation/corelib/dist/dataModel/Ids'
19-
import { ExternalMessageQueue, RundownPlaylists } from '../collections'
11+
import { OrganizationId, StudioId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2012
import { StudioLight } from '@sofie-automation/corelib/dist/dataModel/Studio'
2113

2214
export namespace StudioReadAccess {
@@ -61,25 +53,6 @@ export interface ExternalMessageContentAccess extends StudioContentAccess {
6153
export namespace StudioContentWriteAccess {
6254
// These functions throws if access is not allowed.
6355

64-
export async function rundownPlaylist(
65-
cred0: Credentials,
66-
existingPlaylist: DBRundownPlaylist | RundownPlaylistId
67-
): Promise<StudioContentAccess & { playlist: DBRundownPlaylist }> {
68-
triggerWriteAccess()
69-
if (existingPlaylist && isProtectedString(existingPlaylist)) {
70-
const playlistId = existingPlaylist
71-
const m = await RundownPlaylists.findOneAsync(playlistId)
72-
if (!m) throw new Meteor.Error(404, `RundownPlaylist "${playlistId}" not found!`)
73-
existingPlaylist = m
74-
}
75-
return { ...(await anyContent(cred0, existingPlaylist.studioId)), playlist: existingPlaylist }
76-
}
77-
78-
/** Check for permission to select active routesets in the studio */
79-
export async function routeSet(cred0: Credentials, studioId: StudioId): Promise<StudioContentAccess> {
80-
return anyContent(cred0, studioId)
81-
}
82-
8356
/** Check for permission to modify a bucket or its contents belonging to the studio */
8457
export async function bucket(cred0: Credentials, studioId: StudioId): Promise<StudioContentAccess> {
8558
return anyContent(cred0, studioId)
@@ -90,21 +63,6 @@ export namespace StudioContentWriteAccess {
9063
return anyContent(cred0, studioId)
9164
}
9265

93-
/** Check for permission to modify an ExternalMessageQueueObj */
94-
export async function externalMessage(
95-
cred0: Credentials,
96-
existingMessage: ExternalMessageQueueObj | ExternalMessageQueueObjId
97-
): Promise<ExternalMessageContentAccess> {
98-
triggerWriteAccess()
99-
if (existingMessage && isProtectedString(existingMessage)) {
100-
const messageId = existingMessage
101-
const m = await ExternalMessageQueue.findOneAsync(messageId)
102-
if (!m) throw new Meteor.Error(404, `ExternalMessage "${messageId}" not found!`)
103-
existingMessage = m
104-
}
105-
return { ...(await anyContent(cred0, existingMessage.studioId)), message: existingMessage }
106-
}
107-
10866
/**
10967
* We don't have user levels, so we can use a simple check for all cases
11068
* Return credentials if writing is allowed, throw otherwise

packages/meteor-lib/src/userPermissions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ export interface UserPermissions {
77
developer: boolean
88
testing: boolean
99
service: boolean
10+
gateway: boolean
1011
}
11-
const allowedPermissions = new Set<keyof UserPermissions>(['studio', 'configure', 'developer', 'testing', 'service'])
12+
const allowedPermissions = new Set<keyof UserPermissions>(['studio', 'configure', 'developer', 'testing', 'service','gateway'])
1213

1314
export function parseUserPermissions(encodedPermissions: string | undefined): UserPermissions {
1415
if (encodedPermissions === 'admin') {
@@ -18,6 +19,7 @@ export function parseUserPermissions(encodedPermissions: string | undefined): Us
1819
developer: true,
1920
testing: true,
2021
service: true,
22+
gateway:true
2123
}
2224
}
2325

@@ -26,7 +28,7 @@ export function parseUserPermissions(encodedPermissions: string | undefined): Us
2628
configure: false,
2729
developer: false,
2830
testing: false,
29-
service: false,
31+
service: false,gateway:false
3032
}
3133

3234
if (encodedPermissions && typeof encodedPermissions === 'string') {

0 commit comments

Comments
 (0)