mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Migrate the last batch of authenticatedUser endpoints to the new pattern (#4210)
This commit is contained in:
parent
531ef1eddc
commit
e29797deb0
23 changed files with 657 additions and 761 deletions
|
|
@ -1,30 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
type changeEmailRequest struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
type changeEmailResponse struct {
|
||||
NewEmail string `json:"new_email"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r changeEmailResponse) error() error { return r.Err }
|
||||
|
||||
func makeChangeEmailEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(changeEmailRequest)
|
||||
newEmailAddress, err := svc.ChangeUserEmail(ctx, req.Token)
|
||||
if err != nil {
|
||||
return changeEmailResponse{Err: err}, nil
|
||||
}
|
||||
return changeEmailResponse{NewEmail: newEmailAddress}, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -7,76 +7,6 @@ import (
|
|||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
type createInviteRequest struct {
|
||||
payload fleet.InvitePayload
|
||||
}
|
||||
|
||||
type createInviteResponse struct {
|
||||
Invite *fleet.Invite `json:"invite,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r createInviteResponse) error() error { return r.Err }
|
||||
|
||||
func makeCreateInviteEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(createInviteRequest)
|
||||
invite, err := svc.InviteNewUser(ctx, req.payload)
|
||||
if err != nil {
|
||||
return createInviteResponse{Err: err}, nil
|
||||
}
|
||||
return createInviteResponse{invite, nil}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type listInvitesRequest struct {
|
||||
ListOptions fleet.ListOptions
|
||||
}
|
||||
|
||||
type listInvitesResponse struct {
|
||||
Invites []fleet.Invite `json:"invites"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r listInvitesResponse) error() error { return r.Err }
|
||||
|
||||
func makeListInvitesEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(listInvitesRequest)
|
||||
invites, err := svc.ListInvites(ctx, req.ListOptions)
|
||||
if err != nil {
|
||||
return listInvitesResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp := listInvitesResponse{Invites: []fleet.Invite{}}
|
||||
for _, invite := range invites {
|
||||
resp.Invites = append(resp.Invites, *invite)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
type deleteInviteRequest struct {
|
||||
ID uint
|
||||
}
|
||||
|
||||
type deleteInviteResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r deleteInviteResponse) error() error { return r.Err }
|
||||
|
||||
func makeDeleteInviteEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(deleteInviteRequest)
|
||||
err := svc.DeleteInvite(ctx, req.ID)
|
||||
if err != nil {
|
||||
return deleteInviteResponse{Err: err}, nil
|
||||
}
|
||||
return deleteInviteResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type verifyInviteRequest struct {
|
||||
Token string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
type statusResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m statusResponse) error() error { return m.Err }
|
||||
|
||||
func makeStatusLiveQueryEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var resp statusResponse
|
||||
if err := svc.StatusLiveQuery(ctx); err != nil {
|
||||
resp.Err = err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func makeStatusResultStoreEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var resp statusResponse
|
||||
if err := svc.StatusResultStore(ctx); err != nil {
|
||||
resp.Err = err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Search Targets
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type searchTargetsRequest struct {
|
||||
// MatchQuery is the query SQL
|
||||
MatchQuery string `json:"query"`
|
||||
// QueryID is the ID of a saved query to run (used to determine if this is a
|
||||
// query that observers can run).
|
||||
QueryID *uint `json:"query_id"`
|
||||
Selected fleet.HostTargets `json:"selected"`
|
||||
}
|
||||
|
||||
type hostSearchResult struct {
|
||||
HostResponse
|
||||
DisplayText string `json:"display_text"`
|
||||
}
|
||||
|
||||
type labelSearchResult struct {
|
||||
*fleet.Label
|
||||
DisplayText string `json:"display_text"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type teamSearchResult struct {
|
||||
*fleet.Team
|
||||
DisplayText string `json:"display_text"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type targetsData struct {
|
||||
Hosts []hostSearchResult `json:"hosts"`
|
||||
Labels []labelSearchResult `json:"labels"`
|
||||
Teams []teamSearchResult `json:"teams"`
|
||||
}
|
||||
|
||||
type searchTargetsResponse struct {
|
||||
Targets *targetsData `json:"targets,omitempty"`
|
||||
TargetsCount uint `json:"targets_count"`
|
||||
TargetsOnline uint `json:"targets_online"`
|
||||
TargetsOffline uint `json:"targets_offline"`
|
||||
TargetsMissingInAction uint `json:"targets_missing_in_action"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r searchTargetsResponse) error() error { return r.Err }
|
||||
|
||||
func makeSearchTargetsEndpoint(svc fleet.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(searchTargetsRequest)
|
||||
|
||||
results, err := svc.SearchTargets(ctx, req.MatchQuery, req.QueryID, req.Selected)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
targets := &targetsData{
|
||||
Hosts: []hostSearchResult{},
|
||||
Labels: []labelSearchResult{},
|
||||
Teams: []teamSearchResult{},
|
||||
}
|
||||
|
||||
for _, host := range results.Hosts {
|
||||
targets.Hosts = append(targets.Hosts,
|
||||
hostSearchResult{
|
||||
HostResponse{
|
||||
Host: host,
|
||||
Status: host.Status(time.Now()),
|
||||
},
|
||||
host.Hostname,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, label := range results.Labels {
|
||||
targets.Labels = append(targets.Labels,
|
||||
labelSearchResult{
|
||||
Label: label,
|
||||
DisplayText: label.Name,
|
||||
Count: label.HostCount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, team := range results.Teams {
|
||||
targets.Teams = append(targets.Teams,
|
||||
teamSearchResult{
|
||||
Team: team,
|
||||
DisplayText: team.Name,
|
||||
Count: team.HostCount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
metrics, err := svc.CountHostsInTargets(ctx, req.QueryID, req.Selected)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return searchTargetsResponse{
|
||||
Targets: targets,
|
||||
TargetsCount: metrics.TotalHosts,
|
||||
TargetsOnline: metrics.OnlineHosts,
|
||||
TargetsOffline: metrics.OfflineHosts,
|
||||
TargetsMissingInAction: metrics.MissingInActionHosts,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -30,9 +30,6 @@ type FleetEndpoints struct {
|
|||
ResetPassword endpoint.Endpoint
|
||||
CreateUserWithInvite endpoint.Endpoint
|
||||
PerformRequiredPasswordReset endpoint.Endpoint
|
||||
CreateInvite endpoint.Endpoint
|
||||
ListInvites endpoint.Endpoint
|
||||
DeleteInvite endpoint.Endpoint
|
||||
VerifyInvite endpoint.Endpoint
|
||||
EnrollAgent endpoint.Endpoint
|
||||
GetClientConfig endpoint.Endpoint
|
||||
|
|
@ -41,13 +38,9 @@ type FleetEndpoints struct {
|
|||
SubmitLogs endpoint.Endpoint
|
||||
CarveBegin endpoint.Endpoint
|
||||
CarveBlock endpoint.Endpoint
|
||||
SearchTargets endpoint.Endpoint
|
||||
ChangeEmail endpoint.Endpoint
|
||||
InitiateSSO endpoint.Endpoint
|
||||
CallbackSSO endpoint.Endpoint
|
||||
SSOSettings endpoint.Endpoint
|
||||
StatusResultStore endpoint.Endpoint
|
||||
StatusLiveQuery endpoint.Endpoint
|
||||
}
|
||||
|
||||
// MakeFleetServerEndpoints creates the Fleet API endpoints.
|
||||
|
|
@ -75,18 +68,6 @@ func MakeFleetServerEndpoints(svc fleet.Service, urlPrefix string, limitStore th
|
|||
// logged in user
|
||||
PerformRequiredPasswordReset: logged(canPerformPasswordReset(makePerformRequiredPasswordResetEndpoint(svc))),
|
||||
|
||||
// Standard user authentication routes
|
||||
CreateInvite: authenticatedUser(svc, makeCreateInviteEndpoint(svc)),
|
||||
ListInvites: authenticatedUser(svc, makeListInvitesEndpoint(svc)),
|
||||
DeleteInvite: authenticatedUser(svc, makeDeleteInviteEndpoint(svc)),
|
||||
|
||||
SearchTargets: authenticatedUser(svc, makeSearchTargetsEndpoint(svc)),
|
||||
ChangeEmail: authenticatedUser(svc, makeChangeEmailEndpoint(svc)),
|
||||
|
||||
// Authenticated status endpoints
|
||||
StatusResultStore: authenticatedUser(svc, makeStatusResultStoreEndpoint(svc)),
|
||||
StatusLiveQuery: authenticatedUser(svc, makeStatusLiveQueryEndpoint(svc)),
|
||||
|
||||
// Osquery endpoints
|
||||
EnrollAgent: logged(makeEnrollAgentEndpoint(svc)),
|
||||
// Authenticated osquery endpoints
|
||||
|
|
@ -109,9 +90,6 @@ type fleetHandlers struct {
|
|||
ResetPassword http.Handler
|
||||
CreateUserWithInvite http.Handler
|
||||
PerformRequiredPasswordReset http.Handler
|
||||
CreateInvite http.Handler
|
||||
ListInvites http.Handler
|
||||
DeleteInvite http.Handler
|
||||
VerifyInvite http.Handler
|
||||
EnrollAgent http.Handler
|
||||
GetClientConfig http.Handler
|
||||
|
|
@ -120,13 +98,9 @@ type fleetHandlers struct {
|
|||
SubmitLogs http.Handler
|
||||
CarveBegin http.Handler
|
||||
CarveBlock http.Handler
|
||||
SearchTargets http.Handler
|
||||
ChangeEmail http.Handler
|
||||
InitiateSSO http.Handler
|
||||
CallbackSSO http.Handler
|
||||
SettingsSSO http.Handler
|
||||
StatusResultStore http.Handler
|
||||
StatusLiveQuery http.Handler
|
||||
}
|
||||
|
||||
func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandlers {
|
||||
|
|
@ -141,9 +115,6 @@ func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandle
|
|||
ResetPassword: newServer(e.ResetPassword, decodeResetPasswordRequest),
|
||||
CreateUserWithInvite: newServer(e.CreateUserWithInvite, decodeCreateUserRequest),
|
||||
PerformRequiredPasswordReset: newServer(e.PerformRequiredPasswordReset, decodePerformRequiredPasswordResetRequest),
|
||||
CreateInvite: newServer(e.CreateInvite, decodeCreateInviteRequest),
|
||||
ListInvites: newServer(e.ListInvites, decodeListInvitesRequest),
|
||||
DeleteInvite: newServer(e.DeleteInvite, decodeDeleteInviteRequest),
|
||||
VerifyInvite: newServer(e.VerifyInvite, decodeVerifyInviteRequest),
|
||||
EnrollAgent: newServer(e.EnrollAgent, decodeEnrollAgentRequest),
|
||||
GetClientConfig: newServer(e.GetClientConfig, decodeGetClientConfigRequest),
|
||||
|
|
@ -152,13 +123,9 @@ func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandle
|
|||
SubmitLogs: newServer(e.SubmitLogs, decodeSubmitLogsRequest),
|
||||
CarveBegin: newServer(e.CarveBegin, decodeCarveBeginRequest),
|
||||
CarveBlock: newServer(e.CarveBlock, decodeCarveBlockRequest),
|
||||
SearchTargets: newServer(e.SearchTargets, decodeSearchTargetsRequest),
|
||||
ChangeEmail: newServer(e.ChangeEmail, decodeChangeEmailRequest),
|
||||
InitiateSSO: newServer(e.InitiateSSO, decodeInitiateSSORequest),
|
||||
CallbackSSO: newServer(e.CallbackSSO, decodeCallbackSSORequest),
|
||||
SettingsSSO: newServer(e.SSOSettings, decodeNoParamsRequest),
|
||||
StatusResultStore: newServer(e.StatusResultStore, decodeNoParamsRequest),
|
||||
StatusLiveQuery: newServer(e.StatusLiveQuery, decodeNoParamsRequest),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -340,21 +307,8 @@ func attachFleetAPIRoutes(r *mux.Router, h *fleetHandlers) {
|
|||
r.Handle("/api/v1/fleet/sso", h.InitiateSSO).Methods("POST").Name("intiate_sso")
|
||||
r.Handle("/api/v1/fleet/sso", h.SettingsSSO).Methods("GET").Name("sso_config")
|
||||
r.Handle("/api/v1/fleet/sso/callback", h.CallbackSSO).Methods("POST").Name("callback_sso")
|
||||
|
||||
r.Handle("/api/v1/fleet/users", h.CreateUserWithInvite).Methods("POST").Name("create_user_with_invite")
|
||||
|
||||
r.Handle("/api/v1/fleet/invites", h.CreateInvite).Methods("POST").Name("create_invite")
|
||||
r.Handle("/api/v1/fleet/invites", h.ListInvites).Methods("GET").Name("list_invites")
|
||||
r.Handle("/api/v1/fleet/invites/{id:[0-9]+}", h.DeleteInvite).Methods("DELETE").Name("delete_invite")
|
||||
r.Handle("/api/v1/fleet/invites/{token}", h.VerifyInvite).Methods("GET").Name("verify_invite")
|
||||
|
||||
r.Handle("/api/v1/fleet/email/change/{token}", h.ChangeEmail).Methods("GET").Name("change_email")
|
||||
|
||||
r.Handle("/api/v1/fleet/targets", h.SearchTargets).Methods("POST").Name("search_targets")
|
||||
|
||||
r.Handle("/api/v1/fleet/status/result_store", h.StatusResultStore).Methods("GET").Name("status_result_store")
|
||||
r.Handle("/api/v1/fleet/status/live_query", h.StatusLiveQuery).Methods("GET").Name("status_live_query")
|
||||
|
||||
r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST").Name("enroll_agent")
|
||||
r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST").Name("get_client_config")
|
||||
r.Handle("/api/v1/osquery/distributed/read", h.GetDistributedQueries).Methods("POST").Name("get_distributed_queries")
|
||||
|
|
@ -409,6 +363,14 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht
|
|||
e.DELETE("/api/_version_/fleet/users/{id:[0-9]+}/sessions", deleteSessionsForUserEndpoint, deleteSessionsForUserRequest{})
|
||||
e.POST("/api/_version_/fleet/change_password", changePasswordEndpoint, changePasswordRequest{})
|
||||
|
||||
e.GET("/api/_version_/fleet/email/change/{token}", changeEmailEndpoint, changeEmailRequest{})
|
||||
e.POST("/api/_version_/fleet/targets", searchTargetsEndpoint, searchTargetsRequest{})
|
||||
|
||||
e.POST("/api/_version_/fleet/invites", createInviteEndpoint, createInviteRequest{})
|
||||
e.GET("/api/_version_/fleet/invites", listInvitesEndpoint, listInvitesRequest{})
|
||||
e.DELETE("/api/_version_/fleet/invites/{id:[0-9]+}", deleteInviteEndpoint, deleteInviteRequest{})
|
||||
e.PATCH("/api/_version_/fleet/invites/{id:[0-9]+}", updateInviteEndpoint, updateInviteRequest{})
|
||||
|
||||
e.POST("/api/_version_/fleet/global/policies", globalPolicyEndpoint, globalPolicyRequest{})
|
||||
e.GET("/api/_version_/fleet/global/policies", listGlobalPoliciesEndpoint, nil)
|
||||
e.GET("/api/_version_/fleet/global/policies/{policy_id}", getPolicyByIDEndpoint, getPolicyByIDRequest{})
|
||||
|
|
@ -480,8 +442,6 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht
|
|||
e.POST("/api/_version_/fleet/queries/run", createDistributedQueryCampaignEndpoint, createDistributedQueryCampaignRequest{})
|
||||
e.POST("/api/_version_/fleet/queries/run_by_names", createDistributedQueryCampaignByNamesEndpoint, createDistributedQueryCampaignByNamesRequest{})
|
||||
|
||||
e.PATCH("/api/_version_/fleet/invites/{id:[0-9]+}", updateInviteEndpoint, updateInviteRequest{})
|
||||
|
||||
e.GET("/api/_version_/fleet/activities", listActivitiesEndpoint, listActivitiesRequest{})
|
||||
|
||||
e.GET("/api/_version_/fleet/global/schedule", getGlobalScheduleEndpoint, getGlobalScheduleRequest{})
|
||||
|
|
@ -495,6 +455,9 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht
|
|||
|
||||
e.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/macadmins", getMacadminsDataEndpoint, getMacadminsDataRequest{})
|
||||
e.GET("/api/_version_/fleet/macadmins", getAggregatedMacadminsDataEndpoint, getAggregatedMacadminsDataRequest{})
|
||||
|
||||
e.GET("/api/_version_/fleet/status/result_store", statusResultStoreEndpoint, nil)
|
||||
e.GET("/api/_version_/fleet/status/live_query", statusLiveQueryEndpoint, nil)
|
||||
}
|
||||
|
||||
// TODO: this duplicates the one in makeKitHandler
|
||||
|
|
|
|||
|
|
@ -54,18 +54,6 @@ func TestAPIRoutes(t *testing.T) {
|
|||
verb: "POST",
|
||||
uri: "/api/v1/fleet/reset_password",
|
||||
},
|
||||
{
|
||||
verb: "GET",
|
||||
uri: "/api/v1/fleet/invites",
|
||||
},
|
||||
{
|
||||
verb: "POST",
|
||||
uri: "/api/v1/fleet/invites",
|
||||
},
|
||||
{
|
||||
verb: "DELETE",
|
||||
uri: "/api/v1/fleet/invites/1",
|
||||
},
|
||||
{
|
||||
verb: "POST",
|
||||
uri: "/api/v1/osquery/enroll",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,13 @@ func (s *integrationTestSuite) TearDownTest() {
|
|||
}
|
||||
}
|
||||
|
||||
teams, err := s.ds.ListTeams(ctx, fleet.TeamFilter{User: &u}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
for _, tm := range teams {
|
||||
err := s.ds.DeleteTeam(ctx, tm.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
globalPolicies, err := s.ds.ListGlobalPolicies(ctx)
|
||||
require.NoError(t, err)
|
||||
if len(globalPolicies) > 0 {
|
||||
|
|
@ -871,39 +878,91 @@ func (s *integrationTestSuite) TestInvites() {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
createInviteReq := createInviteRequest{
|
||||
payload: fleet.InvitePayload{
|
||||
Email: ptr.String("some email"),
|
||||
Name: ptr.String("some name"),
|
||||
Position: nil,
|
||||
SSOEnabled: nil,
|
||||
GlobalRole: null.StringFrom(fleet.RoleAdmin),
|
||||
Teams: nil,
|
||||
},
|
||||
}
|
||||
// list invites, none yet
|
||||
var listResp listInvitesResponse
|
||||
s.DoJSON("GET", "/api/v1/fleet/invites", nil, http.StatusOK, &listResp)
|
||||
require.Len(t, listResp.Invites, 0)
|
||||
|
||||
// create valid invite
|
||||
createInviteReq := createInviteRequest{InvitePayload: fleet.InvitePayload{
|
||||
Email: ptr.String("some email"),
|
||||
Name: ptr.String("some name"),
|
||||
GlobalRole: null.StringFrom(fleet.RoleAdmin),
|
||||
}}
|
||||
createInviteResp := createInviteResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/invites", createInviteReq.payload, http.StatusOK, &createInviteResp)
|
||||
s.DoJSON("POST", "/api/v1/fleet/invites", createInviteReq, http.StatusOK, &createInviteResp)
|
||||
require.NotNil(t, createInviteResp.Invite)
|
||||
require.NotZero(t, createInviteResp.Invite.ID)
|
||||
validInvite := *createInviteResp.Invite
|
||||
|
||||
updateInviteReq := updateInviteRequest{
|
||||
InvitePayload: fleet.InvitePayload{
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: team.ID},
|
||||
Role: fleet.RoleObserver,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// create invite without an email
|
||||
createInviteReq = createInviteRequest{InvitePayload: fleet.InvitePayload{
|
||||
Email: nil,
|
||||
Name: ptr.String("some other name"),
|
||||
GlobalRole: null.StringFrom(fleet.RoleObserver),
|
||||
}}
|
||||
createInviteResp = createInviteResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/invites", createInviteReq, http.StatusUnprocessableEntity, &createInviteResp)
|
||||
|
||||
// create invite for an existing user
|
||||
existingEmail := "admin1@example.com"
|
||||
createInviteReq = createInviteRequest{InvitePayload: fleet.InvitePayload{
|
||||
Email: ptr.String(existingEmail),
|
||||
Name: ptr.String("some other name"),
|
||||
GlobalRole: null.StringFrom(fleet.RoleObserver),
|
||||
}}
|
||||
createInviteResp = createInviteResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/invites", createInviteReq, http.StatusUnprocessableEntity, &createInviteResp)
|
||||
|
||||
// create invite for an existing user with email ALL CAPS
|
||||
createInviteReq = createInviteRequest{InvitePayload: fleet.InvitePayload{
|
||||
Email: ptr.String(strings.ToUpper(existingEmail)),
|
||||
Name: ptr.String("some other name"),
|
||||
GlobalRole: null.StringFrom(fleet.RoleObserver),
|
||||
}}
|
||||
createInviteResp = createInviteResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/invites", createInviteReq, http.StatusUnprocessableEntity, &createInviteResp)
|
||||
|
||||
// list invites, we have one now
|
||||
listResp = listInvitesResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/invites", nil, http.StatusOK, &listResp)
|
||||
require.Len(t, listResp.Invites, 1)
|
||||
require.Equal(t, validInvite.ID, listResp.Invites[0].ID)
|
||||
|
||||
// list invites, next page is empty
|
||||
listResp = listInvitesResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/invites", nil, http.StatusOK, &listResp, "page", "1", "per_page", "2")
|
||||
require.Len(t, listResp.Invites, 0)
|
||||
|
||||
// update a non-existing invite
|
||||
updateInviteReq := updateInviteRequest{InvitePayload: fleet.InvitePayload{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: team.ID}, Role: fleet.RoleObserver},
|
||||
}}}
|
||||
updateInviteResp := updateInviteResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/v1/fleet/invites/%d", createInviteResp.Invite.ID), updateInviteReq, http.StatusOK, &updateInviteResp)
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/v1/fleet/invites/%d", validInvite.ID+1), updateInviteReq, http.StatusNotFound, &updateInviteResp)
|
||||
|
||||
verify, err := s.ds.Invite(context.Background(), createInviteResp.Invite.ID)
|
||||
// update the valid invite created earlier, make it an observer of a team
|
||||
updateInviteResp = updateInviteResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/v1/fleet/invites/%d", validInvite.ID), updateInviteReq, http.StatusOK, &updateInviteResp)
|
||||
|
||||
verify, err := s.ds.Invite(context.Background(), validInvite.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", verify.GlobalRole.String)
|
||||
require.Len(t, verify.Teams, 1)
|
||||
assert.Equal(t, team.ID, verify.Teams[0].ID)
|
||||
|
||||
// delete an existing invite
|
||||
var delResp deleteInviteResponse
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/v1/fleet/invites/%d", validInvite.ID), nil, http.StatusOK, &delResp)
|
||||
|
||||
// list invites, is now empty
|
||||
listResp = listInvitesResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/invites", nil, http.StatusOK, &listResp)
|
||||
require.Len(t, listResp.Invites, 0)
|
||||
|
||||
// delete a now non-existing invite
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/v1/fleet/invites/%d", validInvite.ID), nil, http.StatusNotFound, &delResp)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestGetHostSummary() {
|
||||
|
|
@ -2756,6 +2815,92 @@ func (s *integrationTestSuite) TestPaginateListSoftware() {
|
|||
assertResp(lsResp, nil, time.Time{})
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestChangeUserEmail() {
|
||||
t := s.T()
|
||||
|
||||
// create a new test user
|
||||
user := &fleet.User{
|
||||
Name: t.Name(),
|
||||
Email: "testchangeemail@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleObserver),
|
||||
}
|
||||
userRawPwd := "foobarbaz1234!"
|
||||
err := user.SetPassword(userRawPwd, 10, 10)
|
||||
require.Nil(t, err)
|
||||
user, err = s.ds.NewUser(context.Background(), user)
|
||||
require.Nil(t, err)
|
||||
|
||||
// try to change email with an invalid token
|
||||
var changeResp changeEmailResponse
|
||||
s.DoJSON("GET", "/api/v1/fleet/email/change/invalidtoken", nil, http.StatusNotFound, &changeResp)
|
||||
|
||||
// create a valid token for the test user
|
||||
err = s.ds.PendingEmailChange(context.Background(), user.ID, "testchangeemail2@example.com", "validtoken")
|
||||
require.Nil(t, err)
|
||||
|
||||
// try to change email with a valid token, but request made from different user
|
||||
changeResp = changeEmailResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/email/change/validtoken", nil, http.StatusNotFound, &changeResp)
|
||||
|
||||
// switch to the test user and make the change email request
|
||||
s.token = s.getTestToken(user.Email, userRawPwd)
|
||||
defer func() { s.token = s.getTestAdminToken() }()
|
||||
|
||||
changeResp = changeEmailResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/email/change/validtoken", nil, http.StatusOK, &changeResp)
|
||||
require.Equal(t, "testchangeemail2@example.com", changeResp.NewEmail)
|
||||
|
||||
// using the token consumes it, so making another request with the same token fails
|
||||
changeResp = changeEmailResponse{}
|
||||
s.DoJSON("GET", "/api/v1/fleet/email/change/validtoken", nil, http.StatusNotFound, &changeResp)
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestSearchTargets() {
|
||||
t := s.T()
|
||||
|
||||
hosts := s.createHosts(t)
|
||||
|
||||
lblIDs, err := s.ds.LabelIDsByName(context.Background(), []string{"All Hosts"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, lblIDs, 1)
|
||||
|
||||
// no search criteria
|
||||
var searchResp searchTargetsResponse
|
||||
s.DoJSON("POST", "/api/v1/fleet/targets", searchTargetsRequest{}, http.StatusOK, &searchResp)
|
||||
require.Equal(t, uint(0), searchResp.TargetsCount)
|
||||
require.Len(t, searchResp.Targets.Hosts, len(hosts)) // the HostTargets.HostIDs are actually host IDs to *omit* from the search
|
||||
require.Len(t, searchResp.Targets.Labels, 1)
|
||||
require.Len(t, searchResp.Targets.Teams, 0)
|
||||
|
||||
searchResp = searchTargetsResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/targets", searchTargetsRequest{Selected: fleet.HostTargets{LabelIDs: lblIDs}}, http.StatusOK, &searchResp)
|
||||
require.Equal(t, uint(0), searchResp.TargetsCount)
|
||||
require.Len(t, searchResp.Targets.Hosts, len(hosts)) // no omitted host id
|
||||
require.Len(t, searchResp.Targets.Labels, 0) // labels have been omitted
|
||||
require.Len(t, searchResp.Targets.Teams, 0)
|
||||
|
||||
searchResp = searchTargetsResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/targets", searchTargetsRequest{Selected: fleet.HostTargets{HostIDs: []uint{hosts[1].ID}}}, http.StatusOK, &searchResp)
|
||||
require.Equal(t, uint(1), searchResp.TargetsCount)
|
||||
require.Len(t, searchResp.Targets.Hosts, len(hosts)-1) // one omitted host id
|
||||
require.Len(t, searchResp.Targets.Labels, 1) // labels have not been omitted
|
||||
require.Len(t, searchResp.Targets.Teams, 0)
|
||||
|
||||
searchResp = searchTargetsResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/targets", searchTargetsRequest{MatchQuery: "foo.local1"}, http.StatusOK, &searchResp)
|
||||
require.Equal(t, uint(0), searchResp.TargetsCount)
|
||||
require.Len(t, searchResp.Targets.Hosts, 1)
|
||||
require.Len(t, searchResp.Targets.Labels, 1)
|
||||
require.Len(t, searchResp.Targets.Teams, 0)
|
||||
require.Contains(t, searchResp.Targets.Hosts[0].Hostname, "foo.local1")
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestStatus() {
|
||||
var statusResp statusResponse
|
||||
s.DoJSON("GET", "/api/v1/fleet/status/result_store", nil, http.StatusOK, &statusResp)
|
||||
s.DoJSON("GET", "/api/v1/fleet/status/live_query", nil, http.StatusOK, &statusResp)
|
||||
}
|
||||
|
||||
// creates a session and returns it, its key is to be passed as authorization header.
|
||||
func createSession(t *testing.T, uid uint, ds fleet.Datastore) *fleet.Session {
|
||||
key := make([]byte, 64)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,162 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mail"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create invite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type createInviteRequest struct {
|
||||
fleet.InvitePayload
|
||||
}
|
||||
|
||||
type createInviteResponse struct {
|
||||
Invite *fleet.Invite `json:"invite,omitempty"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r createInviteResponse) error() error { return r.Err }
|
||||
|
||||
func createInviteEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*createInviteRequest)
|
||||
invite, err := svc.InviteNewUser(ctx, req.InvitePayload)
|
||||
if err != nil {
|
||||
return createInviteResponse{Err: err}, nil
|
||||
}
|
||||
return createInviteResponse{invite, nil}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) InviteNewUser(ctx context.Context, payload fleet.InvitePayload) (*fleet.Invite, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if payload.Email == nil {
|
||||
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("email", "missing required argument"))
|
||||
}
|
||||
*payload.Email = strings.ToLower(*payload.Email)
|
||||
|
||||
// verify that the user with the given email does not already exist
|
||||
_, err := svc.ds.UserByEmail(ctx, *payload.Email)
|
||||
if err == nil {
|
||||
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("email", "a user with this account already exists"))
|
||||
}
|
||||
var nfe fleet.NotFoundError
|
||||
if !errors.As(err, &nfe) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find the user who created the invite
|
||||
v, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("missing viewer context for create invite")
|
||||
}
|
||||
inviter := v.User
|
||||
|
||||
random, err := server.GenerateRandomText(svc.config.App.TokenKeySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := base64.URLEncoding.EncodeToString([]byte(random))
|
||||
|
||||
invite := &fleet.Invite{
|
||||
Email: *payload.Email,
|
||||
InvitedBy: inviter.ID,
|
||||
Token: token,
|
||||
GlobalRole: payload.GlobalRole,
|
||||
Teams: payload.Teams,
|
||||
}
|
||||
if payload.Position != nil {
|
||||
invite.Position = *payload.Position
|
||||
}
|
||||
if payload.Name != nil {
|
||||
invite.Name = *payload.Name
|
||||
}
|
||||
if payload.SSOEnabled != nil {
|
||||
invite.SSOEnabled = *payload.SSOEnabled
|
||||
}
|
||||
|
||||
invite, err = svc.ds.NewInvite(ctx, invite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := svc.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invitedBy := inviter.Name
|
||||
if invitedBy == "" {
|
||||
invitedBy = inviter.Email
|
||||
}
|
||||
inviteEmail := fleet.Email{
|
||||
Subject: "You are Invited to Fleet",
|
||||
To: []string{invite.Email},
|
||||
Config: config,
|
||||
Mailer: &mail.InviteMailer{
|
||||
Invite: invite,
|
||||
BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
OrgName: config.OrgInfo.OrgName,
|
||||
InvitedBy: invitedBy,
|
||||
},
|
||||
}
|
||||
|
||||
err = svc.mailService.SendEmail(inviteEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// List invites
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type listInvitesRequest struct {
|
||||
ListOptions fleet.ListOptions `url:"list_options"`
|
||||
}
|
||||
|
||||
type listInvitesResponse struct {
|
||||
Invites []fleet.Invite `json:"invites"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r listInvitesResponse) error() error { return r.Err }
|
||||
|
||||
func listInvitesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*listInvitesRequest)
|
||||
invites, err := svc.ListInvites(ctx, req.ListOptions)
|
||||
if err != nil {
|
||||
return listInvitesResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp := listInvitesResponse{Invites: []fleet.Invite{}}
|
||||
for _, invite := range invites {
|
||||
resp.Invites = append(resp.Invites, *invite)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ListInvites(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Invite, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.ds.ListInvites(ctx, opt)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Update invite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -63,3 +215,33 @@ func (svc *Service) UpdateInvite(ctx context.Context, id uint, payload fleet.Inv
|
|||
|
||||
return svc.ds.UpdateInvite(ctx, id, invite)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Delete invite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type deleteInviteRequest struct {
|
||||
ID uint `url:"id"`
|
||||
}
|
||||
|
||||
type deleteInviteResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r deleteInviteResponse) error() error { return r.Err }
|
||||
|
||||
func deleteInviteEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*deleteInviteRequest)
|
||||
err := svc.DeleteInvite(ctx, req.ID)
|
||||
if err != nil {
|
||||
return deleteInviteResponse{Err: err}, nil
|
||||
}
|
||||
return deleteInviteResponse{}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteInvite(ctx context.Context, id uint) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
return svc.ds.DeleteInvite(ctx, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,104 +2,12 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"html/template"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mail"
|
||||
)
|
||||
|
||||
func (svc Service) InviteNewUser(ctx context.Context, payload fleet.InvitePayload) (*fleet.Invite, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verify that the user with the given email does not already exist
|
||||
_, err := svc.ds.UserByEmail(ctx, *payload.Email)
|
||||
if err == nil {
|
||||
return nil, fleet.NewInvalidArgumentError("email", "a user with this account already exists")
|
||||
}
|
||||
var nfe fleet.NotFoundError
|
||||
if !errors.As(err, &nfe) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// find the user who created the invite
|
||||
v, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("missing viewer context for create invite")
|
||||
}
|
||||
inviter := v.User
|
||||
|
||||
random, err := server.GenerateRandomText(svc.config.App.TokenKeySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := base64.URLEncoding.EncodeToString([]byte(random))
|
||||
|
||||
invite := &fleet.Invite{
|
||||
Email: *payload.Email,
|
||||
InvitedBy: inviter.ID,
|
||||
Token: token,
|
||||
GlobalRole: payload.GlobalRole,
|
||||
Teams: payload.Teams,
|
||||
}
|
||||
if payload.Position != nil {
|
||||
invite.Position = *payload.Position
|
||||
}
|
||||
if payload.Name != nil {
|
||||
invite.Name = *payload.Name
|
||||
}
|
||||
if payload.SSOEnabled != nil {
|
||||
invite.SSOEnabled = *payload.SSOEnabled
|
||||
}
|
||||
|
||||
invite, err = svc.ds.NewInvite(ctx, invite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, err := svc.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invitedBy := inviter.Name
|
||||
if invitedBy == "" {
|
||||
invitedBy = inviter.Email
|
||||
}
|
||||
inviteEmail := fleet.Email{
|
||||
Subject: "You are Invited to Fleet",
|
||||
To: []string{invite.Email},
|
||||
Config: config,
|
||||
Mailer: &mail.InviteMailer{
|
||||
Invite: invite,
|
||||
BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix),
|
||||
AssetURL: getAssetURL(),
|
||||
OrgName: config.OrgInfo.OrgName,
|
||||
InvitedBy: invitedBy,
|
||||
},
|
||||
}
|
||||
|
||||
err = svc.mailService.SendEmail(inviteEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ListInvites(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Invite, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.ds.ListInvites(ctx, opt)
|
||||
}
|
||||
|
||||
func (svc *Service) VerifyInvite(ctx context.Context, token string) (*fleet.Invite, error) {
|
||||
// skipauth: There is no viewer context at this point. We rely on verifying
|
||||
// the invite for authNZ.
|
||||
|
|
@ -124,10 +32,3 @@ func (svc *Service) VerifyInvite(ctx context.Context, token string) (*fleet.Invi
|
|||
return invite, nil
|
||||
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteInvite(ctx context.Context, id uint) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Invite{}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
return svc.ds.DeleteInvite(ctx, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
func (svc *Service) StatusResultStore(ctx context.Context) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.resultStore.HealthCheck()
|
||||
}
|
||||
|
||||
func (svc *Service) StatusLiveQuery(ctx context.Context) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieve app config")
|
||||
}
|
||||
|
||||
if cfg.ServerSettings.LiveQueryDisabled {
|
||||
return ctxerr.New(ctx, "disabled by administrator")
|
||||
}
|
||||
|
||||
return svc.StatusResultStore(ctx)
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
func (svc Service) SearchTargets(ctx context.Context, matchQuery string, queryID *uint, targets fleet.HostTargets) (*fleet.TargetSearchResults, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Target{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, fleet.ErrNoContext
|
||||
}
|
||||
|
||||
includeObserver := false
|
||||
if queryID != nil {
|
||||
query, err := svc.ds.Query(ctx, *queryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeObserver = query.ObserverCanRun
|
||||
}
|
||||
|
||||
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
results := &fleet.TargetSearchResults{}
|
||||
|
||||
hosts, err := svc.ds.SearchHosts(ctx, filter, matchQuery, targets.HostIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Hosts = append(results.Hosts, hosts...)
|
||||
|
||||
labels, err := svc.ds.SearchLabels(ctx, filter, matchQuery, targets.LabelIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.Labels = labels
|
||||
|
||||
teams, err := svc.ds.SearchTeams(ctx, filter, matchQuery, targets.TeamIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.Teams = teams
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (svc Service) CountHostsInTargets(ctx context.Context, queryID *uint, targets fleet.HostTargets) (*fleet.TargetMetrics, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Target{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, fleet.ErrNoContext
|
||||
}
|
||||
|
||||
includeObserver := false
|
||||
if queryID != nil {
|
||||
query, err := svc.ds.Query(ctx, *queryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeObserver = query.ObserverCanRun
|
||||
}
|
||||
|
||||
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
metrics, err := svc.ds.CountHostsInTargets(ctx, filter, targets, svc.clock.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metrics, nil
|
||||
}
|
||||
|
|
@ -84,19 +84,6 @@ func (svc *Service) newUser(ctx context.Context, p fleet.UserPayload) (*fleet.Us
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ChangeUserEmail(ctx context.Context, token string) (string, error) {
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return "", fleet.ErrNoContext
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, &fleet.User{ID: vc.UserID()}, fleet.ActionWrite); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return svc.ds.ConfirmPendingEmailChange(ctx, vc.UserID(), token)
|
||||
}
|
||||
|
||||
func (svc *Service) UserUnauthorized(ctx context.Context, id uint) (*fleet.User, error) {
|
||||
// Explicitly no authorization check. Should only be used by middleware.
|
||||
return svc.ds.UserByID(ctx, id)
|
||||
|
|
|
|||
63
server/service/status.go
Normal file
63
server/service/status.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Status Result Store
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type statusResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m statusResponse) error() error { return m.Err }
|
||||
|
||||
func statusResultStoreEndpoint(ctx context.Context, req interface{}, svc fleet.Service) (interface{}, error) {
|
||||
var resp statusResponse
|
||||
if err := svc.StatusResultStore(ctx); err != nil {
|
||||
resp.Err = err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (svc *Service) StatusResultStore(ctx context.Context) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.resultStore.HealthCheck()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Status Live Query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func statusLiveQueryEndpoint(ctx context.Context, req interface{}, svc fleet.Service) (interface{}, error) {
|
||||
var resp statusResponse
|
||||
if err := svc.StatusLiveQuery(ctx); err != nil {
|
||||
resp.Err = err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (svc *Service) StatusLiveQuery(ctx context.Context) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "retrieve app config")
|
||||
}
|
||||
|
||||
if cfg.ServerSettings.LiveQueryDisabled {
|
||||
return ctxerr.New(ctx, "disabled by administrator")
|
||||
}
|
||||
|
||||
return svc.StatusResultStore(ctx)
|
||||
}
|
||||
193
server/service/targets.go
Normal file
193
server/service/targets.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Search Targets
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type searchTargetsRequest struct {
|
||||
// MatchQuery is the query SQL
|
||||
MatchQuery string `json:"query"`
|
||||
// QueryID is the ID of a saved query to run (used to determine if this is a
|
||||
// query that observers can run).
|
||||
QueryID *uint `json:"query_id"`
|
||||
// Selected is the list of IDs that are already selected on the caller side
|
||||
// (e.g. the UI), so those are IDs that will be omitted from the returned
|
||||
// payload.
|
||||
Selected fleet.HostTargets `json:"selected"`
|
||||
}
|
||||
|
||||
type hostSearchResult struct {
|
||||
HostResponse
|
||||
DisplayText string `json:"display_text"`
|
||||
}
|
||||
|
||||
type labelSearchResult struct {
|
||||
*fleet.Label
|
||||
DisplayText string `json:"display_text"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type teamSearchResult struct {
|
||||
*fleet.Team
|
||||
DisplayText string `json:"display_text"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type targetsData struct {
|
||||
Hosts []hostSearchResult `json:"hosts"`
|
||||
Labels []labelSearchResult `json:"labels"`
|
||||
Teams []teamSearchResult `json:"teams"`
|
||||
}
|
||||
|
||||
type searchTargetsResponse struct {
|
||||
Targets *targetsData `json:"targets,omitempty"`
|
||||
TargetsCount uint `json:"targets_count"`
|
||||
TargetsOnline uint `json:"targets_online"`
|
||||
TargetsOffline uint `json:"targets_offline"`
|
||||
TargetsMissingInAction uint `json:"targets_missing_in_action"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r searchTargetsResponse) error() error { return r.Err }
|
||||
|
||||
func searchTargetsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*searchTargetsRequest)
|
||||
|
||||
results, err := svc.SearchTargets(ctx, req.MatchQuery, req.QueryID, req.Selected)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
targets := &targetsData{
|
||||
Hosts: []hostSearchResult{},
|
||||
Labels: []labelSearchResult{},
|
||||
Teams: []teamSearchResult{},
|
||||
}
|
||||
|
||||
for _, host := range results.Hosts {
|
||||
targets.Hosts = append(targets.Hosts,
|
||||
hostSearchResult{
|
||||
HostResponse{
|
||||
Host: host,
|
||||
Status: host.Status(time.Now()),
|
||||
},
|
||||
host.Hostname,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, label := range results.Labels {
|
||||
targets.Labels = append(targets.Labels,
|
||||
labelSearchResult{
|
||||
Label: label,
|
||||
DisplayText: label.Name,
|
||||
Count: label.HostCount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
for _, team := range results.Teams {
|
||||
targets.Teams = append(targets.Teams,
|
||||
teamSearchResult{
|
||||
Team: team,
|
||||
DisplayText: team.Name,
|
||||
Count: team.HostCount,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
metrics, err := svc.CountHostsInTargets(ctx, req.QueryID, req.Selected)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return searchTargetsResponse{
|
||||
Targets: targets,
|
||||
TargetsCount: metrics.TotalHosts,
|
||||
TargetsOnline: metrics.OnlineHosts,
|
||||
TargetsOffline: metrics.OfflineHosts,
|
||||
TargetsMissingInAction: metrics.MissingInActionHosts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) SearchTargets(ctx context.Context, matchQuery string, queryID *uint, targets fleet.HostTargets) (*fleet.TargetSearchResults, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Target{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, fleet.ErrNoContext
|
||||
}
|
||||
|
||||
includeObserver := false
|
||||
if queryID != nil {
|
||||
query, err := svc.ds.Query(ctx, *queryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeObserver = query.ObserverCanRun
|
||||
}
|
||||
|
||||
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
results := &fleet.TargetSearchResults{}
|
||||
|
||||
hosts, err := svc.ds.SearchHosts(ctx, filter, matchQuery, targets.HostIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results.Hosts = append(results.Hosts, hosts...)
|
||||
|
||||
labels, err := svc.ds.SearchLabels(ctx, filter, matchQuery, targets.LabelIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.Labels = labels
|
||||
|
||||
teams, err := svc.ds.SearchTeams(ctx, filter, matchQuery, targets.TeamIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results.Teams = teams
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (svc *Service) CountHostsInTargets(ctx context.Context, queryID *uint, targets fleet.HostTargets) (*fleet.TargetMetrics, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Target{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, fleet.ErrNoContext
|
||||
}
|
||||
|
||||
includeObserver := false
|
||||
if queryID != nil {
|
||||
query, err := svc.ds.Query(ctx, *queryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeObserver = query.ObserverCanRun
|
||||
}
|
||||
|
||||
filter := fleet.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
metrics, err := svc.ds.CountHostsInTargets(ctx, filter, targets, svc.clock.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metrics, nil
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/pubsub"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -43,7 +44,8 @@ type withServer struct {
|
|||
func (ts *withServer) SetupSuite(dbName string) {
|
||||
ts.withDS.SetupSuite(dbName)
|
||||
|
||||
users, server := RunServerForTestsWithDS(ts.s.T(), ts.ds)
|
||||
rs := pubsub.NewInmemQueryResults()
|
||||
users, server := RunServerForTestsWithDS(ts.s.T(), ts.ds, TestServerOpts{Rs: rs})
|
||||
ts.server = server
|
||||
ts.users = users
|
||||
ts.token = ts.getTestAdminToken()
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func decodeChangeEmailRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
vars := mux.Vars(r)
|
||||
token, ok := vars["token"]
|
||||
if !ok {
|
||||
return nil, errBadRoute
|
||||
}
|
||||
|
||||
response := changeEmailRequest{
|
||||
Token: token,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
|
@ -2,33 +2,11 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func decodeCreateInviteRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req createInviteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req.payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.payload.Email != nil {
|
||||
*req.payload.Email = strings.ToLower(*req.payload.Email)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteInviteRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := uintFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deleteInviteRequest{ID: uint(id)}, nil
|
||||
}
|
||||
|
||||
func decodeVerifyInviteRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
vars := mux.Vars(r)
|
||||
token, ok := vars["token"]
|
||||
|
|
@ -37,11 +15,3 @@ func decodeVerifyInviteRequest(ctx context.Context, r *http.Request) (interface{
|
|||
}
|
||||
return verifyInviteRequest{Token: token}, nil
|
||||
}
|
||||
|
||||
func decodeListInvitesRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
opt, err := listOptionsFromRequest(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listInvitesRequest{ListOptions: opt}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeCreateInviteRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/fleet/invites", func(writer http.ResponseWriter, request *http.Request) {
|
||||
_, err := decodeCreateInviteRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
}).Methods("POST")
|
||||
|
||||
t.Run("lowercase email", func(t *testing.T) {
|
||||
var body bytes.Buffer
|
||||
body.Write([]byte(`{
|
||||
"name": "foo",
|
||||
"email": "foo@fleet.co"
|
||||
}`))
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("POST", "/api/v1/fleet/invites", &body),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("uppercase email", func(t *testing.T) {
|
||||
// email string should be lowerased after decode.
|
||||
var body bytes.Buffer
|
||||
body.Write([]byte(`{
|
||||
"name": "foo",
|
||||
"email": "Foo@fleet.co"
|
||||
}`))
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("POST", "/api/v1/fleet/invites", &body),
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestDecodeVerifyInviteRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/fleet/invites/{token}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeCreateInviteRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(verifyInviteRequest)
|
||||
assert.Equal(t, "test_token", params.Token)
|
||||
}).Methods("GET")
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("GET", "/api/v1/fleet/tokens/test_token", nil),
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func decodeSearchTargetsRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req searchTargetsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeSearchTargetsRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/fleet/targets", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeSearchTargetsRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(searchTargetsRequest)
|
||||
assert.Equal(t, "bar", params.MatchQuery)
|
||||
assert.Len(t, params.Selected.HostIDs, 3)
|
||||
assert.Len(t, params.Selected.LabelIDs, 2)
|
||||
}).Methods("POST")
|
||||
var body bytes.Buffer
|
||||
|
||||
body.Write([]byte(`{
|
||||
"query": "bar",
|
||||
"selected": {
|
||||
"hosts": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"labels": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
}`))
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("POST", "/api/v1/fleet/targets", &body),
|
||||
)
|
||||
}
|
||||
|
|
@ -486,6 +486,43 @@ func (svc *Service) DeleteSessionsForUser(ctx context.Context, id uint) error {
|
|||
return svc.ds.DestroyAllSessionsForUser(ctx, id)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Change user email
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type changeEmailRequest struct {
|
||||
Token string `url:"token"`
|
||||
}
|
||||
|
||||
type changeEmailResponse struct {
|
||||
NewEmail string `json:"new_email"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r changeEmailResponse) error() error { return r.Err }
|
||||
|
||||
func changeEmailEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||
req := request.(*changeEmailRequest)
|
||||
newEmailAddress, err := svc.ChangeUserEmail(ctx, req.Token)
|
||||
if err != nil {
|
||||
return changeEmailResponse{Err: err}, nil
|
||||
}
|
||||
return changeEmailResponse{NewEmail: newEmailAddress}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) ChangeUserEmail(ctx context.Context, token string) (string, error) {
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return "", fleet.ErrNoContext
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, &fleet.User{ID: vc.UserID()}, fleet.ActionWrite); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return svc.ds.ConfirmPendingEmailChange(ctx, vc.UserID(), token)
|
||||
}
|
||||
|
||||
func isAdminOfTheModifiedTeams(currentUser *fleet.User, originalUserTeams, newUserTeams []fleet.UserTeam) bool {
|
||||
// If the user is of the right global role, then they can modify the teams
|
||||
if currentUser.GlobalRole != nil && (*currentUser.GlobalRole == fleet.RoleAdmin || *currentUser.GlobalRole == fleet.RoleMaintainer) {
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
func (mw validationMiddleware) InviteNewUser(ctx context.Context, payload fleet.InvitePayload) (*fleet.Invite, error) {
|
||||
invalid := &fleet.InvalidArgumentError{}
|
||||
if payload.Email == nil {
|
||||
invalid.Append("email", "missing required argument")
|
||||
}
|
||||
if invalid.HasErrors() {
|
||||
return nil, ctxerr.Wrap(ctx, invalid)
|
||||
}
|
||||
return mw.Service.InviteNewUser(ctx, payload)
|
||||
}
|
||||
Loading…
Reference in a new issue