Skip to content

Commit b24a551

Browse files
committed
wip: collection updates
1 parent 8383cd6 commit b24a551

File tree

12 files changed

+62
-479
lines changed

12 files changed

+62
-479
lines changed

meteor/server/api/rest/v1/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ClientAPI } from '@sofie-automation/meteor-lib/dist/api/client'
88
import { MethodContextAPI } from '../../methodContext'
99
import { logger } from '../../../logging'
1010
import { CURRENT_SYSTEM_VERSION } from '../../../migration/currentSystemVersion'
11-
import { Credentials } from '../../../security/lib/credentials'
1211
import { triggerWriteAccess } from '../../../security/lib/securityVerify'
1312
import { makeMeteorConnectionFromKoa } from '../koa'
1413
import { registerRoutes as registerBlueprintsRoutes } from './blueprints'
@@ -45,10 +44,6 @@ class APIContext implements ServerAPIContext {
4544
},
4645
}
4746
}
48-
49-
public getCredentials(): Credentials {
50-
return { userId: null }
51-
}
5247
}
5348

5449
export const koaRouter = new KoaRouter()

meteor/server/api/rest/v1/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { UserErrorMessage } from '@sofie-automation/corelib/dist/error'
22
import { Meteor } from 'meteor/meteor'
33
import { ClientAPI } from '@sofie-automation/meteor-lib/dist/api/client'
4-
import { Credentials } from '../../../security/lib/credentials'
54
import { MethodContextAPI } from '../../methodContext'
65

76
export type APIRegisterHook<T> = <Params, Body, Response>(
@@ -24,5 +23,4 @@ export interface APIFactory<T> {
2423

2524
export interface ServerAPIContext {
2625
getMethodContext(connection: Meteor.Connection): MethodContextAPI
27-
getCredentials(): Credentials
2826
}

meteor/server/collections/collection.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ import {
2323
} from '@sofie-automation/meteor-lib/dist/collections/lib'
2424

2525
export interface MongoAllowRules<DBInterface> {
26-
insert?: (userId: UserId | null, doc: DBInterface) => Promise<boolean> | boolean
26+
// insert?: (userId: UserId | null, doc: DBInterface) => Promise<boolean> | boolean
2727
update?: (
2828
userId: UserId | null,
2929
doc: DBInterface,
3030
fieldNames: FieldNames<DBInterface>,
3131
modifier: MongoModifier<DBInterface>
3232
) => Promise<boolean> | boolean
33-
remove?: (userId: UserId | null, doc: DBInterface) => Promise<boolean> | boolean
33+
// remove?: (userId: UserId | null, doc: DBInterface) => Promise<boolean> | boolean
3434
}
3535

3636
/**
@@ -107,15 +107,15 @@ function setupCollectionAllowRules<DBInterface extends { _id: ProtectedString<an
107107
args: MongoAllowRules<DBInterface> | false
108108
) {
109109
if (args) {
110-
const { insert: origInsert, update: origUpdate, remove: origRemove } = args
110+
const { /* insert: origInsert,*/ update: origUpdate /*remove: origRemove*/ } = args
111111

112112
const options: Parameters<Mongo.Collection<DBInterface>['allow']>[0] = {
113-
insert: origInsert ? (userId, doc) => waitForPromise(origInsert(protectString(userId), doc)) : () => false,
113+
insert: () => false,
114114
update: origUpdate
115115
? (userId, doc, fieldNames, modifier) =>
116116
waitForPromise(origUpdate(protectString(userId), doc, fieldNames as any, modifier))
117117
: () => false,
118-
remove: origRemove ? (userId, doc) => waitForPromise(origRemove(protectString(userId), doc)) : () => false,
118+
remove: () => false,
119119
}
120120

121121
collection.allow(options)

meteor/server/collections/index.ts

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,18 @@ import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyE
3232
import { createAsyncOnlyMongoCollection, createAsyncOnlyReadOnlyMongoCollection } from './collection'
3333
import { ObserveChangesForHash } from './lib'
3434
import { logger } from '../logging'
35-
import { resolveCredentials } from '../security/lib/credentials'
36-
import { logNotAllowed, allowOnlyFields, rejectFields } from '../security/lib/lib'
37-
import {
38-
allowAccessToCoreSystem,
39-
allowAccessToOrganization,
40-
allowAccessToShowStyleBase,
41-
allowAccessToStudio,
42-
} from '../security/lib/security'
35+
import { allowOnlyFields, rejectFields } from '../security/lib/lib'
4336
import type { DBNotificationObj } from '@sofie-automation/corelib/dist/dataModel/Notifications'
37+
import { checkUserIdHasOneOfPermissions } from '../security/auth'
4438

4539
export * from './bucket'
4640
export * from './packages-media'
4741
export * from './rundown'
4842

4943
export const Blueprints = createAsyncOnlyMongoCollection<Blueprint>(CollectionName.Blueprints, {
50-
update(_userId, doc, fields, _modifier) {
44+
update(userId, doc, fields, _modifier) {
45+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.Blueprints, 'configure')) return false
46+
5147
return allowOnlyFields(doc, fields, ['name', 'disableVersionChecks'])
5248
},
5349
})
@@ -57,9 +53,7 @@ registerIndex(Blueprints, {
5753

5854
export const CoreSystem = createAsyncOnlyMongoCollection<ICoreSystem>(CollectionName.CoreSystem, {
5955
async update(userId, doc, fields, _modifier) {
60-
const cred = await resolveCredentials({ userId: userId })
61-
const access = await allowAccessToCoreSystem(cred)
62-
if (!access.update) return logNotAllowed('CoreSystem', access.reason)
56+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.CoreSystem, 'configure')) return false
6357

6458
return allowOnlyFields(doc, fields, [
6559
'support',
@@ -119,8 +113,8 @@ registerIndex(Notifications, {
119113

120114
export const Organizations = createAsyncOnlyMongoCollection<DBOrganization>(CollectionName.Organizations, {
121115
async update(userId, doc, fields, _modifier) {
122-
const access = await allowAccessToOrganization({ userId: userId }, doc._id)
123-
if (!access.update) return logNotAllowed('Organization', access.reason)
116+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.Organizations, 'configure')) return false
117+
124118
return allowOnlyFields(doc, fields, ['userRoles'])
125119
},
126120
})
@@ -134,7 +128,9 @@ registerIndex(PeripheralDeviceCommands, {
134128
})
135129

136130
export const PeripheralDevices = createAsyncOnlyMongoCollection<PeripheralDevice>(CollectionName.PeripheralDevices, {
137-
update(_userId, doc, fields, _modifier) {
131+
update(userId, doc, fields, _modifier) {
132+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.PeripheralDevices, 'configure')) return false
133+
138134
return rejectFields(doc, fields, [
139135
'type',
140136
'parentDeviceId',
@@ -163,8 +159,8 @@ registerIndex(PeripheralDevices, {
163159

164160
export const RundownLayouts = createAsyncOnlyMongoCollection<RundownLayoutBase>(CollectionName.RundownLayouts, {
165161
async update(userId, doc, fields) {
166-
const access = await allowAccessToShowStyleBase({ userId: userId }, doc.showStyleBaseId)
167-
if (!access.update) return logNotAllowed('ShowStyleBase', access.reason)
162+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.RundownLayouts, 'configure')) return false
163+
168164
return rejectFields(doc, fields, ['_id', 'showStyleBaseId'])
169165
},
170166
})
@@ -180,8 +176,8 @@ registerIndex(RundownLayouts, {
180176

181177
export const ShowStyleBases = createAsyncOnlyMongoCollection<DBShowStyleBase>(CollectionName.ShowStyleBases, {
182178
async update(userId, doc, fields) {
183-
const access = await allowAccessToShowStyleBase({ userId: userId }, doc._id)
184-
if (!access.update) return logNotAllowed('ShowStyleBase', access.reason)
179+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.ShowStyleBases, 'configure')) return false
180+
185181
return rejectFields(doc, fields, ['_id'])
186182
},
187183
})
@@ -191,8 +187,7 @@ registerIndex(ShowStyleBases, {
191187

192188
export const ShowStyleVariants = createAsyncOnlyMongoCollection<DBShowStyleVariant>(CollectionName.ShowStyleVariants, {
193189
async update(userId, doc, fields) {
194-
const access = await allowAccessToShowStyleBase({ userId: userId }, doc.showStyleBaseId)
195-
if (!access.update) return logNotAllowed('ShowStyleBase', access.reason)
190+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.ShowStyleVariants, 'configure')) return false
196191

197192
return rejectFields(doc, fields, ['showStyleBaseId'])
198193
},
@@ -203,7 +198,9 @@ registerIndex(ShowStyleVariants, {
203198
})
204199

205200
export const Snapshots = createAsyncOnlyMongoCollection<SnapshotItem>(CollectionName.Snapshots, {
206-
update(_userId, doc, fields, _modifier) {
201+
update(userId, doc, fields, _modifier) {
202+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.Snapshots, 'configure')) return false
203+
207204
return allowOnlyFields(doc, fields, ['comment'])
208205
},
209206
})
@@ -216,8 +213,8 @@ registerIndex(Snapshots, {
216213

217214
export const Studios = createAsyncOnlyMongoCollection<DBStudio>(CollectionName.Studios, {
218215
async update(userId, doc, fields, _modifier) {
219-
const access = await allowAccessToStudio({ userId: userId }, doc._id)
220-
if (!access.update) return logNotAllowed('Studio', access.reason)
216+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.Studios, 'configure')) return false
217+
221218
return rejectFields(doc, fields, ['_id'])
222219
},
223220
})
@@ -245,17 +242,9 @@ export const TranslationsBundles = createAsyncOnlyMongoCollection<TranslationsBu
245242

246243
export const TriggeredActions = createAsyncOnlyMongoCollection<DBTriggeredActions>(CollectionName.TriggeredActions, {
247244
async update(userId, doc, fields) {
248-
const cred = await resolveCredentials({ userId: userId })
249-
250-
if (doc.showStyleBaseId) {
251-
const access = await allowAccessToShowStyleBase(cred, doc.showStyleBaseId)
252-
if (!access.update) return logNotAllowed('ShowStyleBase', access.reason)
253-
return rejectFields(doc, fields, ['_id'])
254-
} else {
255-
const access = await allowAccessToCoreSystem(cred)
256-
if (!access.update) return logNotAllowed('CoreSystem', access.reason)
257-
return rejectFields(doc, fields, ['_id'])
258-
}
245+
if (!checkUserIdHasOneOfPermissions(userId, CollectionName.TriggeredActions, 'configure')) return false
246+
247+
return rejectFields(doc, fields, ['_id'])
259248
},
260249
})
261250
registerIndex(TriggeredActions, {

meteor/server/publications/lib.ts

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
import { Meteor, Subscription } from 'meteor/meteor'
22
import { AllPubSubCollections, AllPubSubTypes } from '@sofie-automation/meteor-lib/dist/api/pubsub'
33
import { extractFunctionSignature } from '../lib'
4-
import { MongoQuery } from '@sofie-automation/corelib/dist/mongo'
5-
import { ResolvedCredentials, resolveCredentials } from '../security/lib/credentials'
6-
import { Settings } from '../Settings'
7-
import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice'
84
import { MongoCursor } from '@sofie-automation/meteor-lib/dist/collections/lib'
9-
import {
10-
OrganizationId,
11-
PeripheralDeviceId,
12-
ShowStyleBaseId,
13-
UserId,
14-
} from '@sofie-automation/corelib/dist/dataModel/Ids'
5+
import { UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
156
import { protectStringObject } from '../lib/tempLib'
167
import { waitForPromise } from '../lib/lib'
17-
import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase'
18-
import { PeripheralDevices, ShowStyleBases } from '../collections'
198
import { MetricsGauge } from '@sofie-automation/corelib/dist/prometheus'
209

2110
export const MeteorPublicationSignatures: { [key: string]: string[] } = {}
@@ -80,87 +69,3 @@ export function meteorPublish<K extends keyof AllPubSubTypes>(
8069
): void {
8170
meteorPublishUnsafe(name, callback)
8271
}
83-
84-
export namespace AutoFillSelector {
85-
/** Autofill an empty selector {} with organizationId of the current user */
86-
export async function organizationId<T extends { organizationId?: OrganizationId | null | undefined }>(
87-
userId: UserId | null,
88-
selector: MongoQuery<T>,
89-
token: string | undefined
90-
): Promise<{
91-
cred: ResolvedCredentials | null
92-
selector: MongoQuery<T>
93-
}> {
94-
if (!selector) throw new Meteor.Error(400, 'selector argument missing')
95-
96-
let cred: ResolvedCredentials | null = null
97-
if (Settings.enableUserAccounts) {
98-
if (!selector.organizationId) {
99-
cred = await resolveCredentials({ userId: userId, token })
100-
if (cred.organizationId) selector.organizationId = cred.organizationId as any
101-
// TODO - should this block all access if cred.organizationId is not set
102-
}
103-
}
104-
return { cred, selector }
105-
}
106-
/** Autofill an empty selector {} with deviceId of the current user's peripheralDevices */
107-
export async function deviceId<T extends { deviceId: PeripheralDeviceId }>(
108-
userId: UserId | null,
109-
selector: MongoQuery<T>,
110-
token: string | undefined
111-
): Promise<{
112-
cred: ResolvedCredentials | null
113-
selector: MongoQuery<T>
114-
}> {
115-
if (!selector) throw new Meteor.Error(400, 'selector argument missing')
116-
117-
let cred: ResolvedCredentials | null = null
118-
if (Settings.enableUserAccounts) {
119-
if (!selector.deviceId) {
120-
cred = await resolveCredentials({ userId: userId, token })
121-
if (cred.organizationId) {
122-
const devices = (await PeripheralDevices.findFetchAsync(
123-
{
124-
organizationId: cred.organizationId,
125-
},
126-
{ projection: { _id: 1 } }
127-
)) as Array<Pick<PeripheralDevice, '_id'>>
128-
129-
selector.deviceId = { $in: devices.map((d) => d._id) } as any
130-
}
131-
// TODO - should this block all access if cred.organizationId is not set
132-
}
133-
}
134-
return { cred, selector }
135-
}
136-
/** Autofill an empty selector {} with showStyleBaseId of the current user's showStyleBases */
137-
export async function showStyleBaseId<T extends { showStyleBaseId?: ShowStyleBaseId | null }>(
138-
userId: UserId | null,
139-
selector: MongoQuery<T>,
140-
token: string | undefined
141-
): Promise<{
142-
cred: ResolvedCredentials | null
143-
selector: MongoQuery<T>
144-
}> {
145-
if (!selector) throw new Meteor.Error(400, 'selector argument missing')
146-
147-
let cred: ResolvedCredentials | null = null
148-
if (Settings.enableUserAccounts) {
149-
if (!selector.showStyleBaseId) {
150-
cred = await resolveCredentials({ userId: userId, token })
151-
if (cred.organizationId) {
152-
const showStyleBases = (await ShowStyleBases.findFetchAsync(
153-
{
154-
organizationId: cred.organizationId,
155-
},
156-
{ projection: { _id: 1 } }
157-
)) as Array<Pick<DBShowStyleBase, '_id'>>
158-
159-
selector.showStyleBaseId = { $in: showStyleBases.map((d) => d._id) } as any
160-
}
161-
// TODO - should this block all access if cred.organizationId is not set
162-
}
163-
}
164-
return { cred, selector }
165-
}
166-
}

meteor/server/security/_security.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

meteor/server/security/auth.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { Settings } from '../Settings'
77
import { Meteor } from 'meteor/meteor'
88
import Koa from 'koa'
99
import { triggerWriteAccess } from './lib/securityVerify'
10+
import { UserId } from '@sofie-automation/corelib/dist/dataModel/Ids'
11+
import { unprotectString } from '../lib/tempLib'
12+
import { logger } from '../logging'
13+
import { CollectionName } from '@sofie-automation/corelib/dist/dataModel/Collections'
1014

1115
export type RequestCredentials = Meteor.Connection | Koa.ParameterizedContext
1216

@@ -61,3 +65,24 @@ export function assertConnectionHasOneOfPermissions(
6165
// Nothing matched
6266
throw new Meteor.Error(403, 'Not authorized')
6367
}
68+
69+
export function checkUserIdHasOneOfPermissions(
70+
userId: UserId | null,
71+
collectionName: CollectionName,
72+
...allowedPermissions: Array<keyof UserPermissions>
73+
): boolean {
74+
if (allowedPermissions.length === 0) throw new Meteor.Error(403, 'No permissions specified')
75+
76+
triggerWriteAccess()
77+
78+
if (!userId) throw new Meteor.Error(403, 'UserId is null')
79+
80+
const permissions = parseUserPermissions(unprotectString(userId))
81+
for (const permission of allowedPermissions) {
82+
if (permissions[permission]) return true
83+
}
84+
85+
// Nothing matched
86+
logger.warn(`Not allowed access to ${collectionName}`)
87+
return false
88+
}

0 commit comments

Comments
 (0)