Clean up service and return license errors (#1097)
- Expose license errors instead of permission errors by adding explicit skip authorization. - Remove pre-Teams authorization checks from service. Fixes #964
This commit is contained in:
@@ -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);
|
||||
|
||||
+34
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
+18
-15
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
// },
|
||||
|
||||
+33
-38
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user