diff --git a/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/post-recipe.js b/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/post-recipe.js index 3f4bb02dc93..aa38986a002 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/post-recipe.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/routes/handlers/post-recipe.js @@ -31,9 +31,14 @@ exports.useACL = true; exports.entityName = "recipe" exports.handler = function(request,response,state) { + var server = state.server, + sqlTiddlerDatabase = server.sqlTiddlerDatabase if(state.data.recipe_name && state.data.bag_names) { const result = $tw.mws.store.createRecipe(state.data.recipe_name,$tw.utils.parseStringArray(state.data.bag_names),state.data.description); if(!result) { + if(state.authenticatedUser) { + sqlTiddlerDatabase.assignRecipeToUser(state.data.recipe_name,state.authenticatedUser.user_id); + } state.sendResponse(302,{ "Content-Type": "text/plain", "Location": "/" diff --git a/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js b/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js index cffefdf5315..2bd0449650a 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js @@ -48,33 +48,44 @@ exports.middleware = function (request, response, state, entityType, permissionN var aclRecord = sqlTiddlerDatabase.getACLByName(entityType, decodedEntityName); var isGetRequest = request.method === "GET"; var hasAnonymousAccess = isGetRequest ? state.allowAnonReads : state.allowAnonWrites; - // Get permission record - const permission = sqlTiddlerDatabase.getPermissionByName(permissionName); - // ACL Middleware will only apply if the entity has a middleware record - if(aclRecord && aclRecord?.permission_id === permission?.permission_id) { - // If not authenticated and anonymous access is not allowed, request authentication - if(!state.authenticatedUsername && !state.allowAnon) { - if(state.urlInfo.pathname !== '/login') { - redirectToLogin(response, request.url); - return; - } - } - // Check if user is authenticated - if(!state.authenticatedUser && !hasAnonymousAccess && !response.headersSent) { - response.writeHead(401, "Unauthorized"); - response.end(); - return; - } - - // Check ACL permission - var hasPermission = request.method === "POST" || sqlTiddlerDatabase.checkACLPermission(state.authenticatedUser.user_id, entityType, decodedEntityName, permissionName) - if(!hasPermission && !hasAnonymousAccess) { + var entity = sqlTiddlerDatabase.getEntityByName(entityType, decodedEntityName); + if(entity?.owner_id) { + if(state.authenticatedUser?.user_id !== entity.owner_id) { if(!response.headersSent) { response.writeHead(403, "Forbidden"); response.end(); } return; } + } else { + // Get permission record + const permission = sqlTiddlerDatabase.getPermissionByName(permissionName); + // ACL Middleware will only apply if the entity has a middleware record + if(aclRecord && aclRecord?.permission_id === permission?.permission_id) { + // If not authenticated and anonymous access is not allowed, request authentication + if(!state.authenticatedUsername && !state.allowAnon) { + if(state.urlInfo.pathname !== '/login') { + redirectToLogin(response, request.url); + return; + } + } + // Check if user is authenticated + if(!state.authenticatedUser && !hasAnonymousAccess && !response.headersSent) { + response.writeHead(401, "Unauthorized"); + response.end(); + return; + } + + // Check ACL permission + var hasPermission = request.method === "POST" || sqlTiddlerDatabase.checkACLPermission(state.authenticatedUser.user_id, entityType, decodedEntityName, permissionName) + if(!hasPermission && !hasAnonymousAccess) { + if(!response.headersSent) { + response.writeHead(403, "Forbidden"); + response.end(); + } + return; + } + } } }; diff --git a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js index a2ebda9fb27..d56f12eabf3 100644 --- a/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js +++ b/plugins/tiddlywiki/multiwikiserver/modules/store/sql-tiddler-database.js @@ -137,7 +137,9 @@ SqlTiddlerDatabase.prototype.createTables = function() { CREATE TABLE IF NOT EXISTS recipes ( recipe_id INTEGER PRIMARY KEY AUTOINCREMENT, recipe_name TEXT UNIQUE NOT NULL, - description TEXT NOT NULL + description TEXT NOT NULL, + owner_id INTEGER, + FOREIGN KEY (owner_id) REFERENCES users(user_id) ) `,` -- ...and recipes also have an ordered list of bags @@ -291,6 +293,18 @@ SqlTiddlerDatabase.prototype.createRecipe = function(recipe_name,bag_names,descr return updateRecipes.lastInsertRowid; }; +/* +Assign a recipe to a user +*/ +SqlTiddlerDatabase.prototype.assignRecipeToUser = function(recipe_name,user_id) { + this.engine.runStatement(` + UPDATE recipes SET owner_id = $user_id WHERE recipe_name = $recipe_name + `,{ + $recipe_name: recipe_name, + $user_id: user_id + }); +}; + /* Returns {tiddler_id:} */ @@ -486,6 +500,18 @@ SqlTiddlerDatabase.prototype.getRecipeTiddler = function(title,recipe_name) { Checks if a user has permission to access a recipe */ SqlTiddlerDatabase.prototype.hasRecipePermission = function(userId, recipeName, permissionName) { + // check if the user is the owner of the entity + const recipe = this.engine.runStatementGet(` + SELECT owner_id + FROM recipes + WHERE recipe_name = $recipe_name + `, { + $recipe_name: recipeName + }); + + if(recipe?.owner_id) { + return recipe.owner_id === userId; + } return this.checkACLPermission(userId, "recipe", recipeName, permissionName) }; @@ -556,7 +582,7 @@ SqlTiddlerDatabase.prototype.checkACLPermission = function(userId, entityType, e $permission_id: aclRecord.permission_id }); - const hasPermission = result !== undefined; + let hasPermission = result !== undefined; return hasPermission; }; @@ -578,6 +604,19 @@ SqlTiddlerDatabase.prototype.getEntityAclRecords = function(entityName) { return aclRecords } +/* +Get the entity by name +*/ +SqlTiddlerDatabase.prototype.getEntityByName = function(entityType, entityName) { + const entityInfo = this.entityTypeToTableMap[entityType]; + if (entityInfo) { + return this.engine.runStatementGet(`SELECT * FROM ${entityInfo.table} WHERE ${entityInfo.column} = $entity_name`, { + $entity_name: entityName + }); + } + return null; +} + /* Get the titles of the tiddlers in a bag. Returns an empty array for bags that do not exist */