diff --git a/applications/datastore.go b/applications/datastore.go index c80f08b1..56ac9b6e 100644 --- a/applications/datastore.go +++ b/applications/datastore.go @@ -9,7 +9,7 @@ import ( "time" ) -// Datastore manages devices in a database +// This Datastore manages a list of known applications, and their relationship to devices. type Datastore interface { New(a *Application) (string, error) Applications(params ...interface{}) ([]Application, error) @@ -19,11 +19,6 @@ type Datastore interface { type pgStore struct { *sqlx.DB - logger kitlog.Logger -} - -func NewDatastore(connection *sqlx.DB, logger kitlog.Logger) (Datastore, error) { - return pgStore{DB: connection, logger: logger}, nil } func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) { diff --git a/applications/datastore_test.go b/applications/datastore_test.go index 59bbc12f..b573231c 100644 --- a/applications/datastore_test.go +++ b/applications/datastore_test.go @@ -42,19 +42,22 @@ var appFixtures []Application = []Application{ var logger log.Logger = log.NewNopLogger() -//func TestNewDB(t *testing.T) { -// var logger logger.Logger = logger.NewNopLogger() -// appsDB, err := NewDB("postgres", "host=localhost", logger) -// -// if err != nil { -// t.Error(err) -// } -// -// if _, ok := appsDB.(Datastore); !ok { -// t.Log("Did not get a datastore") -// t.Fail() -// } -//} +func NewDatastore(connection *sqlx.DB, logger log.Logger) (Datastore, error) { + return pgStore{DB: connection, logger: logger}, nil +} + +func TestNewDB(t *testing.T) { + appsDB, err := NewDB("postgres", "host=localhost", logger) + + if err != nil { + t.Error(err) + } + + if _, ok := appsDB.(Datastore); !ok { + t.Log("Did not get a datastore") + t.Fail() + } +} func TestNewDatastore(t *testing.T) { db, _, err := sqlmock.New() diff --git a/certificates/certificate.go b/certificates/certificate.go new file mode 100644 index 00000000..ebf80f47 --- /dev/null +++ b/certificates/certificate.go @@ -0,0 +1,12 @@ +package certificates + +import ( + "crypto/x509" +) + +type Certificate struct { + UUID string `db:"certificate_uuid" json:"uuid"` + Data x509.Certificate `db:"data" json:"data"` + CommonName string `db:"common_name" json:"common_nane"` + IsIdentity bool `db:"is_identity" json:"is_identity"` +} diff --git a/certificates/datastore.go b/certificates/datastore.go new file mode 100644 index 00000000..bb8dd9ed --- /dev/null +++ b/certificates/datastore.go @@ -0,0 +1,112 @@ +package certificates + +import ( + "fmt" + kitlog "github.com/go-kit/kit/log" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" // postgres driver + "github.com/pkg/errors" + "strings" + "time" +) + +var ( + insertCertificateStmt = `INSERT INTO certificates ( + common_name, + data, + is_identity + ) VALUES ($1, $2, $3) + RETURNING certificate_uuid;` + + selectCertificatesStmt = `SELECT + certificate_uuid, + common_name, + data, + is_identity + FROM certificates` +) + +// This Datastore manages a list of certificates assigned to devices. +type Datastore interface { + New(crt *Certificate) (string, error) + Certificates(params ...interface{}) ([]Certificate, error) +} + +type pgStore struct { + *sqlx.DB +} + +func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) { + switch driver { + case "postgres": + db, err := sqlx.Open(driver, conn) + if err != nil { + return nil, errors.Wrap(err, "certificates datastore") + } + var dbError error + maxAttempts := 20 + for attempts := 1; attempts <= maxAttempts; attempts++ { + dbError = db.Ping() + if dbError == nil { + break + } + logger.Log("msg", fmt.Sprintf("could not connect to postgres: %v", dbError)) + time.Sleep(time.Duration(attempts) * time.Second) + } + if dbError != nil { + return nil, errors.Wrap(dbError, "applications datastore") + } + return pgStore{DB: db}, nil + default: + return nil, errors.New("unknown driver") + } +} + +func (store pgStore) New(c *Certificate) (string, error) { + if err := store.QueryRow(insertCertificateStmt, c.CommonName, "", c.IsIdentity).Scan(&c.UUID); err != nil { + return "", err + } + + return c.UUID, nil +} + +func (store pgStore) Certificates(params ...interface{}) ([]Certificate, error) { + stmt := selectCertificatesStmt + stmt = addWhereFilters(stmt, "OR", params...) + var certificates []Certificate + err := store.Select(&certificates, stmt) + if err != nil { + return nil, errors.Wrap(err, "pgStore Certificates") + } + return certificates, nil +} + +// UUID is a filter that can be added as a parameter to narrow down the list of returned results +type UUID struct { + UUID string +} + +func (p UUID) where() string { + return fmt.Sprintf("certificate_uuid = '%s'", p.UUID) +} + +// whereer is for building args passed into a method which finds resources +type whereer interface { + where() string +} + +// add WHERE clause from params +func addWhereFilters(stmt string, separator string, params ...interface{}) string { + var where []string + for _, param := range params { + if f, ok := param.(whereer); ok { + where = append(where, f.where()) + } + } + + if len(where) != 0 { + whereFilter := strings.Join(where, " "+separator+" ") + stmt = fmt.Sprintf("%s WHERE %s", stmt, whereFilter) + } + return stmt +} diff --git a/certificates/datastore_test.go b/certificates/datastore_test.go new file mode 100644 index 00000000..04f6427d --- /dev/null +++ b/certificates/datastore_test.go @@ -0,0 +1,41 @@ +package certificates + +import ( + "database/sql" + "github.com/jmoiron/sqlx" + "gopkg.in/DATA-DOG/go-sqlmock.v1" + "testing" +) + +var ( + db *sql.DB + dbx *sqlx.DB + mock sqlmock.Sqlmock + err error +) + +func setup() { + db, mock, err = sqlmock.New() + if err != nil { + panic("an error was not expected when opening a stub database connection") + } + dbx = sqlx.NewDb(db, "mock") +} + +func teardown() { + dbx.Close() +} + +func NewDatastore(connection *sqlx.DB) (Datastore, error) { + return pgStore{DB: connection}, nil +} + +func TestNewDatastore(t *testing.T) { + setup() + defer teardown() + + _, err := NewDatastore(dbx) + if err != nil { + t.Fatal(err) + } +} diff --git a/connect/service.go b/connect/service.go index d261131e..90cb7184 100644 --- a/connect/service.go +++ b/connect/service.go @@ -49,6 +49,8 @@ func (svc service) Acknowledge(ctx context.Context, req mdm.Response) (int, erro fmt.Printf("Got an error acknowledging InstalledApplicationList: %v\n", err) return 0, err } + case "CertificateList": + return 0, nil default: // Need to handle the absence of RequestType in IOS8 devices if req.QueryResponses.UDID != "" { @@ -232,3 +234,13 @@ skip: return nil } + +// Acknowledge a response to `CertificateList`. +func (svc service) ackCertificateList(req mdm.Response) error { + _, err := svc.devices.GetDeviceByUDID(req.UDID, "device_uuid") + if err != nil { + return errors.Wrap(err, "getting a device record by udid") + } + + return nil +} diff --git a/management/service_test.go b/management/service_test.go new file mode 100644 index 00000000..8e26ad1a --- /dev/null +++ b/management/service_test.go @@ -0,0 +1,23 @@ +package management + +import "testing" + +func svcSetup() { + +} + +func svcTearDown() { + +} + +func TestService_InstalledApps(t *testing.T) { + svcSetup() + defer svcTearDown() + + svc := NewService(nil, nil, nil, nil, nil) + _, err := svc.InstalledApps("00000000-1111-2222-3333-444455556666") + if err != nil { + t.Fatal(err) + } + +} diff --git a/migrations/201607110002_certificates_down.sql b/migrations/201607110002_certificates_down.sql deleted file mode 100644 index 4116a42f..00000000 --- a/migrations/201607110002_certificates_down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS certificates; \ No newline at end of file diff --git a/migrations/201607110002_devices_certificates_down.sql b/migrations/201607110002_devices_certificates_down.sql new file mode 100644 index 00000000..acbb8337 --- /dev/null +++ b/migrations/201607110002_devices_certificates_down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS devices_certificates; \ No newline at end of file diff --git a/migrations/201607110002_certificates_up.sql b/migrations/201607110002_devices_certificates_up.sql similarity index 62% rename from migrations/201607110002_certificates_up.sql rename to migrations/201607110002_devices_certificates_up.sql index 0c73ee0a..f2a08802 100644 --- a/migrations/201607110002_certificates_up.sql +++ b/migrations/201607110002_devices_certificates_up.sql @@ -1,7 +1,8 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -CREATE TABLE IF NOT EXISTS certificates ( +CREATE TABLE IF NOT EXISTS devices_certificates ( certificate_uuid uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + device_uuid uuid REFERENCES devices(device_uuid) ON DELETE CASCADE, common_name text NOT NULL, data BYTEA NOT NULL, is_identity BOOL DEFAULT false