diff --git a/frontend/pages/LoginPage/LoginPage.jsx b/frontend/pages/LoginPage/LoginPage.jsx index 18d668a824..b14bf6cb9d 100644 --- a/frontend/pages/LoginPage/LoginPage.jsx +++ b/frontend/pages/LoginPage/LoginPage.jsx @@ -69,8 +69,15 @@ export class LoginPage extends Component { const { HOME } = paths; const redirectTime = 1500; return dispatch(loginUser(formData)) - .then(() => { + .then((user) => { this.setState({ loginVisible: false }); + + // Redirect to password reset page if user is forced to reset password. + // Any other requests will fail. + if (user.force_password_reset) { + return dispatch(push(paths.RESET_PASSWORD)); + } + setTimeout(() => { const nextLocation = redirectLocation || HOME; dispatch(clearRedirectLocation); diff --git a/server/authz/errors.go b/server/authz/errors.go index 493074683b..6135054f88 100644 --- a/server/authz/errors.go +++ b/server/authz/errors.go @@ -6,6 +6,13 @@ import ( "github.com/fleetdm/fleet/server/fleet" ) +const ( + // ForbiddenErrorMessage is the error message that should be returned to + // clients when an action is forbidden. It is intentionally vague to prevent + // disclosing information that a client should not have access to. + ForbiddenErrorMessage = "forbidden" +) + // Forbidden is the error type for authorization errors type Forbidden struct { internal string @@ -28,10 +35,10 @@ func ForbiddenWithInternal(internal string, subject *fleet.User, object, action // Error implements the error interface. func (e *Forbidden) Error() string { - return "forbidden" + return ForbiddenErrorMessage } -// StatusCode implements the service.ErrWithStatusCode interface. +// StatusCode implements the go-kit http StatusCoder interface. func (e *Forbidden) StatusCode() int { return http.StatusForbidden } @@ -49,3 +56,28 @@ func (e *Forbidden) LogFields() []interface{} { "action", e.action, } } + +// CheckMissing is the error to return when no authorization check was performed +// by the service. +type CheckMissing struct { + response interface{} +} + +// CheckMissingWithResponse creats a new error indicating the authorization +// check was missed, and including the response for further anaylis by the error +// encoder. +func CheckMissingWithResponse(response interface{}) *CheckMissing { + return &CheckMissing{response: response} +} + +func (e *CheckMissing) Error() string { + return ForbiddenErrorMessage +} + +func (e *CheckMissing) Internal() string { + return "Missing authorization check" +} + +func (e *CheckMissing) Response() interface{} { + return e.response +} diff --git a/server/contexts/viewer/viewer.go b/server/contexts/viewer/viewer.go index dbe84a7919..317aa60cea 100644 --- a/server/contexts/viewer/viewer.go +++ b/server/contexts/viewer/viewer.go @@ -93,34 +93,6 @@ func (v Viewer) CanPerformActions() bool { return false } -// CanPerformAdminActions indicates whether or not the current user can perform -// administrative actions. -func (v Viewer) CanPerformAdminActions() bool { - // TODO this needs revisiting for teams! - if v.User != nil { - return v.CanPerformActions() - } - return false -} - -// CanPerformReadActionOnUser returns a bool indicating the current user's -// ability to perform read actions on the given user -func (v Viewer) CanPerformReadActionOnUser(uid uint) bool { - if v.User != nil { - return v.CanPerformActions() || (v.IsLoggedIn() && v.IsUserID(uid)) - } - return false -} - -// CanPerformWriteActionOnUser returns a bool indicating the current user's -// ability to perform write actions on the given user -func (v Viewer) CanPerformWriteActionOnUser(uid uint) bool { - if v.User != nil { - return (v.IsLoggedIn() && v.IsUserID(uid)) || v.CanPerformAdminActions() - } - return false -} - // CanPerformPasswordReset returns a bool indicating the current user's // ability to perform a password reset (in the case they have been required by // the admin). diff --git a/server/fleet/errors.go b/server/fleet/errors.go index 6ffb9abac4..35f0c4a667 100644 --- a/server/fleet/errors.go +++ b/server/fleet/errors.go @@ -7,8 +7,9 @@ import ( ) var ( - ErrNoContext = errors.New("context key not set") - ErrMissingLicense = &LicenseError{} + ErrNoContext = errors.New("context key not set") + ErrPasswordResetRequired = &passwordResetRequiredError{} + ErrMissingLicense = &licenseError{} ) // ErrWithInternal is an interface for errors that include extra "internal" @@ -29,14 +30,6 @@ type ErrWithLogFields interface { LogFields() []interface{} } -// ErrWithStatusCode is an interface for errors that should set a specific HTTP -// status when encoding. -type ErrWithStatusCode interface { - error - // StatusCode returns the HTTP status code that should be returned. - StatusCode() int -} - // ErrWithRetryAfter is an interface for errors that should set a specific HTTP // Header Retry-After value (see // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) @@ -165,13 +158,23 @@ func (e PermissionError) PermissionError() []map[string]string { return forbidden } -// LicenseError is returned when the application is not properly licensed. -type LicenseError struct{} +// licenseError is returned when the application is not properly licensed. +type licenseError struct{} -func (e LicenseError) Error() string { - return "requires Fleet Basic license" +func (e licenseError) Error() string { + return "Requires Fleet Basic license" } -func (e LicenseError) StatusCode() int { +func (e licenseError) StatusCode() int { return http.StatusPaymentRequired } + +type passwordResetRequiredError struct{} + +func (e passwordResetRequiredError) Error() string { + return "password reset required" +} + +func (e passwordResetRequiredError) StatusCode() int { + return http.StatusUnauthorized +} diff --git a/server/service/endpoint_appconfig.go b/server/service/endpoint_appconfig.go index b55cfd78f4..b6a71ca114 100644 --- a/server/service/endpoint_appconfig.go +++ b/server/service/endpoint_appconfig.go @@ -22,7 +22,7 @@ type appConfigResponse struct { HostExpirySettings *fleet.HostExpirySettings `json:"host_expiry_settings,omitempty"` HostSettings *fleet.HostSettings `json:"host_settings,omitempty"` License *fleet.LicenseInfo `json:"license,omitempty"` - Err error `json:"error,omitempty"` + Err error `json:"error,omitempty"` } func (r appConfigResponse) error() error { return r.Err } @@ -41,11 +41,12 @@ func makeGetAppConfigEndpoint(svc fleet.Service) endpoint.Endpoint { if err != nil { return nil, err } + var smtpSettings *fleet.SMTPSettingsPayload var ssoSettings *fleet.SSOSettingsPayload var hostExpirySettings *fleet.HostExpirySettings // only admin can see smtp, sso, and host expiry settings - if vc.CanPerformAdminActions() { + if vc.User.GlobalRole != nil && *vc.User.GlobalRole == fleet.RoleAdmin { smtpSettings = smtpSettingsFromAppConfig(config) if smtpSettings.SMTPPassword != nil { *smtpSettings.SMTPPassword = "********" @@ -71,7 +72,7 @@ func makeGetAppConfigEndpoint(svc fleet.Service) endpoint.Endpoint { OrgLogoURL: &config.OrgLogoURL, }, ServerSettings: &fleet.ServerSettings{ - ServerURL: &config.ServerURL, + ServerURL: &config.ServerURL, LiveQueryDisabled: &config.LiveQueryDisabled, }, SMTPSettings: smtpSettings, @@ -99,7 +100,7 @@ func makeModifyAppConfigEndpoint(svc fleet.Service) endpoint.Endpoint { OrgLogoURL: &config.OrgLogoURL, }, ServerSettings: &fleet.ServerSettings{ - ServerURL: &config.ServerURL, + ServerURL: &config.ServerURL, LiveQueryDisabled: &config.LiveQueryDisabled, }, SMTPSettings: smtpSettingsFromAppConfig(config), @@ -176,7 +177,7 @@ func makeApplyEnrollSecretSpecEndpoint(svc fleet.Service) endpoint.Endpoint { type getEnrollSecretSpecResponse struct { Spec *fleet.EnrollSecretSpec `json:"specs"` - Err error `json:"error,omitempty"` + Err error `json:"error,omitempty"` } func (r getEnrollSecretSpecResponse) error() error { return r.Err } diff --git a/server/service/endpoint_middleware.go b/server/service/endpoint_middleware.go index b4b34119ad..c9a1e3e26f 100644 --- a/server/service/endpoint_middleware.go +++ b/server/service/endpoint_middleware.go @@ -57,10 +57,16 @@ func getNodeKey(r interface{}) (string, error) { // authenticatedUser wraps an endpoint, requires that the Fleet user is // authenticated, and populates the context with a Viewer struct for that user. +// +// If auth fails or the user must reset their password, an error is returned. func authenticatedUser(svc fleet.Service, next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { // first check if already successfully set - if _, ok := viewer.FromContext(ctx); ok { + if v, ok := viewer.FromContext(ctx); ok { + if v.User.AdminForcedPasswordReset { + return nil, fleet.ErrPasswordResetRequired + } + return next(ctx, request) } @@ -75,6 +81,10 @@ func authenticatedUser(svc fleet.Service, next endpoint.Endpoint) endpoint.Endpo return nil, err } + if v.User.AdminForcedPasswordReset { + return nil, fleet.ErrPasswordResetRequired + } + ctx = viewer.NewContext(ctx, *v) return next(ctx, request) } @@ -93,60 +103,6 @@ func authViewer(ctx context.Context, sessionKey string, svc fleet.Service) (*vie return &viewer.Viewer{User: user, Session: session}, nil } -func mustBeAdmin(next endpoint.Endpoint) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - vc, ok := viewer.FromContext(ctx) - if !ok { - return nil, fleet.ErrNoContext - } - if !vc.CanPerformAdminActions() { - return nil, fleet.NewPermissionError("must be an admin") - } - return next(ctx, request) - } -} - -func canPerformActions(next endpoint.Endpoint) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - vc, ok := viewer.FromContext(ctx) - if !ok { - return nil, fleet.ErrNoContext - } - if !vc.CanPerformActions() { - return nil, fleet.NewPermissionError("no read permissions") - } - return next(ctx, request) - } -} - -func canReadUser(next endpoint.Endpoint) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - vc, ok := viewer.FromContext(ctx) - if !ok { - return nil, fleet.ErrNoContext - } - uid := requestUserIDFromContext(ctx) - if !vc.CanPerformReadActionOnUser(uid) { - return nil, fleet.NewPermissionError("no read permissions on user") - } - return next(ctx, request) - } -} - -func canModifyUser(next endpoint.Endpoint) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - vc, ok := viewer.FromContext(ctx) - if !ok { - return nil, fleet.ErrNoContext - } - uid := requestUserIDFromContext(ctx) - if !vc.CanPerformWriteActionOnUser(uid) { - return nil, fleet.NewPermissionError("no write permissions on user") - } - return next(ctx, request) - } -} - func canPerformPasswordReset(next endpoint.Endpoint) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (interface{}, error) { vc, ok := viewer.FromContext(ctx) @@ -159,11 +115,3 @@ func canPerformPasswordReset(next endpoint.Endpoint) endpoint.Endpoint { return next(ctx, request) } } - -func requestUserIDFromContext(ctx context.Context) uint { - userID, ok := ctx.Value("request-id").(uint) - if !ok { - return 0 - } - return userID -} diff --git a/server/service/endpoint_middleware_test.go b/server/service/endpoint_middleware_test.go index 60bade7a0b..a93cfc4adf 100644 --- a/server/service/endpoint_middleware_test.go +++ b/server/service/endpoint_middleware_test.go @@ -62,10 +62,6 @@ import ( // request interface{} // }{ // { -// endpoint: mustBeAdmin(e), -// wantErr: fleet.ErrNoContext, -// }, -// { // endpoint: canReadUser(e), // wantErr: fleet.ErrNoContext, // }, @@ -74,15 +70,6 @@ import ( // wantErr: fleet.ErrNoContext, // }, // { -// endpoint: mustBeAdmin(e), -// vc: &viewer.Viewer{User: admin1, Session: admin1Session}, -// }, -// { -// endpoint: mustBeAdmin(e), -// vc: &viewer.Viewer{User: user1, Session: user1Session}, -// wantErr: permissionError{message: "must be an admin"}, -// }, -// { // endpoint: canModifyUser(e), // vc: &viewer.Viewer{User: admin1, Session: admin1Session}, // }, diff --git a/server/service/handler.go b/server/service/handler.go index 75beba1175..649e53d223 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -141,35 +141,30 @@ func MakeFleetServerEndpoints(svc fleet.Service, urlPrefix string, limitStore th CallbackSSO: makeCallbackSSOEndpoint(svc, urlPrefix), SSOSettings: makeSSOSettingsEndpoint(svc), - // Authenticated user endpoints - // Each of these endpoints should have exactly one - // authorization check around the make.*Endpoint method. At a - // minimum, canPerformActions. Some endpoints use - // stricter/different checks and should NOT also use - // canPerformActions (these other checks should also call - // canPerformActions if that is appropriate). - Me: authenticatedUser(svc, canPerformActions(makeGetSessionUserEndpoint(svc))), - ChangePassword: authenticatedUser(svc, canPerformActions(makeChangePasswordEndpoint(svc))), - GetUser: authenticatedUser(svc, canReadUser(makeGetUserEndpoint(svc))), - ListUsers: authenticatedUser(svc, canPerformActions(makeListUsersEndpoint(svc))), - ModifyUser: authenticatedUser(svc, canModifyUser(makeModifyUserEndpoint(svc))), - DeleteUser: authenticatedUser(svc, canModifyUser(makeDeleteUserEndpoint(svc))), - RequirePasswordReset: authenticatedUser(svc, mustBeAdmin(makeRequirePasswordResetEndpoint(svc))), - CreateUser: authenticatedUser(svc, mustBeAdmin(makeCreateUserEndpoint(svc))), // PerformRequiredPasswordReset needs only to authenticate the // logged in user - PerformRequiredPasswordReset: authenticatedUser(svc, canPerformPasswordReset(makePerformRequiredPasswordResetEndpoint(svc))), - GetSessionsForUserInfo: authenticatedUser(svc, canReadUser(makeGetInfoAboutSessionsForUserEndpoint(svc))), - DeleteSessionsForUser: authenticatedUser(svc, canModifyUser(makeDeleteSessionsForUserEndpoint(svc))), - GetSessionInfo: authenticatedUser(svc, mustBeAdmin(makeGetInfoAboutSessionEndpoint(svc))), - DeleteSession: authenticatedUser(svc, mustBeAdmin(makeDeleteSessionEndpoint(svc))), - GetAppConfig: authenticatedUser(svc, canPerformActions(makeGetAppConfigEndpoint(svc))), - ModifyAppConfig: authenticatedUser(svc, mustBeAdmin(makeModifyAppConfigEndpoint(svc))), - ApplyEnrollSecretSpec: authenticatedUser(svc, mustBeAdmin(makeApplyEnrollSecretSpecEndpoint(svc))), - GetEnrollSecretSpec: authenticatedUser(svc, canPerformActions(makeGetEnrollSecretSpecEndpoint(svc))), - CreateInvite: authenticatedUser(svc, mustBeAdmin(makeCreateInviteEndpoint(svc))), - ListInvites: authenticatedUser(svc, mustBeAdmin(makeListInvitesEndpoint(svc))), - DeleteInvite: authenticatedUser(svc, mustBeAdmin(makeDeleteInviteEndpoint(svc))), + PerformRequiredPasswordReset: canPerformPasswordReset(makePerformRequiredPasswordResetEndpoint(svc)), + + // Standard user authentication routes + Me: authenticatedUser(svc, makeGetSessionUserEndpoint(svc)), + ChangePassword: authenticatedUser(svc, makeChangePasswordEndpoint(svc)), + GetUser: authenticatedUser(svc, makeGetUserEndpoint(svc)), + ListUsers: authenticatedUser(svc, makeListUsersEndpoint(svc)), + ModifyUser: authenticatedUser(svc, makeModifyUserEndpoint(svc)), + DeleteUser: authenticatedUser(svc, makeDeleteUserEndpoint(svc)), + RequirePasswordReset: authenticatedUser(svc, makeRequirePasswordResetEndpoint(svc)), + CreateUser: authenticatedUser(svc, makeCreateUserEndpoint(svc)), + GetSessionsForUserInfo: authenticatedUser(svc, makeGetInfoAboutSessionsForUserEndpoint(svc)), + DeleteSessionsForUser: authenticatedUser(svc, makeDeleteSessionsForUserEndpoint(svc)), + GetSessionInfo: authenticatedUser(svc, makeGetInfoAboutSessionEndpoint(svc)), + DeleteSession: authenticatedUser(svc, makeDeleteSessionEndpoint(svc)), + GetAppConfig: authenticatedUser(svc, makeGetAppConfigEndpoint(svc)), + ModifyAppConfig: authenticatedUser(svc, makeModifyAppConfigEndpoint(svc)), + ApplyEnrollSecretSpec: authenticatedUser(svc, makeApplyEnrollSecretSpecEndpoint(svc)), + GetEnrollSecretSpec: authenticatedUser(svc, makeGetEnrollSecretSpecEndpoint(svc)), + CreateInvite: authenticatedUser(svc, makeCreateInviteEndpoint(svc)), + ListInvites: authenticatedUser(svc, makeListInvitesEndpoint(svc)), + DeleteInvite: authenticatedUser(svc, makeDeleteInviteEndpoint(svc)), GetQuery: authenticatedUser(svc, makeGetQueryEndpoint(svc)), ListQueries: authenticatedUser(svc, makeListQueriesEndpoint(svc)), CreateQuery: authenticatedUser(svc, makeCreateQueryEndpoint(svc)), @@ -221,23 +216,23 @@ func MakeFleetServerEndpoints(svc fleet.Service, urlPrefix string, limitStore th GetCarve: authenticatedUser(svc, makeGetCarveEndpoint(svc)), GetCarveBlock: authenticatedUser(svc, makeGetCarveBlockEndpoint(svc)), Version: authenticatedUser(svc, makeVersionEndpoint(svc)), - // TODO permissions for teams endpoints - CreateTeam: authenticatedUser(svc, makeCreateTeamEndpoint(svc)), - ModifyTeam: authenticatedUser(svc, makeModifyTeamEndpoint(svc)), - ModifyTeamAgentOptions: authenticatedUser(svc, makeModifyTeamAgentOptionsEndpoint(svc)), - DeleteTeam: authenticatedUser(svc, makeDeleteTeamEndpoint(svc)), - ListTeams: authenticatedUser(svc, makeListTeamsEndpoint(svc)), - ListTeamUsers: authenticatedUser(svc, makeListTeamUsersEndpoint(svc)), - AddTeamUsers: authenticatedUser(svc, makeAddTeamUsersEndpoint(svc)), - DeleteTeamUsers: authenticatedUser(svc, makeDeleteTeamUsersEndpoint(svc)), - TeamEnrollSecrets: authenticatedUser(svc, makeTeamEnrollSecretsEndpoint(svc)), + CreateTeam: authenticatedUser(svc, makeCreateTeamEndpoint(svc)), + ModifyTeam: authenticatedUser(svc, makeModifyTeamEndpoint(svc)), + ModifyTeamAgentOptions: authenticatedUser(svc, makeModifyTeamAgentOptionsEndpoint(svc)), + DeleteTeam: authenticatedUser(svc, makeDeleteTeamEndpoint(svc)), + ListTeams: authenticatedUser(svc, makeListTeamsEndpoint(svc)), + ListTeamUsers: authenticatedUser(svc, makeListTeamUsersEndpoint(svc)), + AddTeamUsers: authenticatedUser(svc, makeAddTeamUsersEndpoint(svc)), + DeleteTeamUsers: authenticatedUser(svc, makeDeleteTeamUsersEndpoint(svc)), + TeamEnrollSecrets: authenticatedUser(svc, makeTeamEnrollSecretsEndpoint(svc)), // Authenticated status endpoints StatusResultStore: authenticatedUser(svc, makeStatusResultStoreEndpoint(svc)), StatusLiveQuery: authenticatedUser(svc, makeStatusLiveQueryEndpoint(svc)), // Osquery endpoints - EnrollAgent: makeEnrollAgentEndpoint(svc), + EnrollAgent: makeEnrollAgentEndpoint(svc), + // Authenticated osquery endpoints GetClientConfig: authenticatedHost(svc, makeGetClientConfigEndpoint(svc)), GetDistributedQueries: authenticatedHost(svc, makeGetDistributedQueriesEndpoint(svc)), SubmitDistributedQueryResults: authenticatedHost(svc, makeSubmitDistributedQueryResultsEndpoint(svc)), diff --git a/server/service/middleware/authzcheck/authzcheck.go b/server/service/middleware/authzcheck/authzcheck.go index c36b316bbb..3ee4c4ce9c 100644 --- a/server/service/middleware/authzcheck/authzcheck.go +++ b/server/service/middleware/authzcheck/authzcheck.go @@ -34,17 +34,14 @@ func (m *Middleware) AuthzCheck() endpoint.Middleware { // appropriately). var authFailedError *fleet.AuthFailedError var authRequiredError *fleet.AuthRequiredError - var licenseError *fleet.LicenseError - if errors.As(err, &authFailedError) || - errors.As(err, &authRequiredError) || - errors.As(err, &licenseError) { + if errors.As(err, &authFailedError) || errors.As(err, &authRequiredError) || errors.Is(err, fleet.ErrPasswordResetRequired) { return nil, err } // If authorization was not checked, return a response that will // marshal to a generic error and log that the check was missed. if !authzctx.Checked { - return nil, authz.ForbiddenWithInternal("missed authz check", nil, nil, nil) + return nil, authz.CheckMissingWithResponse(response) } return response, err diff --git a/server/service/service_campaigns.go b/server/service/service_campaigns.go index 85315e557b..56bd5e192d 100644 --- a/server/service/service_campaigns.go +++ b/server/service/service_campaigns.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/fleetdm/fleet/server/authz" "github.com/fleetdm/fleet/server/contexts/viewer" "github.com/fleetdm/fleet/server/fleet" "github.com/fleetdm/fleet/server/ptr" @@ -158,14 +159,14 @@ type campaignStatus struct { func (svc Service) StreamCampaignResults(ctx context.Context, conn *websocket.Conn, campaignID uint) { if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRun); err != nil { level.Info(svc.logger).Log("err", "stream results authorization failed") - conn.WriteJSONError("forbidden") + conn.WriteJSONError(authz.ForbiddenErrorMessage) return } vc, ok := viewer.FromContext(ctx) if !ok { level.Info(svc.logger).Log("err", "stream results viewer missing") - conn.WriteJSONError("forbidden") + conn.WriteJSONError(authz.ForbiddenErrorMessage) return } @@ -183,7 +184,7 @@ func (svc Service) StreamCampaignResults(ctx context.Context, conn *websocket.Co "expected", campaign.UserID, "got", vc.User.ID, ) - conn.WriteJSONError("forbidden") + conn.WriteJSONError(authz.ForbiddenErrorMessage) return } diff --git a/server/service/service_teams.go b/server/service/service_teams.go index 63de10ce58..c34b4c4027 100644 --- a/server/service/service_teams.go +++ b/server/service/service_teams.go @@ -8,37 +8,73 @@ import ( ) func (svc *Service) NewTeam(ctx context.Context, p fleet.TeamPayload) (*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) ModifyTeam(ctx context.Context, id uint, payload fleet.TeamPayload) (*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) AddTeamUsers(ctx context.Context, teamID uint, users []fleet.TeamUser) (*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) DeleteTeamUsers(ctx context.Context, teamID uint, users []fleet.TeamUser) (*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) ListTeamUsers(ctx context.Context, teamID uint, opt fleet.ListOptions) ([]*fleet.User, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) ListTeams(ctx context.Context, opt fleet.ListOptions) ([]*fleet.Team, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } func (svc *Service) DeleteTeam(ctx context.Context, tid uint) error { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return fleet.ErrMissingLicense } func (svc *Service) TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*fleet.EnrollSecret, error) { + // skipauth: No authorization check needed due to implementation returning + // only license error. + svc.authz.SkipAuthorization(ctx) + return nil, fleet.ErrMissingLicense } diff --git a/server/service/transport_error.go b/server/service/transport_error.go index 01e25ee785..3a1561493a 100644 --- a/server/service/transport_error.go +++ b/server/service/transport_error.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/fleetdm/fleet/server/fleet" + kithttp "github.com/go-kit/kit/transport/http" "github.com/pkg/errors" ) @@ -24,9 +25,11 @@ type jsonError struct { // a generic "name" field. The frontend client always expects errors in a // []map[string]string format. func baseError(err string) []map[string]string { - return []map[string]string{map[string]string{ - "name": "base", - "reason": err}, + return []map[string]string{ + { + "name": "base", + "reason": err, + }, } } @@ -67,7 +70,7 @@ func encodeError(ctx context.Context, err error, w http.ResponseWriter) { Message: "Validation Failed", Errors: baseError(err.Error()), } - w.WriteHeader(http.StatusForbidden) + w.WriteHeader(http.StatusUnprocessableEntity) enc.Encode(ve) return } @@ -139,7 +142,7 @@ func encodeError(ctx context.Context, err error, w http.ResponseWriter) { // Get specific status code if it is available from this error type, // defaulting to HTTP 500 status := http.StatusInternalServerError - if e, ok := err.(fleet.ErrWithStatusCode); ok { + if e, ok := err.(kithttp.StatusCoder); ok { status = e.StatusCode() } diff --git a/server/service/validation_users.go b/server/service/validation_users.go index f9fdca31d3..863a16e9ca 100644 --- a/server/service/validation_users.go +++ b/server/service/validation_users.go @@ -135,17 +135,7 @@ func passwordRequiredForEmailChange(ctx context.Context, uid uint, invalid *flee return false } // if a user is changing own email need a password no matter what - if vc.UserID() == uid { - return true - } - // if an admin is changing another users email no password needed - if vc.CanPerformAdminActions() { - return false - } - // should never get here because a non admin can't change the email of another - // user - invalid.Append("auth", "this user can't change another user's email") - return false + return vc.UserID() == uid } func (mw validationMiddleware) ChangePassword(ctx context.Context, oldPass, newPass string) error {