| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //Modified by ArOZ Online Project for websocket message redirection purpose
- package main
- import (
- "log"
- "net/http"
- "encoding/json"
- uuid "github.com/google/uuid"
- "strings"
- )
- // Hub maintains the set of active clients and broadcasts messages to the
- // clients.
- type Hub struct {
- // Registered clients.
- clients map[*Client]bool
- // Inbound messages from the clients.
- broadcast chan msgpackage
- // Register requests from the clients.
- register chan *Client
- // Unregister requests from clients.
- unregister chan *Client
- //Register the username from clients
- usernames map[*Client]string
- //State if the user has logged in
- loggedin map[*Client]bool
- //Module name for application registry
- channel map[*Client] string
- //Instance UUID for direct messaging between two clients
- uuids map[*Client] string
- }
- func newHub() *Hub {
- return &Hub{
- broadcast: make(chan msgpackage),
- register: make(chan *Client),
- unregister: make(chan *Client),
- clients: make(map[*Client]bool),
- usernames: make(map[*Client]string),
- loggedin: make(map[*Client]bool),
- channel: make(map[*Client] string),
- uuids: make(map[*Client] string),
- }
- }
- type validAuthJSON struct {
- username string
- signDevice string
- createdTime int
- expTime int
- discarded bool
- }
- func checkLogin(h *Hub, sender *Client) bool{
- if (h.loggedin[sender] == true){
- return true
- }
- return false
- }
- func sendResp(h *Hub, reciver *Client, message []byte){
- select {
- case reciver.send <- msgpackage{message,reciver}:
- default:
- close(reciver.send)
- delete(h.clients, reciver)
-
- }
- }
- func sendBadReq(h *Hub, reciver *Client, command string){
- message := `{"type":"resp","command":"` + command + `", "data":"400 Bad Request"}`
- select {
- case reciver.send <- msgpackage{[]byte(message),reciver}:
- default:
- close(reciver.send)
- delete(h.clients, reciver)
-
- }
- }
- func runCommand(h *Hub, sender *Client, message string) string{
- commandChunks := strings.Split(message[1:]," ")
- commandOutput := `{"type":"resp","command":"` + commandChunks[0] + `", "data":"405 Method Not Allowed"}`;
- //log.Println(commandChunks)
- switch commandChunks[0]{
- case "login":
- if (len(commandChunks) != 3){
- //Malformated command input.
- sendBadReq(h, sender, "login");
- log.Println("Bad command received.")
- return "";
- }
- registerModuleName := commandChunks[1]
- thisJWT := commandChunks[2]
- validationURL := string(*endpt) + "?token=" + string(thisJWT)
- //Get response from server for authentication check
- resp, err := http.Get(validationURL)
- if err != nil {
- //Cannot reach auth services.
- commandOutput = `{"type":"resp","command":"login", "data":"503 Service Unavailable"}`;
- sendResp(h, sender, []byte(commandOutput))
- log.Println("Failed to access auth service. Is the jwt validation endpoint correct?")
- return "";
- }
- defer resp.Body.Close()
- //log.Printf("%#v\n", resp)
- dec := json.NewDecoder(resp.Body)
- if dec == nil {
- //JSON parse error
- commandOutput = `{"type":"resp","command":"login", "data":"500 Internal Server Error"}`;
- sendResp(h, sender, []byte(commandOutput))
- log.Println("Failed parse JSON return value. Is the aobws version match the ArOZ Online System version?")
- return "";
- //panic("Failed to start decoding JSON data")
- }
- json_map := make(map[string]interface{})
- err = dec.Decode(&json_map)
- if err != nil {
- panic(err)
- }
- if (json_map["error"] == nil){
- //Check if token discarded. If yes, also report as failed to login.
- if (json_map["discarded"] == true){
- //This token is discarded. Ignore login
- h.loggedin[sender] = false
- commandOutput = `{"type":"resp","command":"login", "data":"401 Unauthorized"}`;
- sendResp(h, sender, []byte(commandOutput))
- return "";
- }else{
- //This authentication is successful
- h.loggedin[sender] = true
- h.channel[sender] = registerModuleName
- h.usernames[sender] = json_map["username"].(string)
- commandOutput = `{"type":"resp","command":"login", "data":"202 Accepted"}`;
- }
-
- }else{
- //This authentication failed
- h.loggedin[sender] = false
- commandOutput = `{"type":"resp","command":"login", "data":"401 Unauthorized"}`;
- sendResp(h, sender, []byte(commandOutput))
- return "";
- }
- case "chklogin":
- if (len(commandChunks) != 1){
- //Malformated command input.
- sendBadReq(h, sender, "chklogin");
- log.Println("Bad command received.")
- return "";
- }
- if (checkLogin(h, sender)){
- commandOutput = `{"type":"resp","command":"chklogin", "data":"` + "Logged in as " + h.usernames[sender] + `"}`;
- }else{
- sendResp(h, sender, []byte(`{"type":"resp","command":"chklogin", "data":"Not logged in"}`))
- return ""
- }
- case "logout":
- if (len(commandChunks) != 1){
- //Malformated command input.
- sendBadReq(h, sender, "logout");
- log.Println("Bad command received.")
- return "";
- }
- if (checkLogin(h, sender)){
- h.loggedin[sender] = false
- sendResp(h, sender, []byte(`{"type":"resp","command":"logout", "data":"202 Accepted"}`))
- return ""
- }else{
- commandOutput = `{"type":"resp","command":"logout", "data":"401 Unauthorized"}`;
- }
- case "chkchannel":
- if (len(commandChunks) != 1){
- //Malformated command input.
- sendBadReq(h, sender, "chkchannel");
- log.Println("Bad command received.")
- return "";
- }
- if (checkLogin(h, sender)){
- commandOutput = `{"type":"resp","command":"chkchannel", "data":"` + h.channel[sender] + `"}`;
- }else{
- commandOutput = `{"type":"resp","command":"chkchannel", "data":"401 Unauthorized"}`;
- }
- case "chkuuid":
- if (len(commandChunks) != 1){
- //Malformated command input.
- sendBadReq(h, sender, "chkuuid");
- log.Println("Bad command received.")
- return "";
- }
- if (checkLogin(h, sender)){
- commandOutput = `{"type":"resp","command":"chkuuid", "data":"` + h.uuids[sender] + `"}`;
- }else{
- commandOutput = `{"type":"resp","command":"chkuuid", "data":"401 Unauthorized"}`;
- }
- case "tell":
- //Tell comamnd, given another username
- targetUsername := commandChunks[1]
- messageToBeDelivered := strings.Join(commandChunks[2:]," ")
- //Parse the common communication protocol
- msgpack := "{\"type\": \"tell\",\"sender\": \"" + h.usernames[sender] + "\", \"connUUID\": \"" + h.uuids[sender] + "\", \"data\": \"" + messageToBeDelivered + "\"}"
- if (checkLogin(h, sender)){
- //Send the message to all the clients with given username
- for client := range h.clients {
- if h.usernames[client] == targetUsername && client != sender{
- sendResp(h, client, []byte(msgpack))
- }
- }
- commandOutput = `{"type":"resp","command":"tell", "data":"200 OK"}`;
- }else{
- commandOutput = `{"type":"resp","command":"tell", "data":"401 Unauthorized"}`;
- }
- case "utell":
- //Tell comamnd, but using UUID instead of username
- targetUUID := commandChunks[1]
- messageToBeDelivered := strings.Join(commandChunks[2:]," ")
- msgpack := "{\"type\": \"utell\",\"sender\": \"" + h.usernames[sender] + "\", \"connUUID\": \"" + h.uuids[sender] + "\", \"data\": \"" + messageToBeDelivered + "\"}"
- if (checkLogin(h, sender)){
- //Send the message to all the clients with given username
- for client := range h.clients {
- if h.uuids[client] == targetUUID{
- sendResp(h, client, []byte(msgpack))
- }
- }
- commandOutput = `{"type":"resp","command":"utell", "data":"200 OK"}`;
- }else{
- commandOutput = `{"type":"resp","command":"utell", "data":"401 Unauthorized"}`;
- }
- case "help":
- //The standard help command. Shows the list of command usable via this websocket reflector
- resp := `ArOZ Online Base System WebSocket Reflector
- Usage:
- /login {channel} {shadowJWT token}
- /chklogin
- /logout
- /chkchannel
- /checkuuid
- /tell {username} {message}
- /utell {connnection UUID} {message}`;
- sendResp(h, sender, []byte(resp))
- return ""
- }
- return commandOutput
- }
- func handleMessage(h *Hub, sender *Client, message []byte) (bool, []byte){
- if (string(message)[0:1] == "/"){
- //This is a command.
- returnString := runCommand(h, sender, string(message))
- return false,[]byte(returnString)
- }
- return true,message
- }
- func (h *Hub) run() {
- for {
- select {
- case client := <-h.register:
- h.clients[client] = true //Enable this client as registered user
- h.usernames[client] = "anonymous" //Set its name to anonymous before it login
- h.loggedin[client] = false //Set to not logged in
- h.uuids[client] = (uuid.Must(uuid.NewRandom())).String() //Given this connection an uuid
- case client := <-h.unregister:
- if _, ok := h.clients[client]; ok {
- delete(h.clients, client)
- close(client.send)
- }
- case msg := <-h.broadcast:
- message := msg.message
- sender := msg.clientID
- log.Println(string(message), h.usernames[sender])
- //Handle the message
- brodcaseMessage, message := handleMessage(h,sender, message)
- if (string(message) == ""){
- //Do not return anything
- break;
- }
- //Check if the sender has already logged in
- if (h.loggedin[sender] == true){
- //User logged in
- if brodcaseMessage{
- //Broadcast the message to the Clients in the same channel
- senderChannel := h.channel[sender]
- msgpack := "{\"type\": \"broadcast\",\"sender\": \"" + h.usernames[sender] + "\", \"connUUID\": \"" + h.uuids[sender] + "\", \"data\": \"" + string(message) + "\"}"
- for client := range h.clients {
- //Send the message to all clients in the same channel
- if h.channel[client] == senderChannel{
- select {
- case client.send <- msgpackage{[]byte(msgpack),client}:
- default:
- close(client.send)
- delete(h.clients, client)
-
- }
- }
- }
- }else{
- //Only send reply back to the user
- select {
- case sender.send <- msgpackage{message,sender}:
- default:
- close(sender.send)
- delete(h.clients, sender)
-
- }
- }
- }else{
- //User not logged in
- select {
- case sender.send <- msgpackage{[]byte(`{"type":"resp","command":"generic", "data":"401 Unauthorized"}`),sender}:
- default:
- close(sender.send)
- delete(h.clients, sender)
-
- }
- }
-
- }
- }
- }
|