Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
zekroTJA committed Oct 16, 2020
2 parents 50e81cb + cde8857 commit 3de2db9
Show file tree
Hide file tree
Showing 50 changed files with 2,756 additions and 2,375 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ web/dist/
__debug_bin
deploy/
data/
.vscode/.ropeproject
.vscode/.ropeproject
cmd/test/*
34 changes: 24 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
1.2.1
1.3.0

> MINOR PATCH
> MAJOR PATCH
## Minor Improvements
## Major Implementations

- Discord assets in web interface are now requested with a proper resolution. [#163]
- Report revoke link in web interface is now only shown if the user has the permission to do so.
**Karma System Settings** [#146]

The Karma System is now configurable via the web interface. If you have the permission `sp.guild.config.karma`, you will see this entry in the guild settings of the web interface:
![](https://i.imgur.com/HiaUhJO.png)
There, you can enter the advanced guild settings interface where you can define preferences like enable or disable the karma system, emojis used to increase or decrease karma or the ammount of tokens available per hour per user.
![](https://i.imgur.com/CC7AgCV.png)

**Channel Locking** [#165]

Added the [`lock`](https://github.com/zekroTJA/shinpuru/wiki/Commands#lock) command to write-lock text channels. By either typing `sp!lock` into the channel which shall be locked or remotely by passing a channel resolvable (i.e. `sp!lock general`), you can write-lock channels. That means, that all roles in this channel (*below the role of the executor*) are explicitly disallowed to write messages in this channel. When hitting the same command again onto this channel, the permission state before the first execution of the `lock` command are restored.
![](https://i.imgur.com/wLJMDmP.gif)

## Security

Because shinpuru is using cookies containing a singed JWT with session information to authenticate requests against the HTTP REST API, it was vulnerable to [XSRF (Cross-site request forgery) attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery). This is now fixed by generating session-bound anti-forgery tokens, which are set using the `XSRF-TOKEN` cookie, which is readable by JavaScript. Angular then reads the cookie and sets it as `X-XSRF-TOKEN` header for each following `POST`, `PUT` or `DELETE` request. API-Token based authentications do not need to send the `X-XSRF-TOKEN`, be cause they are already authenticated using headers.

## Bug Fixes

- Fix permission update handling when adding the same permission rule as existent. [#161]
- Fix dropdown style in web interface ([old](https://i.imgur.com/m5uQZdq.png) vs [new](https://i.imgur.com/PWet0kD.png)).
- Fix color reaction spam on message edit. [#162]
- Permission rules bound to `@everyone` are now correctly processed.
- Also non-command specific permission rules are now correctly listed by the `GET /api/guilds/:guildID/:userID/permissions/allowed` endpoint.

## Backstage

- Moved the [permissions](https://github.com/zekroTJA/shinpuru/tree/master/pkg/permissions) and the [twitchnotify](https://github.com/zekroTJA/shinpuru/tree/master/pkg/twitchnotify) packages to the `pkg` public package domain.
- Refactored the [SQLite3 database middleware](https://github.com/zekroTJA/shinpuru/blob/master/internal/core/middleware/sqlite.go) so that it inherits all bindings from the MySQL middleware which redured it from 952 to only 161 lines of code.
- API handlers are now split up in seperate files for better overview.
- The `GetMemberPermissions` function is now moved from the database middlewares to the permission middleware.

# Docker

[Here](https://hub.docker.com/r/zekro/shinpuru) you can find the docker hub page of shinpuru.

Pull the docker image of this release:
```
$ docker pull zekro/shinpuru:1.2.1
$ docker pull zekro/shinpuru:1.3.0
```
4 changes: 2 additions & 2 deletions bughunters.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A list to honor all people who found some bugs, had some great ideas or contribu

| GitHub | Issues | PRs | Points* |
|--------|--------|-----|---------|
| [rmcproductions](https://github.com/rmcproductions) | [#52](https://github.com/zekroTJA/shinpuru/issues/52), [#61](https://github.com/zekroTJA/shinpuru/issues/61), [#67](https://github.com/zekroTJA/shinpuru/issues/67), [#147](https://github.com/zekroTJA/shinpuru/issues/147), [#148](https://github.com/zekroTJA/shinpuru/issues/148), [#150](https://github.com/zekroTJA/shinpuru/issues/150), [#153](https://github.com/zekroTJA/shinpuru/issues/153), [#159](https://github.com/zekroTJA/shinpuru/issues/159), [#163](https://github.com/zekroTJA/shinpuru/issues/163) | | `9` |
| [voxain](https://github.com/voxain) | [#52](https://github.com/zekroTJA/shinpuru/issues/52), [#61](https://github.com/zekroTJA/shinpuru/issues/61), [#67](https://github.com/zekroTJA/shinpuru/issues/67), [#147](https://github.com/zekroTJA/shinpuru/issues/147), [#148](https://github.com/zekroTJA/shinpuru/issues/148), [#150](https://github.com/zekroTJA/shinpuru/issues/150), [#153](https://github.com/zekroTJA/shinpuru/issues/153), [#159](https://github.com/zekroTJA/shinpuru/issues/159), [#163](https://github.com/zekroTJA/shinpuru/issues/163), [#165](https://github.com/zekroTJA/shinpuru/issues/165) | | `10` |
| [error2507](https://github.com/error2507) | [#28](https://github.com/zekroTJA/shinpuru/issues/28), [#29](https://github.com/zekroTJA/shinpuru/issues/29), [#55](https://github.com/zekroTJA/shinpuru/issues/55) | [#1](https://github.com/zekroTJA/shinpuru/pull/1), [#2](https://github.com/zekroTJA/shinpuru/pull/2) | `7` |
| [Eli-Dev](https://github.com/Eli-Dev) | [#45](https://github.com/zekroTJA/shinpuru/issues/45), [#47](https://github.com/zekroTJA/shinpuru/issues/47), [#49](https://github.com/zekroTJA/shinpuru/issues/49) | | `3` |
| [Not-Nik](https://github.com/Not-Nik) | [#53](https://github.com/zekroTJA/shinpuru/issues/53) | [#56](https://github.com/zekroTJA/shinpuru/pull/56) | `3` |
Expand All @@ -13,7 +13,7 @@ A list to honor all people who found some bugs, had some great ideas or contribu
| [Ron31](https://github.com/Ron31) | | [#32](https://github.com/zekroTJA/shinpuru/pull/32) | `2` |
| [Skillkiller](https://github.com/Skillkiller) | | [#79](https://github.com/zekroTJA/shinpuru/pull/79) | `2` |
| [nanderLP](https://github.com/nanderLP) | [#41](https://github.com/zekroTJA/shinpuru/issues/41) | | `1` |
| [DarkAmy](https://github.com/DarkAmy) | [#46](https://github.com/zekroTJA/shinpuru/issues/46) | | `1` |
| [AmyDoesGit](https://github.com/AmyDoesGit) | [#46](https://github.com/zekroTJA/shinpuru/issues/46) | | `1` |
| [newtox](https://github.com/newtox) | [#57](https://github.com/zekroTJA/shinpuru/issues/57) | | `1` |
| [anathemamask](https://github.com/anathemamask) | [#85](https://github.com/zekroTJA/shinpuru/issues/85) | | `1` |
| [Lukaesebrot](https://github.com/Lukaesebrot) | [#90](https://github.com/zekroTJA/shinpuru/issues/90) | | `1` |
Expand Down
16 changes: 16 additions & 0 deletions cmd/shinpuru/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"flag"
"os"
"os/signal"
"runtime/pprof"
"syscall"

"github.com/bwmarrin/discordgo"
Expand All @@ -20,6 +21,11 @@ var (
flagConfigLocation = flag.String("c", "config.yml", "The location of the main config file")
flagDocker = flag.Bool("docker", false, "wether shinpuru is running in a docker container or not")
flagDevMode = flag.Bool("devmode", false, "start in development mode")
flagProfile = flag.String("cpuprofile", "", "Records a CPU profile to the desired location")
)

const (
envKeyProfile = "CPUPROFILE"
)

//////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -50,6 +56,16 @@ func main() {
util.Log.Info("Covered by MIT Licence")
util.Log.Info("Starting up...")

if profLoc := util.GetEnv(envKeyProfile, *flagProfile); profLoc != "" {
f, err := os.Create(profLoc)
if err != nil {
util.Log.Fatal(err)
}
pprof.StartCPUProfile(f)
util.Log.Warningf("CPU profiling is active (loc: %s)", profLoc)
defer pprof.StopCPUProfile()
}

// Initialize discordgo session
session, err := discordgo.New()
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions docs/api-perms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Additional API Permission Rules

- `sp.guild.config.karma`
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/google/go-cmp v0.5.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/makeworld-the-better-one/go-isemoji v1.1.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/minio/minio-go v6.0.14+incompatible
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/makeworld-the-better-one/go-isemoji v1.1.0 h1:wZBHOKB5zAIgaU2vaWnXFDDhatebB8TySrNVxjVV84g=
github.com/makeworld-the-better-one/go-isemoji v1.1.0/go.mod h1:FBjkPl9rr0G4vlZCc+Mr+QcnOfGCTbGWYW8/1sp06I0=
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
Expand Down Expand Up @@ -119,6 +121,7 @@ github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.15.1 h1:eRb5jzWhbCn/cGu3gNJMcOfPUfXgXCcQIOHjh9ajAS8=
Expand Down Expand Up @@ -194,3 +197,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
224 changes: 224 additions & 0 deletions internal/commands/cmdlock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package commands

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"sort"

"github.com/bwmarrin/discordgo"
"github.com/zekroTJA/shinpuru/internal/core/database"
"github.com/zekroTJA/shinpuru/internal/util"
"github.com/zekroTJA/shinpuru/internal/util/static"
"github.com/zekroTJA/shinpuru/pkg/fetch"
"github.com/zekroTJA/shireikan"
)

const allowMask = discordgo.PermissionAll - discordgo.PermissionSendMessages

type CmdLock struct {
}

func (c *CmdLock) GetInvokes() []string {
return []string{"lock", "unlock", "lockchan", "unlockchan", "readonly", "ro", "chatlock"}
}

func (c *CmdLock) GetDescription() string {
return "Locks the channel so that no one can write there anymore until unlocked."
}

func (c *CmdLock) GetHelp() string {
return "`lock (<channelResolvable>)` - locks or unlocks either the current or the passed channel\n"
}

func (c *CmdLock) GetGroup() string {
return shireikan.GroupModeration
}

func (c *CmdLock) GetDomainName() string {
return "sp.guild.mod.lock"
}

func (c *CmdLock) GetSubPermissionRules() []shireikan.SubPermission {
return nil
}

func (c *CmdLock) IsExecutableInDMChannels() bool {
return false
}

func (c *CmdLock) Exec(ctx shireikan.Context) error {
db, _ := ctx.GetObject("db").(database.Database)

target, err := c.getTargetChan(ctx)
if err != nil {
return err
}

_, executorID, encodedPerms, err := db.GetLockChan(target.ID)

if database.IsErrDatabaseNotFound(err) {
return c.lock(target, ctx, db)
} else if err == nil {
return c.unlock(target, ctx, db, executorID, encodedPerms)
}

return err
}

func (c *CmdLock) getTargetChan(ctx shireikan.Context) (ch *discordgo.Channel, err error) {
res := ctx.GetArgs().Get(0).AsString()

if res == "" {
ch = ctx.GetChannel()
return
}

ch, err = fetch.FetchChannel(ctx.GetSession(), ctx.GetGuild().ID, res, func(cc *discordgo.Channel) bool {
return cc.Type == discordgo.ChannelTypeGuildText
})
if err != nil {
return
}
if ch == nil {
err = errors.New("could not fetch any text channel using this resolvable")
}

return
}

func (c *CmdLock) lock(target *discordgo.Channel, ctx shireikan.Context, db database.Database) error {
encodedPerms, err := c.encodePermissionOverrides(target.PermissionOverwrites)
if err != nil {
return err
}

guildRoles := ctx.GetGuild().Roles
sort.Slice(guildRoles, func(i, j int) bool {
return guildRoles[i].Position < guildRoles[j].Position
})

memberRoles := ctx.GetMember().Roles

highest := 0
rolesMap := make(map[string]*discordgo.Role)
for _, r := range guildRoles {
rolesMap[r.ID] = r
for _, mr := range memberRoles {
if r.ID != mr {
continue
}
if r.Position > highest {
highest = r.Position
}
}
}

// The info message needs to be sent before all permissions are set
// to prevent occuring errors due to potential missing permissions.
err = util.SendEmbed(ctx.GetSession(), target.ID,
fmt.Sprintf("This channel is chat-locked by %s.\nYou may not be able to chat "+
"into this channel until the channel is unlocked again.", ctx.GetUser().Mention()),
"", static.ColorEmbedOrange).
Error()
if err != nil {
return err
}

hasSetEveryone := false
for _, po := range target.PermissionOverwrites {
if po.Type == "role" {
if r, ok := rolesMap[po.ID]; ok && r.Position < highest {
if err = ctx.GetSession().ChannelPermissionSet(
target.ID, po.ID, "role", po.Allow&allowMask, po.Deny|discordgo.PermissionSendMessages); err != nil {
return err
}
}
}
if po.Type == "member" && ctx.GetUser().ID != po.ID && ctx.GetSession().State.User.ID != po.ID {
if err = ctx.GetSession().ChannelPermissionSet(
target.ID, po.ID, "member", po.Allow&allowMask, po.Deny|discordgo.PermissionSendMessages); err != nil {
return err
}
if po.ID == target.GuildID {
hasSetEveryone = true
}
}
}

if err = ctx.GetSession().ChannelPermissionSet(
target.ID, ctx.GetSession().State.User.ID, "member", discordgo.PermissionSendMessages&discordgo.PermissionReadMessages, 0); err != nil {
return err
}

if !hasSetEveryone {
if err = ctx.GetSession().ChannelPermissionSet(
target.ID, target.GuildID, "role", 0, discordgo.PermissionSendMessages); err != nil {
return err
}
}

if err = db.SetLockChan(target.ID, target.GuildID, ctx.GetUser().ID, encodedPerms); err != nil {
return err
}

return nil
}

func (c *CmdLock) unlock(target *discordgo.Channel, ctx shireikan.Context, db database.Database, executorID, encodedPerms string) error {
permissionOverrides, err := c.decodePermissionOverrrides(encodedPerms)
if err != nil {
return err
}

failed := 0
for _, po := range permissionOverrides {
if err = ctx.GetSession().ChannelPermissionSet(target.ID, po.ID, po.Type, po.Allow, po.Deny); err != nil {
failed++
}
}

if err = db.DeleteLockChan(target.ID); err != nil {
return err
}

if failed > 0 {
return util.SendEmbed(ctx.GetSession(), target.ID,
fmt.Sprintf("This channel is now unlocked. You can now chat here again.\n*(Unlocked by %s)*\n\n"+
"**Attention:** %d permission actions failed on reset!", ctx.GetUser().Mention(), failed),
"", static.ColorEmbedOrange).
Error()
}

return util.SendEmbed(ctx.GetSession(), target.ID,
fmt.Sprintf("This channel is now unlocked. You can now chat here again.\n*(Unlocked by %s)*", ctx.GetUser().Mention()),
"", static.ColorEmbedGreen).
Error()
}

func (c *CmdLock) encodePermissionOverrides(po []*discordgo.PermissionOverwrite) (res string, err error) {
buff := bytes.NewBuffer([]byte{})

if err = json.NewEncoder(buff).Encode(po); err != nil {
return
}

res = base64.StdEncoding.EncodeToString(buff.Bytes())

return
}

func (c *CmdLock) decodePermissionOverrrides(data string) (po []*discordgo.PermissionOverwrite, err error) {
po = make([]*discordgo.PermissionOverwrite, 0)

dataBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return
}

err = json.NewDecoder(bytes.NewBuffer(dataBytes)).Decode(&po)

return
}
Loading

0 comments on commit 3de2db9

Please sign in to comment.