diff --git a/addons/api/addon/services/sqlite.js b/addons/api/addon/services/sqlite.js index 798a27936..ec0e409b6 100644 --- a/addons/api/addon/services/sqlite.js +++ b/addons/api/addon/services/sqlite.js @@ -49,10 +49,24 @@ export const modelMapping = { scope_id: 'scope.scope_id', created_time: 'created_time', }, + 'credential-store': { + id: 'id', + type: 'type', + name: 'name', + description: 'description', + scope_id: 'scope.scope_id', + created_time: 'created_time', + }, }; // A list of tables that we support searching using FTS5 in SQLite. -export const searchTables = new Set(['target', 'alias', 'role', 'user']); +export const searchTables = new Set([ + 'target', + 'alias', + 'role', + 'user', + 'credential-store', +]); export default class SqliteDbService extends Service { // =attributes diff --git a/addons/api/addon/utils/sqlite-query.js b/addons/api/addon/utils/sqlite-query.js index 5747add6d..75ec96c2c 100644 --- a/addons/api/addon/utils/sqlite-query.js +++ b/addons/api/addon/utils/sqlite-query.js @@ -6,6 +6,7 @@ import { modelMapping } from 'api/services/sqlite'; import { searchTables } from 'api/services/sqlite'; import { typeOf } from '@ember/utils'; +import { underscore } from '@ember/string'; /** * Takes a POJO representing a filter query and builds a SQL query. @@ -29,9 +30,10 @@ export function generateSQLExpressions( let { search, filters, sort = {} } = query; const conditions = []; const parameters = []; + const tableName = underscore(resource); - addFilterConditions(filters, parameters, conditions); - addSearchConditions(search, resource, parameters, conditions); + addFilterConditions({ filters, parameters, conditions }); + addSearchConditions({ search, resource, tableName, parameters, conditions }); const orderByClause = constructOrderByClause(resource, sort); @@ -42,7 +44,7 @@ export function generateSQLExpressions( parameters.push(pageSize, (page - 1) * pageSize); } - const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${resource}"`; + const selectClause = `SELECT ${select ? select.join(', ') : '*'} FROM "${tableName}"`; return { // Replace any empty newlines or leading whitespace on each line to be consistent with formatting @@ -58,7 +60,7 @@ export function generateSQLExpressions( }; } -function addFilterConditions(filters, parameters, conditions) { +function addFilterConditions({ filters, parameters, conditions }) { if (!filters) { return; } @@ -105,7 +107,13 @@ function addFilterConditions(filters, parameters, conditions) { } } -function addSearchConditions(search, resource, parameters, conditions) { +function addSearchConditions({ + search, + resource, + tableName, + parameters, + conditions, +}) { if (!search) { return; } @@ -117,7 +125,7 @@ function addSearchConditions(search, resource, parameters, conditions) { // much more efficient with FTS queries when using rowids or MATCH (or both). // We could have also used a join here but a subquery is simpler. conditions.push( - `rowid IN (SELECT rowid FROM ${resource}_fts WHERE ${resource}_fts MATCH ?)`, + `rowid IN (SELECT rowid FROM ${tableName}_fts WHERE ${tableName}_fts MATCH ?)`, ); return; } diff --git a/addons/api/addon/workers/utils/schema.js b/addons/api/addon/workers/utils/schema.js index dcb0b5665..f1ac9e972 100644 --- a/addons/api/addon/workers/utils/schema.js +++ b/addons/api/addon/workers/utils/schema.js @@ -3,6 +3,8 @@ * SPDX-License-Identifier: BUSL-1.1 */ +import { underscore } from '@ember/string'; + export const CLEAR_DB = ` PRAGMA writable_schema = 1; DELETE FROM sqlite_master; @@ -158,6 +160,41 @@ CREATE TRIGGER IF NOT EXISTS user_ad AFTER DELETE ON user BEGIN VALUES('delete', old.rowid, old.id, old.name, old.description, old.scope_id, old.created_time); END;`; +const createCredentialStoreTables = ` +CREATE TABLE IF NOT EXISTS credential_store ( + id TEXT NOT NULL PRIMARY KEY, + type TEXT NOT NULL, + name TEXT, + description TEXT, + scope_id TEXT NOT NULL, + created_time TEXT NOT NULL, + data TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_credential_store_scope_id_created_time ON credential_store(scope_id, created_time DESC); + +CREATE VIRTUAL TABLE IF NOT EXISTS credential_store_fts USING fts5( + id, + type, + name, + description, + scope_id, + created_time, + content='', +); + +CREATE TRIGGER IF NOT EXISTS credential_store_ai AFTER INSERT ON credential_store BEGIN + INSERT INTO credential_store_fts( + id, type, name, description, scope_id, created_time + ) VALUES ( + new.id, new.type, new.name, new.description, new.scope_id, new.created_time + ); +END; + +CREATE TRIGGER IF NOT EXISTS credential_store_ad AFTER DELETE ON credential_store BEGIN + INSERT INTO credential_store_fts(credential_store_fts, rowid, id, type, name, description, scope_id, created_time) + VALUES('delete', old.rowid, old.id, old.type, old.name, old.description, old.scope_id, old.created_time); +END;`; + export const CREATE_TABLES = (version) => ` BEGIN; @@ -172,6 +209,7 @@ ${createTargetTables} ${createAliasTables} ${createRoleTables} ${createUserTables} +${createCredentialStoreTables} CREATE TABLE IF NOT EXISTS "group" ( id TEXT NOT NULL PRIMARY KEY, @@ -199,8 +237,8 @@ export const INSERT_STATEMENTS = (resource, items, modelMapping) => { const numColumns = Array(columns.length + 1); const placeholders = `(${numColumns.fill('?').join(', ')})`; - return `REPLACE INTO "${resource}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`; + return `REPLACE INTO "${underscore(resource)}" (${columns.join(', ')}, data) VALUES ${items.map(() => placeholders).join(', ')};`; }; export const DELETE_STATEMENT = (resource, ids) => - `DELETE FROM "${resource}" WHERE id IN (${ids.map(() => '?').join(',')})`; + `DELETE FROM "${underscore(resource)}" WHERE id IN (${ids.map(() => '?').join(',')})`; diff --git a/addons/api/tests/unit/services/sqlite-test.js b/addons/api/tests/unit/services/sqlite-test.js index facb7d6ad..88e84c914 100644 --- a/addons/api/tests/unit/services/sqlite-test.js +++ b/addons/api/tests/unit/services/sqlite-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'dummy/tests/helpers'; import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { modelMapping, searchTables } from 'api/services/sqlite'; +import { underscore } from '@ember/string'; const supportedModels = Object.keys(modelMapping); const supportedFtsTables = [...searchTables]; @@ -22,7 +23,7 @@ module('Unit | Service | sqlite', function (hooks) { const service = this.owner.lookup('service:sqlite'); const tableColumns = await service.fetchResource({ - sql: `SELECT * FROM pragma_table_info('${resource}')`, + sql: `SELECT * FROM pragma_table_info('${underscore(resource)}')`, }); const columnNames = tableColumns.map((column) => column.name); @@ -42,7 +43,7 @@ module('Unit | Service | sqlite', function (hooks) { const service = this.owner.lookup('service:sqlite'); const tableColumns = await service.fetchResource({ - sql: `SELECT * FROM pragma_table_info('${resource}_fts')`, + sql: `SELECT * FROM pragma_table_info('${underscore(resource)}_fts')`, }); const columnNames = tableColumns.map((column) => column.name); diff --git a/ui/admin/tests/acceptance/credential-store/create-test.js b/ui/admin/tests/acceptance/credential-store/create-test.js index 9e7476ecc..6097fb244 100644 --- a/ui/admin/tests/acceptance/credential-store/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/create-test.js @@ -8,6 +8,7 @@ import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import * as selectors from './selectors'; @@ -17,6 +18,7 @@ import { setRunOptions } from 'ember-a11y-testing/test-support'; module('Acceptance | credential-stores | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); setupIndexedDb(hooks); let featuresService; diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js index d8d28c134..f53f6ff51 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js @@ -8,6 +8,7 @@ import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import * as selectors from './selectors'; @@ -19,6 +20,7 @@ module( function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); setupIndexedDb(hooks); let getCredentialsCount; diff --git a/ui/admin/tests/acceptance/credential-store/delete-test.js b/ui/admin/tests/acceptance/credential-store/delete-test.js index a887a5503..ba0fc9de9 100644 --- a/ui/admin/tests/acceptance/credential-store/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/delete-test.js @@ -8,6 +8,7 @@ import { visit, click } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; @@ -19,6 +20,7 @@ import { setRunOptions } from 'ember-a11y-testing/test-support'; module('Acceptance | credential-stores | delete', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); setupIndexedDb(hooks); let getStaticCredentialStoresCount; diff --git a/ui/admin/tests/acceptance/credential-store/update-test.js b/ui/admin/tests/acceptance/credential-store/update-test.js index 6a6d954fd..141b5db7a 100644 --- a/ui/admin/tests/acceptance/credential-store/update-test.js +++ b/ui/admin/tests/acceptance/credential-store/update-test.js @@ -8,6 +8,7 @@ import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import * as selectors from './selectors'; @@ -17,6 +18,7 @@ import { setRunOptions } from 'ember-a11y-testing/test-support'; module('Acceptance | credential-stores | update', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); setupIndexedDb(hooks); const instances = { diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index 0efffde2b..1a8cf3ecc 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -18,6 +19,7 @@ import { setRunOptions } from 'ember-a11y-testing/test-support'; module('Acceptance | targets | brokered credential sources', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); let getCredentialLibraryCount; let getCredentialCount; diff --git a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js index 9ede290bc..761eca0de 100644 --- a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_SSH } from 'api/models/target'; @@ -21,6 +22,7 @@ module( function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupSqlite(hooks); let getCredentialLibraryCount; let getCredentialCount; diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js index 1c14e0390..bc378e926 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js @@ -8,6 +8,7 @@ import { setupTest } from 'ember-qunit'; import { setupIntl } from 'ember-intl/test-support'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { waitUntil, visit } from '@ember/test-helpers'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { @@ -20,6 +21,7 @@ module( function (hooks) { setupTest(hooks); setupMirage(hooks); + setupSqlite(hooks); setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js index 2f2ef602b..277abfcde 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { currentURL, visit } from '@ember/test-helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; module( @@ -14,6 +15,7 @@ module( function (hooks) { setupTest(hooks); setupMirage(hooks); + setupSqlite(hooks); let store; let controller; diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js index efda0ae04..bb6959174 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js @@ -7,6 +7,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { currentURL, visit } from '@ember/test-helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; module( @@ -14,6 +15,7 @@ module( function (hooks) { setupTest(hooks); setupMirage(hooks); + setupSqlite(hooks); let store; let controller;