diff --git a/docs/1-Using-Fleet/3-REST-API.md b/docs/1-Using-Fleet/3-REST-API.md index bbb26fe0c5..98bb485e5a 100644 --- a/docs/1-Using-Fleet/3-REST-API.md +++ b/docs/1-Using-Fleet/3-REST-API.md @@ -1434,7 +1434,6 @@ Creates a user account without requiring an invitation, the user is enabled imme | username | string | body | **Required**. The user's username. | | email | string | body | **Required**. The user's email address. | | password | string | body | **Required**. The user's password. | -| invited_by | integer | body | **Required**. ID of the admin creating the user. | | admin | boolean | body | **Required**. Whether the user has admin privileges. | #### Example @@ -1448,7 +1447,6 @@ Creates a user account without requiring an invitation, the user is enabled imme "username": "janedoe", "email": "janedoe@example.com", "password": "test-123", - "invited_by":1, "admin":true } ``` @@ -4070,7 +4068,6 @@ Modifies and/or creates the specified enroll secret(s). | ---------- | ------- | ---- | ------------------------------------------------ | | admin | boolean | body | **Required.** Whether or not the invited user will be granted admin privileges. | | email | string | body | **Required.** The email of the invited user. This email will receive the invitation link. | -| invited_by | integer | body | **Required.** The id of the user that is extending the invitation. See the [Get user information](#get-user-information) endpoint for how to retrieve a user's id. | | name | string | body | **Required.** The name of the invited user. | | sso_enabled | boolean | body | **Required.** Whether or not SSO will be enabled for the invited user. | @@ -4082,7 +4079,6 @@ Modifies and/or creates the specified enroll secret(s). { "admin": false, "email": "john_appleseed@example.com", - "invited_by": 1, "name": John, "sso_enabled": false } diff --git a/frontend/pages/admin/UserManagementPage/components/CreateUserForm/CreateUserForm.tsx b/frontend/pages/admin/UserManagementPage/components/CreateUserForm/CreateUserForm.tsx index 910fc7d838..bd67bd7ea1 100644 --- a/frontend/pages/admin/UserManagementPage/components/CreateUserForm/CreateUserForm.tsx +++ b/frontend/pages/admin/UserManagementPage/components/CreateUserForm/CreateUserForm.tsx @@ -50,7 +50,6 @@ interface IFormData { sso_enabled: boolean; global_role?: string; teams?: ITeam[]; - invited_by?: number; } interface ISubmitData extends IFormData { diff --git a/server/datastore/datastore_queries_test.go b/server/datastore/datastore_queries_test.go index 481f688209..ac7fc53583 100644 --- a/server/datastore/datastore_queries_test.go +++ b/server/datastore/datastore_queries_test.go @@ -340,5 +340,5 @@ func testDuplicateNewQuery(t *testing.T, ds kolide.Datastore) { // Note that we can't do the actual type assertion here because existsError // is private to the individual datastore implementations - assert.Contains(t, err.Error(), "already exists in the datastore") + assert.Contains(t, err.Error(), "already exists") } diff --git a/server/datastore/mysql/errors.go b/server/datastore/mysql/errors.go index ba3fec5506..54963b7eda 100644 --- a/server/datastore/mysql/errors.go +++ b/server/datastore/mysql/errors.go @@ -53,19 +53,19 @@ func (e *notFoundError) IsNotFound() bool { } type existsError struct { - ID uint + Identifier interface{} ResourceType string } -func alreadyExists(kind string, id uint) error { +func alreadyExists(kind string, identifier interface{}) error { return &existsError{ - ID: id, + Identifier: identifier, ResourceType: kind, } } func (e *existsError) Error() string { - return fmt.Sprintf("%s %d already exists in the datastore", e.ResourceType, e.ID) + return fmt.Sprintf("%s %v already exists", e.ResourceType, e.Identifier) } func (e *existsError) IsExists() bool { diff --git a/server/datastore/mysql/invites.go b/server/datastore/mysql/invites.go index 911e89946a..5e3992b6eb 100644 --- a/server/datastore/mysql/invites.go +++ b/server/datastore/mysql/invites.go @@ -20,7 +20,7 @@ func (d *Datastore) NewInvite(i *kolide.Invite) (*kolide.Invite, error) { result, err := d.db.Exec(sqlStmt, i.InvitedBy, i.Email, i.Admin, i.Name, i.Position, i.Token, i.SSOEnabled, i.GlobalRole) if err != nil && isDuplicate(err) { - return nil, alreadyExists("Invite", 0) + return nil, alreadyExists("Invite", i.Email) } else if err != nil { return nil, errors.Wrap(err, "create invite") } diff --git a/server/kolide/invites.go b/server/kolide/invites.go index 5d0b308963..f590673dab 100644 --- a/server/kolide/invites.go +++ b/server/kolide/invites.go @@ -47,7 +47,6 @@ type InviteService interface { // InvitePayload contains fields required to create a new user invite. type InvitePayload struct { - InvitedBy *uint `json:"invited_by"` Email *string Admin *bool Name *string diff --git a/server/mock/datastore_invites_helpers.go b/server/mock/datastore_invites_helpers.go deleted file mode 100644 index 5962299d38..0000000000 --- a/server/mock/datastore_invites_helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package mock - -import "github.com/fleetdm/fleet/server/kolide" - -func ReturnNewInivite(fake *kolide.Invite) NewInviteFunc { - return func(i *kolide.Invite) (*kolide.Invite, error) { - return fake, nil - } -} - -func ReturnFakeInviteByEmail(fake *kolide.Invite) InviteByEmailFunc { - return func(string) (*kolide.Invite, error) { - return fake, nil - } -} - -func ReturnFakeInviteByToken(fake *kolide.Invite) InviteByTokenFunc { - return func(string) (*kolide.Invite, error) { - return fake, nil - } -} - -func ReturnInviteFuncNotFound() InviteFunc { - return func(id uint) (*kolide.Invite, error) { - return nil, &Error{"not found"} - } -} - -func ReturnFakeInviteByID(fake *kolide.Invite) InviteFunc { - return func(id uint) (*kolide.Invite, error) { - return fake, nil - } -} diff --git a/server/service/service_invites.go b/server/service/service_invites.go index d1bbd2657a..ab89fc277d 100644 --- a/server/service/service_invites.go +++ b/server/service/service_invites.go @@ -5,8 +5,10 @@ import ( "encoding/base64" "html/template" + "github.com/fleetdm/fleet/server/contexts/viewer" "github.com/fleetdm/fleet/server/kolide" "github.com/fleetdm/fleet/server/mail" + "github.com/pkg/errors" ) func (svc service) InviteNewUser(ctx context.Context, payload kolide.InvitePayload) (*kolide.Invite, error) { @@ -15,16 +17,16 @@ func (svc service) InviteNewUser(ctx context.Context, payload kolide.InvitePaylo if err == nil { return nil, newInvalidArgumentError("email", "a user with this account already exists") } - if _, ok := err.(kolide.NotFoundError); !ok { return nil, err } // find the user who created the invite - inviter, err := svc.User(ctx, *payload.InvitedBy) - if err != nil { - return nil, err + v, ok := viewer.FromContext(ctx) + if !ok { + return nil, errors.New("missing viewer context for create invite") } + inviter := v.User random, err := kolide.RandomText(svc.config.App.TokenKeySize) if err != nil { diff --git a/server/service/service_invites_test.go b/server/service/service_invites_test.go index 087438321f..7fd5374955 100644 --- a/server/service/service_invites_test.go +++ b/server/service/service_invites_test.go @@ -7,6 +7,7 @@ import ( "github.com/WatchBeam/clock" "github.com/fleetdm/fleet/server/config" + "github.com/fleetdm/fleet/server/contexts/viewer" "github.com/fleetdm/fleet/server/kolide" "github.com/fleetdm/fleet/server/mock" "github.com/stretchr/testify/assert" @@ -14,24 +15,38 @@ import ( ) func TestInviteNewUserMock(t *testing.T) { - svc, mockStore, mailer := setupInviteTest(t) - ctx := context.Background() + ms := new(mock.Store) + ms.UserByEmailFunc = mock.UserWithEmailNotFound() + ms.AppConfigFunc = mock.ReturnFakeAppConfig(&kolide.AppConfig{ + KolideServerURL: "https://acme.co", + }) + ms.NewInviteFunc = func(i *kolide.Invite) (*kolide.Invite, error) { + return i, nil + } + mailer := &mockMailService{SendEmailFn: func(e kolide.Email) error { return nil }} + svc := validationMiddleware{service{ + ds: ms, + config: config.TestConfig(), + mailService: mailer, + clock: clock.NewMockClock(), + }, ms, nil} + ctx := context.Background() + ctx = viewer.NewContext(ctx, viewer.Viewer{User: &kolide.User{ID: 3}}) payload := kolide.InvitePayload{ - Email: stringPtr("user@acme.co"), - InvitedBy: &adminUser.ID, - Admin: boolPtr(false), + Email: stringPtr("user@acme.co"), + Admin: boolPtr(false), } // happy path invite, err := svc.InviteNewUser(ctx, payload) require.Nil(t, err) - assert.Equal(t, invite.ID, validInvite.ID) - assert.True(t, mockStore.NewInviteFuncInvoked) - assert.True(t, mockStore.AppConfigFuncInvoked) + assert.Equal(t, uint(3), invite.InvitedBy) + assert.True(t, ms.NewInviteFuncInvoked) + assert.True(t, ms.AppConfigFuncInvoked) assert.True(t, mailer.Invoked) - mockStore.UserByEmailFunc = mock.UserByEmailWithUser(new(kolide.User)) + ms.UserByEmailFunc = mock.UserByEmailWithUser(new(kolide.User)) _, err = svc.InviteNewUser(ctx, payload) require.NotNil(t, err, "should err if the user we're inviting already exists") } @@ -45,9 +60,19 @@ func TestVerifyInvite(t *testing.T) { } ctx := context.Background() - ms.InviteByTokenFunc = mock.ReturnFakeInviteByToken(expiredInvite) + ms.InviteByTokenFunc = func(token string) (*kolide.Invite, error) { + return &kolide.Invite{ + ID: 1, + Token: "abcd", + UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{ + CreateTimestamp: kolide.CreateTimestamp{ + CreatedAt: time.Now().AddDate(-1, 0, 0), + }, + }, + }, nil + } wantErr := &invalidArgumentError{{name: "invite_token", reason: "Invite token has expired."}} - _, err := svc.VerifyInvite(ctx, expiredInvite.Token) + _, err := svc.VerifyInvite(ctx, "abcd") assert.Equal(t, err, wantErr) wantErr = &invalidArgumentError{{name: "invite_token", @@ -78,44 +103,3 @@ func TestListInvites(t *testing.T) { require.Nil(t, err) assert.True(t, ms.ListInvitesFuncInvoked) } - -func setupInviteTest(t *testing.T) (kolide.Service, *mock.Store, *mockMailService) { - - ms := new(mock.Store) - ms.UserByEmailFunc = mock.UserWithEmailNotFound() - ms.UserByIDFunc = mock.UserWithID(adminUser) - ms.NewInviteFunc = mock.ReturnNewInivite(validInvite) - ms.AppConfigFunc = mock.ReturnFakeAppConfig(&kolide.AppConfig{ - KolideServerURL: "https://acme.co", - }) - mailer := &mockMailService{SendEmailFn: func(e kolide.Email) error { return nil }} - svc := validationMiddleware{service{ - ds: ms, - config: config.TestConfig(), - mailService: mailer, - clock: clock.NewMockClock(), - }, ms, nil} - return svc, ms, mailer -} - -var adminUser = &kolide.User{ - ID: 1, - Email: "admin@acme.co", - Username: "admin", - Name: "Administrator", -} - -var validInvite = &kolide.Invite{ - ID: 1, - Token: "abcd", -} - -var expiredInvite = &kolide.Invite{ - ID: 1, - Token: "abcd", - UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{ - CreateTimestamp: kolide.CreateTimestamp{ - CreatedAt: time.Now().AddDate(-1, 0, 0), - }, - }, -} diff --git a/server/service/transport_invites_test.go b/server/service/transport_invites_test.go index cd0bb22cda..02bab75bbe 100644 --- a/server/service/transport_invites_test.go +++ b/server/service/transport_invites_test.go @@ -14,19 +14,15 @@ import ( func TestDecodeCreateInviteRequest(t *testing.T) { router := mux.NewRouter() router.HandleFunc("/api/v1/fleet/invites", func(writer http.ResponseWriter, request *http.Request) { - r, err := decodeCreateInviteRequest(context.Background(), request) + _, err := decodeCreateInviteRequest(context.Background(), request) assert.Nil(t, err) - - params := r.(createInviteRequest) - assert.Equal(t, uint(1), *params.payload.InvitedBy) }).Methods("POST") t.Run("lowercase email", func(t *testing.T) { var body bytes.Buffer body.Write([]byte(`{ "name": "foo", - "email": "foo@kolide.co", - "invited_by": 1 + "email": "foo@kolide.co" }`)) router.ServeHTTP( @@ -40,8 +36,7 @@ func TestDecodeCreateInviteRequest(t *testing.T) { var body bytes.Buffer body.Write([]byte(`{ "name": "foo", - "email": "Foo@Kolide.co", - "invited_by": 1 + "email": "Foo@Kolide.co" }`)) router.ServeHTTP( diff --git a/server/service/validation_invites.go b/server/service/validation_invites.go index 1275fdd33c..9d7158c760 100644 --- a/server/service/validation_invites.go +++ b/server/service/validation_invites.go @@ -11,9 +11,6 @@ func (mw validationMiddleware) InviteNewUser(ctx context.Context, payload kolide if payload.Email == nil { invalid.Append("email", "missing required argument") } - if payload.InvitedBy == nil { - invalid.Append("invited_by", "missing required argument") - } if payload.Admin == nil { invalid.Append("admin", "missing required argument") }