diff --git a/.changeset/entries/cdb6bd1ba576af9e68b846d50b5e876603932df1df530307867c6a7e8a4174c7.yaml b/.changeset/entries/cdb6bd1ba576af9e68b846d50b5e876603932df1df530307867c6a7e8a4174c7.yaml new file mode 100644 index 0000000000..4e6667031f --- /dev/null +++ b/.changeset/entries/cdb6bd1ba576af9e68b846d50b5e876603932df1df530307867c6a7e8a4174c7.yaml @@ -0,0 +1,6 @@ +type: feat +module: x/posts +pull_request: 998 +description: Allow to set the permission to only comment content +backward_compatible: true +date: 2022-08-31T14:45:52.132548182Z diff --git a/x/posts/keeper/msg_server.go b/x/posts/keeper/msg_server.go index 339d54b9c8..d87eec8e01 100644 --- a/x/posts/keeper/msg_server.go +++ b/x/posts/keeper/msg_server.go @@ -44,9 +44,11 @@ func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) ( return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "subspace section with id %d not found", msg.SectionID) } - // Check the permission to create content - if !k.HasPermission(ctx, msg.SubspaceID, msg.SectionID, msg.Author, types.PermissionWrite) { - return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot create content inside this subspace") + // Check the permission to create this post + canWrite := k.HasPermission(ctx, msg.SubspaceID, msg.SectionID, msg.Author, types.PermissionWrite) + canComment := msg.ConversationID != 0 && k.HasPermission(ctx, msg.SubspaceID, msg.SectionID, msg.Author, types.PermissionComment) + if !canWrite && !canComment { + return nil, sdkerrors.Wrap(subspacestypes.ErrPermissionDenied, "you cannot create posts nor comment inside this section") } // Get the next post id diff --git a/x/posts/keeper/msg_server_test.go b/x/posts/keeper/msg_server_test.go index bb13364700..28717f9fb2 100644 --- a/x/posts/keeper/msg_server_test.go +++ b/x/posts/keeper/msg_server_test.go @@ -93,7 +93,38 @@ func (suite *KeeperTestsuite) TestMsgServer_CreatePost() { shouldErr: true, }, { - name: "user without permission returns error", + name: "user without PermissionWrite returns error", + store: func(ctx sdk.Context) { + err := suite.ak.SaveProfile(ctx, profilestesting.ProfileFromAddr("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd")) + suite.Require().NoError(err) + + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + }, + msg: types.NewMsgCreatePost( + 1, + 0, + "External ID", + "This is a text", + 0, + types.REPLY_SETTING_EVERYONE, + nil, + nil, + nil, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: true, + }, + { + name: "user without PermissionComment returns error", store: func(ctx sdk.Context) { err := suite.ak.SaveProfile(ctx, profilestesting.ProfileFromAddr("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd")) suite.Require().NoError(err) @@ -339,7 +370,7 @@ func (suite *KeeperTestsuite) TestMsgServer_CreatePost() { shouldErr: true, }, { - name: "valid post is stored correctly", + name: "valid post is stored correctly with PermissionWrite", setupCtx: func(ctx sdk.Context) sdk.Context { return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) }, @@ -436,6 +467,119 @@ func (suite *KeeperTestsuite) TestMsgServer_CreatePost() { }, attachments) }, }, + { + name: "valid comment is stored correctly with PermissionComment", + setupCtx: func(ctx sdk.Context) sdk.Context { + return ctx.WithBlockTime(time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC)) + }, + store: func(ctx sdk.Context) { + err := suite.ak.SaveProfile(ctx, profilestesting.ProfileFromAddr("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd")) + suite.Require().NoError(err) + + suite.sk.SaveSubspace(ctx, subspacestypes.NewSubspace( + 1, + "Test", + "Testing subspace", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + "cosmos1sg2j68v5n8qvehew6ml0etun3lmv7zg7r49s67", + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + )) + + suite.sk.SetUserPermissions(ctx, + 1, + 0, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + subspacestypes.NewPermissions(types.PermissionComment), + ) + + suite.k.SavePost(ctx, types.NewPost( + 1, + 0, + 1, + "External ID", + "This is a text", + "cosmos1r9jamre0x0qqy562rhhckt6sryztwhnvhafyz4", + 0, + nil, + nil, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + )) + suite.k.SetNextPostID(ctx, 1, 2) + + suite.k.SetParams(ctx, types.DefaultParams()) + }, + msg: types.NewMsgCreatePost( + 1, + 0, + "External ID", + "This is a text", + 1, + types.REPLY_SETTING_EVERYONE, + nil, + []string{"generic"}, + []types.AttachmentContent{ + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + }, + nil, + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + ), + shouldErr: false, + expResponse: &types.MsgCreatePostResponse{ + PostID: 2, + CreationDate: time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + }, + expEvents: sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, sdk.MsgTypeURL(&types.MsgCreatePost{})), + sdk.NewAttribute(sdk.AttributeKeySender, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + ), + sdk.NewEvent( + types.EventTypeCreatePost, + sdk.NewAttribute(types.AttributeKeySubspaceID, "1"), + sdk.NewAttribute(types.AttributeKeySectionID, "0"), + sdk.NewAttribute(types.AttributeKeyPostID, "2"), + sdk.NewAttribute(types.AttributeKeyAuthor, "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd"), + sdk.NewAttribute(types.AttributeKeyCreationTime, time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC).Format(time.RFC3339)), + ), + }, + check: func(ctx sdk.Context) { + // Check the post + stored, found := suite.k.GetPost(ctx, 1, 2) + suite.Require().True(found) + suite.Require().Equal(types.NewPost( + 1, + 0, + 2, + "External ID", + "This is a text", + "cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd", + 1, + nil, + []string{"generic"}, + nil, + types.REPLY_SETTING_EVERYONE, + time.Date(2020, 1, 1, 12, 00, 00, 000, time.UTC), + nil, + ), stored) + + // Check the attachments + attachments := suite.k.GetPostAttachments(ctx, 1, 2) + suite.Require().Equal([]types.Attachment{ + types.NewAttachment( + 1, + 2, + 1, + types.NewMedia("ftp://user:password@host:post/media.png", "media/png"), + ), + }, attachments) + }, + }, } for _, tc := range testCases { diff --git a/x/posts/spec/06-permissions.md b/x/posts/spec/06-permissions.md index 108e4d66ee..1107ddda14 100644 --- a/x/posts/spec/06-permissions.md +++ b/x/posts/spec/06-permissions.md @@ -12,7 +12,8 @@ of the following permissions. | **Permission Value** | **Permission Description** | |:------------------------|:----------------------------------------------| -| `WRITE_CONTENT` | Allows to create contents | +| `WRITE_CONTENT` | Allows to create any kind of contents | +| `COMMENT_CONTENT` | Allows only to comment existing contents | | `INTERACT_WITH_CONTENT` | Allows to interact with contents (e.g. polls) | | `EDIT_OWN_CONTENT` | Allows to edit the owned contents | | `MODERATE_CONTENT` | Allows to moderate users' contents | \ No newline at end of file diff --git a/x/posts/types/permissions.go b/x/posts/types/permissions.go index 7cc9731aa1..dd493b649d 100644 --- a/x/posts/types/permissions.go +++ b/x/posts/types/permissions.go @@ -10,6 +10,9 @@ var ( // PermissionWrite identifies users that can create content inside the subspace PermissionWrite = subspacestypes.RegisterPermission("write content") + // PermissionComment identifies users that can comment posts inside the subspace + PermissionComment = subspacestypes.RegisterPermission("comment content") + // PermissionInteractWithContent allows users to interact with content inside the subspace (eg. polls) PermissionInteractWithContent = subspacestypes.RegisterPermission("interact with content")