package localserver import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" ) const ( EDIT_DEFAULT_RIGHTS = "edit_default_rights" EDIT_USERS_RIGHTS = "edit_users_rights" LIST_ZONE_MEMBERS = "list_zone_members" LIST_ZONE_MEMBERS_RESPONSE = "list_zone_members_response" GET_USER = "get_user" GET_DEFAULT_RIGHTS = "get_default_rights" MODIFY_USER_CHAT_RIGHTS = "modify_user_chat_rights" ADD_KNOWN_CHAT = "add_known_chat" REMOVE_KNOWN_CHAT = "remove_known_chat" ADD_KNOWN_AUDIO_CHANNEL = "add_known_audio_channel" REMOVE_KNOWN_AUDIO_CHANNEL = "remove_known_audio_channel" ADD_KNOWN_VIDEO_CHANNEL = "add_known_video_channel" REMOVE_KNOWN_VIDEO_CHANNEL = "remove_known_video_channel" ADD_USER = "add_user" REMOVE_USER = "remove_user" ) const ( NEW_ZONE_USER = "new_zone_user" GET_CURRENT_USER_RESPONSE = "get_current_user_response" GET_USER_RESPONSE = "get_user_response" REMOVED_ZONE_USER = "removed_zone_user" DEFAULT_RIGHTS_EDITED = "default_rights_edited" USER_RIGHTS_EDITED = "user_rights_edited" ) type ZoneUsersHandler struct { ZoneId string ZoneMembersId []string DataChannels map[string]*DataChannel UserDataChannels map[string]*DataChannel UserDataChannelsFlag *uint32 Flag *uint32 Publishers []<-chan *ZoneRequest DB *ZoneUsersDBHandler reqChans []chan<- *ZoneRequest } type ZoneMember struct { ID string `json:"id"` Admin bool `json:"admin"` Owner bool `json:"owner"` Config *ZoneUserConfig `json:"config"` } type ZoneUserConfig struct { DefaultChatRights *ChatRights `json:"defaultChatsRights"` DefaultAudioChannelRights *AudioChannelRights `json:"defaultAudioChannelsRights"` DefaultVideoChannelRights *VideoChannelRights `json:"defaultVideoChannelsRights"` DefaultMediaRights *MediaRights `json:"defaultMediaRights"` DefaultFileRights *FileRights `json:"defaultFileRights"` } func NewZoneUsersHandler(zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, flag *uint32) (zoneUsersHandler *ZoneUsersHandler, err error) { _, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "users")) var zoneUsersDBHandler *ZoneUsersDBHandler if err != nil { if os.IsNotExist(err) { // logger.Printf("creating chat directory for zone %s...\n", zoneId) mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zoneId, "users"), 0700) if mkdirErr != nil { return nil, mkdirErr } file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zoneId, "users", "usersConfig.json")) defer func() { _ = file.Close() }() if ferr != nil { return nil, ferr } baseConfig := ZoneUserConfig{ DefaultChatRights: NewChatRights(true), DefaultAudioChannelRights: NewAudioChannelRights(true), DefaultVideoChannelRights: NewVideoChannelRights(true), DefaultMediaRights: NewMediaRights(true), DefaultFileRights: NewFileRights(true), } bs, jsonErr := json.Marshal(baseConfig) if jsonErr != nil { return nil, jsonErr } if _, writeErr := file.WriteString(string(bs)); writeErr != nil { return nil, writeErr } zoneUsersDBHandler, err = NewZoneUsersDBHandler(zoneId, owner, authorizedMembers, true) if err != nil { return } } else { return } } else { zoneUsersDBHandler, err = NewZoneUsersDBHandler(zoneId, owner, authorizedMembers, false) } if err != nil { return } userDCFlag := uint32(0) zoneUsersHandler = &ZoneUsersHandler{ ZoneId: zoneId, DataChannels: dataChannels, UserDataChannels: make(map[string]*DataChannel), UserDataChannelsFlag: &userDCFlag, ZoneMembersId: authorizedMembers, Flag: flag, DB: zoneUsersDBHandler, } return } func (zuh *ZoneUsersHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { users, err := zuh.DB.ListUsers(0, 1000) if err != nil { return } // logger.Println(authorizedMembers) for _, user := range users { var contain bool for _, member := range authorizedMembers { if user.ID == member { contain = true break } } if !contain { // logger.Println("userId", user.ID) if rerr := zuh.RemoveUser(user.ID); rerr != nil { // logger.Println(rerr) } go func(userId string) { for _, rc := range zuh.reqChans { // logger.Println("------------------------ sending to req chan ------------------------") rc <- &ZoneRequest{ ReqType: string(REMOVED_ZONE_AUTHORIZED_MEMBER), From: "node", Payload: map[string]interface{}{ "userId": userId, }, } } }(user.ID) } } return } func (zuh *ZoneUsersHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { done, errCh := make(chan struct{}, 10), make(chan error, 10) go func() { if err := atomicallyExecute(zuh.UserDataChannelsFlag, func() (err error) { if _, ok := zuh.UserDataChannels[to]; ok { bs, jsonErr := json.Marshal(&ZoneResponse{ Type: reqType, From: from, To: to, Payload: payload, }) if jsonErr != nil { return jsonErr } err = zuh.UserDataChannels[to].DataChannel.SendText(string(bs)) } return }); err != nil { errCh <- err return } done <- struct{}{} // logger.Printf("sending %v done\n", payload) }() return done, errCh } func (zuh *ZoneUsersHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error) zuh.reqChans = append(zuh.reqChans, reqChan) go func() { for { select { case <-ctx.Done(): done <- struct{}{} return case req := <-publisher: if err := zuh.handleZoneRequest(ctx, req); err != nil { errCh <- err } } } }() return } func (zuh *ZoneUsersHandler) AddUser(userId string) (err error) { bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zuh.ZoneId, "users", "usersConfig.json")) if err != nil { return } var baseUserConfig ZoneUserConfig if err = json.Unmarshal(bs, &baseUserConfig); err != nil { return } newUser := &User{ ID: userId, Name: userId, ChatRights: baseUserConfig.DefaultChatRights, AudioChannelRights: baseUserConfig.DefaultAudioChannelRights, VideoChannelRights: baseUserConfig.DefaultVideoChannelRights, MediaRights: baseUserConfig.DefaultMediaRights, FileRights: baseUserConfig.DefaultFileRights, KnownChatsId: []string{}, KnownAudioChannelsId: []string{}, KnownVideoChannelsId: make([]string, 0), KnownMediaFolderId: make([]string, 0), KnownFileFolderId: make([]string, 0), } if err = zuh.DB.AddNewUser(newUser); err != nil { return } err = atomicallyExecute(zuh.Flag, func() (err error) { users, err := zuh.DB.ListUsers(0, 10000) if err != nil { return } bs, jsonErr := json.Marshal(&ZoneResponse{ Type: NEW_ZONE_USER, From: "node", To: "user", Payload: map[string]interface{}{ "user": newUser, }, }) if jsonErr != nil { return jsonErr } for _, user := range users { if _, ok := zuh.DataChannels[user.ID]; ok { _ = zuh.DataChannels[user.ID].DataChannel.SendText(string(bs)) } } return }) return } func (zuh *ZoneUsersHandler) RemoveUser(userId string) (err error) { if err = zuh.DB.DeleteUser(userId); err != nil { return } err = atomicallyExecute(zuh.Flag, func() (err error) { users, err := zuh.DB.ListUsers(0, 10000) if err != nil { return } bs, jsonErr := json.Marshal(&ZoneResponse{ Type: REMOVED_ZONE_USER, From: "node", To: "user", Payload: map[string]interface{}{ "userId": userId, }, }) if jsonErr != nil { return jsonErr } if _, ok := zuh.DataChannels[userId]; ok { _ = zuh.DataChannels[userId].DataChannel.SendText(string(bs)) } for _, user := range users { if _, ok := zuh.DataChannels[user.ID]; ok { _ = zuh.DataChannels[user.ID].DataChannel.SendText(string(bs)) } } return }) return } func (zuh *ZoneUsersHandler) AddKnownChat(chatId string, userId string) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } for _, id := range user.KnownChatsId { if id == chatId { err = fmt.Errorf("user already know channel %s", chatId) return } } user.KnownChatsId = append(user.KnownChatsId, chatId) if err = zuh.DB.ModifyUser(userId, user); err != nil { return } return } func (zuh *ZoneUsersHandler) RemoveKnownChat(chatId string, userId string) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } var index int var contain bool for i, id := range user.KnownChatsId { if id == chatId { index = i contain = true break } } if !contain { err = fmt.Errorf("does not know this chat") return } if len(user.KnownChatsId) < 2 { user.KnownChatsId = make([]string, 0) } else { user.KnownChatsId = append(user.KnownChatsId[:index], user.KnownChatsId[index+1:]...) } err = zuh.DB.ModifyUser(userId, user) return } func (zuh *ZoneUsersHandler) AddKnownAudioChannel(channelId string, userId string) (err error) { // logger.Println("added new audio channel", channelId, userId) user, err := zuh.DB.GetUser(userId) if err != nil { return } for _, id := range user.KnownAudioChannelsId { if id == channelId { err = fmt.Errorf("user already know channel %s", channelId) return } } user.KnownAudioChannelsId = append(user.KnownAudioChannelsId, channelId) if err = zuh.DB.ModifyUser(userId, user); err != nil { return } return } func (zuh *ZoneUsersHandler) RemoveKnownAudioChannel(channelId string, userId string) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } var index int var contain bool for i, id := range user.KnownAudioChannelsId { if id == channelId { index = i contain = true break } } if !contain { err = fmt.Errorf("does not know this chat") return } if len(user.KnownAudioChannelsId) < 2 { user.KnownAudioChannelsId = make([]string, 0) } else { user.KnownAudioChannelsId = append(user.KnownAudioChannelsId[:index], user.KnownAudioChannelsId[index+1:]...) } err = zuh.DB.ModifyUser(userId, user) return } func (zuh *ZoneUsersHandler) AddKnownVideoChannel(channelId string, userId string) (err error) { // logger.Println("added new audio channel", channelId, userId) user, err := zuh.DB.GetUser(userId) if err != nil { return } for _, id := range user.KnownVideoChannelsId { if id == channelId { err = fmt.Errorf("user already know channel %s", channelId) return } } user.KnownVideoChannelsId = append(user.KnownVideoChannelsId, channelId) if err = zuh.DB.ModifyUser(userId, user); err != nil { return } return } func (zuh *ZoneUsersHandler) RemoveKnownVideoChannel(channelId string, userId string) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } var index int var contain bool for i, id := range user.KnownVideoChannelsId { if id == channelId { index = i contain = true break } } if !contain { err = fmt.Errorf("does not know this chat") return } if len(user.KnownVideoChannelsId) < 2 { user.KnownVideoChannelsId = make([]string, 0) } else { user.KnownVideoChannelsId = append(user.KnownVideoChannelsId[:index], user.KnownVideoChannelsId[index+1:]...) } err = zuh.DB.ModifyUser(userId, user) return } func (zuh *ZoneUsersHandler) AddKnownFolder(folderId string, userId string) (err error) { // logger.Println("added new known folder", folderId, userId) user, err := zuh.DB.GetUser(userId) if err != nil { return } for _, id := range user.KnownFileFolderId { if id == folderId { err = fmt.Errorf("user already know channel %s", folderId) return } } user.KnownVideoChannelsId = append(user.KnownVideoChannelsId, folderId) if err = zuh.DB.ModifyUser(userId, user); err != nil { return } return } func (zuh *ZoneUsersHandler) RemoveKnownFolder(folderId string, userId string) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } var index int var contain bool for i, id := range user.KnownFileFolderId { if id == folderId { index = i contain = true break } } if !contain { err = fmt.Errorf("does not know this chat") return } if len(user.KnownFileFolderId) < 2 { user.KnownFileFolderId = make([]string, 0) } else { user.KnownFileFolderId = append(user.KnownFileFolderId[:index], user.KnownFileFolderId[index+1:]...) } err = zuh.DB.ModifyUser(userId, user) return } func (zuh *ZoneUsersHandler) GetDefaultRights(userId string) (err error) { bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zuh.ZoneId, "users", "usersConfig.json")) if err != nil { return } var defaultRights ZoneUserConfig if err = json.Unmarshal(bs, &defaultRights); err != nil { return } d, e := zuh.sendDataChannelMessage(GET_DEFAULT_RIGHTS, "node", userId, map[string]interface{}{ "defaultRights": defaultRights, }) select { case <-d: case err = <-e: } return } func (zuh *ZoneUsersHandler) EditDefaultRights(defaultChatRights *ChatRights, defaultAudioChannelRights *AudioChannelRights, defaultVideoChannelRights *VideoChannelRights, defaultMediaRights *MediaRights, defaultFileRights *FileRights) (err error) { bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zuh.ZoneId, "users", "usersConfig.json")) if err != nil { return } var baseUserConfig ZoneUserConfig if err = json.Unmarshal(bs, &baseUserConfig); err != nil { return } baseUserConfig.DefaultChatRights = defaultChatRights baseUserConfig.DefaultAudioChannelRights = defaultAudioChannelRights baseUserConfig.DefaultVideoChannelRights = defaultVideoChannelRights baseUserConfig.DefaultMediaRights = defaultMediaRights baseUserConfig.DefaultFileRights = defaultFileRights bs, err = json.Marshal(baseUserConfig) if err != nil { return } file, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zuh.ZoneId, "users", "usersConfig.json"), os.O_WRONLY|os.O_TRUNC, os.ModeAppend) defer func() { _ = file.Close() }() if err != nil { return } if _, err = file.WriteString(string(bs)); err != nil { return } for _, member := range zuh.ZoneMembersId { done, err := zuh.sendDataChannelMessage(DEFAULT_RIGHTS_EDITED, "node", member, map[string]interface{}{ "defaultRights": baseUserConfig, }) select { case <-done: case <-err: // logger.Println(e) } } return } func (zuh *ZoneUsersHandler) EditUsersRights(users []interface{}, chatRights *ChatRights, audioChannelRights *AudioChannelRights, videoChannelRights *VideoChannelRights, mediaRights *MediaRights, fileRights *FileRights) (err error) { for _, u := range users { if user, ok := u.(string); ok { if err = zuh.editUserRights(user, chatRights, audioChannelRights, videoChannelRights, mediaRights, fileRights); err != nil { // logger.Println(err) } } } return } func (zuh *ZoneUsersHandler) editUserRights(userId string, chatRights *ChatRights, audioChannelRights *AudioChannelRights, videoChannelRights *VideoChannelRights, mediaRights *MediaRights, fileRights *FileRights) (err error) { user, err := zuh.DB.GetUser(userId) if err != nil { return } user.ChatRights = chatRights user.AudioChannelRights = audioChannelRights user.VideoChannelRights = videoChannelRights user.MediaRights = mediaRights user.FileRights = fileRights if err = zuh.DB.ModifyUser(userId, user); err != nil { return } for _, id := range zuh.ZoneMembersId { d, e := zuh.sendDataChannelMessage(USER_RIGHTS_EDITED, "node", userId, map[string]interface{}{ "userId": id, "user": user, }) select { case <-d: case err = <-e: } } return } func (zuh *ZoneUsersHandler) handleDataChannel(ctx context.Context, dc *DataChannel) (catched bool) { var label string _ = atomicallyExecute(dc.l, func() (err error) { label = dc.DataChannel.Label() return }) if strings.Contains(label, "user_data") { command := strings.Split(label, "|") catched = true _ = atomicallyExecute(zuh.UserDataChannelsFlag, func() (err error) { zuh.UserDataChannels[command[1]] = dc return }) dc.DataChannel.OnOpen(func() { done, errCh := zuh.sendDataChannelMessage("user_zone_init", zuh.ZoneId, command[1], map[string]interface{}{}) select { case <-done: fmt.Println("zone init sent") case err := <-errCh: fmt.Println(err) } }) dc.DataChannel.OnClose(func() { fmt.Println("closing gratefully user dc...") _ = atomicallyExecute(zuh.UserDataChannelsFlag, func() (err error) { delete(zuh.UserDataChannels, command[1]) return }) }) dc.DataChannel.OnError(func(err error) { fmt.Println("error in user dc...") _ = atomicallyExecute(zuh.UserDataChannelsFlag, func() (err error) { delete(zuh.UserDataChannels, command[1]) return }) }) } return } func (zuh *ZoneUsersHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { // logger.Println("got request in zone users handler", req) switch req.ReqType { case string(NEW_AUTHORIZED_ZONE_MEMBER): if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } err = zuh.AddUser(req.Payload["userId"].(string)) fmt.Println("done add user") case string(REMOVED_ZONE_AUTHORIZED_MEMBER): if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } err = zuh.RemoveUser(req.Payload["userId"].(string)) fmt.Println("done remove user") case EDIT_DEFAULT_RIGHTS: if _, ok := req.Payload["defaultRights"]; !ok { err = fmt.Errorf("no field defaultRights in request payload") return } bs, jsonErr := json.Marshal(req.Payload["defaultRights"]) if jsonErr != nil { return jsonErr } var config ZoneUserConfig if err = json.Unmarshal(bs, &config); err != nil { err = fmt.Errorf("the defaultRights payload dont match ZoneFSEntity struct pattern : %v", err) return } err = zuh.EditDefaultRights(config.DefaultChatRights, config.DefaultAudioChannelRights, config.DefaultVideoChannelRights, config.DefaultMediaRights, config.DefaultFileRights) case EDIT_USERS_RIGHTS: if err = VerifyFieldsSliceInterface(req.Payload, "users"); err != nil { return } if _, ok := req.Payload["defaultRights"]; !ok { err = fmt.Errorf("no field defaultRights in request payload") return } bs, jsonErr := json.Marshal(req.Payload["defaultRights"]) if jsonErr != nil { return jsonErr } var config ZoneUserConfig if err = json.Unmarshal(bs, &config); err != nil { err = fmt.Errorf("the defaultRights payload dont match ZoneFSEntity struct pattern : %v", err) return } err = zuh.EditUsersRights(req.Payload["users"].([]interface{}), config.DefaultChatRights, config.DefaultAudioChannelRights, config.DefaultVideoChannelRights, config.DefaultMediaRights, config.DefaultFileRights) case REMOVE_USER: if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } err = zuh.RemoveUser(req.Payload["userId"].(string)) case ADD_USER: if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } err = zuh.AddUser(req.Payload["userId"].(string)) case REMOVE_KNOWN_CHAT: if err = VerifyFieldsString(req.Payload, "chatId", "userId"); err != nil { return } err = zuh.RemoveKnownChat(req.Payload["chatId"].(string), req.Payload["userId"].(string)) case ADD_KNOWN_CHAT: if err = VerifyFieldsString(req.Payload, "chatId", "userId"); err != nil { return } err = zuh.AddKnownChat(req.Payload["chatId"].(string), req.Payload["userId"].(string)) case REMOVE_KNOWN_AUDIO_CHANNEL: if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zuh.RemoveKnownAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) case ADD_KNOWN_AUDIO_CHANNEL: if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zuh.AddKnownAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) case REMOVE_KNOWN_VIDEO_CHANNEL: if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zuh.RemoveKnownVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) case ADD_KNOWN_VIDEO_CHANNEL: if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zuh.AddKnownVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) case GET_DEFAULT_RIGHTS: if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } err = zuh.GetDefaultRights(req.Payload["userId"].(string)) case GET_USER: if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } if _, ok := req.Payload["init"]; !ok { err = fmt.Errorf("no field init in req payload for get user") return } if _, ok := req.Payload["init"].(bool); !ok { err = fmt.Errorf("field init is of wrong type") return } user, err := zuh.DB.GetUser(req.Payload["userId"].(string)) if err != nil { return err } // logger.Println("get user done") if req.Payload["init"].(bool) { done, errCh := zuh.sendDataChannelMessage(GET_CURRENT_USER_RESPONSE, "node", req.Payload["userId"].(string), map[string]interface{}{ "user": user, }) select { case <-done: case err = <-errCh: return err } } else { done, errCh := zuh.sendDataChannelMessage(GET_USER_RESPONSE, "node", req.Payload["userId"].(string), map[string]interface{}{ "user": user, }) select { case <-done: case err = <-errCh: return err } } case LIST_ZONE_MEMBERS: user, err := zuh.DB.GetUser(req.From) if err != nil { return err } users, err := zuh.DB.ListUsers(0, 1000) if err != nil { return err } usersIds := []*ZoneMember{} groupCounter := 0 if user.Admin || user.Owner { for _, user := range users { if groupCounter >= 8 { done, errCh := zuh.sendDataChannelMessage(LIST_ZONE_MEMBERS_RESPONSE, zuh.ZoneId, req.From, map[string]interface{}{ "done": false, "users": usersIds, }) select { case <-done: // logger.Println("send get user done") case err = <-errCh: return err } groupCounter = 0 usersIds = []*ZoneMember{} } usersIds = append(usersIds, &ZoneMember{ ID: user.ID, Admin: user.Admin, Owner: user.Owner, Config: &ZoneUserConfig{ DefaultChatRights: user.ChatRights, DefaultAudioChannelRights: user.AudioChannelRights, DefaultVideoChannelRights: user.VideoChannelRights, DefaultMediaRights: user.MediaRights, DefaultFileRights: user.FileRights, }, }) groupCounter++ } } else { for _, user := range users { if groupCounter >= 8 { done, errCh := zuh.sendDataChannelMessage(LIST_ZONE_MEMBERS_RESPONSE, zuh.ZoneId, req.From, map[string]interface{}{ "done": false, "users": usersIds, }) select { case <-done: // logger.Println("send get user done") case err = <-errCh: return err } groupCounter = 0 usersIds = []*ZoneMember{} } usersIds = append(usersIds, &ZoneMember{ ID: user.ID, Admin: user.Admin, Owner: user.Owner, }) groupCounter++ } } // logger.Println("get user done") done, errCh := zuh.sendDataChannelMessage(LIST_ZONE_MEMBERS_RESPONSE, zuh.ZoneId, req.From, map[string]interface{}{ "done": true, "users": usersIds, }) select { case <-done: // logger.Println("send get user done") case err = <-errCh: return err } case MODIFY_USER_CHAT_RIGHTS: } return }