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

[WIP] actions for director tourney rooms #869

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions api/proto/mod_service/mod_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "google/protobuf/timestamp.proto";
// Moderation

enum ModActionType {
// sitewide mute:
MUTE = 0;
SUSPEND_ACCOUNT = 1;
SUSPEND_RATED_GAMES = 2;
Expand All @@ -16,6 +17,8 @@ enum ModActionType {
RESET_STATS_AND_RATINGS = 6;
REMOVE_CHAT = 7;
DELETE_ACCOUNT = 8;
SUSPEND_TOURNAMENT_ROOM = 9;
MUTE_IN_CHANNEL = 10;
}

enum EmailType {
Expand All @@ -39,6 +42,7 @@ message ModAction {
// Note: an optional note from the moderator.
string note = 12;
EmailType email_type = 13;
string suspended_tournament_id = 14;
}

message ModActionsMap { map<string, ModAction> actions = 1; }
Expand Down Expand Up @@ -77,6 +81,8 @@ message NotorietyReport {
}

service ModService {
rpc ApplyTDAction(ModAction) returns (ModActionResponse);
rpc RemoveTDAction(ModAction) returns (ModActionResponse);
rpc ApplyActions(ModActionsList) returns (ModActionResponse);
rpc RemoveActions(ModActionsList) returns (ModActionResponse);
rpc GetActions(GetActionsRequest) returns (ModActionsMap);
Expand Down
11 changes: 9 additions & 2 deletions pkg/bus/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,15 @@ func (b *Bus) chat(ctx context.Context, userID string, evt *pb.ChatMessage) erro
if err != nil {
return err
}

_, err = mod.ActionExists(ctx, b.userStore, userID, false, []ms.ModActionType{ms.ModActionType_SUSPEND_ACCOUNT, ms.ModActionType_MUTE})
_, err = mod.ActionExists(ctx, b.userStore, userID, false,
[]ms.ModActionType{ms.ModActionType_SUSPEND_ACCOUNT, ms.ModActionType_MUTE},
)
if err != nil {
return err
}
// Check specifically if user can chat in this channel.
_, err = mod.ActionExists(ctx, b.userStore, userID, false,
[]ms.ModActionType{ms.ModActionType_MUTE_IN_CHANNEL}, evt.Channel)
if err != nil {
return err
}
Expand Down
20 changes: 15 additions & 5 deletions pkg/mod/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var ModActionDispatching = map[ms.ModActionType]func(context.Context, user.Store
ms.ModActionType_SUSPEND_ACCOUNT,
ms.ModActionType_SUSPEND_RATED_GAMES,
ms.ModActionType_SUSPEND_GAMES,
ms.ModActionType_SUSPEND_TOURNAMENT_ROOM,
*/
ms.ModActionType_RESET_RATINGS: resetRatings,
ms.ModActionType_RESET_STATS: resetStats,
Expand All @@ -38,15 +39,17 @@ var ModActionDispatching = map[ms.ModActionType]func(context.Context, user.Store
}

var ModActionTextMap = map[ms.ModActionType]string{
ms.ModActionType_MUTE: "chatting",
ms.ModActionType_SUSPEND_ACCOUNT: "logging in",
ms.ModActionType_SUSPEND_RATED_GAMES: "playing rated games",
ms.ModActionType_SUSPEND_GAMES: "playing games",
ms.ModActionType_MUTE: "chatting",
ms.ModActionType_MUTE_IN_CHANNEL: "chatting in this channel",
ms.ModActionType_SUSPEND_ACCOUNT: "logging in",
ms.ModActionType_SUSPEND_RATED_GAMES: "playing rated games",
ms.ModActionType_SUSPEND_GAMES: "playing games",
ms.ModActionType_SUSPEND_TOURNAMENT_ROOM: "entering this room",
}

var RemovalDuration = 60

func ActionExists(ctx context.Context, us user.Store, uuid string, forceInsistLogout bool, actionTypes []ms.ModActionType) (bool, error) {
func ActionExists(ctx context.Context, us user.Store, uuid string, forceInsistLogout bool, actionTypes []ms.ModActionType, actionParams ...string) (bool, error) {
currentActions, err := GetActions(ctx, us, uuid)
if err != nil {
return false, err
Expand All @@ -66,6 +69,13 @@ func ActionExists(ctx context.Context, us user.Store, uuid string, forceInsistLo
for _, actionType := range actionTypes {
action, thisActionExists := currentActions[actionType.String()]
if thisActionExists {
if action.Type == ms.ModActionType_MUTE_IN_CHANNEL {
// check extra parameters
if len(actionParams) > 0 {
actionParams
}
}

if !actionExists {
actionExists = true
}
Expand Down
81 changes: 67 additions & 14 deletions pkg/mod/service.go → pkg/mod/service/service.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mod
package service

import (
"context"
Expand All @@ -9,6 +9,8 @@ import (

"github.com/domino14/liwords/pkg/apiserver"
"github.com/domino14/liwords/pkg/entity"
"github.com/domino14/liwords/pkg/mod"
"github.com/domino14/liwords/pkg/tournament"
"github.com/domino14/liwords/pkg/user"

pb "github.com/domino14/liwords/rpc/api/proto/mod_service"
Expand All @@ -23,15 +25,16 @@ type ctxkey string
const rtchankey ctxkey = "realtimechan"

type ModService struct {
userStore user.Store
notorietyStore NotorietyStore
chatStore user.ChatStore
mailgunKey string
discordToken string
userStore user.Store
notorietyStore mod.NotorietyStore
chatStore user.ChatStore
tournamentStore tournament.TournamentStore
mailgunKey string
discordToken string
}

func NewModService(us user.Store, cs user.ChatStore) *ModService {
return &ModService{userStore: us, chatStore: cs}
func NewModService(us user.Store, cs user.ChatStore, ts tournament.TournamentStore) *ModService {
return &ModService{userStore: us, chatStore: cs, tournamentStore: ts}
}

var AdminRequiredMap = map[pb.ModActionType]bool{
Expand All @@ -44,6 +47,14 @@ var AdminRequiredMap = map[pb.ModActionType]bool{
pb.ModActionType_RESET_STATS_AND_RATINGS: true,
pb.ModActionType_REMOVE_CHAT: false,
pb.ModActionType_DELETE_ACCOUNT: true,
pb.ModActionType_SUSPEND_TOURNAMENT_ROOM: false,
pb.ModActionType_MUTE_IN_CHANNEL: false,
}

var TournamentActionMap = map[pb.ModActionType]bool{
pb.ModActionType_SUSPEND_TOURNAMENT_ROOM: true,
pb.ModActionType_MUTE_IN_CHANNEL: true,
pb.ModActionType_REMOVE_CHAT: true,
}

func (ms *ModService) GetNotorietyReport(ctx context.Context, req *pb.GetNotorietyReportRequest) (*pb.NotorietyReport, error) {
Expand All @@ -56,7 +67,7 @@ func (ms *ModService) GetNotorietyReport(ctx context.Context, req *pb.GetNotorie
}
// Default to only getting 50 notorious games, which is probably much more than
// needed anyway.
score, games, err := GetNotorietyReport(ctx, ms.userStore, ms.notorietyStore, req.UserId, 50)
score, games, err := mod.GetNotorietyReport(ctx, ms.userStore, ms.notorietyStore, req.UserId, 50)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand All @@ -71,7 +82,7 @@ func (ms *ModService) ResetNotoriety(ctx context.Context, req *pb.ResetNotoriety
if !(user.IsAdmin || user.IsMod) {
return nil, twirp.NewError(twirp.Unauthenticated, errNotAuthorized.Error())
}
err = ResetNotoriety(ctx, ms.userStore, ms.notorietyStore, req.UserId)
err = mod.ResetNotoriety(ctx, ms.userStore, ms.notorietyStore, req.UserId)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand All @@ -86,7 +97,7 @@ func (ms *ModService) GetActions(ctx context.Context, req *pb.GetActionsRequest)
if !(user.IsAdmin || user.IsMod) {
return nil, twirp.NewError(twirp.Unauthenticated, errNotAuthorized.Error())
}
actions, err := GetActions(ctx, ms.userStore, req.UserId)
actions, err := mod.GetActions(ctx, ms.userStore, req.UserId)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand All @@ -101,7 +112,7 @@ func (ms *ModService) GetActionHistory(ctx context.Context, req *pb.GetActionsRe
if !(user.IsAdmin || user.IsMod) {
return nil, twirp.NewError(twirp.Unauthenticated, errNotAuthorized.Error())
}
history, err := GetActionHistory(ctx, ms.userStore, req.UserId)
history, err := mod.GetActionHistory(ctx, ms.userStore, req.UserId)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand All @@ -113,7 +124,7 @@ func (ms *ModService) RemoveActions(ctx context.Context, req *pb.ModActionsList)
if err != nil {
return nil, err
}
err = RemoveActions(ctx, ms.userStore, req.Actions)
err = mod.RemoveActions(ctx, ms.userStore, req.Actions)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand All @@ -125,7 +136,31 @@ func (ms *ModService) ApplyActions(ctx context.Context, req *pb.ModActionsList)
if err != nil {
return nil, err
}
err = ApplyActions(ctx, ms.userStore, ms.chatStore, req.Actions)
err = mod.ApplyActions(ctx, ms.userStore, ms.chatStore, req.Actions)
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
return &pb.ModActionResponse{}, nil
}

func (ms *ModService) ApplyTDAction(ctx context.Context, req *pb.ModAction) (*pb.ModActionResponse, error) {
err := authenticateDirector(ctx, ms, req)
if err != nil {
return nil, err
}
err = mod.ApplyActions(ctx, ms.userStore, ms.chatStore, []*pb.ModAction{req})
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
return &pb.ModActionResponse{}, nil
}

func (ms *ModService) RemoveTDAction(ctx context.Context, req *pb.ModAction) (*pb.ModActionResponse, error) {
err := authenticateDirector(ctx, ms, req)
if err != nil {
return nil, err
}
err = mod.ApplyActions(ctx, ms.userStore, ms.chatStore, []*pb.ModAction{req})
if err != nil {
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
}
Expand Down Expand Up @@ -165,3 +200,21 @@ func authenticateMod(ctx context.Context, ms *ModService, req *pb.ModActionsList
}
return nil
}

func authenticateDirector(ctx context.Context, ms *ModService, req *pb.ModAction) error {
user, err := sessionUser(ctx, ms)
if err != nil {
return err
}

if !TournamentActionMap[req.Type] {
return twirp.NewError(twirp.Unauthenticated, errNotAuthorized.Error())
}
err = tournament.AuthorizedDirector(ctx, user, ms.tournamentStore, req.SuspendedTournamentId, false)
if err != nil {
return err
}

// This is the director for this tournament.
return nil
}
111 changes: 111 additions & 0 deletions pkg/mod/service/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package service

import (
"context"

"os"
"testing"
"time"

"github.com/jinzhu/gorm"
"github.com/matryer/is"
"github.com/rs/zerolog/log"

"github.com/domino14/liwords/pkg/apiserver"
"github.com/domino14/liwords/pkg/entity"
"github.com/domino14/liwords/pkg/stores/user"
pkguser "github.com/domino14/liwords/pkg/user"
pb "github.com/domino14/liwords/rpc/api/proto/mod_service"
"github.com/twitchtv/twirp"
)

var TestDBHost = os.Getenv("TEST_DB_HOST")
var TestingDBConnStr = "host=" + TestDBHost + " port=5432 user=postgres password=pass sslmode=disable"

func recreateDB() {
// Create a database.
db, err := gorm.Open("postgres", TestingDBConnStr+" dbname=postgres")
if err != nil {
log.Fatal().Err(err).Msg("error")
}
defer db.Close()
db = db.Exec("DROP DATABASE IF EXISTS liwords_test")
if db.Error != nil {
log.Fatal().Err(db.Error).Msg("error")
}
db = db.Exec("CREATE DATABASE liwords_test")
if db.Error != nil {
log.Fatal().Err(db.Error).Msg("error")
}

ustore := userStore(TestingDBConnStr + " dbname=liwords_test")

for _, u := range []*entity.User{
{Username: "Spammer", Email: os.Getenv("TEST_EMAIL_USERNAME") + "[email protected]", UUID: "Spammer"},
{Username: "Sandbagger", Email: "[email protected]", UUID: "Sandbagger"},
{Username: "Cheater", Email: os.Getenv("TEST_EMAIL_USERNAME") + "@woogles.io", UUID: "Cheater"},
{Username: "Hacker", Email: "[email protected]", UUID: "Hacker"},
{Username: "Deleter", Email: "[email protected]", UUID: "Deleter"},
{Username: "Moderator", Email: "[email protected]", UUID: "Moderator", IsMod: true},
} {
err = ustore.New(context.Background(), u)
if err != nil {
log.Fatal().Err(err).Msg("error")
}
}
ustore.(*user.DBStore).Disconnect()
}

func userStore(dbURL string) pkguser.Store {
ustore, err := user.NewDBStore(TestingDBConnStr + " dbname=liwords_test")
if err != nil {
log.Fatal().Err(err).Msg("error")
}
return ustore
}

func TestAuthenticateMod(t *testing.T) {
is := is.New(t)

session := &entity.Session{
ID: "abcdef",
Username: "Moderator",
UserUUID: "Moderator",
Expiry: time.Now().Add(time.Second * 100)}
ctx := context.Background()
ctx = apiserver.PlaceInContext(ctx, session)

cstr := TestingDBConnStr + " dbname=liwords_test"
recreateDB()
us := userStore(cstr)
ms := &ModService{userStore: us}

err := authenticateMod(ctx, ms, &pb.ModActionsList{
Actions: []*pb.ModAction{},
})
is.NoErr(err)
us.(*user.DBStore).Disconnect()
}

func TestAuthenticateModNoAuth(t *testing.T) {
is := is.New(t)

session := &entity.Session{
ID: "defghi",
Username: "Cheater",
UserUUID: "Cheater",
Expiry: time.Now().Add(time.Second * 100)}
ctx := context.Background()
ctx = apiserver.PlaceInContext(ctx, session)

cstr := TestingDBConnStr + " dbname=liwords_test"
recreateDB()
us := userStore(cstr)
ms := &ModService{userStore: us}

err := authenticateMod(ctx, ms, &pb.ModActionsList{
Actions: []*pb.ModAction{},
})
is.Equal(err, twirp.NewError(twirp.Unauthenticated, errNotAuthorized.Error()))
us.(*user.DBStore).Disconnect()
}
Loading