From 9cbb652fdd6e70acd340ab09727a28bcdc8b6488 Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 17:59:15 -0700 Subject: [PATCH 1/6] feat: register new permissions for products Signed-off-by: Erik Kieckhafer --- .../included/product-admin/register.js | 11 ++++ .../server/no-meteor/register.js | 54 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 imports/plugins/included/product-admin/register.js create mode 100644 imports/plugins/included/product-admin/server/no-meteor/register.js diff --git a/imports/plugins/included/product-admin/register.js b/imports/plugins/included/product-admin/register.js new file mode 100644 index 00000000000..add1f847771 --- /dev/null +++ b/imports/plugins/included/product-admin/register.js @@ -0,0 +1,11 @@ +/** + * This file is necessary for backwards compatibility while we refactor + * the API to remove Meteor. The no-meteor `register.js` file will + * eventually become the main entry point of the plugin, but for now + * our Meteor tooling loads this file, so we include this here as a + * temporary bridge. + */ +import Reaction from "/imports/plugins/core/core/server/Reaction"; +import register from "./server/no-meteor/register"; + +Reaction.whenAppInstanceReady(register); diff --git a/imports/plugins/included/product-admin/server/no-meteor/register.js b/imports/plugins/included/product-admin/server/no-meteor/register.js new file mode 100644 index 00000000000..3c020004098 --- /dev/null +++ b/imports/plugins/included/product-admin/server/no-meteor/register.js @@ -0,0 +1,54 @@ +/** + * @summary Import and call this function to add this plugin to your API. + * @param {ReactionNodeApp} app The ReactionNodeApp instance + * @return {undefined} + */ +export default async function register(app) { + await app.registerPlugin({ + label: "Product Admin", + name: "reaction-product-admin", + icon: "fa fa-box", + registry: [ + // `ProductAdmin` is a role that currently clones the `createProduct` role + // which is overused in too many places. By adding `ProductAdmin`, we can use + // that as a catch all wherever `createProduct` was used, and slowly remove `createProduct` + // from places where it doesn't make sense + { + route: "product/admin", + label: "Product Admin", + permission: "productAdmin", + name: "product/admin" + }, + { + route: "product/archive", + label: "Archive Product", + permission: "productArchive", + name: "product/archive" + }, + { + route: "product/clone", + label: "Clone Product", + permission: "productClone", + name: "product/clone" + }, + { + route: "product/create", + label: "Create Product", + permission: "productCreate", + name: "product/create" + }, + { + route: "product/publish", + label: "Publish Product", + permission: "productPublish", + name: "product/publish" + }, + { + route: "product/update", + label: "Update Product", + permission: "productUpdate", + name: "product/update" + } + ] + }); +} From aa713145b1c4a369aa488e8fda44260e0e3553c5 Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 18:01:46 -0700 Subject: [PATCH 2/6] ref: add product/admin, and other permissions, along side createProduct, where needed Signed-off-by: Erik Kieckhafer --- imports/collections/schemas/shops.js | 3 +- .../core/catalog/server/methods/catalog.js | 34 +++++++++---------- .../no-meteor/mutations/publishProducts.js | 2 +- .../core/server/no-meteor/util/sampleData.js | 1 + .../server/publications/collections/media.js | 4 +-- .../publications/collections/product.js | 2 +- .../publications/collections/products.js | 2 +- .../server/publications/collections/tags.js | 2 +- .../server/startup/collection-security.js | 4 +-- .../client/containers/toolbarContainer.js | 2 +- imports/plugins/core/files/server/methods.js | 4 +-- imports/plugins/core/tags/server/methods.js | 2 +- .../core/ui/client/containers/mediaGallery.js | 2 +- .../client/hocs/withProductMedia.js | 2 +- 14 files changed, 34 insertions(+), 32 deletions(-) diff --git a/imports/collections/schemas/shops.js b/imports/collections/schemas/shops.js index fc7a57cf52f..477af913771 100644 --- a/imports/collections/schemas/shops.js +++ b/imports/collections/schemas/shops.js @@ -314,7 +314,7 @@ registerSchema("StorefrontUrls", StorefrontUrls); * @property {String} unitsOfMeasure.$.label default value: `Ounces` * @property {Boolean} unitsOfMeasure.$.default default value: `false` * @property {Metafield[]} metafields optional - * @property {String[]} defaultSellerRoles default values: `["owner", "admin", "seller", "guest", "manage-users", "orders", "account/profile", "product", "createProduct", "tag", "index", "cart/completed"]` + * @property {String[]} defaultSellerRoles default values: `["owner", "admin", "seller", "guest", "manage-users", "orders", "account/profile", "product", "createProduct", "product/admin", tag", "index", "cart/completed"]` * @property {Layout[]} layout optional * @property {ShopTheme} theme optional * @property {BrandAsset[]} brandAssets optional @@ -496,6 +496,7 @@ export const Shop = new SimpleSchema({ "account/profile", "product", "createProduct", + "product/admin", "tag", "index", "cart/completed" diff --git a/imports/plugins/core/catalog/server/methods/catalog.js b/imports/plugins/core/catalog/server/methods/catalog.js index cef1ff0e12b..49d90a9b4aa 100644 --- a/imports/plugins/core/catalog/server/methods/catalog.js +++ b/imports/plugins/core/catalog/server/methods/catalog.js @@ -266,7 +266,7 @@ Meteor.methods({ const authUserId = Reaction.getUserId(); - if (!Reaction.hasPermission("createProduct", authUserId, variant.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/clone"], authUserId, variant.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -384,7 +384,7 @@ Meteor.methods({ } const userId = Reaction.getUserId(); - if (!Reaction.hasPermission("createProduct", userId, product.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/create"], userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -438,7 +438,7 @@ Meteor.methods({ } const authUserId = Reaction.getUserId(); - if (!Reaction.hasPermission("createProduct", authUserId, variant.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/archive"], authUserId, variant.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -504,7 +504,7 @@ Meteor.methods({ // REVIEW: This check may be unnecessary now - checks that user has permission to clone // for active shop - if (!Reaction.hasPermission("createProduct")) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/clone"])) { throw new ReactionError("access-denied", "Access Denied"); } @@ -521,11 +521,11 @@ Meteor.methods({ // For each unique shopId check to make sure that user has permission to clone uniqueShopIds.forEach((shopId) => { - if (!Reaction.hasPermission("createProduct", this.userId, shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/clone"], this.userId, shopId)) { throw new ReactionError("access-denied", "Access Denied"); } }); - } else if (!Reaction.hasPermission("createProduct", this.userId, productOrArray.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/clone"], this.userId, productOrArray.shopId)) { // Single product was passed in - ensure that user has permission to clone throw new ReactionError("access-denied", "Access Denied"); } @@ -635,7 +635,7 @@ Meteor.methods({ */ "products/createProduct"() { // Ensure user has createProduct permission for active shop - if (!Reaction.hasPermission("createProduct")) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/create"])) { throw new ReactionError("access-denied", "Access Denied"); } @@ -676,7 +676,7 @@ Meteor.methods({ const authUserId = Reaction.getUserId(); - if (!Reaction.hasPermission("createProduct", authUserId, product.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/archive"], authUserId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -802,7 +802,7 @@ Meteor.methods({ throw new ReactionError("not-found", "Product not found"); } - if (!Reaction.hasPermission("createProduct", this.userId, doc.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, doc.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -882,7 +882,7 @@ Meteor.methods({ throw new ReactionError("not-found", "Product not found"); } - if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -963,7 +963,7 @@ Meteor.methods({ const product = Products.findOne(productId); if (!product) { throw new ReactionError("not-found", "Product not found"); - } else if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -998,7 +998,7 @@ Meteor.methods({ const product = Products.findOne(productId); if (!product) { throw new ReactionError("not-found", "Product not found"); - } else if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -1033,7 +1033,7 @@ Meteor.methods({ const product = Products.findOne(productId); if (!product) { throw new ReactionError("not-found", "Product not found"); - } else if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -1101,7 +1101,7 @@ Meteor.methods({ // This checks to make sure the user has createProduct permissions for the active shop. // TODO: We should determine if that is the correct role that a user should have // to be permitted to re-arrange products on the grid - if (!Reaction.hasPermission("createProduct", this.userId, shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -1145,7 +1145,7 @@ Meteor.methods({ const product = Products.findOne(productId); if (!product) { throw new ReactionError("not-found", "Product not found"); - } else if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -1219,7 +1219,7 @@ Meteor.methods({ const product = Products.findOne(productId); if (!product) { throw new ReactionError("not-found", "Product not found"); - } else if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + } else if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } @@ -1253,7 +1253,7 @@ Meteor.methods({ throw new ReactionError("not-found", "Product not found"); } - if (!Reaction.hasPermission("createProduct", this.userId, product.shopId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { throw new ReactionError("access-denied", "Access Denied"); } diff --git a/imports/plugins/core/catalog/server/no-meteor/mutations/publishProducts.js b/imports/plugins/core/catalog/server/no-meteor/mutations/publishProducts.js index 327a9f63f13..7db96c13c46 100644 --- a/imports/plugins/core/catalog/server/no-meteor/mutations/publishProducts.js +++ b/imports/plugins/core/catalog/server/no-meteor/mutations/publishProducts.js @@ -30,7 +30,7 @@ export default async function publishProducts(context, productIds) { if (!isInternalCall) { const uniqueShopIds = uniq(products.map((product) => product.shopId)); uniqueShopIds.forEach((shopId) => { - if (!userHasPermission(["createProduct"], shopId)) { + if (!userHasPermission(["createProduct", "product/admin", "product/publish"], shopId)) { throw new ReactionError("access-denied", "Access Denied"); } }); diff --git a/imports/plugins/core/core/server/no-meteor/util/sampleData.js b/imports/plugins/core/core/server/no-meteor/util/sampleData.js index 3844f86abcc..8ce5f2ecf73 100644 --- a/imports/plugins/core/core/server/no-meteor/util/sampleData.js +++ b/imports/plugins/core/core/server/no-meteor/util/sampleData.js @@ -3338,6 +3338,7 @@ export default { "account/profile", "product", "createProduct", + "product/admin", "tag", "index", "cart/completed" diff --git a/imports/plugins/core/core/server/publications/collections/media.js b/imports/plugins/core/core/server/publications/collections/media.js index 5cdd29d1f11..32caedcc806 100644 --- a/imports/plugins/core/core/server/publications/collections/media.js +++ b/imports/plugins/core/core/server/publications/collections/media.js @@ -34,7 +34,7 @@ Meteor.publish("ProductGridMedia", function productGridMediaPublish(productIds) // Product editors can see both published and unpublished images // There is an implied shopId in Reaction.hasPermission that defaults to // the active shopId via Reaction.getShopId - if (!Reaction.hasPermission(["createProduct"], this.userId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/publish"], this.userId)) { selector["metadata.workflow"].$in = [null, "published"]; } @@ -62,7 +62,7 @@ Meteor.publish("ProductMedia", function productMediaPublish(id) { // Product editors can see both published and unpublished images // There is an implied shopId in Reaction.hasPermission that defaults to // the active shopId via Reaction.getShopId - if (!Reaction.hasPermission(["createProduct"], this.userId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/publish"], this.userId)) { selector["metadata.workflow"].$in = [null, "published"]; } diff --git a/imports/plugins/core/core/server/publications/collections/product.js b/imports/plugins/core/core/server/publications/collections/product.js index 8b7dbc10aec..afed52ab5a6 100644 --- a/imports/plugins/core/core/server/publications/collections/product.js +++ b/imports/plugins/core/core/server/publications/collections/product.js @@ -102,7 +102,7 @@ Meteor.publish("Product", function (productIdOrHandle, shopIdOrSlug) { // Authorized content curators for the shop get special publication of the product // all all relevant revisions all is one package - if (Reaction.hasPermission(["owner", "createProduct"], this.userId, product.shopId)) { + if (Reaction.hasPermission(["owner", "createProduct", "product/admin"], this.userId, product.shopId)) { selector.isVisible = { $in: [true, false, undefined] }; diff --git a/imports/plugins/core/core/server/publications/collections/products.js b/imports/plugins/core/core/server/publications/collections/products.js index c87c5a5fd94..ee55397414b 100644 --- a/imports/plugins/core/core/server/publications/collections/products.js +++ b/imports/plugins/core/core/server/publications/collections/products.js @@ -326,7 +326,7 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so } // Get a list of shopIds that this user has "createProduct" permissions for (owner permission is checked by default) - const userAdminShopIds = Reaction.getShopsWithRoles(["createProduct"], this.userId) || []; + const userAdminShopIds = Reaction.getShopsWithRoles(["createProduct", "product/admin"], this.userId) || []; // We publish an admin version of this publication to admins of products who are in "Edit Mode" if (editMode) { diff --git a/imports/plugins/core/core/server/publications/collections/tags.js b/imports/plugins/core/core/server/publications/collections/tags.js index 78f507e8f48..c3aa437c4ac 100644 --- a/imports/plugins/core/core/server/publications/collections/tags.js +++ b/imports/plugins/core/core/server/publications/collections/tags.js @@ -17,7 +17,7 @@ Meteor.publish("Tags", function (tagIds) { const shopId = Reaction.getShopId(); // Only let users what have createProduct permissions see the tags - if (!Reaction.hasPermission(["createProduct"], this.userId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin"], this.userId)) { return this.ready(); } diff --git a/imports/plugins/core/core/server/startup/collection-security.js b/imports/plugins/core/core/server/startup/collection-security.js index 0913a129ee4..b1e67302bdb 100644 --- a/imports/plugins/core/core/server/startup/collection-security.js +++ b/imports/plugins/core/core/server/startup/collection-security.js @@ -126,7 +126,7 @@ export default function () { Security.permit(["insert", "update", "remove"]) .collections([MediaRecords]) - .ifHasRoleForActiveShop({ role: ["admin", "owner", "createProduct"] }) + .ifHasRoleForActiveShop({ role: ["admin", "owner", "createProduct", "product/admin"] }) .ifFileBelongsToShop(); /* @@ -145,7 +145,7 @@ export default function () { */ Products.permit(["insert", "update", "remove"]) - .ifHasRoleForActiveShop({ role: ["createProduct"] }) + .ifHasRoleForActiveShop({ role: ["createProduct", "product/admin"] }) .ifShopIdMatches() .allowInClientCode(); diff --git a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js index 6188991b1ba..70a236d1643 100644 --- a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js +++ b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js @@ -82,7 +82,7 @@ function composer(props, onData) { dashboardHeaderTemplate: props.data.dashboardHeader, isActionViewAtRootView: Reaction.isActionViewAtRootView(), actionViewIsOpen: Reaction.isActionViewOpen(), - hasCreateProductAccess: Reaction.hasPermission("createProduct", Reaction.getUserId(), Reaction.getShopId()), + hasCreateProductAccess: Reaction.hasPermission(["createProduct", "product/admin", "product/create"], Reaction.getUserId(), Reaction.getShopId()), shopId: Reaction.getShopId(), shops, diff --git a/imports/plugins/core/files/server/methods.js b/imports/plugins/core/files/server/methods.js index 09dffa50d18..e02abe74a4d 100644 --- a/imports/plugins/core/files/server/methods.js +++ b/imports/plugins/core/files/server/methods.js @@ -75,7 +75,7 @@ export async function removeMedia(fileRecordId) { export function updateMediaPriorities(sortedMediaIDs) { check(sortedMediaIDs, [String]); - if (!Reaction.hasPermission("createProduct")) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"])) { throw new ReactionError("access-denied", "Access Denied"); } @@ -125,7 +125,7 @@ export function updateMediaPriority(mediaId, priority) { check(mediaId, String); check(priority, Number); - if (!Reaction.hasPermission("createProduct")) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"])) { throw new ReactionError("access-denied", "Access Denied"); } diff --git a/imports/plugins/core/tags/server/methods.js b/imports/plugins/core/tags/server/methods.js index 48b8f462510..14b9ca94627 100644 --- a/imports/plugins/core/tags/server/methods.js +++ b/imports/plugins/core/tags/server/methods.js @@ -9,7 +9,7 @@ Meteor.methods({ check(excludedTagIds, Match.OneOf(undefined, Array)); // Return a blank result set for non admins - if (!Reaction.hasPermission(["admin", "owner", "createProduct"], this.userId)) { + if (!Reaction.hasPermission(["admin", "owner", "createProduct", "product/admin"], this.userId)) { return []; } diff --git a/imports/plugins/core/ui/client/containers/mediaGallery.js b/imports/plugins/core/ui/client/containers/mediaGallery.js index 4df1c3fdeda..31a85da57b1 100644 --- a/imports/plugins/core/ui/client/containers/mediaGallery.js +++ b/imports/plugins/core/ui/client/containers/mediaGallery.js @@ -238,7 +238,7 @@ function sortMedia(media) { */ function composer(props, onData) { onData(null, { - editable: Reaction.hasPermission(props.permission || ["createProduct"]), + editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin"]), media: sortMedia(props.media) }); } diff --git a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js index 5594c9e7be9..e82c328a143 100644 --- a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js +++ b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js @@ -272,7 +272,7 @@ function composer(props, onData) { }); onData(null, { - editable: Reaction.hasPermission(props.permission || ["createProduct"]), + editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin"]), media: sortMedia(media), userId: Reaction.getUserId(), shopId: Reaction.getShopId(), From 2e237229b4aabdf86581a52386df906243daa052 Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 18:14:44 -0700 Subject: [PATCH 3/6] add permission around displaying UI components Signed-off-by: Erik Kieckhafer --- .../client/components/ProductHeader.js | 41 +++++++++++-------- .../client/components/ProductList.js | 25 +++++++---- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/imports/plugins/included/product-admin/client/components/ProductHeader.js b/imports/plugins/included/product-admin/client/components/ProductHeader.js index a95e28a02f5..f23485c3bc6 100644 --- a/imports/plugins/included/product-admin/client/components/ProductHeader.js +++ b/imports/plugins/included/product-admin/client/components/ProductHeader.js @@ -1,7 +1,7 @@ import React, { Fragment } from "react"; import PropTypes from "prop-types"; import { Link } from "react-router-dom"; -import { i18next } from "/client/api"; +import { i18next, Reaction } from "/client/api"; import { compose, withState } from "recompose"; import withStyles from "@material-ui/core/styles/withStyles"; import IconButton from "@material-ui/core/IconButton"; @@ -65,6 +65,9 @@ function ProductHeader(props) { const currentProduct = variant || product; + const hasCloneProductPermission = Reaction.hasPermission(["createProduct", "product/admin", "product/clone"], Reaction.getUserId(), Reaction.getShopId()); + const hasArchiveProductPermission = Reaction.hasPermission(["createProduct", "product/admin", "product/archive"], Reaction.getUserId(), Reaction.getShopId()); + // Archive menu item let archiveMenuItem = ( - { - if (product && variant) { - // Clone variant - onCloneVariant(product._id, variant._id); - } else { - // Clone product - onCloneProduct(product._id); - } - - setMenuAnchorEl(null); - }} - > - {i18next.t("admin.productTable.bulkActions.duplicate")} - - {archiveMenuItem} + {hasCloneProductPermission && + { + if (product && variant) { + // Clone variant + onCloneVariant(product._id, variant._id); + } else { + // Clone product + onCloneProduct(product._id); + } + + setMenuAnchorEl(null); + }} + > + {i18next.t("admin.productTable.bulkActions.duplicate")} + + } + {hasArchiveProductPermission && + archiveMenuItem + } diff --git a/imports/plugins/included/product-admin/client/components/ProductList.js b/imports/plugins/included/product-admin/client/components/ProductList.js index 285fee9c291..f21f30c9ee7 100644 --- a/imports/plugins/included/product-admin/client/components/ProductList.js +++ b/imports/plugins/included/product-admin/client/components/ProductList.js @@ -8,6 +8,7 @@ import List from "@material-ui/core/List"; import ListItem from "@material-ui/core/ListItem"; import ListItemText from "@material-ui/core/ListItemText"; import PlusIcon from "mdi-material-ui/Plus"; +import { Reaction } from "/client/api"; /** * Get url for product, variant or option @@ -50,16 +51,24 @@ function ProductList({ items, title, onCreate, selectedVariantId }) { return null; } + const hasCreateProductPermission = Reaction.hasPermission(["createProduct", "product/admin", "product/create"], Reaction.getUserId(), Reaction.getShopId()); + return ( - - - - } - title={title} - /> + {hasCreateProductPermission ? + + + + } + title={title} + /> + : + + } {items.map((item) => ( From cb9e08eef525678d4c24e4a4515e86e1e1ee8af0 Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 22:08:26 -0700 Subject: [PATCH 4/6] add product/update permission to update various fields Signed-off-by: Erik Kieckhafer --- .../core/core/server/publications/collections/product.js | 2 +- .../plugins/core/core/server/publications/collections/tags.js | 2 +- imports/plugins/core/core/server/startup/collection-security.js | 2 +- imports/plugins/core/tags/server/methods.js | 2 +- imports/plugins/core/ui/client/containers/mediaGallery.js | 2 +- .../included/product-admin/client/hocs/withProductMedia.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/imports/plugins/core/core/server/publications/collections/product.js b/imports/plugins/core/core/server/publications/collections/product.js index afed52ab5a6..41c3417bcda 100644 --- a/imports/plugins/core/core/server/publications/collections/product.js +++ b/imports/plugins/core/core/server/publications/collections/product.js @@ -102,7 +102,7 @@ Meteor.publish("Product", function (productIdOrHandle, shopIdOrSlug) { // Authorized content curators for the shop get special publication of the product // all all relevant revisions all is one package - if (Reaction.hasPermission(["owner", "createProduct", "product/admin"], this.userId, product.shopId)) { + if (Reaction.hasPermission(["owner", "createProduct", "product/admin", "product/update"], this.userId, product.shopId)) { selector.isVisible = { $in: [true, false, undefined] }; diff --git a/imports/plugins/core/core/server/publications/collections/tags.js b/imports/plugins/core/core/server/publications/collections/tags.js index c3aa437c4ac..ff99ef484ad 100644 --- a/imports/plugins/core/core/server/publications/collections/tags.js +++ b/imports/plugins/core/core/server/publications/collections/tags.js @@ -17,7 +17,7 @@ Meteor.publish("Tags", function (tagIds) { const shopId = Reaction.getShopId(); // Only let users what have createProduct permissions see the tags - if (!Reaction.hasPermission(["createProduct", "product/admin"], this.userId)) { + if (!Reaction.hasPermission(["createProduct", "product/admin", "product/update"], this.userId)) { return this.ready(); } diff --git a/imports/plugins/core/core/server/startup/collection-security.js b/imports/plugins/core/core/server/startup/collection-security.js index b1e67302bdb..a877f0601f3 100644 --- a/imports/plugins/core/core/server/startup/collection-security.js +++ b/imports/plugins/core/core/server/startup/collection-security.js @@ -126,7 +126,7 @@ export default function () { Security.permit(["insert", "update", "remove"]) .collections([MediaRecords]) - .ifHasRoleForActiveShop({ role: ["admin", "owner", "createProduct", "product/admin"] }) + .ifHasRoleForActiveShop({ role: ["admin", "owner", "createProduct", "product/admin", "product/update"] }) .ifFileBelongsToShop(); /* diff --git a/imports/plugins/core/tags/server/methods.js b/imports/plugins/core/tags/server/methods.js index 14b9ca94627..64941df6617 100644 --- a/imports/plugins/core/tags/server/methods.js +++ b/imports/plugins/core/tags/server/methods.js @@ -9,7 +9,7 @@ Meteor.methods({ check(excludedTagIds, Match.OneOf(undefined, Array)); // Return a blank result set for non admins - if (!Reaction.hasPermission(["admin", "owner", "createProduct", "product/admin"], this.userId)) { + if (!Reaction.hasPermission(["admin", "owner", "createProduct", "product/admin", "product/update"], this.userId)) { return []; } diff --git a/imports/plugins/core/ui/client/containers/mediaGallery.js b/imports/plugins/core/ui/client/containers/mediaGallery.js index 31a85da57b1..6282c656411 100644 --- a/imports/plugins/core/ui/client/containers/mediaGallery.js +++ b/imports/plugins/core/ui/client/containers/mediaGallery.js @@ -238,7 +238,7 @@ function sortMedia(media) { */ function composer(props, onData) { onData(null, { - editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin"]), + editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin", "product/update"]), media: sortMedia(props.media) }); } diff --git a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js index e82c328a143..b38b04088cd 100644 --- a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js +++ b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js @@ -272,7 +272,7 @@ function composer(props, onData) { }); onData(null, { - editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin"]), + editable: Reaction.hasPermission(props.permission || ["createProduct", "product/admin", "product/update"]), media: sortMedia(media), userId: Reaction.getUserId(), shopId: Reaction.getShopId(), From dd0d7f8c8d9e8cbf8be917dc3a0d79ac3ad71f4a Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 22:32:30 -0700 Subject: [PATCH 5/6] add product/admin to default roles Signed-off-by: Erik Kieckhafer --- .../plugins/core/accounts/server/no-meteor/util/defaultRoles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imports/plugins/core/accounts/server/no-meteor/util/defaultRoles.js b/imports/plugins/core/accounts/server/no-meteor/util/defaultRoles.js index 3ef2e301e91..1a19971b08b 100644 --- a/imports/plugins/core/accounts/server/no-meteor/util/defaultRoles.js +++ b/imports/plugins/core/accounts/server/no-meteor/util/defaultRoles.js @@ -27,6 +27,7 @@ export const defaultOwnerRoles = [ "index", "owner", "product", + "product/admin", "shopSettings", "tag" ]; @@ -39,6 +40,7 @@ export const defaultShopManagerRoles = [ "guest", "index", "product", + "product/admin", "shopSettings", "tag" ]; From 9ea40132915af0f22f9a09ece81c84bb93cff558 Mon Sep 17 00:00:00 2001 From: Erik Kieckhafer Date: Tue, 6 Aug 2019 22:40:57 -0700 Subject: [PATCH 6/6] chore: lint fixes Signed-off-by: Erik Kieckhafer --- .../core/core/server/startup/collection-security.js | 4 ++++ .../dashboard/client/containers/toolbarContainer.js | 10 ++++++++++ imports/plugins/core/files/server/methods.js | 4 ++-- .../product-admin/client/hocs/withProductMedia.js | 5 +---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/imports/plugins/core/core/server/startup/collection-security.js b/imports/plugins/core/core/server/startup/collection-security.js index a877f0601f3..439338f8078 100644 --- a/imports/plugins/core/core/server/startup/collection-security.js +++ b/imports/plugins/core/core/server/startup/collection-security.js @@ -33,6 +33,10 @@ const { * database operation is executed in a server method. */ +/** + * @description security definitions for collections + * @returns {undefined} undefined + */ export default function () { /* * Define some additional rule chain methods diff --git a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js index 70a236d1643..28b0000041e 100644 --- a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js +++ b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js @@ -92,7 +92,17 @@ function composer(props, onData) { }); } +/** + * @name ToolbarContainer + * @param {React.Component} Comp wrapped component + * @returns {React.Component} returns a React component + */ export default function ToolbarContainer(Comp) { + /** + * @name CompositeComponent + * @param {Object} props Component props + * @returns {React.Component} Wrapped Toolbar component + */ function CompositeComponent(props) { return ( diff --git a/imports/plugins/core/files/server/methods.js b/imports/plugins/core/files/server/methods.js index e02abe74a4d..e84fff00363 100644 --- a/imports/plugins/core/files/server/methods.js +++ b/imports/plugins/core/files/server/methods.js @@ -41,7 +41,7 @@ export async function insertMedia(fileRecord) { * @memberof Media/Methods * @summary Unpublish a media record by updating it's workflow * @param {String} fileRecordId - _id of file record to be deleted. - * @return {Boolean} + * @return {Boolean} was media successfully removed */ export async function removeMedia(fileRecordId) { check(fileRecordId, String); @@ -69,7 +69,7 @@ export async function removeMedia(fileRecordId) { * @method * @memberof Media/Methods * @summary sorting media by array indexes - * @param {String[]} sortedMediaIDs + * @param {String[]} sortedMediaIDs ID's of sorted media * @return {Boolean} true */ export function updateMediaPriorities(sortedMediaIDs) { diff --git a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js index b38b04088cd..7ac83fa1f86 100644 --- a/imports/plugins/included/product-admin/client/hocs/withProductMedia.js +++ b/imports/plugins/included/product-admin/client/hocs/withProductMedia.js @@ -11,9 +11,6 @@ import { Logger, Reaction } from "/client/api"; import { Media } from "/imports/plugins/core/files/client"; const wrapComponent = (Comp) => ( - /** - * ProductMediaGallery - */ class ProductMediaGallery extends Component { static propTypes = { editable: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming @@ -120,7 +117,7 @@ const wrapComponent = (Comp) => ( ] }); - // Set local state so the component does't have to wait for a round-trip + // Set local state so the component doesn't have to wait for a round-trip // to the server to get the updated list of variants this.setState({ media: newMediaOrder });