Refactor teams service methods (#910)

- Move team-related service methods to `ee/server/service`.
- Instantiate different service on startup based on license key.
- Refactor service errors into separate package.
- Add support for running E2E tests in both Core and Basic tiers.
This commit is contained in:
Zach Wasserman 2021-05-31 17:07:51 -07:00 committed by GitHub
parent 9876dbe6b6
commit 417ef2c9b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 813 additions and 694 deletions

View file

@ -6,6 +6,7 @@ jobs:
matrix:
os: [ubuntu-latest]
go-version: ['^1.16.0']
fleet-tier: [core, basic]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
@ -68,10 +69,10 @@ jobs:
- name: Run E2E Tests
run: |
make e2e-reset-db
make e2e-serve &
make e2e-serve-${{ matrix.fleet-tier }} &
sleep 3
make e2e-setup
yarn cypress run --config video=false
CYPRESS_FLEET_TIER=${{ matrix.fleet-tier }} yarn cypress run --config video=false
test-js:

View file

@ -229,5 +229,9 @@ e2e-setup:
./build/fleetctl user create --context e2e --email=test+1@example.com --username=test1 --password=admin123#
./build/fleetctl user create --context e2e --email=test+2@example.com --username=test2 --password=admin123#
e2e-serve:
./build/fleet serve --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --auth_jwt_key=insecure --mysql_database=e2e --server_address=localhost:8642
e2e-serve-core:
./build/fleet serve --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --auth_jwt_key=insecure --mysql_database=e2e --server_address=localhost:8642
e2e-serve-basic:
./build/fleet serve --dev_license --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --auth_jwt_key=insecure --mysql_database=e2e --server_address=localhost:8642

View file

@ -16,7 +16,8 @@ import (
"github.com/WatchBeam/clock"
"github.com/e-dard/netbug"
"github.com/fleetdm/fleet/ee/licensing"
"github.com/fleetdm/fleet/ee/server/licensing"
eeservice "github.com/fleetdm/fleet/ee/server/service"
"github.com/fleetdm/fleet/server/config"
"github.com/fleetdm/fleet/server/datastore/mysql"
"github.com/fleetdm/fleet/server/datastore/s3"
@ -53,6 +54,8 @@ func createServeCmd(configManager config.Manager) *cobra.Command {
debug := false
// Whether to enable developer options
dev := false
// Whether to enable development Fleet Basic license
devLicense := false
serveCmd := &cobra.Command{
Use: "serve",
@ -72,6 +75,11 @@ the way that the Fleet server works.
applyDevFlags(&config)
}
if devLicense {
// This license key is valid for development only
config.License.Key = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJGbGVldCBEZXZpY2UgTWFuYWdlbWVudCBJbmMuIiwiZXhwIjoxNjQwOTk1MjAwLCJzdWIiOiJkZXZlbG9wbWVudCIsImRldmljZXMiOjEwMCwibm90ZSI6ImZvciBkZXZlbG9wbWVudCBvbmx5IiwidGllciI6ImJhc2ljIiwiaWF0IjoxNjIyNDI2NTg2fQ.WmZ0kG4seW3IrNvULCHUPBSfFdqj38A_eiXdV_DFunMHechjHbkwtfkf1J6JQJoDyqn8raXpgbdhafDwv3rmDw"
}
license, err := licensing.LoadLicense(config.License.Key)
if err != nil {
initFatal(
@ -233,6 +241,13 @@ the way that the Fleet server works.
initFatal(err, "initializing service")
}
if license.Tier == kolide.TierBasic {
svc, err = eeservice.NewService(svc, ds, logger, config, mailService, clock.C, license)
if err != nil {
initFatal(err, "initial Fleet Basic service")
}
}
go func() {
ticker := time.NewTicker(1 * time.Hour)
for {
@ -409,6 +424,7 @@ the way that the Fleet server works.
serveCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug endpoints")
serveCmd.PersistentFlags().BoolVar(&dev, "dev", false, "Enable developer options")
serveCmd.PersistentFlags().BoolVar(&devLicense, "dev_license", false, "Enable development license")
return serveCmd
}

View file

@ -1,4 +1,5 @@
{
"baseUrl": "https://localhost:8642",
"fixturesFolder": false
"fixturesFolder": false,
"testFiles": "**/*.ts"
}

View file

@ -1,72 +0,0 @@
describe("Teams flow", () => {
beforeEach(() => {
cy.setup();
cy.login();
});
it("Create, edit, and delete a team successfully", () => {
cy.visit("/settings/teams");
cy.findByRole("button", { name: /create team/i }).click();
cy.findByLabelText(/team name/i)
.click()
.type("Valor");
// ^$ forces exact match
cy.findByRole("button", { name: /^create$/i }).click();
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.contains("Valor").click({ force: true });
cy.findByText(/agent options/i).click();
cy.get(".ace_content")
.click()
.type("{selectall}{backspace}apiVersion: v1{enter}kind: options");
cy.findByRole("button", { name: /save/i }).click();
cy.visit("/settings/teams/1/options");
cy.contains(/apiVersion: v1/i).should("be.visible");
cy.contains(/kind: options/i).should("be.visible");
cy.visit("/settings/teams");
cy.contains("Valor").get(".Select-arrow-zone").click();
// need force:true for dropdown
cy.findByText(/edit/i).click({ force: true });
cy.findByLabelText(/team name/i)
.click()
.type("{selectall}{backspace}Mystic");
cy.findByRole("button", { name: /save/i }).click();
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.contains("Mystic").get(".Select-arrow-zone").click();
cy.findByText(/delete/i).click({ force: true });
cy.findByRole("button", { name: /delete/i }).click();
cy.findByText(/successfully deleted/i).should("be.visible");
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.findByText(/mystic/i).should("not.exist");
});
});

View file

@ -0,0 +1,23 @@
# Basic tier tests
These tests should only run when the server is in `basic` tier.
To enable the tests:
```sh
export CYPRESS_FLEET_TIER=basic
```
Before running the appropriate `yarn cypress (open|run)` command.
## Filtering
Any test suite in this directory should use the following pattern for filtering:
**FIXME**: There must be a better way to do this for all tests in the directory rather than having to add the check in each file?
```js
if (Cypress.env("FLEET_TIER") === "basic") {
// test suite here
}
```

View file

@ -0,0 +1,74 @@
if (Cypress.env("FLEET_TIER") === "basic") {
describe("Teams flow", () => {
beforeEach(() => {
cy.setup();
cy.login();
});
it("Create, edit, and delete a team successfully", () => {
cy.visit("/settings/teams");
cy.findByRole("button", { name: /create team/i }).click();
cy.findByLabelText(/team name/i)
.click()
.type("Valor");
// ^$ forces exact match
cy.findByRole("button", { name: /^create$/i }).click();
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.contains("Valor").click({ force: true });
cy.findByText(/agent options/i).click();
cy.get(".ace_content")
.click()
.type("{selectall}{backspace}apiVersion: v1{enter}kind: options");
cy.findByRole("button", { name: /save/i }).click();
cy.visit("/settings/teams/1/options");
cy.contains(/apiVersion: v1/i).should("be.visible");
cy.contains(/kind: options/i).should("be.visible");
cy.visit("/settings/teams");
cy.contains("Valor").get(".Select-arrow-zone").click();
// need force:true for dropdown
cy.findByText(/edit/i).click({ force: true });
cy.findByLabelText(/team name/i)
.click()
.type("{selectall}{backspace}Mystic");
cy.findByRole("button", { name: /save/i }).click();
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.contains("Mystic").get(".Select-arrow-zone").click();
cy.findByText(/delete/i).click({ force: true });
cy.findByRole("button", { name: /delete/i }).click();
cy.findByText(/successfully deleted/i).should("be.visible");
cy.visit("/settings/teams");
// Allow rendering to settle
// TODO this might represent a bug in the React code.
cy.wait(100); // eslint-disable-line cypress/no-unnecessary-waiting
cy.findByText(/mystic/i).should("not.exist");
});
});
}

View file

@ -135,9 +135,18 @@ E2E tests are run using Docker and Cypress.
Make sure dependencies are up to date and the [Fleet binaries are built locally](./1-Building-Fleet.md).
For Fleet Core tests:
```
make e2e-reset-db
make e2e-serve
make e2e-serve-core
```
For Fleet Basic tests:
```
make e2e-reset-db
make e2e-serve-basic
```
This will start a local Fleet server connected to the E2E database. Leave this server running for the duration of end-to-end testing.
@ -154,16 +163,32 @@ Tests can be run in interactive mode, or from the command line.
#### Interactive
For Fleet Core tests:
```
yarn cypress open
CYPRESS_FLEET_TIER=core yarn cypress open
```
For Fleet Basic tests:
```
CYPRESS_FLEET_TIER=basic yarn cypress open
```
Use the graphical UI controls to run and view tests.
#### Command line
For Fleet Core tests:
```
yarn cypress run
CYPRESS_FLEET_TIER=core yarn cypress run
```
For Fleet Basic tests:
```
CYPRESS_FLEET_TIER=basic yarn cypress run
```
Tests will run automatically and results are reported to the shell.

View file

@ -0,0 +1,37 @@
package service
import (
"github.com/WatchBeam/clock"
"github.com/fleetdm/fleet/server/config"
"github.com/fleetdm/fleet/server/kolide"
kitlog "github.com/go-kit/kit/log"
)
type Service struct {
kolide.Service
ds kolide.Datastore
logger kitlog.Logger
config config.KolideConfig
clock clock.Clock
license *kolide.LicenseInfo
}
func NewService(
svc kolide.Service,
ds kolide.Datastore,
logger kitlog.Logger,
config config.KolideConfig,
mailService kolide.MailService,
c clock.Clock,
license *kolide.LicenseInfo,
) (*Service, error) {
return &Service{
Service: svc,
ds: ds,
logger: logger,
config: config,
clock: c,
license: license,
}, nil
}

View file

@ -0,0 +1,160 @@
package service
import (
"context"
"encoding/json"
"fmt"
"github.com/fleetdm/fleet/server/contexts/viewer"
"github.com/fleetdm/fleet/server/kolide"
"github.com/pkg/errors"
)
func (svc *Service) NewTeam(ctx context.Context, p kolide.TeamPayload) (*kolide.Team, error) {
team := &kolide.Team{}
if p.Name == nil {
return nil, kolide.NewInvalidArgumentError("name", "missing required argument")
}
if *p.Name == "" {
return nil, kolide.NewInvalidArgumentError("name", "may not be empty")
}
team.Name = *p.Name
if p.Description != nil {
team.Description = *p.Description
}
if p.Secrets != nil {
team.Secrets = p.Secrets
} else {
// Set up a default enroll secret
secret, err := kolide.RandomText(kolide.EnrollSecretDefaultLength)
if err != nil {
return nil, errors.Wrap(err, "generate enroll secret string")
}
team.Secrets = []*kolide.EnrollSecret{{Secret: secret}}
}
team, err := svc.ds.NewTeam(team)
if err != nil {
return nil, err
}
return team, nil
}
func (svc *Service) ModifyTeam(ctx context.Context, id uint, payload kolide.TeamPayload) (*kolide.Team, error) {
team, err := svc.ds.Team(id)
if err != nil {
return nil, err
}
if payload.Name != nil {
if *payload.Name == "" {
return nil, kolide.NewInvalidArgumentError("name", "may not be empty")
}
team.Name = *payload.Name
}
if payload.Description != nil {
team.Description = *payload.Description
}
if payload.Secrets != nil {
team.Secrets = payload.Secrets
}
return svc.ds.SaveTeam(team)
}
func (svc *Service) ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*kolide.Team, error) {
team, err := svc.ds.Team(id)
if err != nil {
return nil, err
}
if options != nil {
team.AgentOptions = &options
} else {
team.AgentOptions = nil
}
return svc.ds.SaveTeam(team)
}
func (svc *Service) AddTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
idMap := make(map[uint]kolide.TeamUser)
for _, user := range users {
if !kolide.ValidTeamRole(user.Role) {
return nil, kolide.NewInvalidArgumentError("users", fmt.Sprintf("%s is not a valid role for a team user", user.Role))
}
idMap[user.ID] = user
}
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
// Replace existing
for i, existingUser := range team.Users {
if user, ok := idMap[existingUser.ID]; ok {
team.Users[i] = user
delete(idMap, user.ID)
}
}
// Add new (that have not already been replaced)
for _, user := range idMap {
team.Users = append(team.Users, user)
}
return svc.ds.SaveTeam(team)
}
func (svc *Service) DeleteTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
idMap := make(map[uint]bool)
for _, user := range users {
idMap[user.ID] = true
}
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
newUsers := []kolide.TeamUser{}
// Delete existing
for _, existingUser := range team.Users {
if _, ok := idMap[existingUser.ID]; !ok {
// Only add non-deleted users
newUsers = append(newUsers, existingUser)
}
}
team.Users = newUsers
return svc.ds.SaveTeam(team)
}
func (svc *Service) ListTeamUsers(ctx context.Context, teamID uint, opt kolide.ListOptions) ([]*kolide.User, error) {
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
return svc.ds.ListUsers(kolide.UserListOptions{ListOptions: opt, TeamID: team.ID})
}
func (svc *Service) ListTeams(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Team, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, kolide.ErrNoContext
}
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
return svc.ds.ListTeams(filter, opt)
}
func (svc *Service) DeleteTeam(ctx context.Context, tid uint) error {
return svc.ds.DeleteTeam(tid)
}
func (svc *Service) TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*kolide.EnrollSecret, error) {
return svc.ds.TeamEnrollSecrets(teamID)
}

168
server/kolide/errors.go Normal file
View file

@ -0,0 +1,168 @@
package kolide
import (
"errors"
"fmt"
"net/http"
)
var (
ErrNoContext = errors.New("context key not set")
ErrMissingLicense = &LicenseError{}
)
// ErrWithInternal is an interface for errors that include extra "internal"
// information that should be logged in server logs but not sent to clients.
type ErrWithInternal interface {
error
// Internal returns the error string that must only be logged internally,
// not returned to the client.
Internal() string
}
// 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)
type ErrWithRetryAfter interface {
error
// RetryAfter returns the number of seconds to wait before retry.
RetryAfter() int
}
// InvalidArgumentError is the error returned when invalid data is presented to
// a service method.
type InvalidArgumentError []InvalidArgument
// InvalidArgument is the details about a single invalid argument.
type InvalidArgument struct {
name string
reason string
}
// NewInvalidArgumentError returns a InvalidArgumentError with at least
// one error.
func NewInvalidArgumentError(name, reason string) *InvalidArgumentError {
var invalid InvalidArgumentError
invalid = append(invalid, InvalidArgument{
name: name,
reason: reason,
})
return &invalid
}
func (e *InvalidArgumentError) Append(name, reason string) {
*e = append(*e, InvalidArgument{
name: name,
reason: reason,
})
}
func (e *InvalidArgumentError) Appendf(name, reasonFmt string, args ...interface{}) {
*e = append(*e, InvalidArgument{
name: name,
reason: fmt.Sprintf(reasonFmt, args...),
})
}
func (e *InvalidArgumentError) HasErrors() bool {
return len(*e) != 0
}
// Error implements the error interface.
func (e InvalidArgumentError) Error() string {
switch len(e) {
case 0:
return "validation failed"
case 1:
return fmt.Sprintf("validation failed: %s %s", e[0].name, e[0].reason)
default:
return fmt.Sprintf("validation failed: %s %s and %d other errors", e[0].name, e[0].reason,
len(e))
}
}
func (e InvalidArgumentError) Invalid() []map[string]string {
var invalid []map[string]string
for _, i := range e {
invalid = append(invalid, map[string]string{"name": i.name, "reason": i.reason})
}
return invalid
}
type AuthFailedError struct {
// internal is the reason that should only be logged internally
internal string
}
func NewAuthFailedError(internal string) *AuthFailedError {
return &AuthFailedError{internal: internal}
}
func (e AuthFailedError) Error() string {
return "Authentication failed"
}
func (e AuthFailedError) Internal() string {
return e.internal
}
func (e AuthFailedError) StatusCode() int {
return http.StatusUnauthorized
}
type AuthRequiredError struct {
// internal is the reason that should only be logged internally
internal string
}
func NewAuthRequiredError(internal string) *AuthRequiredError {
return &AuthRequiredError{internal: internal}
}
func (e AuthRequiredError) Error() string {
return "Authentication required"
}
func (e AuthRequiredError) Internal() string {
return e.internal
}
func (e AuthRequiredError) StatusCode() int {
return http.StatusUnauthorized
}
// PermissionError, set when user is authenticated, but not allowed to perform action
type PermissionError struct {
message string
}
func NewPermissionError(message string) *PermissionError {
return &PermissionError{message: message}
}
func (e PermissionError) Error() string {
return e.message
}
func (e PermissionError) PermissionError() []map[string]string {
var forbidden []map[string]string
return forbidden
}
// 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) StatusCode() int {
return http.StatusPaymentRequired
}

View file

@ -13,8 +13,6 @@ import (
"github.com/pkg/errors"
)
var errNoContext = errors.New("context key not set")
// authenticatedHost wraps an endpoint, checks the validity of the node_key
// provided in the request, and attaches the corresponding osquery host to the
// context for the request
@ -70,7 +68,7 @@ func authenticatedUser(jwtKey string, svc kolide.Service, next endpoint.Endpoint
// if not succesful, try again this time with errors
bearer, ok := token.FromContext(ctx)
if !ok {
return nil, authRequiredError{internal: "no auth token"}
return nil, kolide.NewAuthRequiredError("no auth token")
}
v, err := authViewer(ctx, jwtKey, bearer, svc)
@ -92,30 +90,30 @@ func authViewer(ctx context.Context, jwtKey string, bearerToken token.Token, svc
return []byte(jwtKey), nil
})
if err != nil {
return nil, authRequiredError{internal: err.Error()}
return nil, kolide.NewAuthRequiredError(err.Error())
}
if !jwtToken.Valid {
return nil, authRequiredError{internal: "invalid jwt token"}
return nil, kolide.NewAuthRequiredError("invalid jwt token")
}
claims, ok := jwtToken.Claims.(jwt.MapClaims)
if !ok {
return nil, authRequiredError{internal: "no jwt claims"}
return nil, kolide.NewAuthRequiredError("no jwt claims")
}
sessionKeyClaim, ok := claims["session_key"]
if !ok {
return nil, authRequiredError{internal: "no session_key in JWT claims"}
return nil, kolide.NewAuthRequiredError("no session_key in JWT claims")
}
sessionKey, ok := sessionKeyClaim.(string)
if !ok {
return nil, authRequiredError{internal: "non-string key in sessionClaim"}
return nil, kolide.NewAuthRequiredError("non-string key in sessionClaim")
}
session, err := svc.GetSessionByKey(ctx, sessionKey)
if err != nil {
return nil, authRequiredError{internal: err.Error()}
return nil, kolide.NewAuthRequiredError(err.Error())
}
user, err := svc.User(ctx, session.UserID)
if err != nil {
return nil, authRequiredError{internal: err.Error()}
return nil, kolide.NewAuthRequiredError(err.Error())
}
return &viewer.Viewer{User: user, Session: session}, nil
}
@ -124,10 +122,10 @@ 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, errNoContext
return nil, kolide.ErrNoContext
}
if !vc.CanPerformAdminActions() {
return nil, permissionError{message: "must be an admin"}
return nil, kolide.NewPermissionError("must be an admin")
}
return next(ctx, request)
}
@ -137,10 +135,10 @@ 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, errNoContext
return nil, kolide.ErrNoContext
}
if !vc.CanPerformActions() {
return nil, permissionError{message: "no read permissions"}
return nil, kolide.NewPermissionError("no read permissions")
}
return next(ctx, request)
}
@ -150,11 +148,11 @@ 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, errNoContext
return nil, kolide.ErrNoContext
}
uid := requestUserIDFromContext(ctx)
if !vc.CanPerformReadActionOnUser(uid) {
return nil, permissionError{message: "no read permissions on user"}
return nil, kolide.NewPermissionError("no read permissions on user")
}
return next(ctx, request)
}
@ -164,11 +162,11 @@ 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, errNoContext
return nil, kolide.ErrNoContext
}
uid := requestUserIDFromContext(ctx)
if !vc.CanPerformWriteActionOnUser(uid) {
return nil, permissionError{message: "no write permissions on user"}
return nil, kolide.NewPermissionError("no write permissions on user")
}
return next(ctx, request)
}
@ -178,10 +176,10 @@ func canPerformPasswordReset(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
if !vc.CanPerformPasswordReset() {
return nil, permissionError{message: "cannot reset password"}
return nil, kolide.NewPermissionError("cannot reset password")
}
return next(ctx, request)
}

View file

@ -64,15 +64,15 @@ import (
// }{
// {
// endpoint: mustBeAdmin(e),
// wantErr: errNoContext,
// wantErr: kolide.ErrNoContext,
// },
// {
// endpoint: canReadUser(e),
// wantErr: errNoContext,
// wantErr: kolide.ErrNoContext,
// },
// {
// endpoint: canModifyUser(e),
// wantErr: errNoContext,
// wantErr: kolide.ErrNoContext,
// },
// {
// endpoint: mustBeAdmin(e),

View file

@ -20,7 +20,7 @@ func NewLoggingService(svc kolide.Service, logger kitlog.Logger) kolide.Service
// loggerDebug returns the the info level if there error is non-nil, otherwise defaulting to the debug level.
func (mw loggingMiddleware) loggerDebug(err error) kitlog.Logger {
logger := mw.logger
if e, ok := err.(ErrWithInternal); ok {
if e, ok := err.(kolide.ErrWithInternal); ok {
logger = kitlog.With(logger, "err_internal", e.Internal())
}
if err != nil {
@ -32,7 +32,7 @@ func (mw loggingMiddleware) loggerDebug(err error) kitlog.Logger {
// loggerInfo returns the info level
func (mw loggingMiddleware) loggerInfo(err error) kitlog.Logger {
logger := mw.logger
if e, ok := err.(ErrWithInternal); ok {
if e, ok := err.(kolide.ErrWithInternal); ok {
logger = kitlog.With(logger, "err_internal", e.Internal())
}
return level.Info(logger)

View file

@ -16,7 +16,7 @@ func (mw loggingMiddleware) InviteNewUser(ctx context.Context, payload kolide.In
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
defer func(begin time.Time) {
_ = mw.loggerInfo(err).Log(
@ -37,7 +37,7 @@ func (mw loggingMiddleware) DeleteInvite(ctx context.Context, id uint) error {
)
vc, ok := viewer.FromContext(ctx)
if !ok {
return errNoContext
return kolide.ErrNoContext
}
defer func(begin time.Time) {
_ = mw.loggerInfo(err).Log(
@ -58,7 +58,7 @@ func (mw loggingMiddleware) ListInvites(ctx context.Context, opt kolide.ListOpti
)
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
defer func(begin time.Time) {
_ = mw.loggerInfo(err).Log(

View file

@ -160,7 +160,7 @@ func (mw loggingMiddleware) ModifyUser(ctx context.Context, userID uint, p kolid
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
defer func(begin time.Time) {

View file

@ -4,10 +4,8 @@ package service
import (
"html/template"
"net/http"
"strings"
"sync"
"time"
"github.com/WatchBeam/clock"
"github.com/fleetdm/fleet/server/config"
@ -19,6 +17,25 @@ import (
"github.com/pkg/errors"
)
// Service is the struct implementing kolide.Service. Create a new one with NewService.
type Service struct {
ds kolide.Datastore
carveStore kolide.CarveStore
resultStore kolide.QueryResultStore
liveQueryStore kolide.LiveQueryStore
logger kitlog.Logger
config config.KolideConfig
clock clock.Clock
license kolide.LicenseInfo
osqueryLogWriter *logging.OsqueryLogger
mailService kolide.MailService
ssoSessionStore sso.SessionStore
seenHostSet *seenHostSet
}
// NewService creates a new service from the config struct
func NewService(ds kolide.Datastore, resultStore kolide.QueryResultStore,
logger kitlog.Logger, config config.KolideConfig, mailService kolide.MailService,
@ -31,7 +48,7 @@ func NewService(ds kolide.Datastore, resultStore kolide.QueryResultStore,
return nil, errors.Wrap(err, "initializing osquery logging")
}
svc = &service{
svc = &Service{
ds: ds,
carveStore: carveStore,
resultStore: resultStore,
@ -43,43 +60,16 @@ func NewService(ds kolide.Datastore, resultStore kolide.QueryResultStore,
mailService: mailService,
ssoSessionStore: sso,
seenHostSet: newSeenHostSet(),
metaDataClient: &http.Client{
Timeout: 5 * time.Second,
},
license: license,
license: license,
}
svc = validationMiddleware{svc, ds, sso}
return svc, nil
}
type service struct {
ds kolide.Datastore
carveStore kolide.CarveStore
resultStore kolide.QueryResultStore
liveQueryStore kolide.LiveQueryStore
logger kitlog.Logger
config config.KolideConfig
clock clock.Clock
osqueryLogWriter *logging.OsqueryLogger
mailService kolide.MailService
ssoSessionStore sso.SessionStore
metaDataClient *http.Client
seenHostSet *seenHostSet
license kolide.LicenseInfo
}
func (s service) SendEmail(mail kolide.Email) error {
func (s Service) SendEmail(mail kolide.Email) error {
return s.mailService.SendEmail(mail)
}
func (s service) Clock() clock.Clock {
return s.clock
}
type validationMiddleware struct {
kolide.Service
ds kolide.Datastore

View file

@ -8,7 +8,7 @@ import (
"github.com/pkg/errors"
)
func (svc service) AgentOptionsForHost(ctx context.Context, host *kolide.Host) (json.RawMessage, error) {
func (svc *Service) AgentOptionsForHost(ctx context.Context, host *kolide.Host) (json.RawMessage, error) {
// If host has a team and team has non-empty options, prioritize that.
if host.TeamID != nil {
team, err := svc.ds.Team(*host.TeamID)

View file

@ -31,7 +31,7 @@ func (e mailError) MailError() []map[string]string {
}
}
func (svc service) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
func (svc Service) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
config, err := svc.ds.AppConfig()
if err != nil {
return nil, err
@ -61,14 +61,14 @@ func (svc service) NewAppConfig(ctx context.Context, p kolide.AppConfigPayload)
return newConfig, nil
}
func (svc service) AppConfig(ctx context.Context) (*kolide.AppConfig, error) {
func (svc *Service) AppConfig(ctx context.Context) (*kolide.AppConfig, error) {
return svc.ds.AppConfig()
}
func (svc service) SendTestEmail(ctx context.Context, config *kolide.AppConfig) error {
func (svc *Service) SendTestEmail(ctx context.Context, config *kolide.AppConfig) error {
vc, ok := viewer.FromContext(ctx)
if !ok {
return errNoContext
return kolide.ErrNoContext
}
testMail := kolide.Email{
@ -88,7 +88,7 @@ func (svc service) SendTestEmail(ctx context.Context, config *kolide.AppConfig)
}
func (svc service) ModifyAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
func (svc *Service) ModifyAppConfig(ctx context.Context, p kolide.AppConfigPayload) (*kolide.AppConfig, error) {
oldAppConfig, err := svc.AppConfig(ctx)
if err != nil {
return nil, err
@ -240,7 +240,7 @@ func appConfigFromAppConfigPayload(p kolide.AppConfigPayload, config kolide.AppC
return &config
}
func (svc service) ApplyEnrollSecretSpec(ctx context.Context, spec *kolide.EnrollSecretSpec) error {
func (svc *Service) ApplyEnrollSecretSpec(ctx context.Context, spec *kolide.EnrollSecretSpec) error {
for _, s := range spec.Secrets {
if s.Secret == "" {
return errors.New("enroll secret must not be empty")
@ -250,7 +250,7 @@ func (svc service) ApplyEnrollSecretSpec(ctx context.Context, spec *kolide.Enrol
return svc.ds.ApplyEnrollSecrets(nil, spec.Secrets)
}
func (svc service) GetEnrollSecretSpec(ctx context.Context) (*kolide.EnrollSecretSpec, error) {
func (svc *Service) GetEnrollSecretSpec(ctx context.Context) (*kolide.EnrollSecretSpec, error) {
secrets, err := svc.ds.GetEnrollSecrets(nil)
if err != nil {
return nil, err
@ -258,11 +258,11 @@ func (svc service) GetEnrollSecretSpec(ctx context.Context) (*kolide.EnrollSecre
return &kolide.EnrollSecretSpec{Secrets: secrets}, nil
}
func (svc service) Version(ctx context.Context) (*version.Info, error) {
func (svc *Service) Version(ctx context.Context) (*version.Info, error) {
info := version.Version()
return &info, nil
}
func (svc service) License(ctx context.Context) (*kolide.LicenseInfo, error) {
func (svc *Service) License(ctx context.Context) (*kolide.LicenseInfo, error) {
return &svc.license, nil
}

View file

@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
)
func (svc service) NewDistributedQueryCampaignByNames(ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string) (*kolide.DistributedQueryCampaign, error) {
func (svc Service) NewDistributedQueryCampaignByNames(ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string) (*kolide.DistributedQueryCampaign, error) {
hostIDs, err := svc.ds.HostIDsByName(hosts)
if err != nil {
return nil, errors.Wrap(err, "finding host IDs")
@ -31,18 +31,18 @@ func (svc service) NewDistributedQueryCampaignByNames(ctx context.Context, query
return svc.NewDistributedQueryCampaign(ctx, queryString, queryID, targets)
}
func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString string, queryID *uint, targets kolide.HostTargets) (*kolide.DistributedQueryCampaign, error) {
func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString string, queryID *uint, targets kolide.HostTargets) (*kolide.DistributedQueryCampaign, error) {
if err := svc.StatusLiveQuery(ctx); err != nil {
return nil, err
}
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
if queryID == nil && queryString == "" {
return nil, newInvalidArgumentError("query", "one of query or query_id must be specified")
return nil, kolide.NewInvalidArgumentError("query", "one of query or query_id must be specified")
}
var query *kolide.Query
@ -150,7 +150,7 @@ type campaignStatus struct {
Status string `json:"status"`
}
func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Conn, campaignID uint) {
func (svc Service) StreamCampaignResults(ctx context.Context, conn *websocket.Conn, campaignID uint) {
// Find the campaign and ensure it is active
campaign, err := svc.ds.DistributedQueryCampaign(campaignID)
if err != nil {

View file

@ -16,7 +16,7 @@ const (
maxBlockSize = 256 * 1024 * 1024 // 256MB
)
func (svc service) CarveBegin(ctx context.Context, payload kolide.CarveBeginPayload) (*kolide.CarveMetadata, error) {
func (svc *Service) CarveBegin(ctx context.Context, payload kolide.CarveBeginPayload) (*kolide.CarveMetadata, error) {
host, ok := hostctx.FromContext(ctx)
if !ok {
return nil, osqueryError{message: "internal error: missing host from request context"}
@ -66,7 +66,7 @@ func (svc service) CarveBegin(ctx context.Context, payload kolide.CarveBeginPayl
return carve, nil
}
func (svc service) CarveBlock(ctx context.Context, payload kolide.CarveBlockPayload) error {
func (svc *Service) CarveBlock(ctx context.Context, payload kolide.CarveBlockPayload) error {
// Note host did not authenticate via node key. We need to authenticate them
// by the session ID and request ID
carve, err := svc.carveStore.CarveBySessionId(payload.SessionId)
@ -99,15 +99,15 @@ func (svc service) CarveBlock(ctx context.Context, payload kolide.CarveBlockPayl
return nil
}
func (svc service) GetCarve(ctx context.Context, id int64) (*kolide.CarveMetadata, error) {
func (svc *Service) GetCarve(ctx context.Context, id int64) (*kolide.CarveMetadata, error) {
return svc.carveStore.Carve(id)
}
func (svc service) ListCarves(ctx context.Context, opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
func (svc *Service) ListCarves(ctx context.Context, opt kolide.CarveListOptions) ([]*kolide.CarveMetadata, error) {
return svc.carveStore.ListCarves(opt)
}
func (svc service) GetBlock(ctx context.Context, carveId, blockId int64) ([]byte, error) {
func (svc *Service) GetBlock(ctx context.Context, carveId, blockId int64) ([]byte, error) {
metadata, err := svc.carveStore.Carve(carveId)
if err != nil {
return nil, errors.Wrap(err, "get carve by name")

View file

@ -22,7 +22,7 @@ func TestCarveBegin(t *testing.T) {
RequestId: "carve_request",
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
expectedMetadata := kolide.CarveMetadata{
ID: 7,
HostId: host.ID,
@ -56,7 +56,7 @@ func TestCarveBeginNewCarveError(t *testing.T) {
RequestId: "carve_request",
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.NewCarveFunc = func(metadata *kolide.CarveMetadata) (*kolide.CarveMetadata, error) {
return nil, fmt.Errorf("ouch!")
}
@ -70,7 +70,7 @@ func TestCarveBeginNewCarveError(t *testing.T) {
func TestCarveBeginEmptyError(t *testing.T) {
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ctx := hostctx.NewContext(context.Background(), kolide.Host{})
_, err := svc.CarveBegin(ctx, kolide.CarveBeginPayload{})
@ -80,7 +80,7 @@ func TestCarveBeginEmptyError(t *testing.T) {
func TestCarveBeginMissingHostError(t *testing.T) {
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
_, err := svc.CarveBegin(context.Background(), kolide.CarveBeginPayload{})
require.Error(t, err)
@ -96,7 +96,7 @@ func TestCarveBeginBlockSizeMaxError(t *testing.T) {
RequestId: "carve_request",
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ctx := hostctx.NewContext(context.Background(), host)
@ -114,7 +114,7 @@ func TestCarveBeginCarveSizeMaxError(t *testing.T) {
RequestId: "carve_request",
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ctx := hostctx.NewContext(context.Background(), host)
@ -132,7 +132,7 @@ func TestCarveBeginCarveSizeError(t *testing.T) {
RequestId: "carve_request",
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ctx := hostctx.NewContext(context.Background(), host)
// Too big
@ -150,7 +150,7 @@ func TestCarveBeginCarveSizeError(t *testing.T) {
func TestCarveCarveBlockGetCarveError(t *testing.T) {
sessionId := "foobar"
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
return nil, fmt.Errorf("ouch!")
}
@ -177,7 +177,7 @@ func TestCarveCarveBlockRequestIdError(t *testing.T) {
SessionId: sessionId,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -206,7 +206,7 @@ func TestCarveCarveBlockBlockCountExceedError(t *testing.T) {
SessionId: sessionId,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -237,7 +237,7 @@ func TestCarveCarveBlockBlockCountMatchError(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -268,7 +268,7 @@ func TestCarveCarveBlockBlockSizeError(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -299,7 +299,7 @@ func TestCarveCarveBlockNewBlockError(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -339,7 +339,7 @@ func TestCarveCarveBlock(t *testing.T) {
BlockId: 4,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveBySessionIdFunc = func(sessionId string) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.SessionId, sessionId)
return metadata, nil
@ -369,7 +369,7 @@ func TestCarveGetBlock(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.ID, carveId)
return metadata, nil
@ -398,7 +398,7 @@ func TestCarveGetBlockNotAvailableError(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.ID, carveId)
return metadata, nil
@ -423,7 +423,7 @@ func TestCarveGetBlockGetBlockError(t *testing.T) {
MaxBlock: 3,
}
ms := new(mock.Store)
svc := service{carveStore: ms}
svc := &Service{carveStore: ms}
ms.CarveFunc = func(carveId int64) (*kolide.CarveMetadata, error) {
assert.Equal(t, metadata.ID, carveId)
return metadata, nil

View file

@ -14,7 +14,7 @@ import (
)
// Certificate returns the PEM encoded certificate chain for osqueryd TLS termination.
func (svc service) CertificateChain(ctx context.Context) ([]byte, error) {
func (svc *Service) CertificateChain(ctx context.Context) ([]byte, error) {
config, err := svc.AppConfig(ctx)
if err != nil {
return nil, err

View file

@ -1,173 +1 @@
package service
import (
"fmt"
"net/http"
)
// ErrWithInternal is an interface for errors that include extra "internal"
// information that should be logged in server logs but not sent to clients.
type ErrWithInternal interface {
error
// Internal returns the error string that must only be logged internally,
// not returned to the client.
Internal() string
}
// 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)
type ErrWithRetryAfter interface {
error
// RetryAfter returns the number of seconds to wait before retry.
RetryAfter() int
}
type invalidArgumentError []invalidArgument
type invalidArgument struct {
name string
reason string
}
// newInvalidArgumentError returns a invalidArgumentError with at least
// one error.
func newInvalidArgumentError(name, reason string) *invalidArgumentError {
var invalid invalidArgumentError
invalid = append(invalid, invalidArgument{
name: name,
reason: reason,
})
return &invalid
}
func (e *invalidArgumentError) Append(name, reason string) {
*e = append(*e, invalidArgument{
name: name,
reason: reason,
})
}
func (e *invalidArgumentError) Appendf(name, reasonFmt string, args ...interface{}) {
*e = append(*e, invalidArgument{
name: name,
reason: fmt.Sprintf(reasonFmt, args...),
})
}
func (e *invalidArgumentError) HasErrors() bool {
return len(*e) != 0
}
// invalidArgumentError is returned when one or more arguments are invalid.
func (e invalidArgumentError) Error() string {
switch len(e) {
case 0:
return "validation failed"
case 1:
return fmt.Sprintf("validation failed: %s %s", e[0].name, e[0].reason)
default:
return fmt.Sprintf("validation failed: %s %s and %d other errors", e[0].name, e[0].reason,
len(e))
}
}
func (e invalidArgumentError) Invalid() []map[string]string {
var invalid []map[string]string
for _, i := range e {
invalid = append(invalid, map[string]string{"name": i.name, "reason": i.reason})
}
return invalid
}
type authFailedError struct {
// internal is the reason that should only be logged internally
internal string
}
func (e authFailedError) Error() string {
return "Authentication failed"
}
func (e authFailedError) Internal() string {
return e.internal
}
func (e authFailedError) StatusCode() int {
return http.StatusUnauthorized
}
type authRequiredError struct {
// internal is the reason that should only be logged internally
internal string
}
func (e authRequiredError) Error() string {
return "Authentication required"
}
func (e authRequiredError) Internal() string {
return e.internal
}
func (e authRequiredError) StatusCode() int {
return http.StatusUnauthorized
}
// permissionError, set when user is authenticated, but not allowed to perform action
type permissionError struct {
message string
badArgs []invalidArgument
}
func newPermissionError(name, reason string) permissionError {
return permissionError{
badArgs: []invalidArgument{
invalidArgument{
name: name,
reason: reason,
},
},
}
}
func (e permissionError) Error() string {
switch len(e.badArgs) {
case 0:
case 1:
e.message = fmt.Sprintf("unauthorized: %s",
e.badArgs[0].reason,
)
default:
e.message = fmt.Sprintf("unauthorized: %s and %d other errors",
e.badArgs[0].reason,
len(e.badArgs),
)
}
if e.message == "" {
return "unauthorized"
}
return e.message
}
func (e permissionError) PermissionError() []map[string]string {
var forbidden []map[string]string
if len(e.badArgs) == 0 {
forbidden = append(forbidden, map[string]string{"reason": e.Error()})
return forbidden
}
for _, arg := range e.badArgs {
forbidden = append(forbidden, map[string]string{
"name": arg.name,
"reason": arg.reason,
})
}
return forbidden
}

View file

@ -7,11 +7,11 @@ import (
"github.com/pkg/errors"
)
func (svc service) ListHosts(ctx context.Context, opt kolide.HostListOptions) ([]*kolide.Host, error) {
func (svc Service) ListHosts(ctx context.Context, opt kolide.HostListOptions) ([]*kolide.Host, error) {
return svc.ds.ListHosts(opt)
}
func (svc service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) {
func (svc Service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) {
host, err := svc.ds.Host(id)
if err != nil {
return nil, errors.Wrap(err, "get host")
@ -20,7 +20,7 @@ func (svc service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, er
return svc.getHostDetails(ctx, host)
}
func (svc service) HostByIdentifier(ctx context.Context, identifier string) (*kolide.HostDetail, error) {
func (svc Service) HostByIdentifier(ctx context.Context, identifier string) (*kolide.HostDetail, error) {
host, err := svc.ds.HostByIdentifier(identifier)
if err != nil {
return nil, errors.Wrap(err, "get host by identifier")
@ -29,7 +29,7 @@ func (svc service) HostByIdentifier(ctx context.Context, identifier string) (*ko
return svc.getHostDetails(ctx, host)
}
func (svc service) getHostDetails(ctx context.Context, host *kolide.Host) (*kolide.HostDetail, error) {
func (svc Service) getHostDetails(ctx context.Context, host *kolide.Host) (*kolide.HostDetail, error) {
if err := svc.ds.LoadHostSoftware(host); err != nil {
return nil, errors.Wrap(err, "load host software")
}
@ -47,7 +47,7 @@ func (svc service) getHostDetails(ctx context.Context, host *kolide.Host) (*koli
return &kolide.HostDetail{Host: *host, Labels: labels, Packs: packs}, nil
}
func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) {
func (svc Service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) {
online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now())
if err != nil {
return nil, err
@ -60,22 +60,22 @@ func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, err
}, nil
}
func (svc service) DeleteHost(ctx context.Context, id uint) error {
func (svc Service) DeleteHost(ctx context.Context, id uint) error {
return svc.ds.DeleteHost(id)
}
func (svc *service) FlushSeenHosts(ctx context.Context) error {
func (svc *Service) FlushSeenHosts(ctx context.Context) error {
hostIDs := svc.seenHostSet.getAndClearHostIDs()
return svc.ds.MarkHostsSeen(hostIDs, svc.clock.Now())
}
func (svc service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error {
func (svc Service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint) error {
return svc.ds.AddHostsToTeam(teamID, hostIDs)
}
func (svc service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt kolide.HostListOptions, lid *uint) error {
func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt kolide.HostListOptions, lid *uint) error {
if opt.StatusFilter != "" && lid != nil {
return newInvalidArgumentError("status", "may not be provided with label_id")
return kolide.NewInvalidArgumentError("status", "may not be provided with label_id")
}
opt.PerPage = kolide.PerPageUnlimited
@ -105,7 +105,7 @@ func (svc service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt
return svc.ds.AddHostsToTeam(teamID, hostIDs)
}
func (svc *service) RefetchHost(ctx context.Context, id uint) error {
func (svc *Service) RefetchHost(ctx context.Context, id uint) error {
host, err := svc.ds.Host(id)
if err != nil {
return errors.Wrap(err, "find host for refetch")

View file

@ -62,7 +62,7 @@ func TestDeleteHost(t *testing.T) {
func TestHostDetails(t *testing.T) {
ds := new(mock.Store)
svc := service{ds: ds}
svc := &Service{ds: ds}
host := &kolide.Host{ID: 3}
ctx := context.Background()
@ -98,7 +98,7 @@ func TestHostDetails(t *testing.T) {
func TestRefetchHost(t *testing.T) {
ds := new(mock.Store)
svc := service{ds: ds}
svc := &Service{ds: ds}
host := &kolide.Host{ID: 3}
ctx := context.Background()
@ -116,7 +116,7 @@ func TestRefetchHost(t *testing.T) {
func TestAddHostsToTeamByFilter(t *testing.T) {
ds := new(mock.Store)
svc := service{ds: ds}
svc := &Service{ds: ds}
ctx := context.Background()
expectedHostIDs := []uint{1, 2, 4}
@ -140,7 +140,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) {
func TestAddHostsToTeamByFilterLabel(t *testing.T) {
ds := new(mock.Store)
svc := service{ds: ds}
svc := &Service{ds: ds}
ctx := context.Background()
expectedHostIDs := []uint{6}
@ -165,7 +165,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) {
func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) {
ds := new(mock.Store)
svc := service{ds: ds}
svc := &Service{ds: ds}
ctx := context.Background()
ds.ListHostsFunc = func(opt kolide.HostListOptions) ([]*kolide.Host, error) {

View file

@ -11,11 +11,11 @@ import (
"github.com/pkg/errors"
)
func (svc service) InviteNewUser(ctx context.Context, payload kolide.InvitePayload) (*kolide.Invite, error) {
func (svc Service) InviteNewUser(ctx context.Context, payload kolide.InvitePayload) (*kolide.Invite, error) {
// verify that the user with the given email does not already exist
_, err := svc.ds.UserByEmail(*payload.Email)
if err == nil {
return nil, newInvalidArgumentError("email", "a user with this account already exists")
return nil, kolide.NewInvalidArgumentError("email", "a user with this account already exists")
}
if _, ok := err.(kolide.NotFoundError); !ok {
return nil, err
@ -85,29 +85,29 @@ func (svc service) InviteNewUser(ctx context.Context, payload kolide.InvitePaylo
return invite, nil
}
func (svc service) ListInvites(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Invite, error) {
func (svc *Service) ListInvites(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Invite, error) {
return svc.ds.ListInvites(opt)
}
func (svc service) VerifyInvite(ctx context.Context, token string) (*kolide.Invite, error) {
func (svc *Service) VerifyInvite(ctx context.Context, token string) (*kolide.Invite, error) {
invite, err := svc.ds.InviteByToken(token)
if err != nil {
return nil, err
}
if invite.Token != token {
return nil, newInvalidArgumentError("invite_token", "Invite Token does not match Email Address.")
return nil, kolide.NewInvalidArgumentError("invite_token", "Invite Token does not match Email Address.")
}
expiresAt := invite.CreatedAt.Add(svc.config.App.InviteTokenValidityPeriod)
if svc.clock.Now().After(expiresAt) {
return nil, newInvalidArgumentError("invite_token", "Invite token has expired.")
return nil, kolide.NewInvalidArgumentError("invite_token", "Invite token has expired.")
}
return invite, nil
}
func (svc service) DeleteInvite(ctx context.Context, id uint) error {
func (svc *Service) DeleteInvite(ctx context.Context, id uint) error {
return svc.ds.DeleteInvite(id)
}

View file

@ -25,7 +25,7 @@ func TestInviteNewUserMock(t *testing.T) {
return i, nil
}
mailer := &mockMailService{SendEmailFn: func(e kolide.Email) error { return nil }}
svc := validationMiddleware{&service{
svc := validationMiddleware{&Service{
ds: ms,
config: config.TestConfig(),
mailService: mailer,
@ -53,7 +53,7 @@ func TestInviteNewUserMock(t *testing.T) {
func TestVerifyInvite(t *testing.T) {
ms := new(mock.Store)
svc := service{
svc := &Service{
ds: ms,
config: config.TestConfig(),
clock: clock.NewMockClock(),
@ -71,12 +71,11 @@ func TestVerifyInvite(t *testing.T) {
},
}, nil
}
wantErr := &invalidArgumentError{{name: "invite_token", reason: "Invite token has expired."}}
wantErr := kolide.NewInvalidArgumentError("invite_token", "Invite token has expired.")
_, err := svc.VerifyInvite(ctx, "abcd")
assert.Equal(t, err, wantErr)
wantErr = &invalidArgumentError{{name: "invite_token",
reason: "Invite Token does not match Email Address."}}
wantErr = kolide.NewInvalidArgumentError("invite_token", "Invite Token does not match Email Address.")
_, err = svc.VerifyInvite(ctx, "bad_token")
assert.Equal(t, err, wantErr)
@ -84,7 +83,7 @@ func TestVerifyInvite(t *testing.T) {
func TestDeleteInvite(t *testing.T) {
ms := new(mock.Store)
svc := service{ds: ms}
svc := &Service{ds: ms}
ms.DeleteInviteFunc = func(uint) error { return nil }
err := svc.DeleteInvite(context.Background(), 1)
@ -94,7 +93,7 @@ func TestDeleteInvite(t *testing.T) {
func TestListInvites(t *testing.T) {
ms := new(mock.Store)
svc := service{ds: ms}
svc := &Service{ds: ms}
ms.ListInvitesFunc = func(kolide.ListOptions) ([]*kolide.Invite, error) {
return nil, nil

View file

@ -7,7 +7,7 @@ import (
"github.com/pkg/errors"
)
func (svc service) ApplyLabelSpecs(ctx context.Context, specs []*kolide.LabelSpec) error {
func (svc Service) ApplyLabelSpecs(ctx context.Context, specs []*kolide.LabelSpec) error {
for _, spec := range specs {
if spec.LabelMembershipType == kolide.LabelMembershipTypeDynamic && len(spec.Hosts) > 0 {
return errors.Errorf("label %s is declared as dynamic but contains `hosts` key", spec.Name)
@ -20,24 +20,24 @@ func (svc service) ApplyLabelSpecs(ctx context.Context, specs []*kolide.LabelSpe
return svc.ds.ApplyLabelSpecs(specs)
}
func (svc service) GetLabelSpecs(ctx context.Context) ([]*kolide.LabelSpec, error) {
func (svc Service) GetLabelSpecs(ctx context.Context) ([]*kolide.LabelSpec, error) {
return svc.ds.GetLabelSpecs()
}
func (svc service) GetLabelSpec(ctx context.Context, name string) (*kolide.LabelSpec, error) {
func (svc Service) GetLabelSpec(ctx context.Context, name string) (*kolide.LabelSpec, error) {
return svc.ds.GetLabelSpec(name)
}
func (svc service) NewLabel(ctx context.Context, p kolide.LabelPayload) (*kolide.Label, error) {
func (svc Service) NewLabel(ctx context.Context, p kolide.LabelPayload) (*kolide.Label, error) {
label := &kolide.Label{}
if p.Name == nil {
return nil, newInvalidArgumentError("name", "missing required argument")
return nil, kolide.NewInvalidArgumentError("name", "missing required argument")
}
label.Name = *p.Name
if p.Query == nil {
return nil, newInvalidArgumentError("query", "missing required argument")
return nil, kolide.NewInvalidArgumentError("query", "missing required argument")
}
label.Query = *p.Query
@ -56,7 +56,7 @@ func (svc service) NewLabel(ctx context.Context, p kolide.LabelPayload) (*kolide
return label, nil
}
func (svc service) ModifyLabel(ctx context.Context, id uint, payload kolide.ModifyLabelPayload) (*kolide.Label, error) {
func (svc Service) ModifyLabel(ctx context.Context, id uint, payload kolide.ModifyLabelPayload) (*kolide.Label, error) {
label, err := svc.ds.Label(id)
if err != nil {
return nil, err
@ -70,19 +70,19 @@ func (svc service) ModifyLabel(ctx context.Context, id uint, payload kolide.Modi
return svc.ds.SaveLabel(label)
}
func (svc service) ListLabels(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Label, error) {
func (svc Service) ListLabels(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Label, error) {
return svc.ds.ListLabels(opt)
}
func (svc service) GetLabel(ctx context.Context, id uint) (*kolide.Label, error) {
func (svc Service) GetLabel(ctx context.Context, id uint) (*kolide.Label, error) {
return svc.ds.Label(id)
}
func (svc service) DeleteLabel(ctx context.Context, name string) error {
func (svc Service) DeleteLabel(ctx context.Context, name string) error {
return svc.ds.DeleteLabel(name)
}
func (svc service) DeleteLabelByID(ctx context.Context, id uint) error {
func (svc Service) DeleteLabelByID(ctx context.Context, id uint) error {
label, err := svc.ds.Label(id)
if err != nil {
return err
@ -90,15 +90,15 @@ func (svc service) DeleteLabelByID(ctx context.Context, id uint) error {
return svc.ds.DeleteLabel(label.Name)
}
func (svc service) ListHostsInLabel(ctx context.Context, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
func (svc Service) ListHostsInLabel(ctx context.Context, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) {
return svc.ds.ListHostsInLabel(lid, opt)
}
func (svc service) ListLabelsForHost(ctx context.Context, hid uint) ([]*kolide.Label, error) {
func (svc Service) ListLabelsForHost(ctx context.Context, hid uint) ([]*kolide.Label, error) {
return svc.ds.ListLabelsForHost(hid)
}
func (svc service) HostIDsForLabel(lid uint) ([]uint, error) {
func (svc *Service) HostIDsForLabel(lid uint) ([]uint, error) {
hosts, err := svc.ds.ListHostsInLabel(lid, kolide.HostListOptions{})
if err != nil {
return nil, err

View file

@ -42,7 +42,7 @@ func emptyToZero(val string) string {
return val
}
func (svc service) AuthenticateHost(ctx context.Context, nodeKey string) (*kolide.Host, error) {
func (svc Service) AuthenticateHost(ctx context.Context, nodeKey string) (*kolide.Host, error) {
if nodeKey == "" {
return nil, osqueryError{
message: "authentication error: missing node key",
@ -77,7 +77,7 @@ func (svc service) AuthenticateHost(ctx context.Context, nodeKey string) (*kolid
return host, nil
}
func (svc service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string, hostDetails map[string](map[string]string)) (string, error) {
func (svc Service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string, hostDetails map[string](map[string]string)) (string, error) {
secret, err := svc.ds.VerifyEnrollSecret(enrollSecret)
if err != nil {
return "", osqueryError{
@ -191,7 +191,7 @@ func getHostIdentifier(logger log.Logger, identifierOption, providedIdentifier s
return providedIdentifier
}
func (svc service) GetClientConfig(ctx context.Context) (map[string]interface{}, error) {
func (svc *Service) GetClientConfig(ctx context.Context) (map[string]interface{}, error) {
host, ok := hostctx.FromContext(ctx)
if !ok {
return nil, osqueryError{message: "internal error: missing host from request context"}
@ -300,14 +300,14 @@ func (svc service) GetClientConfig(ctx context.Context) (map[string]interface{},
return config, nil
}
func (svc service) SubmitStatusLogs(ctx context.Context, logs []json.RawMessage) error {
func (svc *Service) SubmitStatusLogs(ctx context.Context, logs []json.RawMessage) error {
if err := svc.osqueryLogWriter.Status.Write(ctx, logs); err != nil {
return osqueryError{message: "error writing status logs: " + err.Error()}
}
return nil
}
func (svc service) SubmitResultLogs(ctx context.Context, logs []json.RawMessage) error {
func (svc *Service) SubmitResultLogs(ctx context.Context, logs []json.RawMessage) error {
if err := svc.osqueryLogWriter.Result.Write(ctx, logs); err != nil {
return osqueryError{message: "error writing result logs: " + err.Error()}
}
@ -859,7 +859,7 @@ func ingestSoftware(logger log.Logger, host *kolide.Host, rows []map[string]stri
// hostDetailQueries returns the map of queries that should be executed by
// osqueryd to fill in the host details
func (svc service) hostDetailQueries(host kolide.Host) (map[string]string, error) {
func (svc *Service) hostDetailQueries(host kolide.Host) (map[string]string, error) {
queries := make(map[string]string)
if host.DetailUpdateTime.After(svc.clock.Now().Add(-svc.config.Osquery.DetailUpdateInterval)) && !host.RefetchRequested {
// No need to update already fresh details
@ -901,7 +901,7 @@ func (svc service) hostDetailQueries(host kolide.Host) (map[string]string, error
return queries, nil
}
func (svc service) GetDistributedQueries(ctx context.Context) (map[string]string, uint, error) {
func (svc *Service) GetDistributedQueries(ctx context.Context) (map[string]string, uint, error) {
host, ok := hostctx.FromContext(ctx)
if !ok {
return nil, 0, osqueryError{message: "internal error: missing host from request context"}
@ -945,7 +945,7 @@ func (svc service) GetDistributedQueries(ctx context.Context) (map[string]string
// ingestDetailQuery takes the results of a detail query and modifies the
// provided kolide.Host appropriately.
func (svc service) ingestDetailQuery(host *kolide.Host, name string, rows []map[string]string) error {
func (svc *Service) ingestDetailQuery(host *kolide.Host, name string, rows []map[string]string) error {
trimmedQuery := strings.TrimPrefix(name, hostDetailQueryPrefix)
query, ok := detailQueries[trimmedQuery]
if !ok {
@ -966,7 +966,7 @@ func (svc service) ingestDetailQuery(host *kolide.Host, name string, rows []map[
}
// ingestLabelQuery records the results of label queries run by a host
func (svc service) ingestLabelQuery(host kolide.Host, query string, rows []map[string]string, results map[uint]bool) error {
func (svc *Service) ingestLabelQuery(host kolide.Host, query string, rows []map[string]string, results map[uint]bool) error {
trimmedQuery := strings.TrimPrefix(query, hostLabelQueryPrefix)
trimmedQueryNum, err := strconv.Atoi(emptyToZero(trimmedQuery))
if err != nil {
@ -980,7 +980,7 @@ func (svc service) ingestLabelQuery(host kolide.Host, query string, rows []map[s
// ingestDistributedQuery takes the results of a distributed query and modifies the
// provided kolide.Host appropriately.
func (svc service) ingestDistributedQuery(host kolide.Host, name string, rows []map[string]string, failed bool, errMsg string) error {
func (svc *Service) ingestDistributedQuery(host kolide.Host, name string, rows []map[string]string, failed bool, errMsg string) error {
trimmedQuery := strings.TrimPrefix(name, hostDistributedQueryPrefix)
campaignID, err := strconv.Atoi(emptyToZero(trimmedQuery))
@ -1045,7 +1045,7 @@ func (svc service) ingestDistributedQuery(host kolide.Host, name string, rows []
return nil
}
func (svc service) SubmitDistributedQueryResults(ctx context.Context, results kolide.OsqueryDistributedQueryResults, statuses map[string]kolide.OsqueryStatus, messages map[string]string) error {
func (svc *Service) SubmitDistributedQueryResults(ctx context.Context, results kolide.OsqueryDistributedQueryResults, statuses map[string]kolide.OsqueryStatus, messages map[string]string) error {
host, ok := hostctx.FromContext(ctx)
if !ok {

View file

@ -189,7 +189,7 @@ func TestSubmitStatusLogs(t *testing.T) {
require.Nil(t, err)
// Hack to get at the service internals and modify the writer
serv := ((svc.(validationMiddleware)).Service).(*service)
serv := ((svc.(validationMiddleware)).Service).(*Service)
testLogger := &testJSONLogger{}
serv.osqueryLogWriter = &logging.OsqueryLogger{Status: testLogger}
@ -218,7 +218,7 @@ func TestSubmitResultLogs(t *testing.T) {
require.Nil(t, err)
// Hack to get at the service internals and modify the writer
serv := ((svc.(validationMiddleware)).Service).(*service)
serv := ((svc.(validationMiddleware)).Service).(*Service)
testLogger := &testJSONLogger{}
serv.osqueryLogWriter = &logging.OsqueryLogger{Result: testLogger}
@ -271,7 +271,7 @@ func TestHostDetailQueries(t *testing.T) {
UUID: "test_uuid",
}
svc := service{clock: mockClock, config: config.TestConfig(), ds: ds}
svc := &Service{clock: mockClock, config: config.TestConfig(), ds: ds}
queries, err := svc.hostDetailQueries(host)
assert.Nil(t, err)
@ -1302,7 +1302,7 @@ func TestIngestDistributedQueryParseIdError(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1321,7 +1321,7 @@ func TestIngestDistributedQueryOrphanedCampaignLoadError(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1347,7 +1347,7 @@ func TestIngestDistributedQueryOrphanedCampaignWaitListener(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1380,7 +1380,7 @@ func TestIngestDistributedQueryOrphanedCloseError(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1416,7 +1416,7 @@ func TestIngestDistributedQueryOrphanedStopError(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1453,7 +1453,7 @@ func TestIngestDistributedQueryOrphanedStop(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1490,7 +1490,7 @@ func TestIngestDistributedQueryRecordCompletionError(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,
@ -1521,7 +1521,7 @@ func TestIngestDistributedQuery(t *testing.T) {
ds := new(mock.Store)
rs := pubsub.NewInmemQueryResults()
lq := new(live_query.MockLiveQuery)
svc := service{
svc := &Service{
ds: ds,
resultStore: rs,
liveQueryStore: lq,

View file

@ -6,27 +6,27 @@ import (
"github.com/fleetdm/fleet/server/kolide"
)
func (svc service) ApplyPackSpecs(ctx context.Context, specs []*kolide.PackSpec) error {
func (svc *Service) ApplyPackSpecs(ctx context.Context, specs []*kolide.PackSpec) error {
return svc.ds.ApplyPackSpecs(specs)
}
func (svc service) GetPackSpecs(ctx context.Context) ([]*kolide.PackSpec, error) {
func (svc *Service) GetPackSpecs(ctx context.Context) ([]*kolide.PackSpec, error) {
return svc.ds.GetPackSpecs()
}
func (svc service) GetPackSpec(ctx context.Context, name string) (*kolide.PackSpec, error) {
func (svc *Service) GetPackSpec(ctx context.Context, name string) (*kolide.PackSpec, error) {
return svc.ds.GetPackSpec(name)
}
func (svc service) ListPacks(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Pack, error) {
func (svc *Service) ListPacks(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Pack, error) {
return svc.ds.ListPacks(opt)
}
func (svc service) GetPack(ctx context.Context, id uint) (*kolide.Pack, error) {
func (svc *Service) GetPack(ctx context.Context, id uint) (*kolide.Pack, error) {
return svc.ds.Pack(id)
}
func (svc service) NewPack(ctx context.Context, p kolide.PackPayload) (*kolide.Pack, error) {
func (svc *Service) NewPack(ctx context.Context, p kolide.PackPayload) (*kolide.Pack, error) {
var pack kolide.Pack
if p.Name != nil {
@ -71,7 +71,7 @@ func (svc service) NewPack(ctx context.Context, p kolide.PackPayload) (*kolide.P
return &pack, nil
}
func (svc service) ModifyPack(ctx context.Context, id uint, p kolide.PackPayload) (*kolide.Pack, error) {
func (svc *Service) ModifyPack(ctx context.Context, id uint, p kolide.PackPayload) (*kolide.Pack, error) {
pack, err := svc.ds.Pack(id)
if err != nil {
return nil, err
@ -197,11 +197,11 @@ func (svc service) ModifyPack(ctx context.Context, id uint, p kolide.PackPayload
return pack, err
}
func (svc service) DeletePack(ctx context.Context, name string) error {
func (svc *Service) DeletePack(ctx context.Context, name string) error {
return svc.ds.DeletePack(name)
}
func (svc service) DeletePackByID(ctx context.Context, id uint) error {
func (svc *Service) DeletePackByID(ctx context.Context, id uint) error {
pack, err := svc.ds.Pack(id)
if err != nil {
return err
@ -209,34 +209,34 @@ func (svc service) DeletePackByID(ctx context.Context, id uint) error {
return svc.ds.DeletePack(pack.Name)
}
func (svc service) AddLabelToPack(ctx context.Context, lid, pid uint) error {
func (svc *Service) AddLabelToPack(ctx context.Context, lid, pid uint) error {
return svc.ds.AddLabelToPack(lid, pid)
}
func (svc service) RemoveLabelFromPack(ctx context.Context, lid, pid uint) error {
func (svc *Service) RemoveLabelFromPack(ctx context.Context, lid, pid uint) error {
return svc.ds.RemoveLabelFromPack(lid, pid)
}
func (svc service) AddHostToPack(ctx context.Context, hid, pid uint) error {
func (svc *Service) AddHostToPack(ctx context.Context, hid, pid uint) error {
return svc.ds.AddHostToPack(hid, pid)
}
func (svc service) RemoveHostFromPack(ctx context.Context, hid, pid uint) error {
func (svc *Service) RemoveHostFromPack(ctx context.Context, hid, pid uint) error {
return svc.ds.RemoveHostFromPack(hid, pid)
}
func (svc service) ListLabelsForPack(ctx context.Context, pid uint) ([]*kolide.Label, error) {
func (svc *Service) ListLabelsForPack(ctx context.Context, pid uint) ([]*kolide.Label, error) {
return svc.ds.ListLabelsForPack(pid)
}
func (svc service) ListHostsInPack(ctx context.Context, pid uint, opt kolide.ListOptions) ([]uint, error) {
func (svc *Service) ListHostsInPack(ctx context.Context, pid uint, opt kolide.ListOptions) ([]uint, error) {
return svc.ds.ListHostsInPack(pid, opt)
}
func (svc service) ListExplicitHostsInPack(ctx context.Context, pid uint, opt kolide.ListOptions) ([]uint, error) {
func (svc *Service) ListExplicitHostsInPack(ctx context.Context, pid uint, opt kolide.ListOptions) ([]uint, error) {
return svc.ds.ListExplicitHostsInPack(pid, opt)
}
func (svc service) ListPacksForHost(ctx context.Context, hid uint) ([]*kolide.Pack, error) {
func (svc *Service) ListPacksForHost(ctx context.Context, hid uint) ([]*kolide.Pack, error) {
return svc.ds.ListPacksForHost(hid)
}

View file

@ -25,7 +25,7 @@ func specFromQuery(query *kolide.Query) *kolide.QuerySpec {
}
}
func (svc service) ApplyQuerySpecs(ctx context.Context, specs []*kolide.QuerySpec) error {
func (svc Service) ApplyQuerySpecs(ctx context.Context, specs []*kolide.QuerySpec) error {
vc, ok := viewer.FromContext(ctx)
if !ok {
return errors.New("user must be authenticated to apply queries")
@ -46,7 +46,7 @@ func (svc service) ApplyQuerySpecs(ctx context.Context, specs []*kolide.QuerySpe
return errors.Wrap(err, "applying queries")
}
func (svc service) GetQuerySpecs(ctx context.Context) ([]*kolide.QuerySpec, error) {
func (svc Service) GetQuerySpecs(ctx context.Context) ([]*kolide.QuerySpec, error) {
queries, err := svc.ds.ListQueries(kolide.ListOptions{})
if err != nil {
return nil, errors.Wrap(err, "getting queries")
@ -59,7 +59,7 @@ func (svc service) GetQuerySpecs(ctx context.Context) ([]*kolide.QuerySpec, erro
return specs, nil
}
func (svc service) GetQuerySpec(ctx context.Context, name string) (*kolide.QuerySpec, error) {
func (svc Service) GetQuerySpec(ctx context.Context, name string) (*kolide.QuerySpec, error) {
query, err := svc.ds.QueryByName(name)
if err != nil {
return nil, err
@ -67,15 +67,15 @@ func (svc service) GetQuerySpec(ctx context.Context, name string) (*kolide.Query
return specFromQuery(query), nil
}
func (svc service) ListQueries(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Query, error) {
func (svc Service) ListQueries(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Query, error) {
return svc.ds.ListQueries(opt)
}
func (svc service) GetQuery(ctx context.Context, id uint) (*kolide.Query, error) {
func (svc *Service) GetQuery(ctx context.Context, id uint) (*kolide.Query, error) {
return svc.ds.Query(id)
}
func (svc service) NewQuery(ctx context.Context, p kolide.QueryPayload) (*kolide.Query, error) {
func (svc *Service) NewQuery(ctx context.Context, p kolide.QueryPayload) (*kolide.Query, error) {
query := &kolide.Query{Saved: true}
if p.Name != nil {
@ -112,7 +112,7 @@ func (svc service) NewQuery(ctx context.Context, p kolide.QueryPayload) (*kolide
return query, nil
}
func (svc service) ModifyQuery(ctx context.Context, id uint, p kolide.QueryPayload) (*kolide.Query, error) {
func (svc *Service) ModifyQuery(ctx context.Context, id uint, p kolide.QueryPayload) (*kolide.Query, error) {
query, err := svc.ds.Query(id)
if err != nil {
return nil, err
@ -145,11 +145,11 @@ func (svc service) ModifyQuery(ctx context.Context, id uint, p kolide.QueryPaylo
return query, nil
}
func (svc service) DeleteQuery(ctx context.Context, name string) error {
func (svc *Service) DeleteQuery(ctx context.Context, name string) error {
return svc.ds.DeleteQuery(name)
}
func (svc service) DeleteQueryByID(ctx context.Context, id uint) error {
func (svc *Service) DeleteQueryByID(ctx context.Context, id uint) error {
query, err := svc.ds.Query(id)
if err != nil {
return errors.Wrap(err, "lookup query by ID")
@ -158,6 +158,6 @@ func (svc service) DeleteQueryByID(ctx context.Context, id uint) error {
return errors.Wrap(svc.ds.DeleteQuery(query.Name), "delete query")
}
func (svc service) DeleteQueries(ctx context.Context, ids []uint) (uint, error) {
func (svc *Service) DeleteQueries(ctx context.Context, ids []uint) (uint, error) {
return svc.ds.DeleteQueries(ids)
}

View file

@ -7,15 +7,15 @@ import (
"github.com/pkg/errors"
)
func (svc service) GetScheduledQueriesInPack(ctx context.Context, id uint, opts kolide.ListOptions) ([]*kolide.ScheduledQuery, error) {
func (svc *Service) GetScheduledQueriesInPack(ctx context.Context, id uint, opts kolide.ListOptions) ([]*kolide.ScheduledQuery, error) {
return svc.ds.ListScheduledQueriesInPack(id, opts)
}
func (svc service) GetScheduledQuery(ctx context.Context, id uint) (*kolide.ScheduledQuery, error) {
func (svc *Service) GetScheduledQuery(ctx context.Context, id uint) (*kolide.ScheduledQuery, error) {
return svc.ds.ScheduledQuery(id)
}
func (svc service) ScheduleQuery(ctx context.Context, sq *kolide.ScheduledQuery) (*kolide.ScheduledQuery, error) {
func (svc *Service) ScheduleQuery(ctx context.Context, sq *kolide.ScheduledQuery) (*kolide.ScheduledQuery, error) {
// Fill in the name with query name if it is unset (because the UI
// doesn't provide a way to set it)
if sq.Name == "" {
@ -52,7 +52,7 @@ func findNextNameForQuery(name string, scheduled []*kolide.ScheduledQuery) strin
return name
}
func (svc service) ModifyScheduledQuery(ctx context.Context, id uint, p kolide.ScheduledQueryPayload) (*kolide.ScheduledQuery, error) {
func (svc *Service) ModifyScheduledQuery(ctx context.Context, id uint, p kolide.ScheduledQueryPayload) (*kolide.ScheduledQuery, error) {
sq, err := svc.GetScheduledQuery(ctx, id)
if err != nil {
return nil, errors.Wrap(err, "getting scheduled query to modify")
@ -98,6 +98,6 @@ func (svc service) ModifyScheduledQuery(ctx context.Context, id uint, p kolide.S
return svc.ds.SaveScheduledQuery(sq)
}
func (svc service) DeleteScheduledQuery(ctx context.Context, id uint) error {
func (svc *Service) DeleteScheduledQuery(ctx context.Context, id uint) error {
return svc.ds.DeleteScheduledQuery(id)
}

View file

@ -16,7 +16,7 @@ import (
"github.com/pkg/errors"
)
func (svc service) SSOSettings(ctx context.Context) (*kolide.SSOSettings, error) {
func (svc Service) SSOSettings(ctx context.Context) (*kolide.SSOSettings, error) {
appConfig, err := svc.ds.AppConfig()
if err != nil {
return nil, errors.Wrap(err, "SSOSettings getting app config")
@ -29,7 +29,7 @@ func (svc service) SSOSettings(ctx context.Context) (*kolide.SSOSettings, error)
return settings, nil
}
func (svc service) InitiateSSO(ctx context.Context, redirectURL string) (string, error) {
func (svc *Service) InitiateSSO(ctx context.Context, redirectURL string) (string, error) {
appConfig, err := svc.ds.AppConfig()
if err != nil {
return "", errors.Wrap(err, "InitiateSSO getting app config")
@ -67,9 +67,9 @@ func (svc service) InitiateSSO(ctx context.Context, redirectURL string) (string,
return idpURL, nil
}
func (svc service) getMetadata(config *kolide.AppConfig) (*sso.Metadata, error) {
func (svc *Service) getMetadata(config *kolide.AppConfig) (*sso.Metadata, error) {
if config.MetadataURL != "" {
metadata, err := sso.GetMetadata(config.MetadataURL, svc.metaDataClient)
metadata, err := sso.GetMetadata(config.MetadataURL)
if err != nil {
return nil, err
}
@ -85,7 +85,7 @@ func (svc service) getMetadata(config *kolide.AppConfig) (*sso.Metadata, error)
return nil, errors.Errorf("missing metadata for idp %s", config.IDPName)
}
func (svc service) CallbackSSO(ctx context.Context, auth kolide.Auth) (*kolide.SSOSession, error) {
func (svc *Service) CallbackSSO(ctx context.Context, auth kolide.Auth) (*kolide.SSOSession, error) {
appConfig, err := svc.ds.AppConfig()
if err != nil {
return nil, errors.Wrap(err, "get config for sso")
@ -156,7 +156,7 @@ func (svc service) CallbackSSO(ctx context.Context, auth kolide.Auth) (*kolide.S
return result, nil
}
func (svc service) Login(ctx context.Context, username, password string) (*kolide.User, string, error) {
func (svc *Service) Login(ctx context.Context, username, password string) (*kolide.User, string, error) {
// If there is an error, sleep until the request has taken at least 1
// second. This means that generally a login failure for any reason will
// take ~1s and frustrate a timing attack.
@ -169,29 +169,29 @@ func (svc service) Login(ctx context.Context, username, password string) (*kolid
user, err := svc.userByEmailOrUsername(username)
if _, ok := err.(kolide.NotFoundError); ok {
return nil, "", authFailedError{internal: "user not found"}
return nil, "", kolide.NewAuthFailedError("user not found")
}
if err != nil {
return nil, "", authFailedError{internal: err.Error()}
return nil, "", kolide.NewAuthFailedError(err.Error())
}
if err = user.ValidatePassword(password); err != nil {
return nil, "", authFailedError{internal: "invalid password"}
return nil, "", kolide.NewAuthFailedError("invalid password")
}
if user.SSOEnabled {
return nil, "", authFailedError{internal: "password login disabled for sso users"}
return nil, "", kolide.NewAuthFailedError("password login disabled for sso users")
}
token, err := svc.makeSession(user.ID)
if err != nil {
return nil, "", authFailedError{internal: err.Error()}
return nil, "", kolide.NewAuthFailedError(err.Error())
}
return user, token, nil
}
func (svc service) userByEmailOrUsername(username string) (*kolide.User, error) {
func (svc *Service) userByEmailOrUsername(username string) (*kolide.User, error) {
if strings.Contains(username, "@") {
return svc.ds.UserByEmail(username)
}
@ -199,7 +199,7 @@ func (svc service) userByEmailOrUsername(username string) (*kolide.User, error)
}
// makeSession is a helper that creates a new session after authentication
func (svc service) makeSession(id uint) (string, error) {
func (svc *Service) makeSession(id uint) (string, error) {
sessionKeySize := svc.config.Session.KeySize
key := make([]byte, sessionKeySize)
_, err := rand.Read(key)
@ -226,15 +226,15 @@ func (svc service) makeSession(id uint) (string, error) {
return tokenString, nil
}
func (svc service) Logout(ctx context.Context) error {
func (svc *Service) Logout(ctx context.Context) error {
// this should not return an error if the user wasn't logged in
return svc.DestroySession(ctx)
}
func (svc service) DestroySession(ctx context.Context) error {
func (svc *Service) DestroySession(ctx context.Context) error {
vc, ok := viewer.FromContext(ctx)
if !ok {
return errNoContext
return kolide.ErrNoContext
}
session, err := svc.ds.SessionByID(vc.SessionID())
@ -245,7 +245,7 @@ func (svc service) DestroySession(ctx context.Context) error {
return svc.ds.DestroySession(session)
}
func (svc service) GetInfoAboutSessionsForUser(ctx context.Context, id uint) ([]*kolide.Session, error) {
func (svc *Service) GetInfoAboutSessionsForUser(ctx context.Context, id uint) ([]*kolide.Session, error) {
var validatedSessions []*kolide.Session
sessions, err := svc.ds.ListSessionsForUser(id)
@ -262,11 +262,11 @@ func (svc service) GetInfoAboutSessionsForUser(ctx context.Context, id uint) ([]
return validatedSessions, nil
}
func (svc service) DeleteSessionsForUser(ctx context.Context, id uint) error {
func (svc *Service) DeleteSessionsForUser(ctx context.Context, id uint) error {
return svc.ds.DestroyAllSessionsForUser(id)
}
func (svc service) GetInfoAboutSession(ctx context.Context, id uint) (*kolide.Session, error) {
func (svc *Service) GetInfoAboutSession(ctx context.Context, id uint) (*kolide.Session, error) {
session, err := svc.ds.SessionByID(id)
if err != nil {
return nil, err
@ -280,7 +280,7 @@ func (svc service) GetInfoAboutSession(ctx context.Context, id uint) (*kolide.Se
return session, nil
}
func (svc service) GetSessionByKey(ctx context.Context, key string) (*kolide.Session, error) {
func (svc *Service) GetSessionByKey(ctx context.Context, key string) (*kolide.Session, error) {
session, err := svc.ds.SessionByKey(key)
if err != nil {
return nil, err
@ -294,7 +294,7 @@ func (svc service) GetSessionByKey(ctx context.Context, key string) (*kolide.Ses
return session, nil
}
func (svc service) DeleteSession(ctx context.Context, id uint) error {
func (svc *Service) DeleteSession(ctx context.Context, id uint) error {
session, err := svc.ds.SessionByID(id)
if err != nil {
return err
@ -302,9 +302,9 @@ func (svc service) DeleteSession(ctx context.Context, id uint) error {
return svc.ds.DestroySession(session)
}
func (svc service) validateSession(session *kolide.Session) error {
func (svc *Service) validateSession(session *kolide.Session) error {
if session == nil {
return authRequiredError{internal: "active session not present"}
return kolide.NewAuthRequiredError("active session not present")
}
sessionDuration := svc.config.Session.Duration
@ -314,7 +314,7 @@ func (svc service) validateSession(session *kolide.Session) error {
if err != nil {
return errors.Wrap(err, "destroying session")
}
return authRequiredError{internal: "expired session"}
return kolide.NewAuthRequiredError("expired session")
}
return svc.ds.MarkSessionAccessed(session)

View file

@ -6,11 +6,11 @@ import (
"github.com/pkg/errors"
)
func (svc service) StatusResultStore(ctx context.Context) error {
func (svc *Service) StatusResultStore(ctx context.Context) error {
return svc.resultStore.HealthCheck()
}
func (svc service) StatusLiveQuery(ctx context.Context) error {
func (svc *Service) StatusLiveQuery(ctx context.Context) error {
cfg, err := svc.AppConfig(ctx)
if err != nil {
return errors.Wrap(err, "retreiving app config")

View file

@ -7,10 +7,10 @@ import (
"github.com/fleetdm/fleet/server/kolide"
)
func (svc service) SearchTargets(ctx context.Context, matchQuery string, queryID *uint, targets kolide.HostTargets) (*kolide.TargetSearchResults, error) {
func (svc Service) SearchTargets(ctx context.Context, matchQuery string, queryID *uint, targets kolide.HostTargets) (*kolide.TargetSearchResults, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
includeObserver := false
@ -50,10 +50,10 @@ func (svc service) SearchTargets(ctx context.Context, matchQuery string, queryID
return results, nil
}
func (svc service) CountHostsInTargets(ctx context.Context, queryID *uint, targets kolide.HostTargets) (*kolide.TargetMetrics, error) {
func (svc Service) CountHostsInTargets(ctx context.Context, queryID *uint, targets kolide.HostTargets) (*kolide.TargetMetrics, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
includeObserver := false

View file

@ -3,158 +3,42 @@ package service
import (
"context"
"encoding/json"
"fmt"
"github.com/fleetdm/fleet/server/contexts/viewer"
"github.com/fleetdm/fleet/server/kolide"
"github.com/pkg/errors"
)
func (svc service) NewTeam(ctx context.Context, p kolide.TeamPayload) (*kolide.Team, error) {
team := &kolide.Team{}
if p.Name == nil {
return nil, newInvalidArgumentError("name", "missing required argument")
}
if *p.Name == "" {
return nil, newInvalidArgumentError("name", "may not be empty")
}
team.Name = *p.Name
if p.Description != nil {
team.Description = *p.Description
}
if p.Secrets != nil {
team.Secrets = p.Secrets
} else {
// Set up a default enroll secret
secret, err := kolide.RandomText(kolide.EnrollSecretDefaultLength)
if err != nil {
return nil, errors.Wrap(err, "generate enroll secret string")
}
team.Secrets = []*kolide.EnrollSecret{{Secret: secret}}
}
team, err := svc.ds.NewTeam(team)
if err != nil {
return nil, err
}
return team, nil
func (svc *Service) NewTeam(ctx context.Context, p kolide.TeamPayload) (*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) ModifyTeam(ctx context.Context, id uint, payload kolide.TeamPayload) (*kolide.Team, error) {
team, err := svc.ds.Team(id)
if err != nil {
return nil, err
}
if payload.Name != nil {
if *payload.Name == "" {
return nil, newInvalidArgumentError("name", "may not be empty")
}
team.Name = *payload.Name
}
if payload.Description != nil {
team.Description = *payload.Description
}
if payload.Secrets != nil {
team.Secrets = payload.Secrets
}
return svc.ds.SaveTeam(team)
func (svc *Service) ModifyTeam(ctx context.Context, id uint, payload kolide.TeamPayload) (*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*kolide.Team, error) {
team, err := svc.ds.Team(id)
if err != nil {
return nil, err
}
if options != nil {
team.AgentOptions = &options
} else {
team.AgentOptions = nil
}
return svc.ds.SaveTeam(team)
func (svc *Service) ModifyTeamAgentOptions(ctx context.Context, id uint, options json.RawMessage) (*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) AddTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
idMap := make(map[uint]kolide.TeamUser)
for _, user := range users {
if !kolide.ValidTeamRole(user.Role) {
return nil, newInvalidArgumentError("users", fmt.Sprintf("%s is not a valid role for a team user", user.Role))
}
idMap[user.ID] = user
}
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
// Replace existing
for i, existingUser := range team.Users {
if user, ok := idMap[existingUser.ID]; ok {
team.Users[i] = user
delete(idMap, user.ID)
}
}
// Add new (that have not already been replaced)
for _, user := range idMap {
team.Users = append(team.Users, user)
}
return svc.ds.SaveTeam(team)
func (svc *Service) AddTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) DeleteTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
idMap := make(map[uint]bool)
for _, user := range users {
idMap[user.ID] = true
}
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
newUsers := []kolide.TeamUser{}
// Delete existing
for _, existingUser := range team.Users {
if _, ok := idMap[existingUser.ID]; !ok {
// Only add non-deleted users
newUsers = append(newUsers, existingUser)
}
}
team.Users = newUsers
return svc.ds.SaveTeam(team)
func (svc *Service) DeleteTeamUsers(ctx context.Context, teamID uint, users []kolide.TeamUser) (*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) ListTeamUsers(ctx context.Context, teamID uint, opt kolide.ListOptions) ([]*kolide.User, error) {
team, err := svc.ds.Team(teamID)
if err != nil {
return nil, err
}
return svc.ds.ListUsers(kolide.UserListOptions{ListOptions: opt, TeamID: team.ID})
func (svc *Service) ListTeamUsers(ctx context.Context, teamID uint, opt kolide.ListOptions) ([]*kolide.User, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) ListTeams(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Team, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
}
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true}
return svc.ds.ListTeams(filter, opt)
func (svc *Service) ListTeams(ctx context.Context, opt kolide.ListOptions) ([]*kolide.Team, error) {
return nil, kolide.ErrMissingLicense
}
func (svc service) DeleteTeam(ctx context.Context, tid uint) error {
return svc.ds.DeleteTeam(tid)
func (svc *Service) DeleteTeam(ctx context.Context, tid uint) error {
return kolide.ErrMissingLicense
}
func (svc service) TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*kolide.EnrollSecret, error) {
return svc.ds.TeamEnrollSecrets(teamID)
func (svc *Service) TeamEnrollSecrets(ctx context.Context, teamID uint) ([]*kolide.EnrollSecret, error) {
return nil, kolide.ErrMissingLicense
}

View file

@ -13,7 +13,7 @@ import (
"github.com/pkg/errors"
)
func (svc service) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
func (svc Service) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
invite, err := svc.VerifyInvite(ctx, *p.InviteToken)
if err != nil {
return nil, err
@ -35,11 +35,11 @@ func (svc service) CreateUserWithInvite(ctx context.Context, p kolide.UserPayloa
return user, nil
}
func (svc service) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
func (svc Service) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
return svc.newUser(p)
}
func (svc service) newUser(p kolide.UserPayload) (*kolide.User, error) {
func (svc Service) newUser(p kolide.UserPayload) (*kolide.User, error) {
var ssoEnabled bool
// if user is SSO generate a fake password
if (p.SSOInvite != nil && *p.SSOInvite) || (p.SSOEnabled != nil && *p.SSOEnabled) {
@ -62,12 +62,12 @@ func (svc service) newUser(p kolide.UserPayload) (*kolide.User, error) {
return user, nil
}
func (svc service) ChangeUserAdmin(ctx context.Context, id uint, isAdmin bool) (*kolide.User, error) {
func (svc *Service) ChangeUserAdmin(ctx context.Context, id uint, isAdmin bool) (*kolide.User, error) {
// TODO remove this function
return nil, errors.New("This function is being eliminated")
}
func (svc service) ModifyUser(ctx context.Context, userID uint, p kolide.UserPayload) (*kolide.User, error) {
func (svc *Service) ModifyUser(ctx context.Context, userID uint, p kolide.UserPayload) (*kolide.User, error) {
user, err := svc.User(ctx, userID)
if err != nil {
return nil, err
@ -104,7 +104,7 @@ func (svc service) ModifyUser(ctx context.Context, userID uint, p kolide.UserPay
if p.GlobalRole != nil && *p.GlobalRole != "" {
if p.Teams != nil && len(*p.Teams) > 0 {
return nil, newInvalidArgumentError("teams", "may not be specified with global_role")
return nil, kolide.NewInvalidArgumentError("teams", "may not be specified with global_role")
}
user.GlobalRole = p.GlobalRole
user.Teams = []kolide.UserTeam{}
@ -121,12 +121,12 @@ func (svc service) ModifyUser(ctx context.Context, userID uint, p kolide.UserPay
return user, nil
}
func (svc service) modifyEmailAddress(ctx context.Context, user *kolide.User, email string, password *string) error {
func (svc *Service) modifyEmailAddress(ctx context.Context, user *kolide.User, email string, password *string) error {
// password requirement handled in validation middleware
if password != nil {
err := user.ValidatePassword(*password)
if err != nil {
return newPermissionError("password", "incorrect password")
return kolide.NewPermissionError("incorrect password")
}
}
random, err := kolide.RandomText(svc.config.App.TokenKeySize)
@ -155,41 +155,41 @@ func (svc service) modifyEmailAddress(ctx context.Context, user *kolide.User, em
return svc.mailService.SendEmail(changeEmail)
}
func (svc service) DeleteUser(ctx context.Context, id uint) error {
func (svc *Service) DeleteUser(ctx context.Context, id uint) error {
return svc.ds.DeleteUser(id)
}
func (svc service) ChangeUserEmail(ctx context.Context, token string) (string, error) {
func (svc *Service) ChangeUserEmail(ctx context.Context, token string) (string, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return "", errNoContext
return "", kolide.ErrNoContext
}
return svc.ds.ConfirmPendingEmailChange(vc.UserID(), token)
}
func (svc service) User(ctx context.Context, id uint) (*kolide.User, error) {
func (svc *Service) User(ctx context.Context, id uint) (*kolide.User, error) {
return svc.ds.UserByID(id)
}
func (svc service) AuthenticatedUser(ctx context.Context) (*kolide.User, error) {
func (svc *Service) AuthenticatedUser(ctx context.Context) (*kolide.User, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
if !vc.IsLoggedIn() {
return nil, permissionError{}
return nil, kolide.NewPermissionError("not logged in")
}
return vc.User, nil
}
func (svc service) ListUsers(ctx context.Context, opt kolide.UserListOptions) ([]*kolide.User, error) {
func (svc *Service) ListUsers(ctx context.Context, opt kolide.UserListOptions) ([]*kolide.User, error) {
return svc.ds.ListUsers(opt)
}
// setNewPassword is a helper for changing a user's password. It should be
// called to set the new password after proper authorization has been
// performed.
func (svc service) setNewPassword(ctx context.Context, user *kolide.User, password string) error {
func (svc *Service) setNewPassword(ctx context.Context, user *kolide.User, password string) error {
err := user.SetPassword(password, svc.config.Auth.SaltKeySize, svc.config.Auth.BcryptCost)
if err != nil {
return errors.Wrap(err, "setting new password")
@ -205,20 +205,20 @@ func (svc service) setNewPassword(ctx context.Context, user *kolide.User, passwo
return nil
}
func (svc service) ChangePassword(ctx context.Context, oldPass, newPass string) error {
func (svc *Service) ChangePassword(ctx context.Context, oldPass, newPass string) error {
vc, ok := viewer.FromContext(ctx)
if !ok {
return errNoContext
return kolide.ErrNoContext
}
if vc.User.SSOEnabled {
return errors.New("change password for single sign on user not allowed")
}
if err := vc.User.ValidatePassword(newPass); err == nil {
return newInvalidArgumentError("new_password", "cannot reuse old password")
return kolide.NewInvalidArgumentError("new_password", "cannot reuse old password")
}
if err := vc.User.ValidatePassword(oldPass); err != nil {
return newInvalidArgumentError("old_password", "old password does not match")
return kolide.NewInvalidArgumentError("old_password", "old password does not match")
}
if err := svc.setNewPassword(ctx, vc.User, newPass); err != nil {
@ -227,7 +227,7 @@ func (svc service) ChangePassword(ctx context.Context, oldPass, newPass string)
return nil
}
func (svc service) ResetPassword(ctx context.Context, token, password string) error {
func (svc *Service) ResetPassword(ctx context.Context, token, password string) error {
reset, err := svc.ds.FindPassswordResetByToken(token)
if err != nil {
return errors.Wrap(err, "looking up reset by token")
@ -242,7 +242,7 @@ func (svc service) ResetPassword(ctx context.Context, token, password string) er
// prevent setting the same password
if err := user.ValidatePassword(password); err == nil {
return newInvalidArgumentError("new_password", "cannot reuse old password")
return kolide.NewInvalidArgumentError("new_password", "cannot reuse old password")
}
err = svc.setNewPassword(ctx, user, password)
@ -264,10 +264,10 @@ func (svc service) ResetPassword(ctx context.Context, token, password string) er
return nil
}
func (svc service) PerformRequiredPasswordReset(ctx context.Context, password string) (*kolide.User, error) {
func (svc *Service) PerformRequiredPasswordReset(ctx context.Context, password string) (*kolide.User, error) {
vc, ok := viewer.FromContext(ctx)
if !ok {
return nil, errNoContext
return nil, kolide.ErrNoContext
}
user := vc.User
if user.SSOEnabled {
@ -279,7 +279,7 @@ func (svc service) PerformRequiredPasswordReset(ctx context.Context, password st
// prevent setting the same password
if err := user.ValidatePassword(password); err == nil {
return nil, newInvalidArgumentError("new_password", "cannot reuse old password")
return nil, kolide.NewInvalidArgumentError("new_password", "cannot reuse old password")
}
user.AdminForcedPasswordReset = false
@ -294,7 +294,7 @@ func (svc service) PerformRequiredPasswordReset(ctx context.Context, password st
return user, nil
}
func (svc service) RequirePasswordReset(ctx context.Context, uid uint, require bool) (*kolide.User, error) {
func (svc *Service) RequirePasswordReset(ctx context.Context, uid uint, require bool) (*kolide.User, error) {
user, err := svc.ds.UserByID(uid)
if err != nil {
return nil, errors.Wrap(err, "loading user by ID")
@ -318,7 +318,7 @@ func (svc service) RequirePasswordReset(ctx context.Context, uid uint, require b
return user, nil
}
func (svc service) RequestPasswordReset(ctx context.Context, email string) error {
func (svc *Service) RequestPasswordReset(ctx context.Context, email string) error {
// Regardless of error, sleep until the request has taken at least 1 second.
// This means that any request to this method will take ~1s and frustrate a timing attack.
defer func(start time.Time) {
@ -371,7 +371,7 @@ func (svc service) RequestPasswordReset(ctx context.Context, email string) error
// saves user in datastore.
// doesn't need to be exposed to the transport
// the service should expose actions for modifying a user instead
func (svc service) saveUser(user *kolide.User) error {
func (svc *Service) saveUser(user *kolide.User) error {
return svc.ds.SaveUser(user)
}

View file

@ -123,10 +123,9 @@ func TestModifyUserEmailNoPassword(t *testing.T) {
}
_, err = svc.ModifyUser(ctx, 3, payload)
require.NotNil(t, err)
invalid, ok := err.(*invalidArgumentError)
invalid, ok := err.(*kolide.InvalidArgumentError)
require.True(t, ok)
require.Len(t, *invalid, 1)
assert.Equal(t, "cannot be empty if email is changed", (*invalid)[0].reason)
assert.False(t, ms.PendingEmailChangeFuncInvoked)
assert.False(t, ms.SaveUserFuncInvoked)
@ -169,10 +168,9 @@ func TestModifyAdminUserEmailNoPassword(t *testing.T) {
}
_, err = svc.ModifyUser(ctx, 3, payload)
require.NotNil(t, err)
invalid, ok := err.(*invalidArgumentError)
invalid, ok := err.(*kolide.InvalidArgumentError)
require.True(t, ok)
require.Len(t, *invalid, 1)
assert.Equal(t, "cannot be empty if email is changed", (*invalid)[0].reason)
assert.False(t, ms.PendingEmailChangeFuncInvoked)
assert.False(t, ms.SaveUserFuncInvoked)
@ -235,7 +233,7 @@ func TestRequestPasswordReset(t *testing.T) {
var errEmailFn = func(e kolide.Email) error {
return errors.New("test err")
}
svc := service{
svc := &Service{
ds: ds,
config: config.TestConfig(),
}
@ -312,13 +310,13 @@ func TestCreateUserWithInvite(t *testing.T) {
Username: ptr.String("admin2"),
Password: ptr.String("foobarbaz1234!"),
InviteToken: &invites["admin2@example.com"].Token,
wantErr: &invalidArgumentError{invalidArgument{name: "email", reason: "missing required argument"}},
wantErr: kolide.NewInvalidArgumentError("email", "missing required argument"),
},
{
Username: ptr.String("admin2"),
Password: ptr.String("foobarbaz1234!"),
Email: ptr.String("admin2@example.com"),
wantErr: &invalidArgumentError{invalidArgument{name: "invite_token", reason: "missing required argument"}},
wantErr: kolide.NewInvalidArgumentError("invite_token", "missing required argument"),
},
{
Username: ptr.String("admin2"),
@ -342,7 +340,7 @@ func TestCreateUserWithInvite(t *testing.T) {
Email: &invites["expired"].Email,
NeedsPasswordReset: ptr.Bool(true),
InviteToken: &invites["expired"].Token,
wantErr: &invalidArgumentError{{name: "invite_token", reason: "Invite token has expired."}},
wantErr: kolide.NewInvalidArgumentError("invite_token", "Invite token has expired."),
},
{
Username: ptr.String("admin3@example.com"),
@ -434,7 +432,7 @@ func TestChangePassword(t *testing.T) {
user: users["admin1"],
oldPassword: "12345cat!",
newPassword: "foobarbaz1234!",
wantErr: &invalidArgumentError{invalidArgument{name: "new_password", reason: "cannot reuse old password"}},
wantErr: kolide.NewInvalidArgumentError("new_password", "cannot reuse old password"),
},
{ // all good
user: users["user1"],
@ -449,14 +447,7 @@ func TestChangePassword(t *testing.T) {
},
{ // missing old password
newPassword: "123cataaa!",
wantErr: &invalidArgumentError{invalidArgument{name: "old_password", reason: "cannot be empty"}},
},
{ // missing new password
oldPassword: "abcd",
wantErr: &invalidArgumentError{
{name: "new_password", reason: "cannot be empty"},
{name: "new_password", reason: "password does not meet validation requirements"},
},
wantErr: kolide.NewInvalidArgumentError("old_password", "cannot be empty"),
},
}
@ -501,7 +492,7 @@ func TestResetPassword(t *testing.T) {
{ // prevent reuse
token: "abcd",
newPassword: "123cat!",
wantErr: &invalidArgumentError{invalidArgument{name: "new_password", reason: "cannot reuse old password"}},
wantErr: kolide.NewInvalidArgumentError("new_password", "cannot reuse old password"),
},
{ // bad token
token: "dcbaz",
@ -510,14 +501,7 @@ func TestResetPassword(t *testing.T) {
},
{ // missing token
newPassword: "123cat!",
wantErr: &invalidArgumentError{invalidArgument{name: "token", reason: "cannot be empty field"}},
},
{ // missing password
token: "abcd",
wantErr: &invalidArgumentError{
{name: "new_password", reason: "cannot be empty field"},
{name: "new_password", reason: "password does not meet validation requirements"},
},
wantErr: kolide.NewInvalidArgumentError("token", "cannot be empty field"),
},
}

View file

@ -153,13 +153,13 @@ 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.(ErrWithStatusCode); ok {
if e, ok := err.(kolide.ErrWithStatusCode); ok {
status = e.StatusCode()
}
// See header documentation
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
if e, ok := err.(ErrWithRetryAfter); ok {
if e, ok := err.(kolide.ErrWithRetryAfter); ok {
w.Header().Add("Retry-After", strconv.Itoa(e.RetryAfter()))
}

View file

@ -12,7 +12,7 @@ func (mw validationMiddleware) ModifyAppConfig(ctx context.Context, p kolide.App
if err != nil {
return nil, errors.Wrap(err, "fetching existing app config in validation")
}
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
validateSSOSettings(p, existing, invalid)
if invalid.HasErrors() {
return nil, invalid
@ -27,7 +27,7 @@ func isSet(val *string) bool {
return false
}
func validateSSOSettings(p kolide.AppConfigPayload, existing *kolide.AppConfig, invalid *invalidArgumentError) {
func validateSSOSettings(p kolide.AppConfigPayload, existing *kolide.AppConfig, invalid *kolide.InvalidArgumentError) {
if p.SSOSettings != nil && p.SSOSettings.EnableSSO != nil {
if *p.SSOSettings.EnableSSO {
if !isSet(p.SSOSettings.Metadata) && !isSet(p.SSOSettings.MetadataURL) {

View file

@ -9,7 +9,7 @@ import (
)
func TestSSONotPresent(t *testing.T) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
var p kolide.AppConfigPayload
validateSSOSettings(p, &kolide.AppConfig{}, invalid)
assert.False(t, invalid.HasErrors())
@ -17,7 +17,7 @@ func TestSSONotPresent(t *testing.T) {
}
func TestNeedFieldsPresent(t *testing.T) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
config := kolide.AppConfig{
EnableSSO: true,
EntityID: "kolide",
@ -31,7 +31,7 @@ func TestNeedFieldsPresent(t *testing.T) {
}
func TestMissingMetadata(t *testing.T) {
invalid := invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
config := kolide.AppConfig{
EnableSSO: true,
EntityID: "kolide",
@ -39,9 +39,8 @@ func TestMissingMetadata(t *testing.T) {
IDPName: "onelogin",
}
p := appConfigPayloadFromAppConfig(&config)
validateSSOSettings(*p, &kolide.AppConfig{}, &invalid)
validateSSOSettings(*p, &kolide.AppConfig{}, invalid)
require.True(t, invalid.HasErrors())
require.Len(t, invalid, 1)
assert.Equal(t, "metadata", invalid[0].name)
assert.Equal(t, "either metadata or metadata_url must be defined", invalid[0].reason)
assert.Contains(t, invalid.Error(), "metadata")
assert.Contains(t, invalid.Error(), "either metadata or metadata_url must be defined")
}

View file

@ -7,7 +7,7 @@ import (
)
func (mw validationMiddleware) InviteNewUser(ctx context.Context, payload kolide.InvitePayload) (*kolide.Invite, error) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if payload.Email == nil {
invalid.Append("email", "missing required argument")
}

View file

@ -9,7 +9,7 @@ import (
)
func (mw validationMiddleware) NewAppConfig(ctx context.Context, payload kolide.AppConfigPayload) (*kolide.AppConfig, error) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
var serverURLString string
if payload.ServerSettings == nil {
invalid.Append("kolide_server_url", "missing required argument")

View file

@ -10,7 +10,7 @@ import (
)
func (mw validationMiddleware) CreateUserWithInvite(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if p.Username == nil {
invalid.Append("username", "missing required argument")
} else {
@ -56,7 +56,7 @@ func (mw validationMiddleware) CreateUserWithInvite(ctx context.Context, p kolid
}
func (mw validationMiddleware) CreateUser(ctx context.Context, p kolide.UserPayload) (*kolide.User, error) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if p.Username == nil {
invalid.Append("username", "missing required argument username")
} else {
@ -96,7 +96,7 @@ func (mw validationMiddleware) CreateUser(ctx context.Context, p kolide.UserPayl
}
func (mw validationMiddleware) ModifyUser(ctx context.Context, userID uint, p kolide.UserPayload) (*kolide.User, error) {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if p.Username != nil {
if *p.Username == "" {
invalid.Append("username", "cannot be empty")
@ -128,7 +128,7 @@ func (mw validationMiddleware) ModifyUser(ctx context.Context, userID uint, p ko
return mw.Service.ModifyUser(ctx, userID, p)
}
func passwordRequiredForEmailChange(ctx context.Context, uid uint, invalid *invalidArgumentError) bool {
func passwordRequiredForEmailChange(ctx context.Context, uid uint, invalid *kolide.InvalidArgumentError) bool {
vc, ok := viewer.FromContext(ctx)
if !ok {
invalid.Append("viewer", "not present")
@ -149,7 +149,7 @@ func passwordRequiredForEmailChange(ctx context.Context, uid uint, invalid *inva
}
func (mw validationMiddleware) ChangePassword(ctx context.Context, oldPass, newPass string) error {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if oldPass == "" {
invalid.Append("old_password", "cannot be empty")
}
@ -168,7 +168,7 @@ func (mw validationMiddleware) ChangePassword(ctx context.Context, oldPass, newP
}
func (mw validationMiddleware) ResetPassword(ctx context.Context, token, password string) error {
invalid := &invalidArgumentError{}
invalid := &kolide.InvalidArgumentError{}
if token == "" {
invalid.Append("token", "cannot be empty field")
}

View file

@ -4,6 +4,7 @@ import (
"encoding/xml"
"io/ioutil"
"net/http"
"time"
"github.com/pkg/errors"
@ -69,7 +70,10 @@ func ParseMetadata(metadata string) (*Metadata, error) {
// IDP via a remote URL. metadataURL is the location where the metadata is located
// and timeout defines how long to wait to get a response form the metadata
// server.
func GetMetadata(metadataURL string, client *http.Client) (*Metadata, error) {
func GetMetadata(metadataURL string) (*Metadata, error) {
client := &http.Client{
Timeout: 5 * time.Second,
}
request, err := http.NewRequest(http.MethodGet, metadataURL, nil)
if err != nil {
return nil, err

View file

@ -4,7 +4,6 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -62,10 +61,7 @@ func TestGetMetadata(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(metadata))
}))
client := &http.Client{
Timeout: 2 * time.Second,
}
settings, err := GetMetadata(ts.URL, client)
settings, err := GetMetadata(ts.URL)
require.Nil(t, err)
assert.Equal(t, "http://www.okta.com/exka4zkf6dxm8pF220h7", settings.EntityID)
assert.Len(t, settings.IDPSSODescriptor.NameIDFormats, 2)