Skip to content

Commit 082090a

Browse files
committed
wip: more ddp
1 parent 4024948 commit 082090a

File tree

3 files changed

+19
-170
lines changed

3 files changed

+19
-170
lines changed

meteor/server/api/client.ts

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,30 @@ import { ClientAPI, NewClientAPI, ClientAPIMethods } from '@sofie-automation/met
77
import { UserActionsLogItem } from '@sofie-automation/meteor-lib/dist/collections/UserActionsLog'
88
import { registerClassToMeteorMethods } from '../methods'
99
import { MethodContext, MethodContextAPI } from './methodContext'
10-
import { Settings } from '../Settings'
11-
import { resolveCredentials } from '../security/lib/credentials'
1210
import { isInTestWrite, triggerWriteAccessBecauseNoCheckNecessary } from '../security/lib/securityVerify'
13-
import { PeripheralDeviceContentWriteAccess } from '../security/peripheralDevice'
1411
import { endTrace, sendTrace, startTrace } from './integration/influx'
1512
import { interpollateTranslation, translateMessage } from '@sofie-automation/corelib/dist/TranslatableMessage'
1613
import { UserError } from '@sofie-automation/corelib/dist/error'
1714
import { StudioJobFunc } from '@sofie-automation/corelib/dist/worker/studio'
1815
import { QueueStudioJob } from '../worker/worker'
1916
import { profiler } from './profiler'
2017
import {
21-
OrganizationId,
2218
PeripheralDeviceId,
2319
RundownId,
2420
RundownPlaylistId,
2521
StudioId,
2622
UserActionsLogItemId,
27-
UserId,
2823
} from '@sofie-automation/corelib/dist/dataModel/Ids'
2924
import {
3025
checkAccessToPlaylist,
3126
checkAccessToRundown,
3227
VerifiedRundownForUserAction,
3328
VerifiedRundownPlaylistForUserAction,
3429
} from './lib'
35-
import { BasicAccessContext } from '../security/organization'
3630
import { UserActionsLog } from '../collections'
3731
import { executePeripheralDeviceFunctionWithCustomTimeout } from './peripheralDevice/executeFunction'
3832
import { LeveledLogMethodFixed } from '@sofie-automation/corelib/dist/logging'
33+
import { assertConnectionHasOneOfPermissions } from '../security/auth'
3934

4035
function rewrapError(methodName: string, e: any): ClientAPI.ClientResponseError {
4136
const userError = UserError.fromUnknown(e)
@@ -65,7 +60,7 @@ export namespace ServerClientAPI {
6560
eventTime,
6661
`worker.${jobName}`,
6762
jobArguments as any,
68-
async (_credentials, userActionMetadata) => {
63+
async (userActionMetadata) => {
6964
checkArgs()
7065

7166
const playlist = await checkAccessToPlaylist(context, playlistId)
@@ -92,7 +87,7 @@ export namespace ServerClientAPI {
9287
eventTime,
9388
`worker.${jobName}`,
9489
jobArguments as any,
95-
async (_credentials, userActionMetadata) => {
90+
async (userActionMetadata) => {
9691
checkArgs()
9792

9893
const rundown = await checkAccessToRundown(context, rundownId)
@@ -185,11 +180,11 @@ export namespace ServerClientAPI {
185180
eventTime: Time,
186181
methodName: string,
187182
methodArgs: Record<string, unknown>,
188-
fcn: (credentials: BasicAccessContext, userActionMetadata: UserActionMetadata) => Promise<TRes>
183+
fcn: (userActionMetadata: UserActionMetadata) => Promise<TRes>
189184
): Promise<ClientAPI.ClientResponse<TRes>> {
190185
// If we are in the test write auth check mode, then bypass all special logic to ensure errors dont get mangled
191186
if (isInTestWrite()) {
192-
const result = await fcn({ organizationId: null, userId: null }, {})
187+
const result = await fcn({})
193188
return ClientAPI.responseSuccess(result)
194189
}
195190

@@ -203,23 +198,21 @@ export namespace ServerClientAPI {
203198
// Called internally from server-side.
204199
// Just run and return right away:
205200
try {
206-
const result = await fcn({ organizationId: null, userId: null }, {})
201+
const result = await fcn({})
207202

208203
return ClientAPI.responseSuccess(result)
209204
} catch (e) {
210205
return rewrapError(methodName, e)
211206
}
212207
} else {
213-
const credentials = await getLoggedInCredentials(context)
214-
215208
// Start the db entry, but don't wait for it
216209
const actionId: UserActionsLogItemId = getRandomId()
217210
const pInitialInsert = UserActionsLog.insertAsync(
218211
literal<UserActionsLogItem>({
219212
_id: actionId,
220213
clientAddress: context.connection.clientAddress,
221-
organizationId: credentials.organizationId,
222-
userId: credentials.userId,
214+
organizationId: null,
215+
userId: null,
223216
context: userEvent,
224217
method: methodName,
225218
args: JSON.stringify(methodArgs),
@@ -233,7 +226,7 @@ export namespace ServerClientAPI {
233226

234227
const userActionMetadata: UserActionMetadata = {}
235228
try {
236-
const result = await fcn(credentials, userActionMetadata)
229+
const result = await fcn(userActionMetadata)
237230

238231
const completeTime = Date.now()
239232
pInitialInsert
@@ -325,14 +318,15 @@ export namespace ServerClientAPI {
325318
})
326319
}
327320

328-
const access = await PeripheralDeviceContentWriteAccess.executeFunction(methodContext, deviceId)
321+
// TODO - check this. This probably needs to be moved out of this method, with the client using more targetted methods
322+
assertConnectionHasOneOfPermissions(methodContext.connection, 'studio', 'configure', 'service')
329323

330324
await UserActionsLog.insertAsync(
331325
literal<UserActionsLogItem>({
332326
_id: actionId,
333327
clientAddress: methodContext.connection ? methodContext.connection.clientAddress : '',
334-
organizationId: access.organizationId,
335-
userId: access.userId,
328+
organizationId: null,
329+
userId: null,
336330
context: context,
337331
method: `${deviceId}: ${method}`,
338332
args: JSON.stringify(args),
@@ -395,7 +389,8 @@ export namespace ServerClientAPI {
395389
})
396390
}
397391

398-
await PeripheralDeviceContentWriteAccess.executeFunction(methodContext, deviceId)
392+
// TODO - check this. This probably needs to be moved out of this method, with the client using more targetted methods
393+
assertConnectionHasOneOfPermissions(methodContext.connection, 'studio', 'configure', 'service')
399394

400395
return executePeripheralDeviceFunctionWithCustomTimeout(deviceId, timeoutTime, {
401396
functionName,
@@ -407,17 +402,6 @@ export namespace ServerClientAPI {
407402
return Promise.reject(err)
408403
})
409404
}
410-
411-
async function getLoggedInCredentials(methodContext: MethodContext): Promise<BasicAccessContext> {
412-
let userId: UserId | null = null
413-
let organizationId: OrganizationId | null = null
414-
if (Settings.enableUserAccounts) {
415-
const cred = await resolveCredentials({ userId: methodContext.userId })
416-
if (cred.user) userId = cred.user._id
417-
organizationId = cred.organizationId
418-
}
419-
return { userId, organizationId }
420-
}
421405
}
422406

423407
class ServerClientAPIClass extends MethodContextAPI implements NewClientAPI {

meteor/server/security/organization.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,7 @@ import { allowAccessToOrganization } from './lib/security'
55
import { Credentials, ResolvedCredentials } from './lib/credentials'
66
import { Settings } from '../Settings'
77
import { isProtectedString } from '../lib/tempLib'
8-
import { OrganizationId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
9-
10-
export type BasicAccessContext = { organizationId: OrganizationId | null; userId: UserId | null }
11-
12-
export interface OrganizationContentAccess {
13-
userId: UserId | null
14-
organizationId: OrganizationId | null
15-
cred: ResolvedCredentials | Credentials
16-
}
8+
import { OrganizationId } from '@sofie-automation/corelib/dist/dataModel/Ids'
179

1810
export namespace OrganizationReadAccess {
1911
export async function organization(
Lines changed: 3 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import { Meteor } from 'meteor/meteor'
2-
import { check } from '../lib/check'
3-
import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice'
42
import { isProtectedString } from '../lib/tempLib'
53
import { logNotAllowed } from './lib/lib'
6-
import { MediaWorkFlow } from '@sofie-automation/shared-lib/dist/core/model/MediaWorkFlows'
74
import { MongoQueryKey } from '@sofie-automation/corelib/dist/mongo'
8-
import { Credentials, ResolvedCredentials, resolveCredentials } from './lib/credentials'
9-
import { allowAccessToPeripheralDevice, allowAccessToPeripheralDeviceContent } from './lib/security'
5+
import { Credentials, ResolvedCredentials } from './lib/credentials'
6+
import { allowAccessToPeripheralDevice } from './lib/security'
107
import { Settings } from '../Settings'
11-
import { triggerWriteAccess } from './lib/securityVerify'
12-
import { profiler } from '../api/profiler'
13-
import { StudioContentWriteAccess } from './studio'
14-
import { OrganizationId, PeripheralDeviceId, StudioId, UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
15-
import { PeripheralDevices } from '../collections'
8+
import { PeripheralDeviceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
169

1710
export namespace PeripheralDeviceReadAccess {
1811
/** Check for read access for a peripheral device */
@@ -36,123 +29,3 @@ export namespace PeripheralDeviceReadAccess {
3629
return true
3730
}
3831
}
39-
export interface MediaWorkFlowContentAccess extends PeripheralDeviceContentWriteAccess.ContentAccess {
40-
mediaWorkFlow: MediaWorkFlow
41-
}
42-
43-
export namespace PeripheralDeviceContentWriteAccess {
44-
export interface ContentAccess {
45-
userId: UserId | null
46-
organizationId: OrganizationId | null
47-
deviceId: PeripheralDeviceId
48-
device: PeripheralDevice
49-
cred: ResolvedCredentials | Credentials
50-
}
51-
52-
// These functions throws if access is not allowed.
53-
54-
/**
55-
* Check if a user is allowed to execute a PeripheralDevice function in a Studio
56-
*/
57-
export async function executeFunction(cred0: Credentials, deviceId: PeripheralDeviceId): Promise<ContentAccess> {
58-
triggerWriteAccess()
59-
const device = await PeripheralDevices.findOneAsync(deviceId)
60-
if (!device) throw new Meteor.Error(404, `PeripheralDevice "${deviceId}" not found`)
61-
62-
let studioId: StudioId
63-
if (device.studioId) {
64-
studioId = device.studioId
65-
} else if (device.parentDeviceId) {
66-
// Child devices aren't assigned to the studio themselves, instead look up the parent device and use it's studioId:
67-
const parentDevice = await PeripheralDevices.findOneAsync(device.parentDeviceId)
68-
if (!parentDevice)
69-
throw new Meteor.Error(
70-
404,
71-
`Parent PeripheralDevice "${device.parentDeviceId}" of "${deviceId}" not found!`
72-
)
73-
if (!parentDevice.studioId)
74-
throw new Meteor.Error(
75-
404,
76-
`Parent PeripheralDevice "${device.parentDeviceId}" of "${deviceId}" doesn't have any studioId set`
77-
)
78-
studioId = parentDevice.studioId
79-
} else {
80-
throw new Meteor.Error(404, `PeripheralDevice "${deviceId}" doesn't have any studioId set`)
81-
}
82-
83-
const access = await StudioContentWriteAccess.executeFunction(cred0, studioId)
84-
85-
const access2 = await allowAccessToPeripheralDeviceContent(access.cred, device)
86-
if (!access2.playout) throw new Meteor.Error(403, `Not allowed: ${access2.reason}`)
87-
88-
return {
89-
...access,
90-
deviceId: device._id,
91-
device,
92-
}
93-
}
94-
95-
/** Check for permission to modify a peripheralDevice */
96-
export async function peripheralDevice(cred0: Credentials, deviceId: PeripheralDeviceId): Promise<ContentAccess> {
97-
await backwardsCompatibilityfix(cred0, deviceId)
98-
return anyContent(cred0, deviceId)
99-
}
100-
101-
/** Return credentials if writing is allowed, throw otherwise */
102-
async function anyContent(cred0: Credentials, deviceId: PeripheralDeviceId): Promise<ContentAccess> {
103-
const span = profiler.startSpan('PeripheralDeviceContentWriteAccess.anyContent')
104-
triggerWriteAccess()
105-
check(deviceId, String)
106-
const device = await PeripheralDevices.findOneAsync(deviceId)
107-
if (!device) throw new Meteor.Error(404, `PeripheralDevice "${deviceId}" not found`)
108-
109-
// If the device has a parent, use that for access control:
110-
const parentDevice = device.parentDeviceId
111-
? await PeripheralDevices.findOneAsync(device.parentDeviceId)
112-
: device
113-
if (!parentDevice)
114-
throw new Meteor.Error(404, `PeripheralDevice parentDevice "${device.parentDeviceId}" not found`)
115-
116-
if (!Settings.enableUserAccounts) {
117-
// Note: this is kind of a hack to keep backwards compatibility..
118-
if (!device.parentDeviceId && parentDevice.token !== cred0.token) {
119-
throw new Meteor.Error(401, `Not allowed access to peripheralDevice`)
120-
}
121-
122-
span?.end()
123-
return {
124-
userId: null,
125-
organizationId: null,
126-
deviceId: deviceId,
127-
device: device,
128-
cred: cred0,
129-
}
130-
} else {
131-
if (!cred0.userId && parentDevice.token !== cred0.token) {
132-
throw new Meteor.Error(401, `Not allowed access to peripheralDevice`)
133-
}
134-
const cred = await resolveCredentials(cred0)
135-
const access = await allowAccessToPeripheralDeviceContent(cred, parentDevice)
136-
if (!access.update) throw new Meteor.Error(403, `Not allowed: ${access.reason}`)
137-
if (!access.document) throw new Meteor.Error(500, `Internal error: access.document not set`)
138-
139-
span?.end()
140-
return {
141-
userId: cred.user ? cred.user._id : null,
142-
organizationId: cred.organizationId,
143-
deviceId: deviceId,
144-
device: device,
145-
cred: cred,
146-
}
147-
}
148-
}
149-
}
150-
async function backwardsCompatibilityfix(cred0: Credentials, deviceId: PeripheralDeviceId) {
151-
if (!Settings.enableUserAccounts) {
152-
// Note: This is a temporary hack to keep backwards compatibility:
153-
const device = (await PeripheralDevices.findOneAsync(deviceId, { fields: { token: 1 } })) as
154-
| Pick<PeripheralDevice, '_id' | 'token'>
155-
| undefined
156-
if (device) cred0.token = device.token
157-
}
158-
}

0 commit comments

Comments
 (0)