mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-14 16:09:32 +01:00
Implement draft/message-redaction (#2065)
* Makefile: Add dependencies between targets
* Implement draft/message-redaction for channels
Permission to use REDACT mirrors permission for 'HistServ DELETE'
* Error when the given targetmsg does not exist
* gofmt
* Add CanDelete enum type
* gofmt
* Add support for PMs
* Fix documentation of allow-individual-delete.
* Remove 'TODO: add configurable fallback'
slingamn says it's probably not desirable, and I'm on the fence.
Out of laziness, let's omit it for now, as it's not a regression
compared to '/msg HistServ DELETE'.
* Revert "Makefile: Add dependencies between targets"
This reverts commit 2182b1da69
.
---------
Co-authored-by: Val Lorentz <progval+git+ergo@progval.net>
This commit is contained in:
parent
bf33fba33a
commit
48f8c341d7
@ -982,7 +982,8 @@ history:
|
||||
|
||||
# options to control how messages are stored and deleted:
|
||||
retention:
|
||||
# allow users to delete their own messages from history?
|
||||
# allow users to delete their own messages from history,
|
||||
# and channel operators to delete messages in their channel?
|
||||
allow-individual-delete: false
|
||||
|
||||
# if persistent history is enabled, create additional index tables,
|
||||
|
@ -87,6 +87,12 @@ CAPDEFS = [
|
||||
url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
|
||||
standard="proposed IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MessageRedaction",
|
||||
name="draft/message-redaction",
|
||||
url="https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md",
|
||||
standard="proposed IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MessageTags",
|
||||
name="message-tags",
|
||||
|
@ -7,9 +7,9 @@ package caps
|
||||
|
||||
const (
|
||||
// number of recognized capabilities:
|
||||
numCapabs = 32
|
||||
numCapabs = 33
|
||||
// length of the uint32 array that represents the bitset:
|
||||
bitsetLen = 1
|
||||
bitsetLen = 2
|
||||
)
|
||||
|
||||
const (
|
||||
@ -57,6 +57,10 @@ const (
|
||||
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
||||
Languages Capability = iota
|
||||
|
||||
// MessageRedaction is the proposed IRCv3 capability named "draft/message-redaction":
|
||||
// https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md
|
||||
MessageRedaction Capability = iota
|
||||
|
||||
// Multiline is the proposed IRCv3 capability named "draft/multiline":
|
||||
// https://github.com/ircv3/ircv3-specifications/pull/398
|
||||
Multiline Capability = iota
|
||||
@ -156,6 +160,7 @@ var (
|
||||
"draft/chathistory",
|
||||
"draft/event-playback",
|
||||
"draft/languages",
|
||||
"draft/message-redaction",
|
||||
"draft/multiline",
|
||||
"draft/persistence",
|
||||
"draft/pre-away",
|
||||
|
@ -301,6 +301,10 @@ func init() {
|
||||
usablePreReg: true,
|
||||
minParams: 0,
|
||||
},
|
||||
"REDACT": {
|
||||
handler: redactHandler,
|
||||
minParams: 2,
|
||||
},
|
||||
"REHASH": {
|
||||
handler: rehashHandler,
|
||||
minParams: 0,
|
||||
|
@ -2663,6 +2663,97 @@ fail:
|
||||
return false
|
||||
}
|
||||
|
||||
// REDACT <target> <targetmsgid> [:<reason>]
|
||||
func redactHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
||||
target := msg.Params[0]
|
||||
targetmsgid := msg.Params[1]
|
||||
//clientOnlyTags := msg.ClientOnlyTags()
|
||||
var reason string
|
||||
if len(msg.Params) > 2 {
|
||||
reason = msg.Params[2]
|
||||
}
|
||||
var members []*Client // members of a channel, or both parties of a PM
|
||||
var canDelete CanDelete
|
||||
|
||||
msgid := utils.GenerateSecretToken()
|
||||
time := time.Now().UTC().Round(0)
|
||||
details := client.Details()
|
||||
isBot := client.HasMode(modes.Bot)
|
||||
|
||||
if target[0] == '#' {
|
||||
channel := server.channels.Get(target)
|
||||
if channel == nil {
|
||||
rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(target), client.t("No such channel"))
|
||||
return false
|
||||
}
|
||||
members = channel.Members()
|
||||
canDelete = deletionPolicy(server, client, target)
|
||||
} else {
|
||||
targetClient := server.clients.Get(target)
|
||||
if targetClient == nil {
|
||||
rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), target, "No such nick")
|
||||
return false
|
||||
}
|
||||
members = []*Client{client, targetClient}
|
||||
canDelete = canDeleteSelf
|
||||
}
|
||||
|
||||
if canDelete == canDeleteNone {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("You are not authorized to delete messages"))
|
||||
return false
|
||||
}
|
||||
accountName := "*"
|
||||
if canDelete == canDeleteSelf {
|
||||
accountName = client.AccountName()
|
||||
if accountName == "*" {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("You are not authorized to delete because you are logged out"))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
err := server.DeleteMessage(target, targetmsgid, accountName)
|
||||
if err == errNoop {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "UNKNOWN_MSGID", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("This message does not exist or is too old"))
|
||||
return false
|
||||
} else if err != nil {
|
||||
isOper := client.HasRoleCapabs("history")
|
||||
if isOper {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), fmt.Sprintf(client.t("Error deleting message: %v"), err))
|
||||
} else {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("Could not delete message"))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if target[0] != '#' {
|
||||
// If this is a PM, we just removed the message from the buffer of the other party;
|
||||
// now we have to remove it from the buffer of the client who sent the REDACT command
|
||||
err := server.DeleteMessage(client.Nick(), targetmsgid, accountName)
|
||||
|
||||
if err != nil {
|
||||
client.server.logger.Error("internal", fmt.Sprintf("Private message %s is not deletable by %s from their own buffer's even though we just deleted it from %s's. This is a bug, please report it in details.", targetmsgid, client.Nick(), target), client.Nick())
|
||||
isOper := client.HasRoleCapabs("history")
|
||||
if isOper {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), fmt.Sprintf(client.t("Error deleting message: %v"), err))
|
||||
} else {
|
||||
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("Error deleting message"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, member := range members {
|
||||
for _, session := range member.Sessions() {
|
||||
if session.capabilities.Has(caps.MessageRedaction) {
|
||||
session.sendFromClientInternal(false, time, msgid, details.nickMask, details.accountName, isBot, nil, "REDACT", target, targetmsgid, reason)
|
||||
} else {
|
||||
// If we wanted to send a fallback to clients which do not support
|
||||
// draft/message-redaction, we would do it from here.
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func reportPersistenceStatus(client *Client, rb *ResponseBuffer, broadcast bool) {
|
||||
settings := client.AccountSettings()
|
||||
serverSetting := client.server.Config().Accounts.Multiclient.AlwaysOn
|
||||
|
@ -435,6 +435,12 @@ Replies to a PING. Used to check link connectivity.`,
|
||||
text: `PRIVMSG <target>{,<target>} <text to be sent>
|
||||
|
||||
Sends the text to the given targets as a PRIVMSG.`,
|
||||
},
|
||||
"redact": {
|
||||
text: `REDACT <target> <targetmsgid> [<reason>]
|
||||
|
||||
Removes the message of the target msgid from the chat history of a channel
|
||||
or target user.`,
|
||||
},
|
||||
"relaymsg": {
|
||||
text: `RELAYMSG <channel> <spoofed nick> :<message>
|
||||
|
@ -15,6 +15,14 @@ import (
|
||||
"github.com/ergochat/ergo/irc/utils"
|
||||
)
|
||||
|
||||
type CanDelete uint
|
||||
|
||||
const (
|
||||
canDeleteAny CanDelete = iota // User is allowed to delete any message (for a given channel/PM)
|
||||
canDeleteSelf // User is allowed to delete their own messages (ditto)
|
||||
canDeleteNone // User is not allowed to delete any message (ditto)
|
||||
)
|
||||
|
||||
const (
|
||||
histservHelp = `HistServ provides commands related to history.`
|
||||
)
|
||||
@ -92,33 +100,53 @@ func histservForgetHandler(service *ircService, server *Server, client *Client,
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName))
|
||||
}
|
||||
|
||||
func histservDeleteHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
target, msgid := params[0], params[1] // Fix #1881 2 params are required
|
||||
|
||||
// operators can delete; if individual delete is allowed, a chanop or
|
||||
// the message author can delete
|
||||
accountName := "*"
|
||||
isChanop := false
|
||||
// Returns:
|
||||
//
|
||||
// 1. `canDeleteAny` if the client allowed to delete other users' messages from the target, ie.:
|
||||
// - the client is a channel operator, or
|
||||
// - the client is an operator with "history" capability
|
||||
//
|
||||
// 2. `canDeleteSelf` if the client is allowed to delete their own messages from the target
|
||||
// 3. `canDeleteNone` otherwise
|
||||
func deletionPolicy(server *Server, client *Client, target string) CanDelete {
|
||||
isOper := client.HasRoleCapabs("history")
|
||||
if !isOper {
|
||||
if isOper {
|
||||
return canDeleteAny
|
||||
} else {
|
||||
if server.Config().History.Retention.AllowIndividualDelete {
|
||||
channel := server.channels.Get(target)
|
||||
if channel != nil && channel.ClientIsAtLeast(client, modes.Operator) {
|
||||
isChanop = true
|
||||
return canDeleteAny
|
||||
} else {
|
||||
accountName = client.AccountName()
|
||||
return canDeleteSelf
|
||||
}
|
||||
} else {
|
||||
return canDeleteNone
|
||||
}
|
||||
}
|
||||
if !isOper && !isChanop && accountName == "*" {
|
||||
}
|
||||
|
||||
func histservDeleteHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||
target, msgid := params[0], params[1] // Fix #1881 2 params are required
|
||||
|
||||
canDelete := deletionPolicy(server, client, target)
|
||||
accountName := "*"
|
||||
if canDelete == canDeleteNone {
|
||||
service.Notice(rb, client.t("Insufficient privileges"))
|
||||
return
|
||||
} else if canDelete == canDeleteSelf {
|
||||
accountName = client.AccountName()
|
||||
if accountName == "*" {
|
||||
service.Notice(rb, client.t("Insufficient privileges"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := server.DeleteMessage(target, msgid, accountName)
|
||||
if err == nil {
|
||||
service.Notice(rb, client.t("Successfully deleted message"))
|
||||
} else {
|
||||
isOper := client.HasRoleCapabs("history")
|
||||
if isOper {
|
||||
service.Notice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err))
|
||||
} else {
|
||||
|
@ -954,7 +954,8 @@ history:
|
||||
|
||||
# options to control how messages are stored and deleted:
|
||||
retention:
|
||||
# allow users to delete their own messages from history?
|
||||
# allow users to delete their own messages from history,
|
||||
# and channel operators to delete messages in their channel?
|
||||
allow-individual-delete: false
|
||||
|
||||
# if persistent history is enabled, create additional index tables,
|
||||
|
Loading…
Reference in New Issue
Block a user