Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement draft/message-redaction #2065

Merged
merged 10 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ capdef_file = ./irc/caps/defs.go

all: build

install:
install: build
go install -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"

build:
build: ${capdef_file}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather we didn't, this just slows down the build in the normal case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's only a stat() call...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Make won't rebuild a target if a file with the same name already exists and is more recent than its dependencies)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I normally don't use this feature of make (I normally do 100% phony targets). I would prefer if we didn't introduce it because I don't understand it very well :-|

go build -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"

release:
release: build
goreleaser --skip-publish --rm-dist

capdefs:
${capdef_file}: ./gencapdefs.py
python3 ./gencapdefs.py > ${capdef_file}

test:
test: ${capdef_file}
python3 ./gencapdefs.py | diff - ${capdef_file}
go test ./...
go vet ./...
Expand Down
6 changes: 6 additions & 0 deletions gencapdefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
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",
Expand Down
9 changes: 7 additions & 2 deletions irc/caps/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

)

const (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -156,6 +160,7 @@ var (
"draft/chathistory",
"draft/event-playback",
"draft/languages",
"draft/message-redaction",
"draft/multiline",
"draft/persistence",
"draft/pre-away",
Expand Down
4 changes: 4 additions & 0 deletions irc/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ func init() {
usablePreReg: true,
minParams: 0,
},
"REDACT": {
handler: redactHandler,
minParams: 2,
},
"REHASH": {
handler: rehashHandler,
minParams: 0,
Expand Down
65 changes: 65 additions & 0 deletions irc/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2663,6 +2663,71 @@ 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]
}

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
}

canDelete := canDelete(server, client, target)
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 || true {
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
}

for _, member := range channel.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 {
// TODO: add configurable fallback
}
}
}
} else {
// TODO
}
return false
}

func reportPersistenceStatus(client *Client, rb *ResponseBuffer, broadcast bool) {
settings := client.AccountSettings()
serverSetting := client.server.Config().Accounts.Multiclient.AlwaysOn
Expand Down
6 changes: 6 additions & 0 deletions irc/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
48 changes: 37 additions & 11 deletions irc/histserv.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import (
"github.com/ergochat/ergo/irc/utils"
)

const (
canDeleteAny = iota // User is allowed to delete any message (for a given channel/PM)
progval marked this conversation as resolved.
Show resolved Hide resolved
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.`
)
Expand Down Expand Up @@ -92,33 +98,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 canDelete(server *Server, client *Client, target string) int {
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 := canDelete(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 {
Expand Down