diff --git a/docs/api/sync.md b/docs/api/sync.md
index 01b0aa0..eeef21b 100644
--- a/docs/api/sync.md
+++ b/docs/api/sync.md
@@ -44,6 +44,7 @@ The integrity field of a action may have got one of the following values:
- an empty string when no user authentication is required/ for app logic actions (e.g. incrementing the used time)
- the string ``device`` in case of parent actions if a parent is assigned to the device and asking for the password was disabled
- ``sha512(sequence number as string with the base 10 + the device id as string + the hash of the user password using the second salt as string + the encoded action as string)`` for parent and child actions
+- the string ``childDevice`` in case the child wants to add limits for itself using parent actions; this feature must be enabled for the child and this allows only some actions with some parameters
In case of a invalid integrity value, the action is ignored and the client is told to do a full sync
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts b/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
index 870bce1..7acfd68 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/addcategoryapps.ts
@@ -18,11 +18,13 @@
import * as Sequelize from 'sequelize'
import { AddCategoryAppsAction } from '../../../../action'
import { CategoryAppAttributes } from '../../../../database/categoryapp'
+import { getCategoryWithParentCategories } from '../../../../util/category'
import { Cache } from '../cache'
-export async function dispatchAddCategoryApps ({ action, cache }: {
+export async function dispatchAddCategoryApps ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: AddCategoryAppsAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
const categoryEntryUnsafe = await cache.database.category.findOne({
where: {
@@ -39,14 +41,25 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
const { childId } = categoryEntryUnsafe
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (childId !== fromChildSelfLimitAddChildUserId) {
+ throw new Error('can not add apps to other users')
+ }
+ }
+
const categoriesOfSameChild = await cache.database.category.findAll({
where: {
familyId: cache.familyId,
childId
},
- attributes: ['categoryId'],
+ attributes: ['categoryId', 'parentCategoryId'],
transaction: cache.transaction
- }).map((item) => ({ categoryId: item.categoryId }))
+ }).map((item) => ({
+ categoryId: item.categoryId,
+ parentCategoryId: item.parentCategoryId
+ }))
+
+ const userCategoryIds = categoriesOfSameChild.map((item) => item.categoryId)
const oldCategories = await cache.database.categoryApp.findAll({
attributes: [ 'categoryId' ],
@@ -54,7 +67,7 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
where: {
familyId: cache.familyId,
categoryId: {
- [Sequelize.Op.in]: categoriesOfSameChild.map((item) => item.categoryId)
+ [Sequelize.Op.in]: userCategoryIds
},
packageName: {
[Sequelize.Op.in]: action.packageNames
@@ -63,6 +76,68 @@ export async function dispatchAddCategoryApps ({ action, cache }: {
transaction: cache.transaction
}).map((item) => item.categoryId)
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ const parentCategoriesOfTargetCategory = getCategoryWithParentCategories(categoriesOfSameChild, action.categoryId)
+ const userEntryUnsafe = await cache.database.user.findOne({
+ attributes: [ 'categoryForNotAssignedApps' ],
+ where: {
+ familyId: cache.familyId,
+ userId: fromChildSelfLimitAddChildUserId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!userEntryUnsafe) {
+ throw new Error('illegal state')
+ }
+
+ const userEntry = { categoryForNotAssignedApps: userEntryUnsafe.categoryForNotAssignedApps }
+ const validatedDefaultCategoryId = categoriesOfSameChild.find((item) => item.categoryId === userEntry.categoryForNotAssignedApps)?.categoryId
+ const allowUnassignedElements = validatedDefaultCategoryId !== undefined &&
+ parentCategoriesOfTargetCategory.indexOf(validatedDefaultCategoryId) !== -1
+
+ const assertCanAddApp = async (packageName: string, isApp: boolean) => {
+ const categoryAppEntryUnsafe = await cache.database.categoryApp.findOne({
+ attributes: [ 'categoryId' ],
+ where: {
+ familyId: cache.familyId,
+ categoryId: {
+ [Sequelize.Op.in]: userCategoryIds
+ },
+ packageName: packageName
+ },
+ transaction: cache.transaction
+ })
+
+ const categoryAppEntry = categoryAppEntryUnsafe ? { categoryId: categoryAppEntryUnsafe.categoryId } : null
+
+ if (categoryAppEntry === null) {
+ if ((isApp && allowUnassignedElements) || (!isApp)) {
+ // allow
+ } else {
+ throw new Error('can not assign apps without category as child')
+ }
+ } else {
+ if (parentCategoriesOfTargetCategory.indexOf(categoryAppEntry.categoryId) !== -1) {
+ // allow
+ } else {
+ throw new Error('can not add app which is not contained in the parent category')
+ }
+ }
+ }
+
+ for (let i = 0; i < action.packageNames.length; i++) {
+ const packageName = action.packageNames[i]
+
+ if (packageName.indexOf(':') !== -1) {
+ await assertCanAddApp(packageName.substring(0, packageName.indexOf(':')), true)
+ await assertCanAddApp(packageName, false)
+ } else {
+ await assertCanAddApp(packageName, true)
+ }
+ }
+ }
+
if (oldCategories.length > 0) {
await cache.database.categoryApp.destroy({
where: {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts
index b6f8aa9..46cd92f 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/createcategory.ts
@@ -19,10 +19,17 @@ import { CreateCategoryAction } from '../../../../action'
import { generateVersionId } from '../../../../util/token'
import { Cache } from '../cache'
-export async function dispatchCreateCategory ({ action, cache }: {
+export async function dispatchCreateCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: CreateCategoryAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (fromChildSelfLimitAddChildUserId !== action.childId) {
+ throw new Error('can not create categories for other child users')
+ }
+ }
+
// check that the child exists
const childEntry = await cache.database.user.findOne({
where: {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts b/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
index a20beef..bf64b99 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/createtimelimitrule.ts
@@ -18,16 +18,30 @@
import { CreateTimeLimitRuleAction } from '../../../../action'
import { Cache } from '../cache'
-export async function dispatchCreateTimeLimitRule ({ action, cache }: {
+export async function dispatchCreateTimeLimitRule ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: CreateTimeLimitRuleAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
- const doesCategoryExist = await cache.doesCategoryExist(action.rule.categoryId)
+ const categoryEntryUnsafe = await cache.database.category.findOne({
+ where: {
+ familyId: cache.familyId,
+ categoryId: action.rule.categoryId
+ },
+ transaction: cache.transaction,
+ attributes: ['childId']
+ })
- if (!doesCategoryExist) {
+ if (!categoryEntryUnsafe) {
throw new Error('invalid category id for new rule')
}
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
+ throw new Error('can not add rules for other users')
+ }
+ }
+
await cache.database.timelimitRule.create({
familyId: cache.familyId,
ruleId: action.rule.ruleId,
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/index.ts b/src/function/sync/apply-actions/dispatch-parent-action/index.ts
index 51202c9..886e33e 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/index.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/index.ts
@@ -102,94 +102,99 @@ import { dispatchUpdateTimelimitRule } from './updatetimelimitrule'
import { dispatchUpdateUserFlagsAction } from './updateuserflags'
import { dispatchUpdateUserLimitLoginCategoryAction } from './updateuserlimitlogincategory'
-export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId }: {
+export const dispatchParentAction = async ({ action, cache, parentUserId, sourceDeviceId, fromChildSelfLimitAddChildUserId }: {
action: ParentAction
cache: Cache
parentUserId: string
sourceDeviceId: string | null
+ fromChildSelfLimitAddChildUserId: string | null
}) => {
if (action instanceof AddCategoryAppsAction) {
- await dispatchAddCategoryApps({ action, cache })
- } else if (action instanceof AddUserAction) {
- await dispatchAddUser({ action, cache })
- } else if (action instanceof RemoveCategoryAppsAction) {
- await dispatchRemoveCategoryApps({ action, cache })
+ return dispatchAddCategoryApps({ action, cache, fromChildSelfLimitAddChildUserId })
} else if (action instanceof CreateCategoryAction) {
- await dispatchCreateCategory({ action, cache })
+ return dispatchCreateCategory({ action, cache, fromChildSelfLimitAddChildUserId })
} else if (action instanceof CreateTimeLimitRuleAction) {
- await dispatchCreateTimeLimitRule({ action, cache })
- } else if (action instanceof DeleteCategoryAction) {
- await dispatchDeleteCategory({ action, cache })
- } else if (action instanceof UpdateCategoryTitleAction) {
- await dispatchUpdateCategoryTitle({ action, cache })
- } else if (action instanceof SetCategoryExtraTimeAction) {
- await dispatchSetCategoryExtraTime({ action, cache })
- } else if (action instanceof SetCategoryForUnassignedAppsAction) {
- await dispatchSetCategoryForUnassignedApps({ action, cache })
- } else if (action instanceof SetChildPasswordAction) {
- await dispatchSetChildPassword({ action, cache })
- } else if (action instanceof SetConsiderRebootManipulationAction) {
- await dispatchSetConsiderRebootManipulation({ action, cache })
- } else if (action instanceof SetDeviceDefaultUserAction) {
- await dispatchSetDeviceDefaultUser({ action, cache })
- } else if (action instanceof SetDeviceDefaultUserTimeoutAction) {
- await dispatchSetDeviceDefaultUserTimeout({ action, cache })
- } else if (action instanceof SetDeviceUserAction) {
- await dispatchSetDeviceUser({ action, cache })
- } else if (action instanceof SetKeepSignedInAction) {
- await dispatchSetKeepSignedIn({ action, cache, parentUserId })
- } else if (action instanceof SetParentCategoryAction) {
- await dispatchSetParentCategory({ action, cache })
- } else if (action instanceof SetRelaxPrimaryDeviceAction) {
- await dispatchSetRelaxPrimaryDevice({ action, cache })
- } else if (action instanceof SetSendDeviceConnected) {
- await dispatchSetSendDeviceConnected({ action, cache, sourceDeviceId })
- } else if (action instanceof SetUserDisableLimitsUntilAction) {
- await dispatchUserSetDisableLimitsUntil({ action, cache })
- } else if (action instanceof SetUserTimezoneAction) {
- await dispatchSetUserTimezone({ action, cache })
- } else if (action instanceof UpdateCategoryBatteryLimitAction) {
- await dispatchUpdateCategoryBatteryLimit({ action, cache })
+ return dispatchCreateTimeLimitRule({ action, cache, fromChildSelfLimitAddChildUserId })
} else if (action instanceof UpdateCategoryBlockAllNotificationsAction) {
- await dispatchUpdateCategoryBlockAllNotifications({ action, cache })
- } else if (action instanceof UpdateCategoryBlockedTimesAction) {
- await dispatchUpdateCategoryBlockedTimes({ action, cache })
- } else if (action instanceof UpdateCategorySortingAction) {
- await dispatchUpdateCategorySorting({ action, cache })
- } else if (action instanceof IncrementCategoryExtraTimeAction) {
- await dispatchIncrementCategoryExtraTime({ action, cache })
+ return dispatchUpdateCategoryBlockAllNotifications({ action, cache, fromChildSelfLimitAddChildUserId })
+ } else if (action instanceof SetParentCategoryAction) {
+ return dispatchSetParentCategory({ action, cache, fromChildSelfLimitAddChildUserId })
} else if (action instanceof UpdateCategoryTemporarilyBlockedAction) {
- await dispatchUpdateCategoryTemporarilyBlocked({ action, cache })
- } else if (action instanceof DeleteTimeLimitRuleAction) {
- await dispatchDeleteTimeLimitRule({ action, cache })
- } else if (action instanceof UpdateDeviceNameAction) {
- await dispatchUpdateDeviceName({ action, cache })
- } else if (action instanceof UpdateEnableActivityLevelBlockingAction) {
- await dispatchUpdateEnableActivityLevelBlocking({ action, cache })
- } else if (action instanceof UpdateNetworkTimeVerificationAction) {
- await dispatchUpdateNetworkTimeVerification({ action, cache })
- } else if (action instanceof UpdateParentNotificationFlagsAction) {
- await dispatchUpdateParentNotificationFlags({ action, cache })
- } else if (action instanceof UpdateTimelimitRuleAction) {
- await dispatchUpdateTimelimitRule({ action, cache })
- } else if (action instanceof RemoveUserAction) {
- await dispatchRemoveUser({ action, cache, parentUserId })
- } else if (action instanceof RenameChildAction) {
- await dispatchRenameChild({ action, cache })
- } else if (action instanceof ChangeParentPasswordAction) {
- await dispatchChangeParentPassword({ action, cache })
- } else if (action instanceof IgnoreManipulationAction) {
- await dispatchIgnoreManipulation({ action, cache })
- } else if (action instanceof UpdateCategoryTimeWarningsAction) {
- await dispatchUpdateCategoryTimeWarnings({ action, cache })
- } else if (action instanceof ResetParentBlockedTimesAction) {
- await dispatchResetParentBlockedTimes({ action, cache })
- } else if (action instanceof UpdateParentBlockedTimesAction) {
- await dispatchUpdateParentBlockedTimes({ action, cache, parentUserId })
- } else if (action instanceof UpdateUserFlagsAction) {
- await dispatchUpdateUserFlagsAction({ action, cache })
- } else if (action instanceof UpdateUserLimitLoginCategory) {
- await dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
+ return dispatchUpdateCategoryTemporarilyBlocked({ action, cache, fromChildSelfLimitAddChildUserId })
+ }
+
+ if (fromChildSelfLimitAddChildUserId === null) {
+ if (action instanceof AddUserAction) {
+ return dispatchAddUser({ action, cache })
+ } else if (action instanceof RemoveCategoryAppsAction) {
+ return dispatchRemoveCategoryApps({ action, cache })
+ } else if (action instanceof DeleteCategoryAction) {
+ return dispatchDeleteCategory({ action, cache })
+ } else if (action instanceof UpdateCategoryTitleAction) {
+ return dispatchUpdateCategoryTitle({ action, cache })
+ } else if (action instanceof SetCategoryExtraTimeAction) {
+ return dispatchSetCategoryExtraTime({ action, cache })
+ } else if (action instanceof SetCategoryForUnassignedAppsAction) {
+ return dispatchSetCategoryForUnassignedApps({ action, cache })
+ } else if (action instanceof SetChildPasswordAction) {
+ return dispatchSetChildPassword({ action, cache })
+ } else if (action instanceof SetConsiderRebootManipulationAction) {
+ return dispatchSetConsiderRebootManipulation({ action, cache })
+ } else if (action instanceof SetDeviceDefaultUserAction) {
+ return dispatchSetDeviceDefaultUser({ action, cache })
+ } else if (action instanceof SetDeviceDefaultUserTimeoutAction) {
+ return dispatchSetDeviceDefaultUserTimeout({ action, cache })
+ } else if (action instanceof SetDeviceUserAction) {
+ return dispatchSetDeviceUser({ action, cache })
+ } else if (action instanceof SetKeepSignedInAction) {
+ return dispatchSetKeepSignedIn({ action, cache, parentUserId })
+ } else if (action instanceof SetRelaxPrimaryDeviceAction) {
+ return dispatchSetRelaxPrimaryDevice({ action, cache })
+ } else if (action instanceof SetSendDeviceConnected) {
+ return dispatchSetSendDeviceConnected({ action, cache, sourceDeviceId })
+ } else if (action instanceof SetUserDisableLimitsUntilAction) {
+ return dispatchUserSetDisableLimitsUntil({ action, cache })
+ } else if (action instanceof SetUserTimezoneAction) {
+ return dispatchSetUserTimezone({ action, cache })
+ } else if (action instanceof UpdateCategoryBatteryLimitAction) {
+ return dispatchUpdateCategoryBatteryLimit({ action, cache })
+ } else if (action instanceof UpdateCategoryBlockedTimesAction) {
+ return dispatchUpdateCategoryBlockedTimes({ action, cache })
+ } else if (action instanceof UpdateCategorySortingAction) {
+ return dispatchUpdateCategorySorting({ action, cache })
+ } else if (action instanceof IncrementCategoryExtraTimeAction) {
+ return dispatchIncrementCategoryExtraTime({ action, cache })
+ } else if (action instanceof DeleteTimeLimitRuleAction) {
+ return dispatchDeleteTimeLimitRule({ action, cache })
+ } else if (action instanceof UpdateDeviceNameAction) {
+ return dispatchUpdateDeviceName({ action, cache })
+ } else if (action instanceof UpdateEnableActivityLevelBlockingAction) {
+ return dispatchUpdateEnableActivityLevelBlocking({ action, cache })
+ } else if (action instanceof UpdateNetworkTimeVerificationAction) {
+ return dispatchUpdateNetworkTimeVerification({ action, cache })
+ } else if (action instanceof UpdateParentNotificationFlagsAction) {
+ return dispatchUpdateParentNotificationFlags({ action, cache })
+ } else if (action instanceof UpdateTimelimitRuleAction) {
+ return dispatchUpdateTimelimitRule({ action, cache })
+ } else if (action instanceof RemoveUserAction) {
+ return dispatchRemoveUser({ action, cache, parentUserId })
+ } else if (action instanceof RenameChildAction) {
+ return dispatchRenameChild({ action, cache })
+ } else if (action instanceof ChangeParentPasswordAction) {
+ return dispatchChangeParentPassword({ action, cache })
+ } else if (action instanceof IgnoreManipulationAction) {
+ return dispatchIgnoreManipulation({ action, cache })
+ } else if (action instanceof UpdateCategoryTimeWarningsAction) {
+ return dispatchUpdateCategoryTimeWarnings({ action, cache })
+ } else if (action instanceof ResetParentBlockedTimesAction) {
+ return dispatchResetParentBlockedTimes({ action, cache })
+ } else if (action instanceof UpdateParentBlockedTimesAction) {
+ return dispatchUpdateParentBlockedTimes({ action, cache, parentUserId })
+ } else if (action instanceof UpdateUserFlagsAction) {
+ return dispatchUpdateUserFlagsAction({ action, cache })
+ } else if (action instanceof UpdateUserLimitLoginCategory) {
+ return dispatchUpdateUserLimitLoginCategoryAction({ action, cache, parentUserId })
+ }
} else {
throw new Error('unsupported action type')
}
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts b/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
index 5ad807f..b1050bb 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/setparentcategory.ts
@@ -16,11 +16,13 @@
*/
import { SetParentCategoryAction } from '../../../../action'
+import { getCategoryWithParentCategories } from '../../../../util/category'
import { Cache } from '../cache'
-export async function dispatchSetParentCategory ({ action, cache }: {
+export async function dispatchSetParentCategory ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: SetParentCategoryAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
const categoryEntry = await cache.database.category.findOne({
where: {
@@ -34,6 +36,12 @@ export async function dispatchSetParentCategory ({ action, cache }: {
throw new Error('tried to set parent category of non existent category')
}
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (categoryEntry.childId !== fromChildSelfLimitAddChildUserId) {
+ throw new Error('can not set parent category for other user')
+ }
+ }
+
if (action.parentCategory !== '') {
const categoriesByUserId = (await cache.database.category.findAll({
where: {
@@ -74,6 +82,16 @@ export async function dispatchSetParentCategory ({ action, cache }: {
if (childCategoryIds.has(action.parentCategory) || action.parentCategory === action.categoryId) {
throw new Error('can not set a category as parent which is a child of the category')
}
+
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ const ownParentCategory = categoriesByUserId.find((item) => item.categoryId === categoryEntry.parentCategoryId)
+ const enableDueToLimitAddingWhenChild = ownParentCategory === undefined ||
+ getCategoryWithParentCategories(categoriesByUserId, action.parentCategory).indexOf(ownParentCategory.categoryId) !== -1
+
+ if (!enableDueToLimitAddingWhenChild) {
+ throw new Error('can not change parent categories in a way which reduces limits')
+ }
+ }
}
await cache.database.category.update({
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
index 542dd41..a67959c 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategoryblockallnotifications.ts
@@ -1,6 +1,6 @@
/*
* server component for the TimeLimit App
- * Copyright (C) 2019 Jonas Lochmann
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -18,10 +18,34 @@
import { UpdateCategoryBlockAllNotificationsAction } from '../../../../action'
import { Cache } from '../cache'
-export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache }: {
+export async function dispatchUpdateCategoryBlockAllNotifications ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: UpdateCategoryBlockAllNotificationsAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
+ const categoryEntryUnsafe = await cache.database.category.findOne({
+ where: {
+ familyId: cache.familyId,
+ categoryId: action.categoryId
+ },
+ transaction: cache.transaction,
+ attributes: ['childId']
+ })
+
+ if (!categoryEntryUnsafe) {
+ throw new Error('invalid category id for updating notification blocking')
+ }
+
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (fromChildSelfLimitAddChildUserId !== categoryEntryUnsafe.childId) {
+ throw new Error('can not add rules for other users')
+ }
+
+ if (!action.blocked) {
+ throw new Error('can not disable filter as child')
+ }
+ }
+
const [affectedRows] = await cache.database.category.update({
blockAllNotifications: action.blocked
}, {
diff --git a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
index 070dda0..ffe2f04 100644
--- a/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
+++ b/src/function/sync/apply-actions/dispatch-parent-action/updatecategorytemporarilyblocked.ts
@@ -18,9 +18,10 @@
import { UpdateCategoryTemporarilyBlockedAction } from '../../../../action'
import { Cache } from '../cache'
-export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache }: {
+export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache, fromChildSelfLimitAddChildUserId }: {
action: UpdateCategoryTemporarilyBlockedAction
cache: Cache
+ fromChildSelfLimitAddChildUserId: string | null
}) {
if (action.blocked === true) {
if (!cache.hasFullVersion) {
@@ -28,6 +29,41 @@ export async function dispatchUpdateCategoryTemporarilyBlocked ({ action, cache
}
}
+ const categoryEntryUnsafe = await cache.database.category.findOne({
+ where: {
+ familyId: cache.familyId,
+ categoryId: action.categoryId
+ },
+ transaction: cache.transaction,
+ attributes: ['childId', 'temporarilyBlocked', 'temporarilyBlockedEndTime']
+ })
+
+ if (!categoryEntryUnsafe) {
+ throw new Error('invalid category id for updating temporarily blocking')
+ }
+
+ const categoryEntry = {
+ childId: categoryEntryUnsafe.childId,
+ temporarilyBlocked: categoryEntryUnsafe.temporarilyBlocked,
+ temporarilyBlockedEndTime: categoryEntryUnsafe.temporarilyBlockedEndTime
+ }
+
+ if (fromChildSelfLimitAddChildUserId !== null) {
+ if (fromChildSelfLimitAddChildUserId !== categoryEntry.childId) {
+ throw new Error('can not update temporarily blocking as child for other users')
+ }
+
+ if (action.endTime === undefined || !action.blocked) {
+ throw new Error('the child may only enable a temporarily blocking')
+ }
+
+ if (categoryEntry.temporarilyBlocked) {
+ if (action.endTime < categoryEntry.temporarilyBlockedEndTime || categoryEntry.temporarilyBlockedEndTime === 0) {
+ throw new Error('the child may not reduce the temporarily blocking')
+ }
+ }
+ }
+
const [affectedRows] = await cache.database.category.update({
temporarilyBlocked: action.blocked,
temporarilyBlockedEndTime: action.blocked ? (action.endTime ?? 0) : 0
diff --git a/src/function/sync/apply-actions/index.ts b/src/function/sync/apply-actions/index.ts
index 30683ae..16d812f 100644
--- a/src/function/sync/apply-actions/index.ts
+++ b/src/function/sync/apply-actions/index.ts
@@ -22,6 +22,7 @@ import { ClientPushChangesRequest } from '../../../api/schema'
import { isSerializedAppLogicAction, isSerializedChildAction, isSerializedParentAction } from '../../../api/validator'
import { VisibleConnectedDevicesManager } from '../../../connected-devices'
import { Database } from '../../../database'
+import { UserFlags } from '../../../model/userflags'
import { EventHandler } from '../../../monitoring/eventhandler'
import { WebsocketApi } from '../../../websocket'
import { notifyClientsAboutChanges } from '../../websocket'
@@ -106,6 +107,8 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
// update the next sequence number
nextSequenceNumber = action.sequenceNumber + 1
+ let isChildLimitAdding = false
+
if (action.type === 'parent') {
if (action.integrity === 'device') {
const deviceEntryUnsafe2 = await cache.database.device.findOne({
@@ -125,6 +128,9 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
// this ensures that the parent exists
await cache.getSecondPasswordHashOfParent(action.userId)
+ } else if (action.integrity === 'childDevice') {
+ // will be checked later
+ isChildLimitAdding = true
} else {
const parentSecondHash = await cache.getSecondPasswordHashOfParent(action.userId)
@@ -191,19 +197,68 @@ export const applyActionsFromDevice = async ({ database, request, websocket, con
throw new Error('invalid action' + action.encodedAction)
}
- eventHandler.countEvent('applyActionsFromDevice action:' + parsedSerializedAction.type)
+ eventHandler.countEvent('applyActionsFromDevice, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
const parsedAction = parseParentAction(parsedSerializedAction)
try {
- await dispatchParentAction({
- action: parsedAction,
- cache,
- parentUserId: action.userId,
- sourceDeviceId: deviceEntry.deviceId
- })
+ if (isChildLimitAdding) {
+ const deviceEntryUnsafe2 = await cache.database.device.findOne({
+ attributes: ['currentUserId'],
+ where: {
+ familyId: cache.familyId,
+ deviceId: deviceEntry.deviceId,
+ currentUserId: action.userId
+ },
+ transaction: cache.transaction
+ })
+
+ if (!deviceEntryUnsafe2) {
+ throw new Error('illegal state')
+ }
+
+ const deviceUserId = deviceEntryUnsafe2.currentUserId
+
+ if (!deviceUserId) {
+ throw new Error('no device user id set but child add self limit action requested')
+ }
+
+ const deviceUserEntryUnsafe = await cache.database.user.findOne({
+ attributes: ['flags'],
+ where: {
+ familyId: cache.familyId,
+ userId: deviceUserId,
+ type: 'child'
+ },
+ transaction: cache.transaction
+ })
+
+ if (!deviceUserEntryUnsafe) {
+ throw new Error('no child user found for child limit adding action')
+ }
+
+ if ((parseInt(deviceUserEntryUnsafe.flags, 10) & UserFlags.ALLOW_SELF_LIMIT_ADD) !== UserFlags.ALLOW_SELF_LIMIT_ADD) {
+ throw new Error('child add limit action found but not allowed')
+ }
+
+ await dispatchParentAction({
+ action: parsedAction,
+ cache,
+ parentUserId: action.userId,
+ sourceDeviceId: deviceEntry.deviceId,
+ fromChildSelfLimitAddChildUserId: deviceUserId
+ })
+ } else {
+ await dispatchParentAction({
+ action: parsedAction,
+ cache,
+ parentUserId: action.userId,
+ sourceDeviceId: deviceEntry.deviceId,
+ fromChildSelfLimitAddChildUserId: null
+ })
+ }
} catch (ex) {
- eventHandler.countEvent('applyActionsFromDevice actionWithError:' + parsedSerializedAction.type)
+ eventHandler.countEvent('applyActionsFromDeviceWithError, childAddLimit: ' + isChildLimitAdding + ' action:' + parsedSerializedAction.type)
throw ex
}
diff --git a/src/model/userflags.ts b/src/model/userflags.ts
index 9f4ee52..c6bf39d 100644
--- a/src/model/userflags.ts
+++ b/src/model/userflags.ts
@@ -17,5 +17,6 @@
export const UserFlags = {
RESTRICT_VIEWING_TO_PARENTS: 1,
- ALL_FLAGS: 1
+ ALLOW_SELF_LIMIT_ADD: 2,
+ ALL_FLAGS: 1 | 2
}
diff --git a/src/util/category.ts b/src/util/category.ts
new file mode 100644
index 0000000..310d918
--- /dev/null
+++ b/src/util/category.ts
@@ -0,0 +1,40 @@
+/*
+ * server component for the TimeLimit App
+ * Copyright (C) 2019 - 2020 Jonas Lochmann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+export function getCategoryWithParentCategories (categories: Array<{ categoryId: string, parentCategoryId: string }>, startCategoryId: string): Array {
+ const categoryById = new Map()
+
+ categories.forEach((category) => categoryById.set(category.categoryId, category))
+
+ const startCategory = categoryById.get(startCategoryId)
+
+ if (!startCategory) {
+ throw new Error('start category not found')
+ }
+
+ const categoryIds = [ startCategoryId ]
+
+ let currentCategory = categoryById.get(startCategory.parentCategoryId)
+
+ while (currentCategory !== undefined && categoryIds.indexOf(currentCategory.categoryId) === -1) {
+ categoryIds.push(currentCategory.categoryId)
+
+ currentCategory = categoryById.get(currentCategory.parentCategoryId)
+ }
+
+ return categoryIds
+}