mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Drop CloudAuth client files
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import flushPromises from "flush-promises";
|
||||
import { getLocalVue } from "tests/jest/helpers";
|
||||
|
||||
import { default as CloudAuth } from "./CloudAuth";
|
||||
import CloudAuthItem from "./CloudAuthItem";
|
||||
|
||||
jest.mock("./model/service", () => ({
|
||||
listCredentials: async () => {
|
||||
const listCredentials = require("./testdata/listCredentials.json");
|
||||
const Credential = require("./model").Credential;
|
||||
return listCredentials.map(Credential.create);
|
||||
},
|
||||
}));
|
||||
|
||||
const localVue = getLocalVue();
|
||||
|
||||
describe("CloudAuth component", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
wrapper = shallowMount(CloudAuth, { localVue });
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
describe("initialization", () => {
|
||||
it("should render the initial list", () => {
|
||||
expect(wrapper).toBeTruthy();
|
||||
expect(wrapper.findComponent(CloudAuthItem).exists()).toBeTruthy();
|
||||
expect(wrapper.vm.items.length == 2).toBeTruthy();
|
||||
expect(wrapper.vm.filteredItems.length == 2).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("text filter", () => {
|
||||
it("should show filter result by text match", () => {
|
||||
let results;
|
||||
|
||||
wrapper.vm.filter = "aws";
|
||||
results = wrapper.vm.filteredItems;
|
||||
expect(wrapper.findComponent(CloudAuthItem).exists()).toBeTruthy();
|
||||
expect(results.length == 1).toBeTruthy();
|
||||
|
||||
wrapper.vm.filter = "azure";
|
||||
results = wrapper.vm.filteredItems;
|
||||
expect(results.length == 1).toBeTruthy();
|
||||
|
||||
wrapper.vm.filter = "";
|
||||
results = wrapper.vm.filteredItems;
|
||||
expect(results.length == 2).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("create button", () => {
|
||||
it("clicking create button should add a blank key", () => {
|
||||
let results = wrapper.vm.filteredItems;
|
||||
expect(wrapper.findComponent(CloudAuthItem).exists()).toBeTruthy();
|
||||
expect(results.length == 2).toBeTruthy();
|
||||
|
||||
const button = wrapper.find("button[name=createNewKey]");
|
||||
expect(button).toBeTruthy();
|
||||
button.trigger("click");
|
||||
|
||||
results = wrapper.vm.filteredItems;
|
||||
expect(results.length == 3).toBeTruthy();
|
||||
|
||||
const blank = results.find((i) => i.id == null);
|
||||
expect(blank).toBeTruthy();
|
||||
expect(blank.id == null).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,372 +0,0 @@
|
||||
<template>
|
||||
<section class="cloud-auth">
|
||||
<header>
|
||||
<b-alert dismissible fade variant="warning" :show="errorMessage !== null" @dismissed="errorMessage = null">
|
||||
{{ errorMessage }}
|
||||
</b-alert>
|
||||
|
||||
<hgroup class="cloud-auth-title">
|
||||
<h1 v-localize class="h-lg">Manage Cloud Authorization</h1>
|
||||
<nav class="operations">
|
||||
<ul>
|
||||
<li class="cloudKeyHelp">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
:class="{ active: showHelp }"
|
||||
aria-label="Instructions"
|
||||
title="Instructions"
|
||||
@click.prevent="showHelp = !showHelp">
|
||||
<span>Instructions</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="cloudKeyFilter">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
:class="{ active: showFilter }"
|
||||
aria-label="Filter Results"
|
||||
title="Filter Results"
|
||||
@click.prevent="showFilter = !showFilter">
|
||||
<span>Filter</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="createCloudKey">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
aria-label="Create New Key"
|
||||
title="Create New Key"
|
||||
@click.prevent="onCreate">
|
||||
<span>Create New Key</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</hgroup>
|
||||
|
||||
<transition name="fade">
|
||||
<hgroup v-if="showHelp">
|
||||
<p>See the online <a href="https://galaxyproject.org/authnz/">Documentation</a>.</p>
|
||||
</hgroup>
|
||||
</transition>
|
||||
|
||||
<transition name="fade">
|
||||
<hgroup v-if="showFilter">
|
||||
<b-form-group :description="filterDescription">
|
||||
<b-form-input v-model="filter" type="text" placeholder="Filter" />
|
||||
</b-form-group>
|
||||
</hgroup>
|
||||
</transition>
|
||||
|
||||
<b-button-group class="mb-4">
|
||||
<button name="createNewKey" @click="onCreate">Create New Authorization Key</button>
|
||||
</b-button-group>
|
||||
</header>
|
||||
|
||||
<div class="scroll-container">
|
||||
<b-list-group>
|
||||
<transition-group name="fade">
|
||||
<CloudAuthItem
|
||||
v-for="credential in filteredItems"
|
||||
:key="credential.counter"
|
||||
:credential="credential"
|
||||
class="mb-1"
|
||||
@delete="onDelete"
|
||||
@save="onSave"
|
||||
@expand="expand(credential, $event)" />
|
||||
</transition-group>
|
||||
</b-list-group>
|
||||
</div>
|
||||
|
||||
<b-modal
|
||||
id="deleteCredentialModal"
|
||||
ref="deleteModal"
|
||||
v-model="hasDoomed"
|
||||
centered
|
||||
title="Delete Key?"
|
||||
size="sm"
|
||||
@ok="deleteKey"
|
||||
@cancel="doomedItem = null">
|
||||
</b-modal>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import Vue from "vue";
|
||||
|
||||
import CloudAuthItem from "./CloudAuthItem";
|
||||
import { Credential } from "./model";
|
||||
import svc from "./model/service";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CloudAuthItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
filter: "",
|
||||
showHelp: true,
|
||||
showFilter: false,
|
||||
loading: false,
|
||||
doomedItem: null,
|
||||
errorMessage: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredItems() {
|
||||
return this.items.filter((o) => o.match(this.filter));
|
||||
},
|
||||
filterDescription() {
|
||||
return `${this.filteredItems.length} matches out of ${this.items.length} items`;
|
||||
},
|
||||
deleteButtonVariant() {
|
||||
return this.showDeleted ? "primary" : "secondary";
|
||||
},
|
||||
hasDoomed: {
|
||||
get() {
|
||||
return this.doomedItem !== null;
|
||||
},
|
||||
// This setter is here because vue-bootstrap modal
|
||||
// tries to set this property for unfathomable reasons
|
||||
set() {},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showDeleted(deleted) {
|
||||
this.loadCredentials({ deleted });
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadCredentials();
|
||||
},
|
||||
methods: {
|
||||
loadCredentials(params = {}) {
|
||||
this.loading = true;
|
||||
svc.listCredentials(params)
|
||||
.then((items) => (this.items = items))
|
||||
.catch(this.setError("Unable to load cloud keys."))
|
||||
.finally(() => (this.loading = false));
|
||||
},
|
||||
onCreate() {
|
||||
const newItem = Credential.create({ expanded: true });
|
||||
this.addItem(newItem);
|
||||
},
|
||||
onSave(item) {
|
||||
if (!item.valid) {
|
||||
return;
|
||||
}
|
||||
item.loading = true;
|
||||
svc.saveCredential(item)
|
||||
.then((result) => {
|
||||
item.id = result.id;
|
||||
item.updateState();
|
||||
})
|
||||
.catch(this.setError("Unable to save key."))
|
||||
.finally(() => (item.loading = false));
|
||||
},
|
||||
onDelete(doomed) {
|
||||
this.doomedItem = doomed;
|
||||
if (doomed.id) {
|
||||
this.$refs.deleteModal.show();
|
||||
} else {
|
||||
this.removeItem(doomed);
|
||||
this.doomedItem = null;
|
||||
}
|
||||
},
|
||||
deleteKey() {
|
||||
// Called when the modal is closed with an "OK"
|
||||
svc.deleteCredential(this.doomedItem)
|
||||
.then(() => this.removeItem(this.doomedItem))
|
||||
.catch(this.setError("Unable to delete cloud key."))
|
||||
.finally(() => {
|
||||
this.doomedItem = null;
|
||||
});
|
||||
},
|
||||
expand(credential, { expanded }) {
|
||||
credential.expanded = expanded;
|
||||
if (expanded) {
|
||||
this.items.filter((i) => i !== credential).forEach((i) => (i.expanded = false));
|
||||
}
|
||||
},
|
||||
addItem(item) {
|
||||
this.items = [...this.items, item];
|
||||
},
|
||||
removeItem(item) {
|
||||
this.items = this.items.filter((o) => o !== item);
|
||||
},
|
||||
setError(msg) {
|
||||
return (err) => {
|
||||
this.errorMessage = msg;
|
||||
console.warn(err);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~bootstrap/scss/functions";
|
||||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/mixins";
|
||||
@import "~bootstrap/scss/utilities/spacing";
|
||||
|
||||
@import "scss/theme/blue.scss";
|
||||
@import "scss/mixins";
|
||||
|
||||
// TODO: build reusable icon menu? Maybe make into a component?
|
||||
// The existing one has lots of reusability problems
|
||||
|
||||
.operations {
|
||||
margin-bottom: 0;
|
||||
|
||||
ul {
|
||||
@include list_reset();
|
||||
display: flex;
|
||||
li:not(:first-child) {
|
||||
@extend .ml-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// General Layout
|
||||
|
||||
.cloud-auth {
|
||||
// header sticks, bottom part scrolls
|
||||
@include scrollingListLayout("header", ".scroll-container");
|
||||
|
||||
// title left, icons right
|
||||
.cloud-auth-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
// top icon menu
|
||||
.operations {
|
||||
li {
|
||||
a,
|
||||
a::before {
|
||||
font-size: 1rem;
|
||||
color: $gray-400;
|
||||
}
|
||||
a.active::before,
|
||||
a:hover::before {
|
||||
color: $brand-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.cloudKeyHelp a {
|
||||
@include fontawesome($fa-var-info-circle);
|
||||
}
|
||||
.cloudKeyFilter a {
|
||||
@include fontawesome($fa-var-filter);
|
||||
}
|
||||
.createCloudKey a {
|
||||
@include fontawesome($fa-var-plus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single list item
|
||||
|
||||
.cloud-auth-key {
|
||||
header hgroup {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
> * {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@extend .my-3;
|
||||
@extend .pt-3;
|
||||
|
||||
// removes wierd double arrows on select
|
||||
.custom-select {
|
||||
background: none;
|
||||
}
|
||||
|
||||
// Undo butchering from base.css so side-by-side labels work
|
||||
.form-row {
|
||||
display: flex;
|
||||
input,
|
||||
select {
|
||||
max-width: none;
|
||||
}
|
||||
label {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
// button list at bottom of form
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
@extend .pt-3;
|
||||
|
||||
button:not(:first-child) {
|
||||
@extend .ml-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// icon menu
|
||||
|
||||
.operations {
|
||||
.save a {
|
||||
@include fontawesome($fa-var-save);
|
||||
}
|
||||
.delete a {
|
||||
@include fontawesome($fa-var-times);
|
||||
}
|
||||
.details a {
|
||||
@include fontawesome($fa-var-window-minimize);
|
||||
&:hover {
|
||||
@include fontawesome($fa-var-window-maximize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reverse icons when key is expanded
|
||||
|
||||
&.expanded .details a {
|
||||
@include fontawesome($fa-var-window-maximize);
|
||||
&:hover {
|
||||
@include fontawesome($fa-var-window-minimize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transitions
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// Delete modal
|
||||
|
||||
#deleteCredentialModal {
|
||||
.modal-body {
|
||||
display: none;
|
||||
}
|
||||
.modal-dialog {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<b-list-group-item class="cloud-auth-key" :class="statusClasses" :variant="variant" :button="!expanded">
|
||||
<header>
|
||||
<hgroup>
|
||||
<h2 class="h-sm" @click.prevent="expand()">{{ credential.title }}</h2>
|
||||
<nav class="operations">
|
||||
<ul>
|
||||
<transition name="fade">
|
||||
<li v-if="expanded && credential.dirty" class="save">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
aria-label="Save Key"
|
||||
title="Save Key"
|
||||
@click.prevent="$emit('save', credential)">
|
||||
<span>Save Key</span>
|
||||
</a>
|
||||
</li>
|
||||
</transition>
|
||||
<li class="delete">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
aria-label="Delete Key"
|
||||
title="Delete Key"
|
||||
@click.prevent="$emit('delete', credential)">
|
||||
<span>Delete Key</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="details">
|
||||
<a
|
||||
v-b-tooltip.hover
|
||||
aria-label="Show Details"
|
||||
title="Show Details"
|
||||
@click.prevent="expand()">
|
||||
<span>Details</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</hgroup>
|
||||
</header>
|
||||
<!-- TODO: Restructure credential handling so we're not mutating the prop -->
|
||||
<!-- eslint-disable vue/no-mutating-props-->
|
||||
<CredentialForm
|
||||
v-if="expanded"
|
||||
v-model="credential"
|
||||
class="border-top"
|
||||
@click.self="expand()"
|
||||
@save="$emit('save', credential)"
|
||||
@delete="$emit('delete', credential)" />
|
||||
<!-- eslint-enable vue/no-mutating-props-->
|
||||
</b-list-group-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CredentialForm from "./CredentialForm";
|
||||
import { Credential } from "./model";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CredentialForm,
|
||||
},
|
||||
props: {
|
||||
credential: { type: Credential, required: true },
|
||||
},
|
||||
computed: {
|
||||
statusClasses() {
|
||||
const { expanded, valid, dirty, loading } = this.credential;
|
||||
return { loading, expanded, valid, dirty, collapsed: !expanded };
|
||||
},
|
||||
variant() {
|
||||
if (this.expanded) {
|
||||
return "";
|
||||
}
|
||||
if (this.credential.dirty) {
|
||||
return "warning";
|
||||
}
|
||||
if (!this.credential.valid) {
|
||||
return "danger";
|
||||
}
|
||||
return "primary";
|
||||
},
|
||||
expanded() {
|
||||
return this.credential.expanded;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
expand(forceState) {
|
||||
const expanded = forceState !== undefined ? forceState : !this.expanded;
|
||||
this.$emit("expand", { expanded });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group
|
||||
v-for="(field, fieldName) in fieldDefs"
|
||||
:key="fieldName"
|
||||
:label="field.label || fieldName"
|
||||
:label-for="'config-' + fieldName"
|
||||
label-cols-lg="3"
|
||||
:state="config.fieldValid(fieldName)"
|
||||
:invalid-feedback="config.errorMessage(fieldName)">
|
||||
<MaskedInput
|
||||
:id="'config-' + fieldName"
|
||||
v-model="config[fieldName]"
|
||||
:mask="field.mask"
|
||||
:placeholder="field.placeholder"
|
||||
:maxlength="field.maxlength || Math.Infinity"
|
||||
:state="config.fieldValid(fieldName)"
|
||||
trim />
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MaskedInput from "components/MaskedInput";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MaskedInput,
|
||||
},
|
||||
props: {
|
||||
value: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
config() {
|
||||
return this.value;
|
||||
},
|
||||
fieldDefs() {
|
||||
return this.value.constructor.fields;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<b-form :validated="credential.isValid">
|
||||
<!-- user label -->
|
||||
<b-form-group label="Description" label-for="credentialDescription" label-cols-lg="3">
|
||||
<b-form-input
|
||||
id="credentialDescription"
|
||||
v-model="credential.description"
|
||||
:state="credential.fieldValid('description')"
|
||||
placeholder="Description (optional)"
|
||||
trim />
|
||||
</b-form-group>
|
||||
|
||||
<!-- google-openidconnect -->
|
||||
<b-form-group
|
||||
v-if="identityProviders.length > 1"
|
||||
label="Identity Provider"
|
||||
label-for="identityProvider"
|
||||
label-cols-lg="3">
|
||||
<b-form-select
|
||||
id="identityProvider"
|
||||
v-model="credential.authn_id"
|
||||
:state="credential.fieldValid('authn_id')"
|
||||
:options="identityProviders" />
|
||||
</b-form-group>
|
||||
|
||||
<!-- aws/azure, etc -->
|
||||
<b-form-group label="Resource Provider" label-for="resourceProvider" label-cols-lg="3">
|
||||
<b-form-select
|
||||
id="resourceProvider"
|
||||
v-model="credential.resourceProvider"
|
||||
:state="credential.fieldValid('provider')"
|
||||
:options="resourceProviderOptions" />
|
||||
</b-form-group>
|
||||
|
||||
<CredentialConfig v-model="credential.config" />
|
||||
|
||||
<footer class="border-top">
|
||||
<b-button variant="secondary" @click.prevent="$emit('delete', credential)"> Delete Key </b-button>
|
||||
<b-button
|
||||
aria-label="Save Key"
|
||||
:variant="saveButtonVariant"
|
||||
:disabled="saveButtonDisabled"
|
||||
@click.prevent="$emit('save', credential)">
|
||||
{{ saveButtonTitle }}
|
||||
</b-button>
|
||||
</footer>
|
||||
</b-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CredentialConfig from "./CredentialConfig";
|
||||
import { Credential, ResourceProviders } from "./model";
|
||||
import { getIdentityProviders } from "./model/service";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CredentialConfig,
|
||||
},
|
||||
props: {
|
||||
value: { type: Credential, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
identityProviders: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
credential() {
|
||||
return this.value;
|
||||
},
|
||||
// transformed for bootstrap component
|
||||
resourceProviderOptions() {
|
||||
return Array.from(ResourceProviders.entries()).map(([value, o]) => ({ value, text: o.label }));
|
||||
},
|
||||
config() {
|
||||
return this.credential.config;
|
||||
},
|
||||
loading() {
|
||||
return this.credential.loading;
|
||||
},
|
||||
saveButtonDisabled() {
|
||||
return !(this.credential.valid && this.credential.dirty);
|
||||
},
|
||||
saveButtonVariant() {
|
||||
return this.saveButtonDisabled ? "secondary" : "primary";
|
||||
},
|
||||
saveButtonTitle() {
|
||||
return this.loading ? "Saving Key..." : "Save Key";
|
||||
},
|
||||
},
|
||||
created() {
|
||||
getIdentityProviders().then((result) => {
|
||||
if (!this.credential.authn_id && result.length == 1) {
|
||||
this.credential.authn_id = result[0].authn_id;
|
||||
this.credential.updateState();
|
||||
}
|
||||
this.identityProviders = result;
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1 +0,0 @@
|
||||
export { default as CloudAuth } from "./CloudAuth.vue";
|
||||
@@ -1,28 +0,0 @@
|
||||
import { safeAssign } from "utils/safeAssign";
|
||||
|
||||
import { BaseModel } from "./BaseModel";
|
||||
|
||||
export class AwsConfig extends BaseModel {
|
||||
constructor(props = {}) {
|
||||
super();
|
||||
this.role_arn = "";
|
||||
safeAssign(this, props);
|
||||
this.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
AwsConfig.setValidator(function (model) {
|
||||
const errors = {};
|
||||
if (!model.role_arn.length) {
|
||||
errors.role_arn = "Missing role_arn";
|
||||
}
|
||||
return errors;
|
||||
});
|
||||
|
||||
AwsConfig.fields = {
|
||||
role_arn: {
|
||||
label: "Role ARN",
|
||||
description: "The Amazon resource name (ARN) of the role to be assumed by Galaxy.",
|
||||
placeholder: "arn:aws:iam::XXXXXXXXXXXX:role/XXXXXXXXXXX",
|
||||
},
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import { AwsConfig } from "./AwsConfig";
|
||||
|
||||
describe("AwsConfig", () => {
|
||||
it("should instantiate", () => {
|
||||
const instance = new AwsConfig();
|
||||
expect(instance).toBeTruthy();
|
||||
expect(!instance.dirty).toBeTruthy();
|
||||
expect(!instance.valid).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should validate role_arn", () => {
|
||||
const instance = new AwsConfig();
|
||||
instance.role_arn = "abc";
|
||||
expect(instance.fieldValid("role_arn")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should invalidate role_arn", () => {
|
||||
const instance = new AwsConfig();
|
||||
instance.role_arn = "";
|
||||
expect(!instance.fieldValid("role_arn")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
import { safeAssign } from "utils/safeAssign";
|
||||
|
||||
import { BaseModel } from "./BaseModel";
|
||||
|
||||
export class AzureConfig extends BaseModel {
|
||||
constructor(props = {}) {
|
||||
super();
|
||||
this.tenant_id = "";
|
||||
this.client_id = "";
|
||||
this.client_secret = "";
|
||||
safeAssign(this, props);
|
||||
this.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
AzureConfig.setValidator(function (model) {
|
||||
const errors = {};
|
||||
|
||||
if (model.tenant_id.length < 36) {
|
||||
errors.tenant_id = "Tenant ID too short";
|
||||
}
|
||||
if (!model.tenant_id) {
|
||||
errors.tenant_id = "Missing Tenant ID";
|
||||
}
|
||||
|
||||
if (model.client_id.length < 36) {
|
||||
errors.client_id = "Client ID too short";
|
||||
}
|
||||
if (!model.client_id) {
|
||||
errors.client_id = "Missing client ID";
|
||||
}
|
||||
|
||||
if (!model.client_secret) {
|
||||
errors.client_secret = "Missing secret";
|
||||
}
|
||||
|
||||
return errors;
|
||||
});
|
||||
|
||||
AzureConfig.fields = {
|
||||
tenant_id: {
|
||||
label: "Tenant ID",
|
||||
mask: "********-****-****-****-************",
|
||||
placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
maxlength: 36,
|
||||
description: "Your Tenant ID (or Directory ID) on Azure.",
|
||||
},
|
||||
client_id: {
|
||||
label: "Client ID",
|
||||
mask: "********-****-****-****-************",
|
||||
placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
maxlength: 36,
|
||||
description: "The Client ID (or Application ID) you defined for Galaxy on your Azure directory.",
|
||||
},
|
||||
client_secret: {
|
||||
label: "Client Secret",
|
||||
mask: "",
|
||||
placeholder: "Client Secret",
|
||||
description:
|
||||
"A secret string you obtained from Azure portal that Galaxy can use to prove its identity when requesting tokens to access your resources.",
|
||||
},
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
import { AzureConfig } from "./AzureConfig";
|
||||
|
||||
describe("AzureConfig", () => {
|
||||
it("should instantiate", () => {
|
||||
const instance = new AzureConfig();
|
||||
expect(instance).toBeTruthy();
|
||||
expect(!instance.dirty).toBeTruthy();
|
||||
expect(!instance.valid).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("client_secret", () => {
|
||||
it("should validate client_secret", () => {
|
||||
const instance = new AzureConfig();
|
||||
instance.client_secret = "abc";
|
||||
expect(instance.fieldValid("client_secret")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should invalidate client_secret", () => {
|
||||
const instance = new AzureConfig();
|
||||
instance.client_secret = "";
|
||||
expect(!instance.fieldValid("client_secret")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("tenant_id", () => {
|
||||
it("should validate tenant_id", () => {
|
||||
const validTenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||
const instance = new AzureConfig();
|
||||
instance.tenant_id = validTenantId;
|
||||
expect(instance.fieldValid("tenant_id")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should invalidate tenant_id", () => {
|
||||
const instance = new AzureConfig();
|
||||
instance.tenant_id = "";
|
||||
expect(!instance.fieldValid("tenant_id")).toBeTruthy();
|
||||
instance.tenant_id = "asdfa";
|
||||
expect(!instance.fieldValid("tenant_id")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("client_id", () => {
|
||||
it("should validate client_id", () => {
|
||||
const validClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||
const instance = new AzureConfig();
|
||||
instance.client_id = validClientId;
|
||||
expect(instance.fieldValid("client_id")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should invalidate client_id", () => {
|
||||
const instance = new AzureConfig();
|
||||
instance.client_id = "";
|
||||
expect(!instance.fieldValid("client_id")).toBeTruthy();
|
||||
instance.client_id = "asdf";
|
||||
expect(!instance.fieldValid("client_id")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
const lastState = new WeakMap();
|
||||
const validators = new WeakMap();
|
||||
const transients = new WeakMap();
|
||||
const counter = new WeakMap();
|
||||
|
||||
// Helps with Vue for-loop keys
|
||||
let instanceCounter = 0;
|
||||
|
||||
export class BaseModel {
|
||||
constructor() {
|
||||
counter.set(this, instanceCounter++);
|
||||
}
|
||||
|
||||
/* object ID */
|
||||
|
||||
get counter() {
|
||||
return counter.get(this);
|
||||
}
|
||||
|
||||
/* Dirty state tracking */
|
||||
|
||||
get dirty() {
|
||||
return lastState.get(this) != this.state;
|
||||
}
|
||||
|
||||
get clean() {
|
||||
return !this.dirty;
|
||||
}
|
||||
|
||||
get state() {
|
||||
// build state JSON ignoring transient fields
|
||||
const tFields = this.transientFields;
|
||||
return JSON.stringify(this, function (key) {
|
||||
if (!tFields.has(key)) {
|
||||
return this[key];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
get lastState() {
|
||||
return lastState.get(this);
|
||||
}
|
||||
|
||||
get transientFields() {
|
||||
const key = this.constructor;
|
||||
if (!transients.has(key)) {
|
||||
transients.set(key, new Set());
|
||||
}
|
||||
return transients.get(key);
|
||||
}
|
||||
|
||||
updateState() {
|
||||
lastState.set(this, this.state);
|
||||
}
|
||||
|
||||
// Setting a property name as transient for a class means its
|
||||
// state will be caclulated without regard to that propety
|
||||
static setTransient(...fieldNames) {
|
||||
const klass = this; // this will be a class
|
||||
if (!transients.has(klass)) {
|
||||
transients.set(klass, new Set());
|
||||
}
|
||||
|
||||
const fields = transients.get(klass);
|
||||
fieldNames.forEach((fieldName) => fields.add(fieldName));
|
||||
transients.set(klass, fields);
|
||||
}
|
||||
|
||||
/* Validation */
|
||||
|
||||
get valid() {
|
||||
return Object.keys(this.validationErrors).length == 0;
|
||||
}
|
||||
|
||||
get validationErrors() {
|
||||
return this.constructor.validate(this);
|
||||
}
|
||||
|
||||
errorMessage(field) {
|
||||
if (field in this.validationErrors) {
|
||||
return this.validationErrors[field];
|
||||
}
|
||||
return "errmessage";
|
||||
}
|
||||
|
||||
fieldValid(fieldName) {
|
||||
return !(fieldName in this.validationErrors);
|
||||
}
|
||||
|
||||
static validate(model) {
|
||||
if (validators.has(this)) {
|
||||
const validator = validators.get(this);
|
||||
return validator(model);
|
||||
}
|
||||
throw new Error("Missing validator");
|
||||
}
|
||||
|
||||
static setValidator(validationFunction) {
|
||||
validators.set(this, validationFunction);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { safeAssign } from "utils/safeAssign";
|
||||
|
||||
import { BaseModel } from "./BaseModel";
|
||||
import { ResourceProviders } from "./ResourceProviders";
|
||||
|
||||
export class Credential extends BaseModel {
|
||||
constructor(props = {}) {
|
||||
super();
|
||||
|
||||
this.id = null;
|
||||
this.description = "";
|
||||
this.authn_id = null; // identity provider
|
||||
this.provider = null; // resource provider
|
||||
|
||||
// transient props, exclude from state
|
||||
this.expanded = false;
|
||||
this.loading = false;
|
||||
|
||||
// populate props
|
||||
const options = Object.assign({}, Credential.defaults, props);
|
||||
safeAssign(this, options);
|
||||
|
||||
// init nested config
|
||||
this.config = new this.configClass(options.config);
|
||||
|
||||
// initialize state
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.description.length ? this.description : this.provider;
|
||||
}
|
||||
|
||||
get valid() {
|
||||
return super.valid && this.config.valid;
|
||||
}
|
||||
|
||||
// Alias for provider also changes config object when updated
|
||||
// Set this when updating in the UI
|
||||
|
||||
get resourceProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
set resourceProvider(newProvider) {
|
||||
this.provider = newProvider;
|
||||
this.config = new this.configClass({});
|
||||
}
|
||||
|
||||
// Polymorphic config class
|
||||
|
||||
get configClass() {
|
||||
return ResourceProviders.get(this.provider).klass;
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
match(searchText = "") {
|
||||
// TODO: more robust object matching?
|
||||
return searchText.length ? this.title.includes(searchText) : true;
|
||||
}
|
||||
|
||||
// Statics
|
||||
|
||||
static get defaults() {
|
||||
return {
|
||||
authn_id: null,
|
||||
provider: "aws",
|
||||
expanded: false,
|
||||
};
|
||||
}
|
||||
|
||||
static create(props = {}) {
|
||||
return new Credential(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transient fields do not factor into the state
|
||||
* for purposes of dirty tracking
|
||||
*/
|
||||
Credential.setTransient("expanded", "loading");
|
||||
|
||||
/**
|
||||
* A validator function's job is to return an object
|
||||
* where the keys are field names and the values are
|
||||
* error messages.
|
||||
*/
|
||||
Credential.setValidator(function (model) {
|
||||
const errors = {};
|
||||
|
||||
if (!model.provider) {
|
||||
errors.provider = "Provider must be set";
|
||||
}
|
||||
|
||||
if (!model.authn_id) {
|
||||
errors.authn_id = "Please pick an identity provider";
|
||||
}
|
||||
|
||||
if (!model.config.valid) {
|
||||
errors.config = "Invalid config object";
|
||||
}
|
||||
|
||||
return errors;
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import { Credential } from "./Credential";
|
||||
|
||||
describe("Credential model", () => {
|
||||
describe("basic model props", () => {
|
||||
it("should exist", () => {
|
||||
expect(Credential).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should build with defaults", () => {
|
||||
const instance = new Credential();
|
||||
expect(instance).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should build with props", () => {
|
||||
const description = "i am the test description";
|
||||
const props = { description };
|
||||
const instance = new Credential(props);
|
||||
expect(instance).toBeTruthy();
|
||||
expect(instance.description == description).toBeTruthy();
|
||||
});
|
||||
|
||||
it("default config object should be AWS", () => {
|
||||
const instance = new Credential();
|
||||
expect("role_arn" in instance.config).toBeTruthy();
|
||||
expect(instance.config.constructor.name == "AwsConfig").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should load a different config object given the right provider", () => {
|
||||
const provider = "azure";
|
||||
const props = { provider };
|
||||
const instance = new Credential(props);
|
||||
expect(instance.provider == provider).toBeTruthy();
|
||||
expect(instance.config.constructor.name == "AzureConfig").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should dynamically switch config objects as resourceProvider is changed", () => {
|
||||
const instance = new Credential();
|
||||
expect(instance.provider == "aws").toBeTruthy();
|
||||
instance.resourceProvider = "azure";
|
||||
expect(instance.config.constructor.name == "AzureConfig").toBeTruthy();
|
||||
instance.resourceProvider = "aws";
|
||||
expect(instance.config.constructor.name == "AwsConfig").toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("dirty state", () => {
|
||||
it("should flag as dirty when a prop is changed", () => {
|
||||
const instance = new Credential();
|
||||
instance.description = "foo";
|
||||
expect(instance.dirty).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should flag as clean when a prop is restored", () => {
|
||||
const instance = new Credential();
|
||||
instance.description = "foo";
|
||||
expect(instance.dirty).toBeTruthy();
|
||||
instance.description = "";
|
||||
expect(!instance.dirty).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not flag as dirty when a transient prop is changed", () => {
|
||||
const instance = new Credential();
|
||||
instance.loading = true;
|
||||
expect(!instance.dirty).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("validation", () => {
|
||||
it("a new object should be invalid", () => {
|
||||
const instance = new Credential();
|
||||
expect(!instance.valid).toBeTruthy();
|
||||
});
|
||||
|
||||
it("it should become valid when props are assigned", () => {
|
||||
const instance = new Credential();
|
||||
instance.authn_id = "floob";
|
||||
instance.resourceProvider = "aws";
|
||||
instance.config.role_arn = "abc";
|
||||
expect(instance.valid).toBeTruthy();
|
||||
});
|
||||
|
||||
it("it should be valid when initialized with correct props", () => {
|
||||
const props = {
|
||||
authn_id: "asdfasdf",
|
||||
provider: "aws",
|
||||
config: {
|
||||
role_arn: "floobar",
|
||||
},
|
||||
};
|
||||
const instance = new Credential(props);
|
||||
expect(instance.valid).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should be invalid when bad props assigned", () => {
|
||||
const props = {
|
||||
authn_id: "asdfasdf",
|
||||
provider: "aws",
|
||||
config: {
|
||||
role_arn: "",
|
||||
},
|
||||
};
|
||||
const instance = new Credential(props);
|
||||
expect(!instance.valid).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
import { safeAssign } from "utils/safeAssign";
|
||||
|
||||
export class IdentityProvider {
|
||||
constructor(props = {}) {
|
||||
this.id = "";
|
||||
this.provider = "";
|
||||
safeAssign(this, props);
|
||||
}
|
||||
|
||||
// Aliases
|
||||
|
||||
get authn_id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
get text() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
// Statics
|
||||
|
||||
static create(props = {}) {
|
||||
return new IdentityProvider(props);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { AwsConfig } from "./AwsConfig";
|
||||
import { AzureConfig } from "./AzureConfig";
|
||||
|
||||
export const ResourceProviders = new Map();
|
||||
|
||||
ResourceProviders.set("aws", {
|
||||
klass: AwsConfig,
|
||||
label: "Amazon Web Services (AWS)",
|
||||
});
|
||||
|
||||
ResourceProviders.set("azure", {
|
||||
klass: AzureConfig,
|
||||
label: "Microsoft Azure",
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
export { Credential } from "./Credential";
|
||||
export { IdentityProvider } from "./IdentityProvider";
|
||||
export { ResourceProviders } from "./ResourceProviders";
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Data retrieval/storage for the auth keys
|
||||
*/
|
||||
|
||||
import axios from "axios";
|
||||
import { getRootFromIndexLink } from "onload";
|
||||
|
||||
import { Credential, IdentityProvider } from "./index";
|
||||
|
||||
const getUrl = (path) => getRootFromIndexLink() + path;
|
||||
|
||||
export async function listCredentials() {
|
||||
const url = getUrl("api/cloud/authz");
|
||||
const response = await axios.get(url);
|
||||
if (response.status != 200) {
|
||||
throw new Error("Unexpected response from listing.");
|
||||
}
|
||||
return response.data.map(Credential.create);
|
||||
}
|
||||
|
||||
export async function getCredential(id) {
|
||||
const url = getUrl("api/cloud/authz/${id}");
|
||||
const response = await axios.get(url);
|
||||
if (response.status != 200) {
|
||||
throw new Error("Unexpected response loading key.");
|
||||
}
|
||||
return Credential.create(response.data);
|
||||
}
|
||||
|
||||
export async function saveCredential(newItem) {
|
||||
const model = Credential.create(newItem);
|
||||
const response = await saveOrUpdate(model);
|
||||
if (response.status != 200) {
|
||||
throw new Error("Save failure.");
|
||||
}
|
||||
return Credential.create(response.data);
|
||||
}
|
||||
|
||||
async function saveOrUpdate(model) {
|
||||
return model.id
|
||||
? axios.put(getUrl(`api/cloud/authz/${model.id}`), model)
|
||||
: axios.post(getUrl("api/cloud/authz"), model);
|
||||
}
|
||||
|
||||
export async function deleteCredential(doomed) {
|
||||
const model = Credential.create(doomed);
|
||||
if (model.id) {
|
||||
const url = getUrl(`api/cloud/authz/${doomed.id}`);
|
||||
const response = await axios.delete(url);
|
||||
if (response.status != 200) {
|
||||
throw new Error("Delete failure.");
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
// Memoize results (basically never changes)
|
||||
|
||||
let identityProviders;
|
||||
|
||||
export async function getIdentityProviders() {
|
||||
if (!identityProviders) {
|
||||
const url = getUrl("authnz");
|
||||
const response = await axios.get(url);
|
||||
if (response.status != 200) {
|
||||
throw new Error("Unable to load identity providers");
|
||||
}
|
||||
// This should be idempotent (and safe).
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
identityProviders = response.data.map(IdentityProvider.create);
|
||||
}
|
||||
return identityProviders;
|
||||
}
|
||||
|
||||
export default {
|
||||
listCredentials,
|
||||
getCredential,
|
||||
saveCredential,
|
||||
deleteCredential,
|
||||
getIdentityProviders,
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
[
|
||||
{
|
||||
"authn_id": "f2db41e1fa331b3e",
|
||||
"user_id": "f2db41e1fa331b3e",
|
||||
"description": "aws description",
|
||||
"last_update": "2019-04-04 09:13:16.135032",
|
||||
"last_activity": "2019-04-04 09:13:16.135042",
|
||||
"create_time": "2019-04-04 16:13:16.135755",
|
||||
"provider": "aws",
|
||||
"model_class": "CloudAuthz",
|
||||
"config": {
|
||||
"role_arn": "floobadooba"
|
||||
},
|
||||
"id": "a7db2fac67043c7e"
|
||||
},
|
||||
{
|
||||
"authn_id": "f2db41e1fa331b3e",
|
||||
"user_id": "f2db41e1fa331b3e",
|
||||
"description": "azure description",
|
||||
"last_update": "2019-04-03 20:21:51.350748",
|
||||
"last_activity": "2019-04-03 20:21:51.350804",
|
||||
"create_time": "2019-04-04 03:21:51.351508",
|
||||
"provider": "azure",
|
||||
"model_class": "CloudAuthz",
|
||||
"config": {
|
||||
"client_secret": "dannon likes Nickleback",
|
||||
"client_id": "22222222-2222-2222-2222-222222222222",
|
||||
"tenant_id": "11111111-1111-1111-1111-111111111111"
|
||||
},
|
||||
"id": "b472e2eb553fa0d1"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user