// Authenticate checks a request's basic authentication, and associates a *models.User with the context // if so. Otherwise it handles responding to and closing the request. // // contextWithUser, authWasSuccessful := routes.Authenticate(ctx, w, r, logger, db) // // Use for any requests which expect to act on behalf of a user (which is most) func Authenticate(ctx context.Context, w http.ResponseWriter, r *http.Request, l services.Logger, db services.DB) (context.Context, bool) { l = l.WithPrefix("routes.Authenticate: ") var ( c *models.Credential u *models.User err error ) public, private, ok := r.BasicAuth() if !ok { l.Printf("authentication reverting from basic auth to session") // assume std lib didn't make a mistake, and the BasicAuth simply wasn't given // fall back to cookie if sesh, err := session(r, db); err != nil { switch err { case http.ErrNoCookie: l.Printf("no session cookie") case data.ErrNotFound: l.Printf("session token not found") default: l.Printf("session(r, db) error: %s", err) } l.Printf("authentication reverting from cookie to form values") public, private = r.FormValue(publicParam), r.FormValue(privateParam) } else if sesh.Valid() { if u, err := sesh.Owner(db); err != nil { l.Printf("sesh.Owner(db) error: %s", err) public, private = r.FormValue(publicParam), r.FormValue(privateParam) } else { return user.NewContext(ctx, u), true } } else { l.Printf("session no longer valid") public, private = r.FormValue(publicParam), r.FormValue(privateParam) } } if c, err = access.Authenticate(db, public, private); err != nil { l.Printf("authentication of (%s, %s) failed: couldn't find credential: %s", public, private, err) goto unauthorized // this error is on us, but it manifests as a failure to authenticate } if u, err = c.Owner(db); err != nil { l.Printf("authentication failed: couldn't load user's owner: %s", err) goto unauthorized // this error is also on us, but also manifests as a failure to authenticate } // successful authentications return user.NewContext(ctx, u), true // rejection unauthorized: http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return nil, false }
func TestDeletePOST(t *testing.T) { db := mem.NewDB() u, _, err := user.Create(db, "username", "password") if err != nil { t.Fatalf("user.Create error: %v", err) } e := &models.Event{ Id: db.NewID().String(), OwnerId: u.Id, Name: "event name", Data: map[string]interface{}{ "sensor": 45.3, }, } if err := db.Save(e); err != nil { t.Fatalf("db.Save error: %v", err) } s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.RequestURI, "/records/delete/") { records.DeletePOST(user.NewContext(context.Background(), u), w, r, db, services.NewTestLogger(t)) } records.QueryGET(user.NewContext(context.Background(), u), w, r, db, services.NewTestLogger(t)) })) p := s.URL + "/records/delete/?" + url.Values{ "kind": []string{"event"}, "id": []string{e.Id}, }.Encode() resp, err := http.Post(p, "", new(bytes.Buffer)) if err != nil { t.Fatalf("http.Post error: %v", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("ioutil.ReadAll error: %v", err) } t.Logf("resp.Body:\n%s", body) if got, want := db.PopulateByID(e), data.ErrNotFound; got != want { t.Fatalf("db.PopulateByID: got %v, want %v", got, want) } }
func ContextualizeCommandWebGET(ctx context.Context, db data.DB, logger services.Logger) websocket.Handler { return func(c *websocket.Conn) { if err := c.Request().ParseForm(); err != nil { logger.Print("Failure parsing form") return } public := c.Request().Form.Get("public") private := c.Request().Form.Get("private") if public == "" || private == "" { logger.Print("failed to retrieve credentials") return } cred, err := access.Authenticate(db, public, private) if err != nil { logger.Print("failed to auth") return } u, _ := cred.Owner(db) CommandWebGET(user.NewContext(ctx, u), c, logger, db) } }
func BenchmarkRecordPostEvent(b *testing.B) { db := mem.NewDB() u, _, err := user.Create(db, "username", "password") if err != nil { b.Fatalf("user.Create error: %v", err) } ctx := user.NewContext(context.Background(), u) logger := services.NewTestLogger(b) for i := 0; i < b.N; i++ { rec := httptest.NewRecorder() req, err := http.NewRequest( "POST", "http://www.elos.pw/record/?"+url.Values{ "kind": []string{models.EventKind.String()}, }.Encode(), bytes.NewBuffer( []byte(`{ "name": "event name", "data": { "sensor1": 34, "sensor2": 4.3 }, "owner_id": "`+u.ID().String()+`" }`), )) if err != nil { b.Fatalf("http.NewRequest error: %v", err) } routes.RecordPOST(ctx, rec, req, logger, db) } }
func ContextualizeRecordChangesGET(ctx context.Context, db data.DB, logger services.Logger) websocket.Handler { return func(ws *websocket.Conn) { defer ws.Close() l := logger.WithPrefix("ContextualizeRecordChangesGET: ") if err := ws.Request().ParseForm(); err != nil { l.Printf("error parsing form: %s", err) return } public := ws.Request().Form.Get(publicParam) private := ws.Request().Form.Get(privateParam) if public == "" || private == "" { l.Print("failed to retrieve credentials") return } cred, err := access.Authenticate(db, public, private) if err != nil { l.Print("failed to authenticate") return } if u, err := cred.Owner(db); err != nil { l.Print("error retrieving user: %s", err) } else { RecordChangesGET(user.NewContext(ctx, u), ws, db, logger) } } }
func TestViewGET(t *testing.T) { db := mem.WithData(map[data.Kind][]data.Record{ models.UserKind: { &models.User{ Id: "1", CredentialsIds: []string{"2"}, }, }, models.CredentialKind: { &models.Credential{ Id: "2", OwnerId: "1", Spec: "password", Public: "public", Private: "private", }, }, }) s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { records.ViewGET(user.NewContext(context.Background(), &models.User{Id: "1"}), w, r, db, services.NewTestLogger(t)) })) p := s.URL + "?" + url.Values{ "kind": []string{"credential"}, "id": []string{"2"}, }.Encode() resp, err := http.Get(p) if err != nil { t.Fatalf("http.Get(%q) error: %v", err) } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("ioutil.ReadAll error: %v", err) } body := string(b) t.Logf("resp.Body:\n%s", body) contents := map[string]bool{ "credential": true, "public": true, "password": true, "owner": true, "/records/edit/?kind=credential&id=2": true, "user": false, } for content, want := range contents { if got := strings.Contains(body, content); got != want { t.Fatalf("strings.Contains(body, %q): got %t, want %t", content, got, want) } } }
func TestEditGET(t *testing.T) { db := mem.NewDB() u, _, err := user.Create(db, "username", "password") if err != nil { t.Fatalf("user.Create error: %v", err) } e := &models.Event{ Id: db.NewID().String(), OwnerId: u.Id, Name: "old name", Data: map[string]interface{}{ "sensor": 4, }, } if err := db.Save(e); err != nil { t.Fatalf("db.Save error: %v", err) } s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { records.EditGET(user.NewContext(context.Background(), u), w, r, db, services.NewTestLogger(t)) })) p := s.URL + "?" + url.Values{ "kind": []string{"event"}, "id": []string{e.Id}, }.Encode() resp, err := http.Get(p) if err != nil { t.Fatalf("http.Get(%q) error: %v", p, err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("ioutil.ReadAll error: %v", err) } t.Logf("resp.Body:\n%s", body) if got, want := bytes.Contains(body, []byte(`event`)), true; got != want { t.Fatalf("bytes.Contains(body, %q): got %t, want %t", "event", got, want) } if got, want := bytes.Contains(body, []byte(`old name`)), true; got != want { t.Fatalf("bytes.Contains(body, %q): got %t, want %t", "old name", got, want) } if got, want := bytes.Contains(body, []byte(`/records/view/?kind=event&id=3`)), true; got != want { t.Fatalf("bytes.Contains(body, %q): got %t, want %t", `/records/view/?kind=event&id=3`, got, want) } }
func TestEditPOST(t *testing.T) { db := mem.NewDB() u, _, err := user.Create(db, "username", "password") if err != nil { t.Fatalf("user.Create error: %v", err) } e := &models.Event{ Id: db.NewID().String(), OwnerId: u.Id, Name: "old name", Data: map[string]interface{}{ "sensor": 4, }, Time: time.Now(), } if err := db.Save(e); err != nil { t.Fatalf("db.Save error: %v", err) } s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.RequestURI, "/records/edit/") { records.EditPOST(user.NewContext(context.Background(), u), w, r, db, services.NewTestLogger(t)) return } w.WriteHeader(http.StatusOK) w.Write([]byte(`default test handler`)) })) p := s.URL + "/records/edit/?" + url.Values{ "kind": []string{"event"}, "id": []string{e.Id}, "event/Name": []string{"new eman"}, "event/Data": []string{`{"sensor": 4, "sensor2": 9}`}, }.Encode() resp, err := http.Post(p, "", new(bytes.Buffer)) if err != nil { t.Fatalf("http.Post error: %v", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("iotuil.ReadAll error: %v", err) } t.Logf("resp.Body:\n%s", body) ue := &models.Event{Id: e.Id} if err := db.PopulateByID(ue); err != nil { t.Fatalf("db.PopulateByID error: %v", err) } if got, want := ue.Name, "new eman"; got != want { t.Errorf("ue.Name: got %q, want %q", got, want) } if got, want := ue.Data, map[string]interface{}{"sensor": 4.0, "sensor2": 9.0}; !reflect.DeepEqual(got, want) { t.Errorf("ue.Data: got %v, want %v", got, want) } if got, want := ue.Time, e.Time; got != want { t.Errorf("ue.Time: got %v, want %v", got, want) } if got, want := ue.OwnerId, e.OwnerId; got != want { t.Errorf("ue.OwnerId: got %q, want %q", got, want) } }
func TestCreatePOST(t *testing.T) { adb, dbc, ac, closers, err := records_test.ClientsFromState(data.State{ models.Kind_USER: []*data.Record{ *data.Record{ Kind: models.Kind_USER, User: &models.User{ Id: "1", }, }, }, models.Kind_CREDENTIAL: []*data.Record{ &data.Record{ Kind: models.Kind_CREDENTIAL, Credential: &models.Credential{ Id: "2", OwnerId: "1", Type: models.Credential_PASSWORD, Public: "username", Private: "password", }, }, }, }) defer func() { records_test.CloseAll(closers) }() wui := records.NewWebUI(adb, ac) s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { records.CreatePOST(user.NewContext(context.Background(), u), w, r, db, services.NewTestLogger(t), webuiclient) })) p := s.URL + "?" + url.Values{ "kind": []string{"EVENT"}, "EVENT/OwnerId": []string{"1"}, "EVENT/Name": []string{"event name"}, "EVENT/Quantities": []string{`[{"name": "sensor", "value": 45}]`}, }.Encode() resp, err := http.Post(p, "", new(bytes.Buffer)) if err != nil { t.Fatalf("http.Post error: %v", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("ioutil.ReadAll error: %v", err) } t.Logf("resp.Body:\n%s", body) if err := data.CompareState(dbc, data.State{ models.Kind_USER: []*data.Record{ *data.Record{ Kind: models.Kind_USER, User: &models.User{ Id: "1", }, }, }, models.Kind_CREDENTIAL: []*data.Record{ &data.Record{ Kind: models.Kind_CREDENTIAL, Credential: &models.Credential{ Id: "2", OwnerId: "1", Type: models.Credential_PASSWORD, Public: "username", Private: "password", }, }, }, models.Kind_EVENT: []*data.Record{ &data.Record{ Kind: models.Kind_EVENT, Event: &models.Event{ Id: "3", OwnerId: "1", Name: "event name", Quantities: []*Quantity{ Name: "sensor", Value: 45, }, }, }, }, }); err != nil { t.Errorf("data.CompareState error: %v", err) } }
func TestQueryGET(t *testing.T) { db := mem.WithData(map[data.Kind][]data.Record{ models.UserKind: []data.Record{ &models.User{ Id: "1", CredentialsIds: []string{"2"}, }, &models.User{ Id: "2", }, }, models.CredentialKind: []data.Record{ &models.Credential{ Id: "3", CreatedAt: time.Now(), Spec: "password", Public: "username", Private: "password", }, }, models.EventKind: []data.Record{ &models.Event{ Id: "4", OwnerId: "1", Name: "event 1", }, &models.Event{ Id: "5", OwnerId: "1", Name: "event 2", }, &models.Event{ Id: "6", OwnerId: "1", Name: "event 3", }, &models.Event{ Id: "7", OwnerId: "1", Name: "generic event", }, &models.Event{ Id: "8", OwnerId: "1", Name: "generic event", }, // Not owned by User 1. &models.Event{ Id: "9", OwnerId: "2", Name: "not owned", }, }, }) s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { records.QueryGET(user.NewContext(context.Background(), &models.User{Id: "1"}), w, r, db, services.NewTestLogger(t)) })) p := s.URL + "?" + url.Values{ "query/Kind": []string{"event"}, }.Encode() resp, err := http.Get(p) if err != nil { t.Fatalf("http.Get error: %v", err) } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) body := string(b) if err != nil { t.Fatalf("ioutil.ReadAll error: %v", err) } t.Logf("resp.Body:\n%s", body) contents := map[string]bool{ "Query": true, "5 results.": true, "event 1": true, "event 2": true, "event 3": true, "generic event": true, "Edit": true, "/records/edit/?kind=event&id=8": true, "/records/edit/?kind=event&id=9": false, "not owned": false, } for content, want := range contents { if got := strings.Contains(body, content); got != want { t.Errorf("bytes.Contains(body, %q): got %t, want %t", content, got, want) } } }