|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "context" |
| 6 | + "encoding/json" |
| 7 | + "flag" |
| 8 | + "fmt" |
| 9 | + "io/ioutil" |
| 10 | + "log" |
| 11 | + "net/http" |
| 12 | + "net/url" |
| 13 | + "os" |
| 14 | + "os/signal" |
| 15 | + "time" |
| 16 | + |
| 17 | + "github.com/gorilla/websocket" |
| 18 | +) |
| 19 | + |
| 20 | +var eventId = flag.Int("event", 0, "Event id to make users from. On the format: 123") |
| 21 | +var wsUrl = flag.String("socket-url", "", `Websocket url, full url w/token. Look in console on abakus.no. On the format: "wss://ws.abakus-domain.no/?jwt=long-jwt-here"`) |
| 22 | +var authCookieVote = flag.String("vote-cookie", "", `Vote session cookie. On the format: "connect.sid=xasda"`) |
| 23 | +var csrfToken = flag.String("csrf-token", "", `A csrf token from VOTE. Look in a request. On the format: "RaNdom-String" `) |
| 24 | +var voteEndpoint = flag.String("vote-endpoint", "", `Endpoint to VOTE generate. On the format: "https://vote.abakus-domain.no/api/user/generate"`) |
| 25 | +var legoUserEndpont = flag.String("lego-user-endpoint", "", `Endpoint to users on LEGO. On the format: "https://lego-domain.no/api/v1/users/"`) |
| 26 | + |
| 27 | +type LegoAction struct { |
| 28 | + Type string `json:"type"` |
| 29 | +} |
| 30 | +type LegoRegisterAction struct { |
| 31 | + Meta struct { |
| 32 | + EventID int `json:"eventId"` |
| 33 | + ActivationTime time.Time `json:"activationTime"` |
| 34 | + FromPool interface{} `json:"fromPool"` |
| 35 | + } `json:"meta"` |
| 36 | + Payload struct { |
| 37 | + ID int `json:"id"` |
| 38 | + User struct { |
| 39 | + ID int `json:"id"` |
| 40 | + Username string `json:"username"` |
| 41 | + FirstName string `json:"firstName"` |
| 42 | + LastName string `json:"lastName"` |
| 43 | + FullName string `json:"fullName"` |
| 44 | + Gender string `json:"gender"` |
| 45 | + ProfilePicture string `json:"profilePicture"` |
| 46 | + InternalEmailAddress string `json:"internalEmailAddress"` |
| 47 | + } `json:"user"` |
| 48 | + Pool interface{} `json:"pool"` |
| 49 | + Status string `json:"status"` |
| 50 | + } `json:"payload"` |
| 51 | +} |
| 52 | +type LegoUnregisterAction struct { |
| 53 | + Meta struct { |
| 54 | + EventID int `json:"eventId"` |
| 55 | + ActivationTime time.Time `json:"activationTime"` |
| 56 | + FromPool interface{} `json:"fromPool"` |
| 57 | + } `json:"meta"` |
| 58 | + Payload struct { |
| 59 | + ID int `json:"id"` |
| 60 | + User struct { |
| 61 | + ID int `json:"id"` |
| 62 | + Username string `json:"username"` |
| 63 | + FirstName string `json:"firstName"` |
| 64 | + LastName string `json:"lastName"` |
| 65 | + FullName string `json:"fullName"` |
| 66 | + Gender string `json:"gender"` |
| 67 | + ProfilePicture string `json:"profilePicture"` |
| 68 | + InternalEmailAddress string `json:"internalEmailAddress"` |
| 69 | + } `json:"user"` |
| 70 | + Pool interface{} `json:"pool"` |
| 71 | + Status string `json:"status"` |
| 72 | + } `json:"payload"` |
| 73 | +} |
| 74 | + |
| 75 | +func main() { |
| 76 | + flag.Parse() |
| 77 | + if len(os.Args) == 1 { |
| 78 | + flag.Usage() |
| 79 | + os.Exit(2) |
| 80 | + } |
| 81 | + |
| 82 | + if len(*wsUrl) == 0 { |
| 83 | + log.Fatal("Missing wsUrl") |
| 84 | + } |
| 85 | + if *eventId == 0 { |
| 86 | + log.Fatal("Missing eventId") |
| 87 | + } |
| 88 | + if len(*authCookieVote) == 0 { |
| 89 | + log.Fatal("Missing authCookieVote") |
| 90 | + } |
| 91 | + if len(*csrfToken) == 0 { |
| 92 | + log.Fatal("Missing csrfToken") |
| 93 | + } |
| 94 | + if len(*voteEndpoint) == 0 { |
| 95 | + log.Fatal("Missing authCookieVote") |
| 96 | + } |
| 97 | + if len(*legoUserEndpont) == 0 { |
| 98 | + log.Fatal("Missing legoUserEndpoint") |
| 99 | + } |
| 100 | + |
| 101 | + log.SetFlags(0) |
| 102 | + |
| 103 | + interrupt := make(chan os.Signal, 1) |
| 104 | + signal.Notify(interrupt, os.Interrupt) |
| 105 | + |
| 106 | + u, _ := url.Parse(*wsUrl) |
| 107 | + jwt := u.Query()["jwt"][0] |
| 108 | + |
| 109 | + for { |
| 110 | + err := runLegoToVoteSync(interrupt, u, jwt) |
| 111 | + |
| 112 | + // Exit if runLegoToVoteSync returns nil, otherwise reconnect |
| 113 | + if err == nil { |
| 114 | + break |
| 115 | + } |
| 116 | + log.Printf("connection error, will reconnect: %e\n", err) |
| 117 | + time.Sleep(2 * time.Second) |
| 118 | + } |
| 119 | + |
| 120 | +} |
| 121 | +func runLegoToVoteSync(interrupt chan os.Signal, url *url.URL, jwt string) error { |
| 122 | + log.Printf("connecting to %s", url.String()) |
| 123 | + |
| 124 | + c, _, err := websocket.DefaultDialer.Dial(url.String(), nil) |
| 125 | + if err != nil { |
| 126 | + return fmt.Errorf("dial: %e", err) |
| 127 | + } |
| 128 | + defer c.Close() |
| 129 | + |
| 130 | + done := make(chan struct{}) |
| 131 | + |
| 132 | + go func() { |
| 133 | + defer close(done) |
| 134 | + for { |
| 135 | + _, message, err := c.ReadMessage() |
| 136 | + if err != nil { |
| 137 | + log.Println("read:", err) |
| 138 | + return |
| 139 | + } |
| 140 | + var abstractAction LegoAction |
| 141 | + err = json.Unmarshal(message, &abstractAction) |
| 142 | + if err != nil { |
| 143 | + log.Printf("Error decoding msg: %q with err: %e\n", message, err) |
| 144 | + continue |
| 145 | + } |
| 146 | + switch abstractAction.Type { |
| 147 | + case "Event.SOCKET_REGISTRATION.SUCCESS": |
| 148 | + var action LegoRegisterAction |
| 149 | + err = json.Unmarshal(message, &action) |
| 150 | + |
| 151 | + if err != nil { |
| 152 | + log.Printf("Error when decoding action: %e\n", err) |
| 153 | + break |
| 154 | + } |
| 155 | + |
| 156 | + if action.Meta.EventID != *eventId { |
| 157 | + break |
| 158 | + } |
| 159 | + |
| 160 | + log.Printf("Registering user %q in VOTE\n", action.Payload.User.FullName) |
| 161 | + client := &http.Client{} |
| 162 | + |
| 163 | + ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) |
| 164 | + defer cnl() |
| 165 | + |
| 166 | + legoGet, err := http.NewRequestWithContext(ctx, "GET", *legoUserEndpont+action.Payload.User.Username, nil) |
| 167 | + if err != nil { |
| 168 | + log.Printf("Error when creating lego user req: %e\n", err) |
| 169 | + break |
| 170 | + } |
| 171 | + legoGet.Header.Add("Authorization", "JWT "+jwt) |
| 172 | + |
| 173 | + resp, err := client.Do(legoGet) |
| 174 | + if err != nil { |
| 175 | + log.Printf("Error when fetching lego user: %e\n", err) |
| 176 | + break |
| 177 | + } |
| 178 | + var legoUserData struct { |
| 179 | + Email string `json:"email"` |
| 180 | + } |
| 181 | + defer resp.Body.Close() |
| 182 | + respData, err := ioutil.ReadAll(resp.Body) |
| 183 | + if err != nil { |
| 184 | + log.Printf("Error when reading lego user: %e\n", err) |
| 185 | + break |
| 186 | + } |
| 187 | + |
| 188 | + err = json.Unmarshal(respData, &legoUserData) |
| 189 | + |
| 190 | + if err != nil { |
| 191 | + log.Printf("Error when unmarshalling user: %e\n", err) |
| 192 | + break |
| 193 | + } |
| 194 | + |
| 195 | + voteFormData := struct { |
| 196 | + Email string `json:"email"` |
| 197 | + LegoUser string `json:"legoUser"` |
| 198 | + }{ |
| 199 | + Email: legoUserData.Email, |
| 200 | + LegoUser: action.Payload.User.Username, |
| 201 | + } |
| 202 | + |
| 203 | + out, err := json.Marshal(voteFormData) |
| 204 | + if err != nil { |
| 205 | + log.Printf("Error when marshalling user: %e\n", err) |
| 206 | + break |
| 207 | + } |
| 208 | + |
| 209 | + log.Printf("Posting to VOTE: %q", string(out)) |
| 210 | + req, err := http.NewRequest("POST", *voteEndpoint, bytes.NewBuffer(out)) |
| 211 | + if err != nil { |
| 212 | + log.Printf("Error when creating VOTE req: %e\n", err) |
| 213 | + break |
| 214 | + } |
| 215 | + req.Header.Add("CSRF-Token", *csrfToken) |
| 216 | + req.Header.Add("content-type", "application/json;charset=UTF-8") |
| 217 | + req.Header.Set("Cookie", *authCookieVote) |
| 218 | + |
| 219 | + resp, err = client.Do(req) |
| 220 | + if err != nil { |
| 221 | + log.Printf("Error when fetching VOTE: %e\n", err) |
| 222 | + break |
| 223 | + } |
| 224 | + |
| 225 | + defer resp.Body.Close() |
| 226 | + respData, err = ioutil.ReadAll(resp.Body) |
| 227 | + if err != nil { |
| 228 | + log.Printf("Error when reading from vote: %e\n", err) |
| 229 | + break |
| 230 | + } |
| 231 | + log.Printf("Creating user returned: %s, %s\n", resp.Status, respData) |
| 232 | + |
| 233 | + case "Event.SOCKET_UNREGISTRATION.SUCCESS": |
| 234 | + var action LegoUnregisterAction |
| 235 | + _ = json.Unmarshal(message, &action) |
| 236 | + |
| 237 | + if action.Meta.EventID != *eventId { |
| 238 | + break |
| 239 | + } |
| 240 | + |
| 241 | + log.Printf("User %q unregistered from event %d\n", action.Payload.User.FullName, *eventId) |
| 242 | + |
| 243 | + // We don't care? |
| 244 | + |
| 245 | + } |
| 246 | + } |
| 247 | + }() |
| 248 | + |
| 249 | + ticker := time.NewTicker(time.Second) |
| 250 | + defer ticker.Stop() |
| 251 | + |
| 252 | + for { |
| 253 | + select { |
| 254 | + case <-done: |
| 255 | + return fmt.Errorf("unexpected disconnect") |
| 256 | + case t := <-ticker.C: |
| 257 | + // Keepalive by chatting a bit I guess |
| 258 | + err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) |
| 259 | + if err != nil { |
| 260 | + log.Println("write:", err) |
| 261 | + return err |
| 262 | + } |
| 263 | + case <-interrupt: |
| 264 | + log.Println("interrupt") |
| 265 | + |
| 266 | + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) |
| 267 | + if err != nil { |
| 268 | + log.Println("write close:", err) |
| 269 | + return nil |
| 270 | + } |
| 271 | + select { |
| 272 | + case <-done: |
| 273 | + case <-time.After(time.Second): |
| 274 | + } |
| 275 | + return nil |
| 276 | + } |
| 277 | + } |
| 278 | + |
| 279 | +} |
0 commit comments