// Handler that clients can use to get a jwt token. // Payload needs to be json in the form of {"username": "******", "password": "******"}. // Reply will be of the form {"token": "TOKEN"}. func (mw *JWTMiddleware) LoginHandler(writer rest.ResponseWriter, request *rest.Request) { login_vals := login{} err := request.DecodeJsonPayload(&login_vals) if err != nil { apiutils.WriteRestError(writer, apierrors.NewBadRequest(err.Error())) return } if login_vals.Username == "" { apiutils.WriteRestError(writer, apierrors.NewInvalid("login", "", fielderrors.ValidationErrorList{ fielderrors.NewFieldRequired("username")})) return } if login_vals.Password == "" { apiutils.WriteRestError(writer, apierrors.NewInvalid("login", "", fielderrors.ValidationErrorList{ fielderrors.NewFieldRequired("password")})) return } if !mw.Authenticator(login_vals.Username, login_vals.Password) { mw.unauthorized(writer) return } token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) if mw.PayloadFunc != nil { for key, value := range mw.PayloadFunc(login_vals.Username) { token.Claims[key] = value } } token.Claims["id"] = login_vals.Username token.Claims["exp"] = time.Now().Add(mw.Timeout).Unix() if mw.MaxRefresh != 0 { token.Claims["orig_iat"] = time.Now().Unix() } tokenString, err := token.SignedString(mw.Key) if err != nil { mw.unauthorized(writer) return } writer.WriteJson(&map[string]string{"token": tokenString}) }
func PostTransportTraffic(w rest.ResponseWriter, r *rest.Request) { form := shared.TransportTraffic{} err := r.DecodeJsonPayload(&form) if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } transportTrafficMu.Lock() defer transportTrafficMu.Unlock() transportTraffic = form if lg.V(10) { if len(transportTrafficLog) == 6 { lg.Infof("transport traffic: %.0fkb/s %.0fkb/s %.0fkb/s %.0fkb/s %.0fkb/s %.0fkb/s", (transportTrafficLog[0].Throughput)/1024, (transportTrafficLog[1].Throughput)/1024, (transportTrafficLog[2].Throughput)/1024, (transportTrafficLog[3].Throughput)/1024, (transportTrafficLog[4].Throughput)/1024, (transportTrafficLog[5].Throughput)/1024, ) transportTrafficLog = make([]shared.TransportTraffic, 0) } if transportTraffic.Throughput > 1024 { transportTrafficLog = append(transportTrafficLog, form) } } response := true w.WriteJson(response) }
func DeleteConnection(w rest.ResponseWriter, r *rest.Request) { ID := r.PathParam("id") err := clientconfig.Update(func(conf *clientconfig.Config) error { all := conf.Settings.Connections var result []shared.Connection // validate that the id exists found := false nEnabled := 0 for _, v := range all { if v.ID == ID { found = true if v.Protected { apiutils.WriteRestError(w, apierrors.NewForbidden("connection", ID, nil)) return nil } } else { if !v.Disabled { nEnabled += 1 } result = append(result, v) } } if !found { apiutils.WriteRestError(w, apierrors.NewNotFound("connection", ID)) return nil } if nEnabled < 1 { apiutils.WriteRestError(w, apierrors.NewForbidden("connection", ID, errors.New("one connection must be enabled"))) return nil } service.UpdateConnections(result) conf.Settings.Connections = result return nil }) if err != nil { lg.Errorln(err) } clientconfig.Write() w.WriteJson(true) }
func GetSuggestion(w rest.ResponseWriter, r *rest.Request) { ID := r.PathParam("id") form, ok := client.GetSuggestion(ID) if !ok { apiutils.WriteRestError(w, apierrors.NewNotFound("suggestion", ID)) return } w.WriteJson(form) }
func PostExportChromeExtension(w rest.ResponseWriter, r *rest.Request) { err := saveChromeExtension() if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } w.WriteJson(true) }
func PostBrowsercodeToClipboard(w rest.ResponseWriter, r *rest.Request) { conf := clientconfig.Get() ak := conf.Settings.Local.ClientAuthKey addr := conf.Settings.Local.ClientBindAddr bc := browsercode.BrowserCode{Key: ak} err := bc.SetHostport(addr) if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } err = bc.CopyToClipboard() if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } w.WriteJson(true) }
func ToggleConnection(w rest.ResponseWriter, r *rest.Request) { ID := r.PathParam("id") err := clientconfig.Update(func(conf *clientconfig.Config) error { // validate that the id exists, if supplied all := conf.Settings.Connections foundIdx := 0 found := false nEnabled := 0 for k, v := range all { if !v.Disabled { nEnabled += 1 } if v.ID == ID { found = true foundIdx = k } } if !found { apiutils.WriteRestError(w, apierrors.NewNotFound("connection", ID)) return nil } if nEnabled < 2 && !conf.Settings.Connections[foundIdx].Disabled { apiutils.WriteRestError(w, apierrors.NewForbidden("connection", ID, errors.New("one connection must be enabled"))) return nil } conf.Settings.Connections[foundIdx].Disabled = !conf.Settings.Connections[foundIdx].Disabled service.UpdateConnections(conf.Settings.Connections) return nil }) if err != nil { lg.Errorln(err) } clientconfig.Write() w.WriteJson(true) }
func PostUserSettings(w rest.ResponseWriter, r *rest.Request) { form := UserSettings{} err := r.DecodeJsonPayload(&form) if err != nil { apiutils.WriteRestError(w, apierrors.NewBadRequest(err.Error())) return } changed := false // true if config really was updated err = clientconfig.Update(func(conf *clientconfig.Config) error { s := &conf.Settings.Local prevLang := s.Language if ValidLanguage(form.Language) { s.Language = form.Language } else { s.Language = "en" } if prevLang != s.Language { ui.Language(s.Language) changed = true } if s.CountryCode != form.CountryCode { s.CountryCode = form.CountryCode changed = true } if s.ClientAutoUpdate != form.ClientAutoUpdate { s.ClientAutoUpdate = form.ClientAutoUpdate changed = true } if s.BlocklistAutoUpdate != form.BlocklistAutoUpdate { s.BlocklistAutoUpdate = form.BlocklistAutoUpdate changed = true } return nil }) if changed { err := clientconfig.Write() if err != nil { lg.Errorln(err) } } if err != nil { lg.Errorln(err) } }
func ValidateConnectionString(w rest.ResponseWriter, r *rest.Request) { form := ValidateConnectionStringRequest{} err := r.DecodeJsonPayload(&form) if err != nil { apiutils.WriteRestError(w, apierrors.NewBadRequest("can't decode json")) return } c, err := shared.DecodeConnection(form.ConnectionString) if err != nil { w.WriteJson(ValidateConnectionStringResponse{ Ok: false, }) return } w.WriteJson(ValidateConnectionStringResponse{ Ok: true, Name: c.DisplayName(), }) }
func (mw *JWTMiddleware) unauthorized(writer rest.ResponseWriter) { writer.Header().Set("WWW-Authenticate", "JWT realm="+mw.Realm) apiutils.WriteRestError(writer, apierrors.NewUnauthorized("")) }
// SubmitSuggestion initiates the comminication with Central for a Submission // session. func SubmitSuggestion(w rest.ResponseWriter, r *rest.Request) { // // TODO This is the response that must be forwarded from central/api and parsed by client and passed on to the browser. // apiutils.WriteRestError(w, // apierrors.NewInvalid("object", "suggestion", // fielderrors.ValidationErrorList{ // fielderrors.NewFieldValueNotSupported("URL", "...", []string{})})) // return ID := r.PathParam("id") suggestion, ok := client.GetSuggestion(ID) if !ok { apiutils.WriteRestError(w, apierrors.NewNotFound("suggestion", ID)) return } wanip := shared.GetPublicIPAddr() if wanip == nil { lg.Warning("could not resolve public ip addr") } conf := clientconfig.Get() restclient, err := NewRestClient() if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } tokenResp, err := suggestion.RequestToken( restclient, wanip, conf.Settings.Local.CountryCode) if err != nil { if apiutils.IsNetError(err) { apiutils.WriteRestError(w, apierrors.NewServerTimeout("alkasir-central", "request-submission-token", 0)) } else { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) } return } n, err := suggestion.SendSamples(restclient) if err != nil { lg.Warningln("error sending samples", err.Error()) } lg.V(5).Infof("sent %d samples", n) // continue sending samples if future measuremetns are expected to come prepared, err := suggestion.Prepared() if err != nil { lg.Errorln(err) } else if !prepared { lg.V(5).Infof("all samples not collected, will try to send the rest when they are done") go func(s client.Suggestion) { start := time.Now() t := time.NewTicker(30 * time.Second) defer t.Stop() submitSamples: for range t.C { if time.Now().After(start.Add(15 * time.Minute)) { lg.Errorln("Stopping trying to send additional samples") return } prepared, err := suggestion.Prepared() if err != nil { lg.Errorln(err) return } if prepared { restclient, err := NewRestClient() if err != nil { continue submitSamples } n, err := suggestion.SendSamples(restclient) lg.V(5).Infof("sent %d samples", n) if err != nil { lg.Warningln("error sending samples", err.Error()) continue submitSamples } return } } }(suggestion) } u, err := url.Parse(suggestion.URL) if err != nil { lg.Errorln(err) } else { err := clientconfig.Update(func(conf *clientconfig.Config) error { conf.BlockedHosts.Add(u.Host) lastBlocklistChange = time.Now() pac.UpdateBlockedList(conf.BlockedHostsCentral.Hosts, conf.BlockedHosts.Hosts) return nil }) if err != nil { lg.Errorln(err) } } w.WriteJson(tokenResp) }
// CreateSuggestion . func CreateSuggestion(w rest.ResponseWriter, r *rest.Request) { form := shared.BrowserSuggestionTokenRequest{} err := r.DecodeJsonPayload(&form) if err != nil { // apiError(w, err.Error(), http.StatusInternalServerError) apiutils.WriteRestError(w, err) return } var invalids fielderrors.ValidationErrorList // parse and validate url. URL := strings.TrimSpace(form.URL) if URL == "" { invalids = append(invalids, fielderrors.NewFieldRequired("URL")) // apiError(w, "no or empty URL", http.StatusBadRequest) } u, err := url.Parse(URL) if err != nil { invalids = append(invalids, fielderrors.NewFieldInvalid("URL", URL, err.Error())) // apiError(w, fmt.Sprintf("%s is not a valid URL", URL), http.StatusBadRequest) } host := u.Host if strings.Contains(host, ":") { host, _, err = net.SplitHostPort(u.Host) if err != nil { invalids = append(invalids, fielderrors.NewFieldInvalid("URL", URL, err.Error())) } } if !shared.AcceptedURL(u) { invalids = append(invalids, fielderrors.NewFieldValueNotSupported("URL", URL, nil)) } if len(invalids) > 0 { apiutils.WriteRestError(w, apierrors.NewInvalid("create-suggestion", "URL", invalids)) return } s := client.NewSuggestion(u.String()) defer s.DoneAddingSamples() measurers, err := measure.DefaultMeasurements(form.URL) if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } for _, v := range measurers { m, err := v.Measure() if err != nil { lg.Errorf("could not measure: %s", err.Error()) } else { switch m.Type() { case sampletypes.DNSQuery, sampletypes.HTTPHeader: err = s.AddMeasurement(m) if err != nil { lg.Errorln(err.Error()) return } default: lg.Warningf("unsupported sample type: %s", m.Type().String()) } } } }
func PostConnection(w rest.ResponseWriter, r *rest.Request) { form := ConnectionSetting{} err := r.DecodeJsonPayload(&form) if err != nil { apiutils.WriteRestError(w, apierrors.NewBadRequest(err.Error())) return } err = clientconfig.Update(func(conf *clientconfig.Config) error { all := conf.Settings.Connections var connection shared.Connection // decoded connection var invalids fielderrors.ValidationErrorList // Verify that the encoded field is set { if form.Encoded == "" { invalids = append(invalids, fielderrors.NewFieldRequired("encoded")) } } // verify that the connection is decodeable { var err error connection, err = shared.DecodeConnection(form.Encoded) if err != nil { invalids = append(invalids, fielderrors.NewFieldInvalid("encoded", form.Encoded, "invalid formatting")) } } // validate that the id exists, if supplied foundIdx := 0 { if form.ID != "" { found := false for k, v := range all { if v.ID == form.ID { found = true foundIdx = k } } if !found { invalids = append(invalids, fielderrors.NewFieldNotFound("id", form.ID)) } } } // validate that the connection doesnt alreay exist { encoded, err := connection.Encode() if err != nil { apiutils.WriteRestError(w, err) return nil } found := false for _, v := range all { enc2, err := v.Encode() if err != nil { lg.Errorln(err) continue } if enc2 == encoded { found = true } } if found { invalids = append(invalids, fielderrors.NewFieldDuplicate("encoded", form.Encoded)) } } // end of field validations if len(invalids) > 0 { apiutils.WriteRestError(w, apierrors.NewInvalid("post-connection", "", invalids)) return nil } // add connection to settings and save if form.ID == "" { connection.EnsureID() conf.Settings.Connections = append(conf.Settings.Connections, connection) } else { conf.Settings.Connections[foundIdx] = connection } service.UpdateConnections(conf.Settings.Connections) return nil }) if err != nil { lg.Errorln(err) } clientconfig.Write() w.WriteJson(true) }
// SubmitSuggestion initiates the comminication with Central for a Submission // session. func SubmitSuggestion(w rest.ResponseWriter, r *rest.Request) { // // TODO This is the response that must be forwarded from central/api and parsed by client and passed on to the browser. // apiutils.WriteRestError(w, // apierrors.NewInvalid("object", "suggestion", // fielderrors.ValidationErrorList{ // fielderrors.NewFieldValueNotSupported("URL", "...", []string{})})) // return ID := r.PathParam("id") suggestion, ok := client.GetSuggestion(ID) if !ok { apiutils.WriteRestError(w, apierrors.NewNotFound("suggestion", ID)) return } wanip := shared.GetPublicIPAddr() if wanip == nil { lg.Warning("could not resolve public ip addr") } conf := clientconfig.Get() restclient, err := NewRestClient() if err != nil { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) return } tokenResp, err := suggestion.RequestToken( restclient, wanip, conf.Settings.Local.CountryCode) if err != nil { if apiutils.IsNetError(err) { apiutils.WriteRestError(w, apierrors.NewServerTimeout("alkasir-central", "request-submission-token", 0)) } else { apiutils.WriteRestError(w, apierrors.NewInternalError(err)) } return } n, err := suggestion.SendSamples(restclient) if err != nil { lg.Warningln("error sending samples", err.Error()) } lg.V(5).Infoln("sent ", n) // FIXME PRESENTATION: just add the url locally u, err := url.Parse(suggestion.URL) if err != nil { lg.Errorln(err) } else { err := clientconfig.Update(func(conf *clientconfig.Config) error { conf.BlockedHosts.Add(u.Host) lastBlocklistChange = time.Now() pac.UpdateBlockedList(conf.BlockedHostsCentral.Hosts, conf.BlockedHosts.Hosts) return nil }) if err != nil { lg.Errorln(err) } } w.WriteJson(tokenResp) }
// Update list of blocked hosts for an IP address. func GetUpgrade(dbclients db.Clients) func(w rest.ResponseWriter, r *rest.Request) { return func(w rest.ResponseWriter, r *rest.Request) { req := shared.BinaryUpgradeRequest{} err := r.DecodeJsonPayload(&req) // TODO: proper validation response if err != nil { apiError(w, err.Error(), http.StatusInternalServerError) return } if req.FromVersion == "" { apiError(w, "fromversion required", http.StatusInternalServerError) return } v, err := version.NewVersion(req.FromVersion) if err != nil { lg.Warningln(err) apiError(w, fmt.Sprintf("invalid fromVersion %s", req.FromVersion), http.StatusInternalServerError) return } if req.Artifact == "" { apiError(w, "artifact required", http.StatusInternalServerError) return } res, found, err := dbclients.DB.GetUpgrade( db.GetUpgradeQuery{ Artifact: req.Artifact, }) if err != nil { apiError(w, err.Error(), http.StatusInternalServerError) return } if !found { apiutils.WriteRestError(w, apierrors.NewNotFound( "upgrade", fmt.Sprintf("%s-%s", req.Artifact, req.FromVersion))) return } serverVersion, err := version.NewVersion(res.Version) if err != nil { apiError(w, err.Error(), http.StatusInternalServerError) return } if serverVersion.LessThan(v) || serverVersion.Equal(v) { apiutils.WriteRestError(w, apierrors.NewNotFound( "upgrade", fmt.Sprintf("%s-%s", req.Artifact, req.FromVersion))) return } w.WriteJson(shared.BinaryUpgradeResponse{ Artifact: res.Artifact, Version: res.Version, CreatedAt: res.CreatedAt, SHA256Sum: res.SHA256Sum, ED25519Signature: res.ED25519Signature, }) } }