Compare commits

..
2 Commits
901 changed files with 7460 additions and 19906 deletions

No files matched your search

-2
View File
@@ -1,2 +0,0 @@
# Anyone attached to this rule will always been assigned to a pull request
* @hashicorp/boundary-ui
-17
View File
@@ -1,17 +0,0 @@
:tickets: [Jira ticket](https://hashicorp.atlassian.net/browse/JIRA_TICKET_NUMBER)
## Description
<!--
Replace PATH_TO_FEATURE with Vercel link
-->
:technologist: [Admin preview](PATH_TO_FEATURE)
:rose: [Rose preview](PATH_TO_FEATURE)
:desktop_computer: [Desktop preview](PATH_TO_FEATURE)
<!-- Add a brief description of changes here. Include any other necessary relevant links -->
### Screenshots (if appropriate):
-100
View File
@@ -1,100 +0,0 @@
name: Build Admin UI
on:
push:
branches:
- main
jobs:
dependencies:
name: Install dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v3
with:
node-version: '16.x'
- run: yarn install
build-admin-ui-oss:
name: Build Admin UI OSS
needs: [dependencies]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- name: Build
uses: actions/setup-node@v3
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn run build:ui:admin:oss
- name: Upload artifact admin-ui
uses: actions/upload-artifact@v3
with:
name: admin-ui
path: ./ui/admin/dist/
- name: Upload artifact OSS admin UI
uses: actions/upload-artifact@v3
with:
name: admin-ui-oss
path: ./ui/admin/dist/
build-admin-ui-enterprise:
name: Build Admin UI Enterprise
needs: [dependencies]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- name: Build
uses: actions/setup-node@v3
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn run build:ui:admin:enterprise
- name: Upload artifact enterprise admin UI
uses: actions/upload-artifact@v3
with:
name: admin-ui-ent
path: ./ui/admin/dist/
build-admin-ui-hcp:
name: Build Admin UI HCP
needs: [dependencies]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- name: Build
uses: actions/setup-node@v3
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn run build:ui:admin:hcp
- name: Upload artifact HCP admin UI
uses: actions/upload-artifact@v3
with:
name: admin-ui-hcp
path: ./ui/admin/dist/
+14 -14
View File
@@ -6,15 +6,15 @@ jobs:
name: Install dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v3
- uses: actions/setup-node@v2
with:
node-version: '16.x'
- run: yarn install
@@ -24,15 +24,15 @@ jobs:
needs: [dependencies]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v3
- uses: actions/setup-node@v2
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
@@ -40,19 +40,19 @@ jobs:
lint:
name: Lint
needs: [dependencies, compliance]
needs: [dependencies]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v3
- uses: actions/setup-node@v2
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
@@ -60,17 +60,17 @@ jobs:
test:
name: Test
needs: [lint]
needs: [dependencies]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- uses: actions/setup-node@v3
- uses: actions/setup-node@v2
with:
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn compliance:licenses
npx lint-staged
yarn test
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# Don't run commitizen for merge or squash commits
if [ "${COMMIT_SOURCE}" = merge ] || [ "${COMMIT_SOURCE}" = squash ];then
exit 0
fi
# This is to allow interaction with the terminal for commitizen
exec < /dev/tty && npx git-cz --hook || true
-3
View File
@@ -1,3 +0,0 @@
url_license = "https://github.com/hashicorp/boundary-ui/blob/main/LICENSE"
url_project_website = "https://learn.hashicorp.com/tutorials/boundary/getting-started-desktop-app"
url_source_repository = "https://github.com/hashicorp/boundary-ui"
+9 -9
View File
@@ -38,9 +38,9 @@ You will need the following things properly installed on your computer.
* [Git][git]
* [Node.js][node]
* Supported versions:`v12`, `v14` and `v16`.
* v10–v15
* **Apple Silicon Users**: if you experience problems building or running
Boundary UIs, try Node v14.
Boundary UIs, try Node v15.
* [Yarn][yarn] installed globally
* [Google Chrome][chrome]
* [Firefox][firefox]
@@ -66,6 +66,7 @@ List of available project commands. `yarn run <command-name>`
|---------------------|---|
| build | Builds Admin UI. |
| test | Runs tests in CI mode. |
| commit | Replaces `git commit`, ensures compliance, audit, lint, and test checks pass before commit is allowed and normalizes commit messages across authors. |
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
@@ -159,16 +160,15 @@ You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start
In login screen, authenticate using generated admin login name and password.
Enter the cluster URL of your Boundary dev instance, by default is: `http://localhost:9200`.
Enter the origin of your Boundary dev instance, by default is: `http://localhost:9200`.
### Committing
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
commit is completed. Simply do a `yarn install` to make sure the hooks are installed.
Use a normal `git commit` to go through the checks, if you need to skip these checks,
use `git commit --no-verify` to bypass them. However, a consistent commit message will
still be enforced even if you use `--no-verify`.
Instead of the default `git commit`, we prescribe a custom command that ensures
linting and other checks pass before a commit may be completed. It also ensures
that all contributors author consistent commit messages. To get started with a
commit, stage your files as usual. Then run `yarn commit` and follow the
on-screen instructions to author your message.
#### License Checking
+8 -7
View File
@@ -2,19 +2,20 @@
module.exports = {
root: true,
parser: '@babel/eslint-parser',
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
// This is specific babel-config. If grows consider creating a babel config file
requireConfigFile: false,
babelOptions: {
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]],
ecmaFeatures: {
legacyDecorators: true,
},
// end of babel-config
},
plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended', 'prettier'],
extends: [
'eslint:recommended',
'plugin:ember/recommended',
'plugin:prettier/recommended',
],
env: {
browser: true,
},
+1 -1
View File
@@ -1,5 +1,5 @@
'use strict';
module.exports = {
extends: ['recommended', 'stylistic'],
extends: 'octane',
};
+2 -9
View File
@@ -47,28 +47,21 @@ List of available project commands. `yarn run <command-name>`
| build:development | Builds the dummy app in development mode. |
| build | Builds the dummy app for production. |
| lint | Runs all lint commands. |
| lint:fix | Runs automatic lint fixes for all type of file. |
| lint:hbs | Runs lint for `hbs` template files. |
| lint:hbs:fix | Runs automatic lint fixes for `hbs` template files. |
| lint:js | Lints `js` files. |
| lint:js:fix | Runs automatic lint fixes for `js` files. |
| format | Runs all auto-formatters. |
| format:js | Auto-formats `js` files using Prettier. |
| start | Runs the dummy app local server. |
| test | Runs all tests. |
| test:ember-compatibility | Runs tests across multiple Ember versions with ember-try. |
| precommit | Runs all lint and format. |
| test:all | Runs tests across multiple Ember versions with ember-try. |
| doc:toc | Automatically generates a table of contents for this README file. |
Additional commands in the monorepo package may affect this projects.
## Linting
* `yarn lint:fix`
* `yarn lint:hbs`
* `yarn lint:hbs:fix`
* `yarn lint:js`
* `yarn lint:js:fix`
* `yarn lint:js --fix`
## Formatting
@@ -1,15 +0,0 @@
import ModelAbility from './model';
/**
* Provides abilities for credential.
*/
export default class CredentialLibaryAbility extends ModelAbility {
// =permissions
/**
* Only "known" credential libary types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
}
@@ -1,22 +0,0 @@
import ModelAbility from './model';
import { inject as service } from '@ember/service';
/**
* Provides abilities for credential store.
*/
export default class CredentialStoreAbility extends ModelAbility {
// =services
@service features;
// =permissions
/**
* Only "known" credential store types may be read.
* @type {boolean}
*/
get canRead() {
return this.features.isEnabled('static-credentials')
? this.hasAuthorizedAction('read')
: !this.model.isStatic && this.hasAuthorizedAction('read');
}
}
-15
View File
@@ -1,15 +0,0 @@
import ModelAbility from "./model";
/**
* Provides abilities for credential.
*/
export default class CredentialAbility extends ModelAbility {
// =permissions
/**
* Only "known" credential types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
}
+1 -9
View File
@@ -30,18 +30,10 @@ export default class TargetAbility extends ModelAbility {
/**
* @type {boolean}
*/
get canAddBrokeredCredentialSources() {
get canAddCredentialSources() {
return this.hasAuthorizedAction('add-credential-sources');
}
/**
* @type {boolean}
*/
get canAddInjectedApplicationCredentialSources() {
return (
this.model.isSSH && this.hasAuthorizedAction('add-credential-sources')
);
}
/**
* @type {boolean}
*/
-15
View File
@@ -1,15 +0,0 @@
import ModelAbility from './model';
/**
* Provides abilities for workers.
*/
export default class WorkerAbility extends ModelAbility {
// =permissions
/**
* @type {boolean}
*/
get canCreateWorkerLed() {
return this.hasAuthorizedCollectionAction('create:worker-led');
}
}
@@ -43,20 +43,4 @@ export default class GeneratedAccountModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
// =attributes (password)
@attr('string', {
for: 'password',
isNestedAttribute: true,
description: 'The account login name',
})
login_name;
// =attributes (OIDC)
@attr('string', { for: 'oidc', isNestedAttribute: true }) subject;
@attr('string', { for: 'oidc', isNestedAttribute: true }) issuer;
@attr('string', { for: 'oidc', isNestedAttribute: true, readOnly: true })
email;
@attr('string', { for: 'oidc', isNestedAttribute: true, readOnly: true })
full_name;
}
@@ -44,27 +44,4 @@ export default class GeneratedCredentialLibraryModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
//= attributes (vault)
@attr('string', {
isNestedAttribute: true,
description:
'The HTTP method the library uses when requesting credentials from Vault.',
defaultValue: () => 'GET',
})
http_method;
@attr('string', {
isNestedAttribute: true,
description:
'The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.',
})
http_request_body;
@attr('string', {
isNestedAttribute: true,
description: 'The path in Vault to request credentials from.',
})
path;
}
@@ -39,90 +39,4 @@ export default class GeneratedCredentialStoreModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
// =attributes (vault)
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
})
address;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The namespace within Vault to use.',
})
namespace;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
})
ca_cert;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'Name to use as the SNI host when connecting to Vault via TLS.',
})
tls_server_name;
@attr('boolean', {
for: 'vault',
isNestedAttribute: true,
description: 'Whether or not to skip TLS verification.',
})
tls_skip_verify;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'A token used for accessing Vault.',
})
token;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The Vault token hmac.',
readOnly: true,
})
token_hmac;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'A PEM-encoded client certificate to use for TLS authentication to the Vault server.',
})
client_certificate;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"A PEM-encoded private key matching the client certificate from 'client_certificate'.",
})
client_certificate_key;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The Vault client certificate key hmac.',
readOnly: true,
})
client_certificate_key_hmac;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'Filters to the worker(s) who can handle Vault requests for this cred store.',
})
worker_filter;
}
@@ -1,79 +0,0 @@
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A credential is a data structure containing one or more secrets that binds an
* identity to a set of permissions or capabilities on a host for a session.
*/
export default class GeneratedCredentialModel extends BaseModel {
// =attributes
@attr('string', {
description:
'The ID of the Credential Store of which this Credential is a part.',
})
credential_store_id;
@attr('string', {
description: 'The type of the resource, to help differentiate schemas',
})
type;
@attr('string', {
description: 'Optional name for identification purposes',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
// =attributes (username_password, ssh_private_key)
@attr('string', {
for: ['username_password', 'ssh_private_key'],
isNestedAttribute: true,
description: 'The username for credential.',
})
username;
// =attributes (username_password)
@attr('string', {
for: 'username_password',
isNestedAttribute: true,
description: 'The password for credential.',
})
password;
// =attributes (ssh_private_key)
@attr('string', {
for: 'ssh_private_key',
isNestedAttribute: true,
description: 'The private key for credential.',
})
private_key;
@attr('string', {
for: 'ssh_private_key',
isNestedAttribute: true,
description: 'The private key passphrase for credential.',
})
private_key_passphrase;
}
@@ -44,13 +44,4 @@ export default class GeneratedManagedGroupModel extends BaseModel {
'The ID of the Auth Method that is associated with this ManagedGroup',
})
auth_method_id;
// =attributes (OIDC)
@attr('string', {
isNestedAttribute: true,
description:
'The boolean expression filter to use to determine membership.',
})
filter;
}
+1 -6
View File
@@ -30,15 +30,10 @@ export default class GeneratedTargetModel extends BaseModel {
@attr('number', {
description: '',
defaultValue: 1,
})
session_connection_limit;
@attr('string', {
description:
'Optional boolean expression to filter the workers that are allowed to satisfy this request.',
})
worker_filter;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
@@ -1,70 +0,0 @@
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* Worker contains all fields related to a worker resource
*/
export default class GeneratedWorkerModel extends BaseModel {
// =attributes
@attr('string', {
description: 'Optional name for identification purposes.',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes.',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description: 'The address that this worker is reachable at.\nOutput only.',
readOnly: true,
})
address;
@attr('date', {
description:
'The time this worker was last reported its status.\nOutput only.',
readOnly: true,
})
last_status_time;
@attr('string', {
description:
'The version of the Boundary binary the worker is running.\nOutput only.',
readOnly: true,
})
release_version;
@attr('number', {
description:
'The number of connections that this worker is currently handling.\nOutput only.',
readOnly: true,
})
active_connection_count;
@attr('string', {
description:
'The type of the worker, denoted by how it authenticates: pki or kms.\nOutput only.',
readOnly: true,
})
type;
}
+9 -2
View File
@@ -1,10 +1,17 @@
import GeneratedAccountModel from '../generated/models/account';
import { fragment } from 'ember-data-model-fragments/attributes';
import { computed } from '@ember/object';
import { equal } from '@ember/object/computed';
export default class AccountModel extends GeneratedAccountModel {
// =attributes
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentAccountAttributesModel}
*/
@fragment('fragment-account-attributes', { defaultValue: {} }) attributes;
/**
* @type {boolean}
*/
@@ -19,9 +26,9 @@ export default class AccountModel extends GeneratedAccountModel {
* Convenience for getting username in account.
* @type {string}
*/
@computed('{email,full_name,login_name}')
@computed('attributes.{email,full_name,login_name}')
get accountName() {
const { email, full_name, login_name } = this;
const { email, full_name, login_name } = this.attributes;
return email || full_name || login_name;
}
+3 -5
View File
@@ -1,5 +1,6 @@
import Model from '@ember-data/model';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { computed } from '@ember/object';
/**
@@ -29,8 +30,9 @@ export default class BaseModel extends Model {
/**
* Scope of this resource, if any, represented as a JSON fragment.
* @type {FragmentScope}
*/
@attr() scope;
@fragment('fragment-scope') scope;
// =attributes
@@ -53,10 +55,6 @@ export default class BaseModel extends Model {
if (model) {
const json = model.serialize();
json.scope_id = model.id;
json.type = model.type;
json.isGlobal = model.isGlobal;
json.isOrg = model.isOrg;
json.isProject = model.isProject;
this.scope = json;
}
}
+15 -18
View File
@@ -1,4 +1,6 @@
import GeneratedCredentialLibraryModel from '../generated/models/credential-library';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
/**
* Enum options for credential library.
@@ -7,27 +9,22 @@ export const options = {
http_method: ['GET', 'POST'],
};
/**
* Supported Credential Library types.
*/
export const types = ['vault'];
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_http_method;
@attr('string', { readOnly: true }) attributes_http_request_body;
@attr('string', { readOnly: true }) attributes_path;
// =attributes
/**
* True if credential is an unknown type.
* @type {boolean}
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentCredentialLibraryAttributesModel}
*/
get isUnknown() {
return !types.includes(this.type);
}
/**
* True if credential is a vault type.
* @type {boolean}
*/
get isVault() {
return this.type === 'vault';
}
@fragment('fragment-credential-library-attributes', { defaultValue: {} })
attributes;
}
+23 -5
View File
@@ -1,16 +1,34 @@
import GeneratedCredentialStoreModel from '../generated/models/credential-store';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { equal } from '@ember/object/computed';
export default class CredentialStoreModel extends GeneratedCredentialStoreModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_address;
@attr('string', { readOnly: true }) attributes_namespace;
@attr('string', { readOnly: true }) attributes_ca_cert;
@attr('string', { readOnly: true }) attributes_tls_server_name;
@attr('string', { readOnly: true }) attributes_tls_skip_verify;
@attr('string', { readOnly: true }) attributes_token;
@attr('string', { readOnly: true }) attributes_client_certificate;
@attr('string', { readOnly: true }) attributes_client_certificate_key;
// =attributes
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentAuthMethodAttributesModel}
*/
@fragment('fragment-credential-store-attributes', { defaultValue: {} })
attributes;
/**
* @type {boolean}
*/
@equal('type', 'vault') isVault;
/**
* @type {boolean}
*/
@equal('type', 'static') isStatic;
}
-28
View File
@@ -1,28 +0,0 @@
import GeneratedCredentialModel from '../generated/models/credential';
/**
* Supported Credential types.
*/
export const types = ['username_password', 'ssh_private_key'];
export default class CredentialModel extends GeneratedCredentialModel {
// =attributes
/**
* All Credentials are prefixed with "cred" and are considered
* static due to their relation with Static Credential Stores.
*
* True if credential starts with the "cred" prefix.
* @type {boolean}
*/
get isStatic() {
return this.id.startsWith('cred');
}
/**
* True if credential is an unknown type.
* @type {boolean}
*/
get isUnknown() {
return !types.includes(this.type);
}
}
@@ -0,0 +1,17 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentAccountAttributesModel extends Fragment {
// =attributes (password)
@attr('string', {
for: 'password',
description: 'The account login name',
})
login_name;
// =attributes (OIDC)
@attr('string', { for: 'oidc' }) subject;
@attr('string', { for: 'oidc' }) issuer;
@attr('string', { for: 'oidc', readOnly: true }) email;
@attr('string', { for: 'oidc', readOnly: true }) full_name;
}
@@ -0,0 +1,25 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
import { options } from './credential-library';
export default class FragmentCredentialLibraryAttributesModel extends Fragment {
//= attributes (vault)
@attr('string', {
description:
'The HTTP method the library uses when requesting credentials from Vault.',
defaultValue: () => options.http_method[0],
})
http_method;
@attr('string', {
description:
'The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.',
})
http_request_body;
@attr('string', {
description: 'The path in Vault to request credentials from.',
})
path;
}
@@ -0,0 +1,72 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentCredentialStoreAttributesModel extends Fragment {
// =attributes (vault)
@attr('string', {
for: 'vault',
description:
"The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
})
address;
@attr('string', {
for: 'vault',
description: 'The namespace within Vault to use.',
})
namespace;
@attr('string', {
for: 'vault',
description:
"A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
})
ca_cert;
@attr('string', {
for: 'vault',
description:
'Name to use as the SNI host when connecting to Vault via TLS.',
})
tls_server_name;
@attr('boolean', {
for: 'vault',
description: 'Whether or not to skip TLS verification.',
})
tls_skip_verify;
@attr('string', {
for: 'vault',
description: 'A token used for accessing Vault.',
})
token;
@attr('string', {
for: 'vault',
description: 'The Vault token hmac.',
readOnly: true,
})
token_hmac;
@attr('string', {
for: 'vault',
description:
'A PEM-encoded client certificate to use for TLS authentication to the Vault server.',
})
client_certificate;
@attr('string', {
for: 'vault',
description:
"A PEM-encoded private key matching the client certificate from 'client_certificate'.",
})
client_certificate_key;
@attr('string', {
for: 'vault',
description: 'The Vault client certificate key hmac.',
readOnly: true,
})
client_certificate_key_hmac;
}
@@ -0,0 +1,16 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentHostSourceModel extends Fragment {
// =attributes
@attr('string', {
description: 'ID of the related host source',
})
host_source_id;
@attr('string', {
description: 'ID of the catalog to which the host set belongs',
})
host_catalog_id;
}
@@ -0,0 +1,12 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentManagedGroupAttributesModel extends Fragment {
// =attributes (OIDC)
@attr('string', {
description:
'The boolean expression filter to use to determine membership.',
})
filter;
}
@@ -0,0 +1,21 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
export default class FragmentPrincipal extends Fragment {
// =attributes
@attr('string', {
description: 'ID of the related scope',
})
scope_id;
@attr('string', {
description: 'ID of the related principal (user or group)',
})
principal_id;
@attr('string', {
description: 'Type of the related principal (user or group)',
})
type;
}
+42
View File
@@ -0,0 +1,42 @@
import Fragment from 'ember-data-model-fragments/fragment';
import { computed } from '@ember/object';
import { attr } from '@ember-data/model';
import { scopeTypes } from './scope';
export default class FragmentScope extends Fragment {
// =attributes
@attr('string', {
description: 'ID of the related scope',
})
scope_id;
@attr('string', {
description: 'The type of the resource',
})
type;
/**
* @type {boolean}
*/
@computed('type')
get isGlobal() {
return this.type === scopeTypes.global;
}
/**
* @type {boolean}
*/
@computed('type')
get isOrg() {
return this.type === scopeTypes.org;
}
/**
* @type {boolean}
*/
@computed('type')
get isProject() {
return this.type === scopeTypes.project;
}
}
+9
View File
@@ -1,10 +1,16 @@
import GeneratedManagedGroupModel from '../generated/models/managed-group';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { A } from '@ember/array';
export default class ManagedGroupModel extends GeneratedManagedGroupModel {
// =attributes
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_filter;
/**
@@ -17,4 +23,7 @@ export default class ManagedGroupModel extends GeneratedManagedGroupModel {
emptyArrayIfMissing: true,
})
member_ids;
@fragment('fragment-managed-group-attributes', { defaultValue: {} })
attributes;
}
+3 -12
View File
@@ -1,5 +1,5 @@
import GeneratedRoleModel from '../generated/models/role';
import { attr } from '@ember-data/model';
import { fragmentArray, array } from 'ember-data-model-fragments/attributes';
export default class RoleModel extends GeneratedRoleModel {
// =attributes
@@ -11,7 +11,7 @@ export default class RoleModel extends GeneratedRoleModel {
* see obvious. Instead, the application layer is expected to load referenced
* users and groups as needed.
*/
@attr('principal-array', {
@fragmentArray('fragment-principal', {
readOnly: true,
emptyArrayIfMissing: true,
})
@@ -21,16 +21,7 @@ export default class RoleModel extends GeneratedRoleModel {
* Grant strings are read-only. But grants can be persisted via a dedicated
* call to `saveGrantStrings(grants)`.
*/
@attr({ readOnly: true, emptyArrayIfMissing: true }) grant_strings;
/**
* Convenience for looking up the grant scope, if loaded.
*/
get grantScope() {
return this.grant_scope_id
? this.store.peekRecord('scope', this.grant_scope_id)
: null;
}
@array('string', { readOnly: true }) grant_strings;
// =methods
+4 -4
View File
@@ -13,7 +13,7 @@ class SessionCredential {
/**
*
*/
static Source = class Source {
static Library = class Library {
id;
name;
description;
@@ -42,7 +42,7 @@ class SessionCredential {
// =attributes
rawCredential;
source;
library;
#payloadSecret;
/**
@@ -72,8 +72,8 @@ class SessionCredential {
// =methods
constructor(cred) {
const { id, name, description, type } = cred.credential_source;
this.source = new SessionCredential.Source(id, name, description, type);
const { id, name, description, type } = cred.credential_library;
this.library = new SessionCredential.Library(id, name, description, type);
this.#payloadSecret = cred.secret;
this.rawCredential = cred;
}
+31 -132
View File
@@ -1,43 +1,35 @@
import GeneratedTargetModel from '../generated/models/target';
import { attr } from '@ember-data/model';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { computed } from '@ember/object';
export default class TargetModel extends GeneratedTargetModel {
// =attributes
@attr('number', {
isNestedAttribute: true,
description: 'The default port a target should use if present.',
description: 'The default port a target should use if present.',
})
default_port;
/**
* @type Array
* @type {[FragmentHostSourceModel]}
*/
@attr('host-source-id-array', {
@fragmentArray('fragment-host-source', {
readOnly: true,
emptyArrayIfMissing: true,
})
host_sources;
/**
* Brokered Credential source ids are read only and can be
* persisted via a dedicated call to `addBrokeredCredentialSources()`.
* Credential source ids are read only and can be
* persisted via a dedicated call to `addCredentialSources()`.
*/
@attr('string-array', {
@fragmentArray('fragment-string', {
readOnly: true,
emptyArrayIfMissing: true,
})
brokered_credential_source_ids;
/**
* Injected Application Credential source ids are read only and can be
* persisted via a dedicated call to `addInjectedApplicationCredentialSources()`.
*/
@attr('string-array', {
readOnly: true,
emptyArrayIfMissing: true,
})
injected_application_credential_source_ids;
application_credential_source_ids;
/**
* An array of resolved host set and host catalog instances. Model instances
@@ -45,6 +37,7 @@ export default class TargetModel extends GeneratedTargetModel {
* instances). Unresolvable instances are excluded from the array.
* @type {[{model: HostSetModel, hostCatalog: HostCatalogModel}]}
*/
@computed('host_sources.[]', 'store')
get hostSets() {
return this.host_sources
.map(({ host_source_id, host_catalog_id }) => ({
@@ -55,38 +48,17 @@ export default class TargetModel extends GeneratedTargetModel {
}
/**
* An array of resolved credential library and credential instances. Model instances
* An array of resolved credential library instances. Model instances
* must already be loaded into the store (this method will not load unloaded
* instances). Unresolvable instances are excluded from the array.
* @type {[CredentialLibraryModel, CredentialModel]}
* @type {[CredentialLibraryModel]}
*/
get brokeredCredentialSources() {
return this.brokered_credential_source_ids
.map((source) => {
if (source.value.startsWith('cred')) {
return this.store.peekRecord('credential', source.value);
} else {
return this.store.peekRecord('credential-library', source.value);
}
})
.filter(Boolean);
}
/**
* An array of resolved credential library and credential instances. Model instances
* must already be loaded into the store (this method will not load unloaded
* instances). Unresolvable instances are excluded from the array.
* @type {[CredentialLibraryModel, CredentialModel]}
*/
get injectedApplicationCredentialSources() {
return this.injected_application_credential_source_ids
.map((source) => {
if (source.value.startsWith('cred')) {
return this.store.peekRecord('credential', source.value);
} else {
return this.store.peekRecord('credential-library', source.value);
}
})
@computed('application_credential_source_ids.[]', 'store')
get credentialLibraries() {
return this.application_credential_source_ids
.map((source) =>
this.store.peekRecord('credential-library', source.value)
)
.filter(Boolean);
}
@@ -178,20 +150,17 @@ export default class TargetModel extends GeneratedTargetModel {
}
/**
* Adds credential sources via the `add-credential-sources` method.
* Adds credential libraries via the `add-credential-sources` method.
* See serializer and adapter for more information.
* @param {[string]} brokeredCredentialSourceIDs
* @param {[string]} credentialLibraryIDs
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
addBrokeredCredentialSources(
brokeredCredentialSourceIDs,
options = { adapterOptions: {} }
) {
addCredentialSources(credentialLibraryIDs, options = { adapterOptions: {} }) {
const defaultAdapterOptions = {
method: 'add-credential-sources',
brokeredCredentialSourceIDs,
credentialLibraryIDs,
};
// There is no "deep merge" in ES.
return this.save({
@@ -204,46 +173,20 @@ export default class TargetModel extends GeneratedTargetModel {
}
/**
* Adds credential sources via the `add-credential-sources` method.
* Delete credential libraries via the `remove-credential-sources` method.
* See serializer and adapter for more information.
* @param {[string]} injectedApplicationCredentialSourceIDs
* @param {[string]} credentialLibraryIDs
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
addInjectedApplicationCredentialSources(
injectedApplicationCredentialSourceIDs,
options = { adapterOptions: {} }
) {
const defaultAdapterOptions = {
method: 'add-credential-sources',
injectedApplicationCredentialSourceIDs,
};
// There is no "deep merge" in ES.
return this.save({
...options,
adapterOptions: {
...defaultAdapterOptions,
...options.adapterOptions,
},
});
}
/**
* Delete credential libraries and credentials via the `remove-credential-sources` method.
* See serializer and adapter for more information.
* @param {[string]} brokeredCredentialSourceIDs
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
removeBrokeredCredentialSources(
brokeredCredentialSourceIDs,
removeCredentialSources(
credentialLibraryIDs,
options = { adapterOptions: {} }
) {
const defaultAdapterOptions = {
method: 'remove-credential-sources',
brokeredCredentialSourceIDs,
credentialLibraryIDs,
};
// There is no "deep merge" in ES.
return this.save({
@@ -256,59 +199,15 @@ export default class TargetModel extends GeneratedTargetModel {
}
/**
* Delete a single credential library/credential set via the `remove-credential-sources` method.
* @param {number} brokeredCredentialSourceID
* Delete a single credential library set via the `remove-credential-sources` method.
* @param {number} credentialLibraryID
* @param {object} options
* @return {Promise}
*/
removeBrokeredCredentialSource(brokeredCredentialSourceID, options) {
return this.removeBrokeredCredentialSources(
[brokeredCredentialSourceID],
options
);
removeCredentialSource(credentialLibraryID, options) {
return this.removeCredentialSources([credentialLibraryID], options);
}
/**
* Delete credential libraries and credentials via the `remove-credential-sources` method.
* See serializer and adapter for more information.
* @param {[string]} injectedApplicationCredentialSourceIDs
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
removeInjectedApplicationCredentialSources(
injectedApplicationCredentialSourceIDs,
options = { adapterOptions: {} }
) {
const defaultAdapterOptions = {
method: 'remove-credential-sources',
injectedApplicationCredentialSourceIDs,
};
// There is no "deep merge" in ES.
return this.save({
...options,
adapterOptions: {
...defaultAdapterOptions,
...options.adapterOptions,
},
});
}
/**
* Delete a single credential library/credential set via the `remove-credential-sources` method.
* @param {number} injectedApplicationCredentialSourceID
* @param {object} options
* @return {Promise}
*/
removeInjectedApplicationCredentialSource(
injectedApplicationCredentialSourceID,
options
) {
return this.removeInjectedApplicationCredentialSources(
[injectedApplicationCredentialSourceID],
options
);
}
/**
* True if the target type is tcp.
* @type {boolean}
+1 -1
View File
@@ -40,7 +40,7 @@ export default class UserModel extends GeneratedUserModel {
* There are many options, so we fallback in order of precedence.
* @type {string}
*/
@computed('email', 'full_name', 'login_name')
@computed('email,full_name,login_name,displayName')
get accountName() {
const { email, full_name, login_name } = this;
return email || full_name || login_name;
-88
View File
@@ -1,88 +0,0 @@
import GeneratedWorkerModel from '../generated/models/worker';
import { attr } from '@ember-data/model';
export default class WorkerModel extends GeneratedWorkerModel {
// =attributes
/**
* Names are optional on models in our API. Thus we need to fallback on ID
* for display purposes.
* @type {string}
*/
get displayName() {
return this.name || this.id;
}
/**
* Returns whether the worker is a pki worker.
* @type {boolean}
*/
get isPki() {
return this.type === 'pki';
}
/**
* Returns the number of config tags present on the worker.
* @type {number}
*/
get tagCount() {
if (!this.config_tags) {
return 0;
}
return Object.values(this.config_tags).reduce(
(previousCount, currentTags) => previousCount + currentTags.length,
0
);
}
/**
* Returns the config tags as an array of key/value pair objects.
* @type {[object]}
*/
getConfigTagList() {
if (!this.config_tags) {
return null;
}
return Object.entries(this.config_tags).flatMap(([key, value]) =>
value.map((tag) => ({ key, value: tag }))
);
}
@attr({
description:
'The deduplicated union of the tags reported by the worker ' +
'from its configuration and any tags added through other means.\nOutput only.',
readOnly: true,
})
canonical_tags;
@attr({
description:
"The tags set in the worker's configuration file.\nOutput only.",
readOnly: true,
})
config_tags;
/**
* Method to modify the adapter to handle custom POST route for creating worker.
* @param {string} workerGeneratedAuthToken
* @param {object} options
* @param {object} options.adapterOptions
* @return {Promise}
*/
createWorkerLed(workerGeneratedAuthToken, options = { adapterOptions: {} }) {
const defaultAdapterOptions = {
method: 'create:worker-led',
workerGeneratedAuthToken,
};
return super.save({
...options,
adapterOptions: {
...defaultAdapterOptions,
...options.adapterOptions,
},
});
}
}
+3 -3
View File
@@ -33,12 +33,12 @@ export default class AccountSerializer extends ApplicationSerializer {
* `attributes` entirely on update.
*/
serializeOIDC(snapshot) {
const { isNew } = snapshot?.record || {};
const { isNew } = snapshot?.record;
let serialized = super.serialize(...arguments);
delete serialized.attributes;
if (isNew) {
serialized.attributes = {};
const { issuer, subject } = snapshot.record;
const { issuer, subject } = snapshot.attr('attributes').record;
if (issuer) serialized.attributes.issuer = issuer;
if (subject) serialized.attributes.subject = subject;
}
@@ -70,7 +70,7 @@ export default class AccountSerializer extends ApplicationSerializer {
}
// Change password custom method
if (snapshot?.adapterOptions?.method === 'change-password') {
const { currentPassword, newPassword } = snapshot?.adapterOptions || {};
const { currentPassword, newPassword } = snapshot?.adapterOptions;
serialized = this.serializeForChangePassword(
snapshot,
currentPassword,
+6 -55
View File
@@ -96,7 +96,7 @@ export default class ApplicationSerializer extends RESTSerializer {
delete serialized.scope;
// And serialize `scope_id`
if (this.serializeScopeID) {
const scope_id = snapshot?.attr('scope')?.scope_id;
const scope_id = snapshot?.attr('scope')?.attr('scope_id');
if (scope_id) serialized.scope_id = scope_id;
}
return serialized;
@@ -137,7 +137,7 @@ export default class ApplicationSerializer extends RESTSerializer {
}
/**
* In our API, singular resources are _unrooted_, whereas Ember Data expects
* In our API, singluar resources are _unrooted_, whereas Ember Data expects
* them to be rooted under their model name, e.g. `{modelName: {…}}`.
* This method makes this transformation to accommodate Ember Data.
*
@@ -199,9 +199,11 @@ export default class ApplicationSerializer extends RESTSerializer {
}
/**
* Hydrates the incoming scope JSON with additional boolean fields and
* delegates nested attributes and secrets normalization.
* One consequence of using model fragments to represent the embedded scope
* is that they cannot have `id` fields. We still need to know scope IDs,
* so we copy the `scope.id` value into the `scope.scope_id` field.
* @override
* @see FragmentScope
* @param {Model} typeClass
* @param {Object} hash
* @return {Object}
@@ -209,14 +211,7 @@ export default class ApplicationSerializer extends RESTSerializer {
normalize(typeClass, hash) {
let normalizedHash = copy(hash, true);
const scopeID = get(normalizedHash, 'scope.id');
const scopeType = get(normalizedHash, 'scope.type');
if (scopeID) normalizedHash.scope.scope_id = scopeID;
if (scopeType) {
normalizedHash.scope.type = scopeType;
normalizedHash.scope.isGlobal = scopeType === 'global';
normalizedHash.scope.isOrg = scopeType === 'org';
normalizedHash.scope.isProject = scopeType === 'project';
}
normalizedHash = this.normalizeNestedAttributes(typeClass, normalizedHash);
normalizedHash = this.normalizeNestedSecrets(typeClass, normalizedHash);
return super.normalize(typeClass, normalizedHash);
@@ -273,48 +268,4 @@ export default class ApplicationSerializer extends RESTSerializer {
});
return hash;
}
/**
* If the API returns an updated response with removed fields, we need to
* explicitly set any read only field that is on the model but wasn't
* returned in the response to `null`.
* This will let ember data know the field was removed, otherwise
* it won't touch the removed field and will use the old value.
*
* @override
* @param {object} store
* @param {object} primaryModelClass
* @param {object} payload
* @param {?string} id
* @param {string} requestType
* @return {object}
*/
normalizeUpdateRecordResponse(
store,
primaryModelClass,
payload,
id,
requestType
) {
const newPayload = copy(payload, true);
primaryModelClass.attributes.forEach((attribute) => {
const {
name,
options: { readOnly },
} = attribute;
if (readOnly && !Object.prototype.hasOwnProperty.call(newPayload, name)) {
newPayload[name] = null;
}
});
return super.normalizeUpdateRecordResponse(
store,
primaryModelClass,
newPayload,
id,
requestType
);
}
}
@@ -81,6 +81,7 @@ export default class AuthMethodSerializer extends ApplicationSerializer {
*
* TODO: generalize this so that all zero values are reified.
* @override
* @see FragmentScope
* @param {Model} typeClass
* @param {Object} hash
* @return {Object}
@@ -1,25 +1,3 @@
import ApplicationSerializer from './application';
export default class CredentialLibrarySerializer extends ApplicationSerializer {
// =properties
/**
* @type {boolean}
*/
serializeScopeID = false;
/**
* @override
* @method serialize
* @param {Snapshot} snapshot
*/
serialize() {
const serialized = super.serialize(...arguments);
if (serialized.attributes) {
// Serialize `http_request_body` only if `http_method` is POST
if (!serialized.attributes?.http_method?.match(/post/i))
delete serialized.attributes.http_request_body;
}
return serialized;
}
}
export default class CredentialLibrarySerializer extends ApplicationSerializer {}
@@ -1,50 +1,3 @@
import ApplicationSerializer from './application';
import { copy } from 'ember-copy';
export default class CredentialStoreSerializer extends ApplicationSerializer {
/**
* @override
* @method serialize
* @param {Snapshot} snapshot
*/
serialize(snapshot) {
switch (snapshot.record.type) {
case 'vault':
return this.serializeVault(...arguments);
case 'static':
default:
return this.serializeStatic(...arguments);
}
}
serializeVault() {
const serialized = super.serialize(...arguments);
if (serialized.attributes) {
// Token cannot be unset. If it's falsy, it must be removed.
if (!serialized.attributes?.token) delete serialized.attributes.token;
// Client certificate key cannot be unset when certificate is set.
// If it's falsy, it must be removed.
if (
!serialized.attributes?.client_certificate_key &&
serialized.attributes?.client_certificate
)
delete serialized.attributes.client_certificate_key;
}
return serialized;
}
serializeStatic() {
const serialized = super.serialize(...arguments);
// Delete attributes for static cred store
delete serialized.attributes;
return serialized;
}
normalize(typeClass, hash, ...rest) {
const normalizedHash = copy(hash, true);
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
// Remove secret fields as we don't track them after being created/updated
normalized.data.attributes.token = '';
return normalized;
}
}
export default class CredentialStoreSerializer extends ApplicationSerializer {}
@@ -1,66 +0,0 @@
import ApplicationSerializer from './application';
import { copy } from 'ember-copy';
export default class CredentialSerializer extends ApplicationSerializer {
/**
* @override
* @method serialize
* @param {Snapshot} snapshot
*/
/**
* @type {boolean}
*/
serializeScopeID = false;
serialize(snapshot) {
switch (snapshot.record.type) {
case 'username_password':
return this.serializeUsernamePassword(...arguments);
case 'ssh_private_key':
default:
return this.serializeSSHPrivateKey(...arguments);
}
}
serializeUsernamePassword() {
const serialized = super.serialize(...arguments);
// Remove non-username_password type attributes
delete serialized.attributes.private_key;
delete serialized.attributes.private_key_passphrase;
// Remove password from the payload if null
// or empty string. This allows the user to only update
// the username without providing the password again.
// Users cannot unset the password, they would have
// to delete the credential entirely.
if (!serialized?.attributes?.password)
delete serialized.attributes.password;
return serialized;
}
serializeSSHPrivateKey() {
const serialized = super.serialize(...arguments);
// Remove non-ssh_private_key type attributes
delete serialized.attributes.password;
// Remove private_key and/or private_key_passphrase from the payload
// if null or empty string. This allows the user to only update
// the username without providing the private key and passphrase again.
// Users cannot unset the private key or passphrase, they would have
// to delete the credential entirely.
if (!serialized?.attributes?.private_key_passphrase)
delete serialized.attributes.private_key_passphrase;
if (!serialized?.attributes?.private_key)
delete serialized.attributes.private_key;
return serialized;
}
normalize(typeClass, hash, ...rest) {
const normalizedHash = copy(hash, true);
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
// Remove secret fields as we don't track them after being created/updated
normalized.data.attributes.password = '';
normalized.data.attributes.private_key = '';
normalized.data.attributes.private_key_passphrase = '';
return normalized;
}
}
@@ -0,0 +1,26 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentAccountAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
*
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Client secret cannot be unset. If it's falsy, it must be removed.
if (key === 'client_secret' && !json[key]) delete json[key];
return value;
}
}
@@ -0,0 +1,27 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentCredentialLibraryAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
* Only serialize http_request_body when `http_method` is set to `POST`.
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Do not serialize http_request_body unless http_method is POST
if (key === 'http_request_body' && !json['http_method']?.match(/post/i))
delete json[key];
return value;
}
}
@@ -0,0 +1,34 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentCredentialStoreAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
*
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty strings to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Token cannot be unset. If it's falsy, it must be removed.
if (key === 'token' && !json[key]) delete json[key];
// Client certificate key cannot be unset when certificate is set.
// If it's falsy, it must be removed.
if (
key === 'client_certificate_key' &&
!json[key] &&
json['client_certificate']
)
delete json[key];
return value;
}
}
@@ -0,0 +1,16 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentHostSourceSerializer extends JSONSerializer {
// =methods
/**
* Renames `id` to `host_source_id` because fragments may not have IDs.
* Otherwise normalizes as normal.
* @param {ModelClass} typeClass
* @param {object} resourceHash
* @return {object}
*/
normalize(typeClass, { id: host_source_id, ...obj }) {
return super.normalize(typeClass, { host_source_id, ...obj });
}
}
@@ -0,0 +1,27 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentManagedGroupAttributesSerializer extends JSONSerializer {
/**
* If an attribute is annotated as readOnly in the model, don't serialize it.
* Otherwise delegate to default attribute serializer.
* @override
* @method serializeAttribute
* @param {Snapshot} snapshot
* @param {Object} json
* @param {String} key
* @param {Object} attribute
*/
serializeAttribute(snapshot, json, key, attribute) {
const { type, options } = attribute;
let value = super.serializeAttribute(...arguments);
// Convert empty string to null.
if (type === 'string' && json[key] === '') json[key] = null;
// Do not serialize read-only attributes.
if (options.readOnly) delete json[key];
// Version is sent only if it has a non-nullish value
if (key === 'version') {
if (json[key] === null || json[key] === undefined) delete json[key];
}
return value;
}
}
@@ -0,0 +1,16 @@
import JSONSerializer from '@ember-data/serializer/json';
export default class FragmentPrincipalSerializer extends JSONSerializer {
// =methods
/**
* Renames `id` to `principal_id` because fragments may not have IDs.
* Otherwise normalizes as normal.
* @param {ModelClass} typeClass
* @param {object} resourceHash
* @return {object}
*/
normalize(typeClass, { id: principal_id, ...obj }) {
return super.normalize(typeClass, { principal_id, ...obj });
}
}
+2 -2
View File
@@ -80,8 +80,8 @@ export default class HostSetSerializer extends ApplicationSerializer {
*/
normalize(typeClass, hash, ...rest) {
const normalizedHash = copy(hash, true);
if (typeof normalizedHash?.attributes?.filters === 'string') {
normalizedHash.attributes.filters = [normalizedHash.attributes.filters];
if (typeof normalizedHash?.filters === 'string') {
normalizedHash.filters = [normalizedHash.filters];
}
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
return normalized;
+11 -40
View File
@@ -7,10 +7,10 @@ export default class TargetSerializer extends ApplicationSerializer {
* If `adapterOptions.hostSets` is set to an array of host set models,
* the resulting target serialization should include **only host sets**
* and the version.
* If `adapterOptions.credentialSources` is set to an array of
* credential library and credential models, the resulting target serialization should
* include **only credential libraries and crendentials** and the version.
* Normally, neither host sets or credential libraries or credentials are not serialized.
* If `adapterOptions.credentialLibraries` is set to an array of
* credential library models, the resulting target serialization should
* include **only credential libraries** and the version.
* Normally, neither host sets or credential libraries are not serialized.
* @override
* @param {Snapshot} snapshot
* @return {object}
@@ -21,20 +21,11 @@ export default class TargetSerializer extends ApplicationSerializer {
if (hostSourceIDs) {
serialized = this.serializeWithHostSources(snapshot, hostSourceIDs);
}
const brokeredCredentialSourceIDs =
snapshot?.adapterOptions?.brokeredCredentialSourceIDs;
const injectedApplicationCredentialSourceIDs =
snapshot?.adapterOptions?.injectedApplicationCredentialSourceIDs;
if (brokeredCredentialSourceIDs)
serialized = this.serializeWithBrokeredCredentialSources(
const credentialSourceIDs = snapshot?.adapterOptions?.credentialLibraryIDs;
if (credentialSourceIDs)
serialized = this.serializeWithCredentialSources(
snapshot,
brokeredCredentialSourceIDs
);
if (injectedApplicationCredentialSourceIDs)
serialized = this.serializeWithInjectedApplicationCredentialSources(
snapshot,
injectedApplicationCredentialSourceIDs
credentialSourceIDs
);
return serialized;
}
@@ -57,33 +48,13 @@ export default class TargetSerializer extends ApplicationSerializer {
* Returns a payload containing only version and an array of passed IDs,
* rather than existing instances on the model.
* @param {Snapshot} snapshot
* @param {[string]} brokered_credential_source_ids
* @param {[string]} credentialSourceIDs
* @return {object}
*/
serializeWithBrokeredCredentialSources(
snapshot,
brokered_credential_source_ids
) {
serializeWithCredentialSources(snapshot, credentialSourceIDs) {
return {
version: snapshot.attr('version'),
brokered_credential_source_ids,
};
}
/**
* Returns a payload containing only version and an array of passed IDs,
* rather than existing instances on the model.
* @param {Snapshot} snapshot
* @param {[string]} injected_application_credential_source_ids
* @return {object}
*/
serializeWithInjectedApplicationCredentialSources(
snapshot,
injected_application_credential_source_ids
) {
return {
version: snapshot.attr('version'),
injected_application_credential_source_ids,
application_credential_source_ids: credentialSourceIDs,
};
}
}
-37
View File
@@ -1,37 +0,0 @@
import ApplicationSerializer from './application';
export default class WorkerSerializer extends ApplicationSerializer {
// =methods
/**
* If `adapterOptions.workerGeneratedAuthToken` is set,
* then the payload is serialized via `serializedWithGeneratedToken`.
* @override
* @param {Snapshot} snapshot
* @return {object}
*/
serialize(snapshot) {
let serialized = super.serialize(...arguments);
const workerGeneratedAuthToken =
snapshot?.adapterOptions?.workerGeneratedAuthToken;
if (workerGeneratedAuthToken)
serialized = this.serializeWithGeneratedToken(
snapshot,
workerGeneratedAuthToken
);
return serialized;
}
/**
* Returns a payload with `worker_generated_auth_token` serialized.
* @param {Snapshot} snapshot
* @param {[string]} worker_generated_auth_token
* @return {object}
*/
serializeWithGeneratedToken(snapshot, workerGeneratedAuthToken) {
return {
scope_id: snapshot.attr('scope').scope_id,
worker_generated_auth_token: workerGeneratedAuthToken,
};
}
}
@@ -1,16 +0,0 @@
import Transform from '@ember-data/serializer/transform';
export default class HostSourceIdArrayTransform extends Transform {
deserialize(serialized) {
const stringValues = serialized || [];
return stringValues.map(({ id: host_source_id, ...obj }) => ({
host_source_id,
...obj,
}));
}
serialize(deserialized) {
const strings = deserialized || [];
return strings.map(({ host_source_id: id, ...obj }) => ({ id, ...obj }));
}
}
@@ -1,21 +0,0 @@
import Transform from '@ember-data/serializer/transform';
export default class PrincipalArrayTransform extends Transform {
deserialize(serialized) {
const stringValues = serialized || [];
return stringValues.map(({ id: principal_id, scope_id, type }) => ({
principal_id,
scope_id,
type,
}));
}
serialize(deserialized) {
const strings = deserialized || [];
return strings.map(({ principal_id: id, scope_id, type }) => ({
id,
scope_id,
type,
}));
}
}
@@ -1,13 +0,0 @@
import Transform from '@ember-data/serializer/transform';
export default class StringArrayTransform extends Transform {
deserialize(serialized) {
const stringValues = serialized || [];
return stringValues.map((value) => ({ value }));
}
serialize(deserialized) {
const strings = deserialized || [];
return strings.map(({ value }) => value);
}
}
@@ -1 +0,0 @@
export { default } from 'api/abilities/credential-library';
@@ -1 +0,0 @@
export { default } from 'api/abilities/credential-store';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/abilities/credential';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/abilities/worker';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/models/credential';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-account-attributes';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-credential-library-attributes';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-credential-store-attributes';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-host-source';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-managed-group-attributes';
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-principal';
+1
View File
@@ -0,0 +1 @@
export { default } from 'api/models/fragment-scope';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/models/worker';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/serializers/credential';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-account-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-credential-library-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-credential-store-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-host-source';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-managed-group-attributes';
@@ -0,0 +1 @@
export { default } from 'api/serializers/fragment-principal';
-1
View File
@@ -1 +0,0 @@
export { default } from 'api/serializers/worker';
@@ -1 +0,0 @@
export { default } from 'api/transforms/host-source-id-array';
@@ -1 +0,0 @@
export { default } from 'api/transforms/principal-array';
@@ -1 +0,0 @@
export { default } from 'api/transforms/string-array';
+55 -102
View File
@@ -1,6 +1,5 @@
import config from '../config/environment';
import { authHandler, deauthHandler } from './route-handlers/auth';
import { targetHandler } from './route-handlers/target';
import { pickRandomStatusString } from './factories/session';
import { Response } from 'miragejs';
import initializeMockIPC from './scenarios/ipc';
@@ -58,23 +57,8 @@ export default function () {
this.get(
'/auth-methods',
({ authMethods }, { queryParams: { scope_id, recursive, filter } }) => {
let resultSet;
if (recursive && scope_id === 'global') {
resultSet = authMethods.all();
} else if (recursive) {
resultSet = authMethods.where((authMethod) => {
const authMethodModel = authMethods.find(authMethod.id);
return (
authMethod.scopeId === scope_id ||
authMethodModel?.scope?.scope?.id === scope_id
);
});
} else {
resultSet = authMethods.where(
(authMethod) => authMethod.scopeId === scope_id
);
}
({ authMethods }, { queryParams: { scope_id: scopeId, filter } }) => {
const resultSet = authMethods.where({ scopeId });
return resultSet.filter(makeBooleanFilter(filter));
}
);
@@ -139,29 +123,12 @@ export default function () {
// Authenticate + auth methods custom routes
this.post('/auth-methods/:id_method', authHandler);
// Deauthenticate route
this.delete('/auth-tokens/:id', deauthHandler);
this.post('/scopes/:id_method', deauthHandler);
// IAM : Users
this.get(
'/users',
({ users }, { queryParams: { scope_id, recursive, filter } }) => {
let resultSet;
if (recursive && scope_id === 'global') {
resultSet = users.all();
} else if (recursive) {
resultSet = users.where((user) => {
const userModel = users.find(user.id);
return (
user.scopeId === scope_id ||
userModel?.scope?.scope?.id === scope_id
);
});
} else {
resultSet = users.where((user) => user.scopeId === scope_id);
}
return resultSet.filter(makeBooleanFilter(filter));
}
);
this.get('/users', ({ users }, { queryParams: { scope_id: scopeId } }) => {
return users.where({ scopeId });
});
this.post('/users');
this.get('/users/:id');
this.patch('/users/:id');
@@ -193,26 +160,9 @@ export default function () {
});
// IAM: Groups
this.get(
'/groups',
({ groups }, { queryParams: { scope_id, recursive, filter } }) => {
let resultSet;
if (recursive && scope_id === 'global') {
resultSet = groups.all();
} else if (recursive) {
resultSet = groups.where((group) => {
const groupModel = groups.find(group.id);
return (
group.scopeId === scope_id ||
groupModel?.scope?.scope?.id === scope_id
);
});
} else {
resultSet = groups.where((group) => group.scopeId === scope_id);
}
return resultSet.filter(makeBooleanFilter(filter));
}
);
this.get('/groups', ({ groups }, { queryParams: { scope_id: scopeId } }) => {
return groups.where({ scopeId });
});
this.post('/groups');
this.get('/groups/:id');
this.patch('/groups/:id');
@@ -436,7 +386,52 @@ export default function () {
this.get('/targets/:id');
this.patch('/targets/:id');
this.del('/targets/:id');
this.post('/targets/:idMethod', targetHandler);
this.post(
'/targets/:idMethod',
function ({ targets }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
const method = idMethod.split(':')[1];
const target = targets.find(id);
const updatedAttrs = {
version: attrs.version,
};
// If adding host sources, push them into the array
if (method === 'add-host-sources') {
updatedAttrs.hostSetIds = target.hostSetIds;
attrs.hostSourceIds.forEach((id) => {
if (!updatedAttrs.hostSetIds.includes(id)) {
updatedAttrs.hostSetIds.push(id);
}
});
}
// If deleting host sources, filter them out of the array
if (method === 'remove-host-sources') {
updatedAttrs.hostSetIds = target.hostSetIds;
updatedAttrs.hostSetIds = updatedAttrs.hostSetIds.filter((id) => {
return !attrs.hostSourceIds.includes(id);
});
}
// If adding credential sources, push them into the array
if (method === 'add-credential-sources') {
updatedAttrs.credentialLibraryIds = target.credentialLibraryIds;
attrs.applicationCredentialSourceIds.forEach((id) => {
if (!updatedAttrs.credentialLibraryIds.includes(id)) {
updatedAttrs.credentialLibraryIds.push(id);
}
});
}
// If deleting credential sources, filter them out of the array
if (method === 'remove-credential-sources') {
updatedAttrs.credentialLibraryIds = target.credentialLibraryIds;
updatedAttrs.credentialLibraryIds =
updatedAttrs.credentialLibraryIds.filter((id) => {
return !attrs.applicationCredentialSourceIds.includes(id);
});
}
return target.update(updatedAttrs);
}
);
// session
@@ -546,26 +541,6 @@ export default function () {
this.del('/credential-libraries/:id');
this.patch('/credential-libraries/:id');
// credentials
this.get(
'/credentials',
(
{ credentials },
{ queryParams: { credential_store_id: credentialStoreId } }
) => credentials.where({ credentialStoreId })
);
this.get('/credentials/:id');
this.post('/credentials', function ({ credentialStores, credentials }) {
const attrs = this.normalizedRequestAttrs();
const credentialStore = credentialStores.find(attrs.credentialStoreId);
attrs.scopeId = credentialStore.scope.id;
return credentials.create(attrs);
});
this.del('/credentials/:id');
this.patch('/credentials/:id');
// managed-groups
this.get(
'/managed-groups',
@@ -578,28 +553,6 @@ export default function () {
this.patch('/managed-groups/:id');
this.del('/managed-groups/:id');
// worker
this.get(
'/workers',
({ workers }, { queryParams: { scope_id: scopeId } }) => {
return workers.where({ scopeId });
}
);
this.get('/workers/:id');
this.del('/workers/:id');
this.patch('/workers/:id');
this.post('/workers:create:worker-led', ({ workers, scopes }) => {
const globalScope = scopes.find('global');
// This POST only takes in a token so we need to generate a random worker to return
const newWorker = this.create('worker', {
type: 'pki',
scope: globalScope,
});
return workers.create(newWorker.attrs);
});
/* Uncomment the following line and the Response import above
* Then change the response code to simulate error responses.
* this.get('/scopes', () => new Response(505));
+5 -9
View File
@@ -1,7 +1,6 @@
import factory from '../generated/factories/account';
import { faker } from '@faker-js/faker';
import { random, internet } from 'faker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
export default factory.extend({
authorized_actions: () =>
@@ -12,19 +11,16 @@ export default factory.extend({
'delete',
'set-password',
],
id: () => generateId('acct_'),
attributes() {
switch (this.type) {
case 'password':
return { login_name: faker.random.words() };
return { login_name: random.words() };
case 'oidc':
return {
issuer: faker.internet.ip(),
issuer: internet.ip(),
subject: 'sub',
email: faker.internet.email(),
full_name: faker.random.words(),
email: internet.email(),
full_name: random.words(),
};
}
},
+1 -4
View File
@@ -1,7 +1,6 @@
import factory from '../generated/factories/auth-method';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
const types = ['password', 'oidc'];
@@ -12,7 +11,6 @@ export default factory.extend({
'read',
'update',
'delete',
'authenticate',
],
authorized_collection_actions: () => {
return {
@@ -20,8 +18,7 @@ export default factory.extend({
'managed-groups': ['create', 'list'],
};
},
id: () => generateId('am_'),
id: (i) => `auth-method-id-${i}`,
// Cycle through available types
type: (i) => types[i % types.length],
@@ -1,12 +1,11 @@
import factory from '../generated/factories/credential-library';
import { faker } from '@faker-js/faker';
import { random, system } from 'faker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
const types = ['vault'];
export default factory.extend({
id: () => generateId('cl_'),
id: (i) => `credential-library-id-${i}`,
type: (i) => types[i % types.length],
authorized_actions: () =>
@@ -22,8 +21,8 @@ export default factory.extend({
case 'vault':
return {
http_method: 'GET',
http_request_body: faker.random.word(),
path: faker.system.directoryPath(),
http_request_body: random.word(),
path: system.directoryPath(),
};
}
},
+16 -36
View File
@@ -1,10 +1,9 @@
import factory from '../generated/factories/credential-store';
import { faker } from '@faker-js/faker';
import { random, internet, datatype } from 'faker';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
const types = ['vault', 'static'];
const types = ['vault'];
export default factory.extend({
authorized_actions: () =>
@@ -14,56 +13,37 @@ export default factory.extend({
'update',
'delete',
],
authorized_collection_actions() {
switch (this.type) {
case 'vault':
return {
'credential-libraries': ['create', 'list'],
};
case 'static':
default:
return {
credentials: ['create', 'list'],
};
}
authorized_collection_actions: () => {
return {
'credential-libraries': ['create', 'list'],
};
},
id: () => generateId('cs_'),
id: (i) => `credential-store-id-${i}`,
type: (i) => types[i % types.length],
attributes() {
switch (this.type) {
case 'vault':
return {
address: faker.internet.ip(),
namespace: faker.random.word(),
address: internet.ip(),
namespace: random.word(),
ca_cert: 'ca-cert-123',
tls_server_name: faker.internet.domainName(),
tls_skip_verify: faker.datatype.boolean(),
tls_server_name: internet.domainName(),
tls_skip_verify: datatype.boolean(),
token_hmac: 'token-hmac-abcde0987654321',
client_certificate: 'client-cert-123',
client_certificate_key_hmac: 'client-cert-key-hmac-abcde0987654321',
worker_filter: faker.random.words(),
};
}
},
withAssociations: trait({
afterCreate(credentialStore, server) {
const { scope, type } = credentialStore;
switch (type) {
case 'vault':
server.createList('credential-library', 2, {
scope,
credentialStore,
});
break;
case 'static':
default:
server.createList('credential', 3, {
scope,
credentialStore,
});
}
const { scope } = credentialStore;
server.createList('credential-library', 2, {
scope,
credentialStore,
});
},
}),
});
-32
View File
@@ -1,32 +0,0 @@
import factory from '../generated/factories/credential';
import { faker } from '@faker-js/faker';
import permissions from '../helpers/permissions';
import generatedId from '../helpers/id';
const types = ['username_password', 'ssh_private_key', 'json'];
export default factory.extend({
type: (i) => types[i % types.length],
id() {
switch (this.type) {
case 'ssh_private_key':
return generatedId('credspk_');
case 'username_password':
return generatedId('credup_');
case 'json':
return generatedId('credjson_');
}
},
authorized_actions: () =>
permissions.authorizedActionsFor('credential') || [
'no-op',
'read',
'update',
'delete',
],
attributes() {
return {
username: faker.random.word(),
};
},
});
+1 -3
View File
@@ -1,7 +1,6 @@
import factory from '../generated/factories/group';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
export default factory.extend({
authorized_actions: () =>
@@ -13,8 +12,7 @@ export default factory.extend({
'add-members',
'remove-members',
],
id: () => generateId('g_'),
id: (i) => `group-${i}`,
/**
* Generates some users for the same scope and assigns them to the group
+13 -14
View File
@@ -1,8 +1,7 @@
import factory from '../generated/factories/host-catalog';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import { faker } from '@faker-js/faker';
import generateId from '../helpers/id';
import { datatype, address, random } from 'faker';
const types = ['static', 'plugin'];
// Represents known plugin types, except "foobar" which models the possibility
@@ -12,7 +11,7 @@ const pluginTypes = ['aws', 'azure', 'foobar'];
let pluginTypeCounter = 1;
export default factory.extend({
id: () => generateId('hc_'),
id: (i) => `host-catalog-id-${i}`,
// Cycle through available types
type: (i) => types[i % types.length],
@@ -38,7 +37,7 @@ export default factory.extend({
return {
id: `plugin-id-${i}`,
name: pluginTypes[pluginTypeCounter++ % pluginTypes.length],
description: faker.random.words(),
description: random.words(),
};
}
},
@@ -47,15 +46,15 @@ export default factory.extend({
switch (this.plugin?.name) {
case 'aws':
return {
disable_credential_rotation: faker.datatype.boolean(),
region: faker.address.state(),
disable_credential_rotation: datatype.boolean(),
region: address.state(),
};
case 'azure':
return {
disable_credential_rotation: faker.datatype.boolean(),
tenant_id: faker.datatype.hexadecimal(6),
client_id: faker.datatype.hexadecimal(6),
subscription_id: faker.datatype.hexadecimal(8),
disable_credential_rotation: datatype.boolean(),
tenant_id: datatype.hexaDecimal(6),
client_id: datatype.hexaDecimal(6),
subscription_id: datatype.hexaDecimal(8),
};
}
},
@@ -64,13 +63,13 @@ export default factory.extend({
switch (this.plugin?.name) {
case 'aws':
return {
access_key_id: faker.datatype.hexadecimal(8),
secret_access_key: faker.datatype.string(12),
access_key_id: datatype.hexaDecimal(8),
secret_access_key: datatype.string(12),
};
case 'azure':
return {
secret_id: faker.datatype.hexadecimal(6),
secret_value: faker.datatype.string(12),
secret_id: datatype.hexaDecimal(6),
secret_value: datatype.string(12),
};
}
},
+15 -19
View File
@@ -1,10 +1,9 @@
import factory from '../generated/factories/host-set';
import permissions from '../helpers/permissions';
import { faker } from '@faker-js/faker';
import generateId from '../helpers/id';
import { datatype, internet, database } from 'faker';
export default factory.extend({
id: () => generateId('hs_'),
id: (i) => `host-set-id-${i}`,
type() {
return this.hostCatalog?.type || factory.attrs.type;
},
@@ -22,37 +21,34 @@ export default factory.extend({
},
preferred_endpoints() {
if (this.type === 'plugin') {
const enpointsAmount = faker.datatype.number({ min: 1, max: 5 });
const enpointsAmount = datatype.number({ min: 1, max: 5 });
let result = [];
for (let i = 0; i < enpointsAmount; ++i) {
result.push(faker.internet.ip());
result.push(internet.ip());
}
return result;
}
},
sync_interval_seconds() {
if (this.type === 'plugin') {
return faker.datatype.number();
return datatype.number();
}
},
attributes() {
// AWS specific
// AWS specific
filters() {
if (this.plugin?.name === 'aws') {
const filtersAmount = faker.datatype.number({ min: 1, max: 5 });
let filters = [];
const filtersAmount = datatype.number({ min: 1, max: 5 });
let result = [];
for (let i = 0; i < filtersAmount; ++i) {
filters.push(`${faker.datatype.string(3)}=${faker.datatype.string(8)}`);
result.push(`${datatype.string(3)}=${datatype.string(8)}`);
}
return {
filters,
};
return result;
}
// Azure specific
},
// Azure specific
filter() {
if (this.plugin?.name === 'azure') {
return {
filter: `${faker.database.column()}=${faker.database.collation()}`,
};
return `${database.column()}=${database.collation()}`;
}
},
});
+5 -7
View File
@@ -1,11 +1,9 @@
import factory from '../generated/factories/host';
import permissions from '../helpers/permissions';
import { faker } from '@faker-js/faker';
import generateId from '../helpers/id';
import { internet, datatype } from 'faker';
export default factory.extend({
id: () => generateId('h_'),
id: (i) => `host-id-${i}`,
type() {
return this.hostCatalog?.type || factory.attrs.type;
},
@@ -25,12 +23,12 @@ export default factory.extend({
const addressesAmount = Math.floor(Math.random() * 6) + 1;
let result = [];
for (let i = 0; i < addressesAmount; ++i) {
result.push(faker.internet.ip());
result.push(internet.ip());
}
return result;
},
external_id() {
return faker.datatype.uuid();
return datatype.uuid();
},
// Only aws plugins have dns_names
dns_names() {
@@ -38,7 +36,7 @@ export default factory.extend({
const dnsNamesAmount = Math.floor(Math.random() * 6) + 1;
let result = [];
for (let i = 0; i < dnsNamesAmount; i++) {
result.push(faker.internet.domainName());
result.push(internet.domainName());
}
return result;
}
+3 -5
View File
@@ -1,11 +1,11 @@
import factory from '../generated/factories/managed-group';
import { faker } from '@faker-js/faker';
import { random } from 'faker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
const types = ['oidc'];
export default factory.extend({
id: (i) => `managed-groups-id-${i}`,
type: (i) => types[i % types.length],
authorized_actions: () =>
@@ -16,13 +16,11 @@ export default factory.extend({
'delete',
],
id: () => generateId('mg_'),
attributes() {
switch (this.type) {
case 'oidc':
return {
filter: faker.random.words(),
filter: random.words(),
};
}
},
-3
View File
@@ -1,7 +1,6 @@
import factory from '../generated/factories/role';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
export default factory.extend({
authorized_actions: () =>
@@ -20,8 +19,6 @@ export default factory.extend({
'id=*;type=host-catalog;actions=create,read',
],
id: () => generateId('r_'),
/**
* Adds principals to the role.
*/
+11 -11
View File
@@ -1,7 +1,6 @@
import factory from '../generated/factories/scope';
import { trait } from 'ember-cli-mirage';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
export default factory.extend({
type: 'global',
@@ -14,8 +13,8 @@ export default factory.extend({
'delete',
],
authorized_collection_actions() {
const collectionActions = {
authorized_collection_actions: () => {
return {
scopes: ['create', 'list'],
users: ['create', 'list'],
sessions: ['list'],
@@ -26,16 +25,17 @@ export default factory.extend({
'auth-methods': ['create', 'list'],
'host-catalogs': ['create', 'list'],
};
// Worker permissions only available on the global scope
if (this.type === 'global') {
collectionActions.workers = ['create:worker-led', 'list'];
}
return collectionActions;
},
id: () => generateId('s_'),
/**
* Generates realistic-ish IDs while still being deterministic.
*/
id(i) {
let id = 'tv6fv3yz8p';
if (i % 3) id = 'xk1i4dk50v';
if (i % 2) id = 's9n819zgko';
return `s_${id}${i}`;
},
withChildren: trait({
afterCreate(record, server) {
+3 -6
View File
@@ -1,16 +1,13 @@
import factory from '../generated/factories/session';
import { trait } from 'ember-cli-mirage';
import { faker } from '@faker-js/faker';
import generateId from '../helpers/id';
import { random } from 'faker';
const statusStrings = ['pending', 'active', 'canceling', 'terminated'];
export const pickRandomStatusString = () =>
faker.helpers.arrayElement(statusStrings);
export const pickRandomStatusString = () => random.arrayElement(statusStrings);
export default factory.extend({
id: () => generateId('ss_'),
id: (i) => `session-id-${i}`,
status: pickRandomStatusString,
/**
+7 -25
View File
@@ -1,8 +1,7 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
import { faker } from '@faker-js/faker';
import { random, datatype } from 'faker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
const randomBoolean = (chance = 0.5) => Math.random() < chance;
const hostSetChance = 0.3;
@@ -22,14 +21,10 @@ export default factory.extend({
'remove-credential-sources',
],
id: () => generateId('t_'),
/**
* -1 means "unlimited" and we want to generate these on occasion.
*/
session_connection_limit: () =>
faker.helpers.arrayElement([-1, faker.datatype.number()]),
worker_filter: (i) => (i % 2 === 0 ? faker.random.words() : null),
session_connection_limit: () => random.arrayElement([-1, datatype.number()]),
type: (i) => types[i % types.length],
/**
* Generates attributes fields by type.
@@ -38,7 +33,7 @@ export default factory.extend({
if (target.type === 'tcp' || target.type === 'ssh') {
target.update({
attributes: {
default_port: faker.datatype.number(),
default_port: datatype.number(),
},
});
}
@@ -49,9 +44,7 @@ export default factory.extend({
*/
withAssociations: trait({
afterCreate(target, server) {
let randomlySelectedHostSets,
randomlySelectedCredentialLibraries,
randomlySelectedCredentials;
let randomlySelectedHostSets, randomlySelectedCredentialLibraries;
randomlySelectedHostSets = server.schema.hostSets
// BLERG: fun fact, for no reason at all, the element passed
// into a where function is not a full model instance, as you might
@@ -65,22 +58,11 @@ export default factory.extend({
.where(
(credentialLibrary) => credentialLibrary.scopeId === target.scope.id
)
.models.filter(() => randomBoolean())
.map((cred) => cred.id);
randomlySelectedCredentials = server.schema.credentials
.where((credential) => credential.scopeId === target.scope.id)
.models.filter(() => randomBoolean())
.map((cred) => cred.id);
.models.filter(() => randomBoolean());
target.update({
hostSets: randomlySelectedHostSets,
brokeredCredentialSourceIds: [
...randomlySelectedCredentialLibraries,
...randomlySelectedCredentials,
],
injectedApplicationCredentialSourceIds: [
...randomlySelectedCredentialLibraries,
...randomlySelectedCredentials,
],
credentialLibraries: randomlySelectedCredentialLibraries,
});
},
}),
+1 -2
View File
@@ -1,6 +1,5 @@
import factory from '../generated/factories/user';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
export default factory.extend({
authorized_actions: () =>
@@ -12,5 +11,5 @@ export default factory.extend({
'add-accounts',
'remove-accounts',
],
id: () => generateId('u_'),
id: (i) => `user-${i}`,
});
-44
View File
@@ -1,44 +0,0 @@
import factory from '../generated/factories/worker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
import { faker } from '@faker-js/faker';
const types = ['pki', 'kms'];
export default factory.extend({
id: () => generateId('w_'),
authorized_actions: () =>
permissions.authorizedActionsFor('worker') || [
'no-op',
'read',
'update',
'delete',
],
type: (i) => types[i % types.length],
canonical_tags: (i) => {
if (i % 3 === 0) {
return { os: ['ubuntu'] };
}
if (i % 2 === 0) {
return {
type: [faker.random.word(), faker.random.word()],
os: ['ubuntu'],
env: ['dev', 'local'],
};
}
return undefined;
},
config_tags: (i) => {
if (i % 3 === 0) {
return { os: ['ubuntu'] };
}
if (i % 2 === 0) {
return {
type: [faker.random.word(), faker.random.word()],
os: ['ubuntu'],
env: ['dev', 'local'],
};
}
return undefined;
},
});
Loaded 100 of 901 files, more files were not shown because too many files have changed in this diff. Show more