Merge branch 'next' of github.com:OliveTin/OliveTin into next
This commit is contained in:
commit
08d0f6d574
|
|
@ -1,132 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# npm updates for frontend - targeting "next" branch
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/frontend"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "next"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
labels:
|
|
||||||
- "3k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# npm updates for frontend - targeting "release/2k" branch (security updates only)
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/frontend"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "release/2k"
|
|
||||||
open-pull-requests-limit: 0
|
|
||||||
labels:
|
|
||||||
- "2k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# npm updates for integration-tests - targeting "next" branch
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/integration-tests"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "next"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
labels:
|
|
||||||
- "3k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# npm updates for integration-tests - targeting "release/2k" branch (security updates only)
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/integration-tests"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "release/2k"
|
|
||||||
open-pull-requests-limit: 0
|
|
||||||
labels:
|
|
||||||
- "2k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Go modules updates for service - targeting "next" branch
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/service"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "next"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
labels:
|
|
||||||
- "3k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Go modules updates for service - targeting "release/2k" branch (security updates only)
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/service"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "release/2k"
|
|
||||||
open-pull-requests-limit: 0
|
|
||||||
labels:
|
|
||||||
- "2k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Go modules updates for lang - targeting "next" branch
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/lang"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "next"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
labels:
|
|
||||||
- "3k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Go modules updates for lang - targeting "release/2k" branch (security updates only)
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/lang"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "release/2k"
|
|
||||||
open-pull-requests-limit: 0
|
|
||||||
labels:
|
|
||||||
- "2k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Docker updates - targeting "next" branch
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "next"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
labels:
|
|
||||||
- "3k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
# Docker updates - targeting "release/2k" branch (security updates only)
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
target-branch: "release/2k"
|
|
||||||
open-pull-requests-limit: 0
|
|
||||||
labels:
|
|
||||||
- "2k"
|
|
||||||
- "dependencies"
|
|
||||||
cooldown:
|
|
||||||
default-days: 7
|
|
||||||
|
|
||||||
|
|
@ -10,6 +10,7 @@ A full list of argument types are below;
|
||||||
| (default) | xref:args/input.adoc[Textbox] | If a `type:` is not set, and `choices:` is empty, then ascii will be used, and a warning will be logged. It is recommended that you set the type explicitly, rather than relying on defaults.
|
| (default) | xref:args/input.adoc[Textbox] | If a `type:` is not set, and `choices:` is empty, then ascii will be used, and a warning will be logged. It is recommended that you set the type explicitly, rather than relying on defaults.
|
||||||
| ascii | xref:args/input.adoc[Textbox] | a-z (case insensitive), 0-9, but no spaces or punctuation
|
| ascii | xref:args/input.adoc[Textbox] | a-z (case insensitive), 0-9, but no spaces or punctuation
|
||||||
| ascii_identifier | xref:args/input.adoc[Textbox] | Like a DNS name, a-Z (case insensitive), 0-9, `-`, `.`, and `_`.
|
| ascii_identifier | xref:args/input.adoc[Textbox] | Like a DNS name, a-Z (case insensitive), 0-9, `-`, `.`, and `_`.
|
||||||
|
| shell_safe_identifier | xref:args/input.adoc[Textbox] | Like an ascii identifier, but also allows `@` and `+`. Useful for shell-safe usernames and email-style identifiers.
|
||||||
| ascii_sentence | xref:args/input.adoc[Textbox] | a-z (case insensitive), 0-9, with spaces, `.` and `,`.
|
| ascii_sentence | xref:args/input.adoc[Textbox] | a-z (case insensitive), 0-9, with spaces, `.` and `,`.
|
||||||
| unicode_identifier | xref:args/input.adoc[Textbox] | Like an ascii identifier, but allows unicode characters. This is useful for languages that use non-ascii characters, such as Chinese, Japanese, etc.
|
| unicode_identifier | xref:args/input.adoc[Textbox] | Like an ascii identifier, but allows unicode characters. This is useful for languages that use non-ascii characters, such as Chinese, Japanese, etc.
|
||||||
| email | xref:args/input.adoc[Textbox] | An email address.
|
| email | xref:args/input.adoc[Textbox] | An email address.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"stylelint-config-standard": "^40.0.0"
|
"stylelint-config-standard": "^40.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "node --test resources/vue/components/*.test.mjs"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"parcelIgnore": [
|
"parcelIgnore": [
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,16 @@ export declare type ActionWebhookExecHint = Message<"olivetin.api.v1.ActionWebho
|
||||||
* @generated from field: string match_path = 2;
|
* @generated from field: string match_path = 2;
|
||||||
*/
|
*/
|
||||||
matchPath: string;
|
matchPath: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, string> match_headers = 3;
|
||||||
|
*/
|
||||||
|
matchHeaders: { [key: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, string> match_query = 4;
|
||||||
|
*/
|
||||||
|
matchQuery: { [key: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@
|
||||||
height="1em"
|
height="1em"
|
||||||
class="action-icon-glyph-svg"
|
class="action-icon-glyph-svg"
|
||||||
/>
|
/>
|
||||||
|
<span v-else-if="decodedTextGlyphIsHtml" v-html="decodedTextGlyph"></span>
|
||||||
<span v-else v-text="decodedTextGlyph"></span>
|
<span v-else v-text="decodedTextGlyph"></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -15,6 +16,7 @@
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { HugeiconsIcon } from '@hugeicons/vue'
|
import { HugeiconsIcon } from '@hugeicons/vue'
|
||||||
import { CommandLineIcon } from '@hugeicons/core-free-icons'
|
import { CommandLineIcon } from '@hugeicons/core-free-icons'
|
||||||
|
import { decodeHtmlEntities, glyphLooksLikeHtml } from './actionIconGlyphHelpers.mjs'
|
||||||
|
|
||||||
const hugeiconsPrefix = 'hugeicons:'
|
const hugeiconsPrefix = 'hugeicons:'
|
||||||
|
|
||||||
|
|
@ -46,16 +48,6 @@ const hugeiconsModel = computed(() => {
|
||||||
return iconModel ?? CommandLineIcon
|
return iconModel ?? CommandLineIcon
|
||||||
})
|
})
|
||||||
|
|
||||||
function decodeHtmlEntities(text) {
|
|
||||||
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
|
||||||
const codePoint = Number.parseInt(hex, 16)
|
|
||||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
|
||||||
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
|
||||||
const codePoint = Number.parseInt(decimal, 10)
|
|
||||||
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedTextGlyph = computed(() => {
|
const decodedTextGlyph = computed(() => {
|
||||||
if (hugeiconsModel.value) {
|
if (hugeiconsModel.value) {
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -63,6 +55,8 @@ const decodedTextGlyph = computed(() => {
|
||||||
|
|
||||||
return decodeHtmlEntities(props.glyph)
|
return decodeHtmlEntities(props.glyph)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const decodedTextGlyphIsHtml = computed(() => glyphLooksLikeHtml(decodedTextGlyph.value))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
const fallbackNamedHtmlEntities = {
|
||||||
|
amp: '&',
|
||||||
|
apos: "'",
|
||||||
|
darr: '\u2193',
|
||||||
|
gt: '>',
|
||||||
|
laquo: '\u00ab',
|
||||||
|
larr: '\u2190',
|
||||||
|
nbsp: '\u00a0',
|
||||||
|
quot: '"',
|
||||||
|
raquo: '\u00bb',
|
||||||
|
rarr: '\u2192',
|
||||||
|
uarr: '\u2191',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeHtmlEntities(text) {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.innerHTML = text
|
||||||
|
|
||||||
|
return textarea.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.replace(/&#x([0-9a-fA-F]+);?/g, (_, hex) => {
|
||||||
|
const codePoint = Number.parseInt(hex, 16)
|
||||||
|
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||||
|
}).replace(/&#(\d+);?/g, (_, decimal) => {
|
||||||
|
const codePoint = Number.parseInt(decimal, 10)
|
||||||
|
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : ''
|
||||||
|
}).replace(/&([a-zA-Z][a-zA-Z0-9]+);?/g, (entity, name) => {
|
||||||
|
return fallbackNamedHtmlEntities[name] ?? entity
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function glyphLooksLikeHtml(text) {
|
||||||
|
const trimmedText = text.trim()
|
||||||
|
|
||||||
|
return trimmedText.startsWith('<') || /<img\b/i.test(text) || /\/custom-webui\//i.test(text)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { decodeHtmlEntities, glyphLooksLikeHtml } from './actionIconGlyphHelpers.mjs'
|
||||||
|
|
||||||
|
test('decodeHtmlEntities decodes named entity icons as plain glyph text', () => {
|
||||||
|
assert.equal(decodeHtmlEntities('«'), '\u00ab')
|
||||||
|
assert.equal(decodeHtmlEntities('→'), '\u2192')
|
||||||
|
assert.equal(decodeHtmlEntities('« next →'), '\u00ab next \u2192')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('decoded named entity icons are not treated as HTML markup', () => {
|
||||||
|
const decodedGlyph = decodeHtmlEntities('→')
|
||||||
|
|
||||||
|
assert.equal(glyphLooksLikeHtml(decodedGlyph), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('decodeHtmlEntities keeps existing numeric entity icon support', () => {
|
||||||
|
assert.equal(decodeHtmlEntities('💩'), '\ud83d\udca9')
|
||||||
|
assert.equal(decodeHtmlEntities('💾'), '\ud83d\udcbe')
|
||||||
|
})
|
||||||
|
|
@ -76,7 +76,9 @@
|
||||||
<li v-for="(wh, idx) in action.execOnWebhooks" :key="'wh-' + idx">
|
<li v-for="(wh, idx) in action.execOnWebhooks" :key="'wh-' + idx">
|
||||||
<span v-if="wh.template">template: <code>{{ wh.template }}</code></span>
|
<span v-if="wh.template">template: <code>{{ wh.template }}</code></span>
|
||||||
<span v-if="wh.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span>
|
<span v-if="wh.matchPath"> · matchPath: <code>{{ wh.matchPath }}</code></span>
|
||||||
<span v-if="!wh.template && !wh.matchPath">Webhook trigger (no template or match path in response)</span>
|
<span v-if="nonEmptyObject(wh.matchHeaders)"> · matchHeaders: <code>{{ wh.matchHeaders }}</code></span>
|
||||||
|
<span v-if="nonEmptyObject(wh.matchQuery)"> · matchQuery: <code>{{ wh.matchQuery }}</code></span>
|
||||||
|
<span v-if="!webhookHasCondition(wh)">Webhook trigger (no conditions in response)</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -117,6 +119,14 @@ function nonEmptyList(list) {
|
||||||
return Array.isArray(list) && list.length > 0
|
return Array.isArray(list) && list.length > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nonEmptyObject(object) {
|
||||||
|
return object && Object.keys(object).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function webhookHasCondition(webhook) {
|
||||||
|
return webhook.template || webhook.matchPath || nonEmptyObject(webhook.matchHeaders) || nonEmptyObject(webhook.matchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
const hasConfiguredTriggers = computed(() => {
|
const hasConfiguredTriggers = computed(() => {
|
||||||
const a = action.value
|
const a = action.value
|
||||||
if (!a) {
|
if (!a) {
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ function getInputType(arg) {
|
||||||
return 'checkbox'
|
return 'checkbox'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.type === 'ascii_identifier' || arg.type === 'ascii' || arg.type === 'ascii_sentence') {
|
if (arg.type === 'ascii_identifier' || arg.type === 'shell_safe_identifier' || arg.type === 'ascii' || arg.type === 'ascii_sentence') {
|
||||||
return 'text'
|
return 'text'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ message Action {
|
||||||
message ActionWebhookExecHint {
|
message ActionWebhookExecHint {
|
||||||
string template = 1;
|
string template = 1;
|
||||||
string match_path = 2;
|
string match_path = 2;
|
||||||
|
map<string, string> match_headers = 3;
|
||||||
|
map<string, string> match_query = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ActionArgument {
|
message ActionArgument {
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,8 @@ type ActionWebhookExecHint struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Template string `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"`
|
Template string `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"`
|
||||||
MatchPath string `protobuf:"bytes,2,opt,name=match_path,json=matchPath,proto3" json:"match_path,omitempty"`
|
MatchPath string `protobuf:"bytes,2,opt,name=match_path,json=matchPath,proto3" json:"match_path,omitempty"`
|
||||||
|
MatchHeaders map[string]string `protobuf:"bytes,3,rep,name=match_headers,json=matchHeaders,proto3" json:"match_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
|
MatchQuery map[string]string `protobuf:"bytes,4,rep,name=match_query,json=matchQuery,proto3" json:"match_query,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -229,6 +231,20 @@ func (x *ActionWebhookExecHint) GetMatchPath() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ActionWebhookExecHint) GetMatchHeaders() map[string]string {
|
||||||
|
if x != nil {
|
||||||
|
return x.MatchHeaders
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ActionWebhookExecHint) GetMatchQuery() map[string]string {
|
||||||
|
if x != nil {
|
||||||
|
return x.MatchQuery
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ActionArgument struct {
|
type ActionArgument struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
|
@ -4022,11 +4038,20 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
|
||||||
"\x1bexec_on_file_created_in_dir\x18\f \x03(\tR\x16execOnFileCreatedInDir\x12;\n" +
|
"\x1bexec_on_file_created_in_dir\x18\f \x03(\tR\x16execOnFileCreatedInDir\x12;\n" +
|
||||||
"\x1bexec_on_file_changed_in_dir\x18\r \x03(\tR\x16execOnFileChangedInDir\x121\n" +
|
"\x1bexec_on_file_changed_in_dir\x18\r \x03(\tR\x16execOnFileChangedInDir\x121\n" +
|
||||||
"\x15exec_on_calendar_file\x18\x0e \x01(\tR\x12execOnCalendarFile\x12P\n" +
|
"\x15exec_on_calendar_file\x18\x0e \x01(\tR\x12execOnCalendarFile\x12P\n" +
|
||||||
"\x10exec_on_webhooks\x18\x0f \x03(\v2&.olivetin.api.v1.ActionWebhookExecHintR\x0eexecOnWebhooks\"R\n" +
|
"\x10exec_on_webhooks\x18\x0f \x03(\v2&.olivetin.api.v1.ActionWebhookExecHintR\x0eexecOnWebhooks\"\x8a\x03\n" +
|
||||||
"\x15ActionWebhookExecHint\x12\x1a\n" +
|
"\x15ActionWebhookExecHint\x12\x1a\n" +
|
||||||
"\btemplate\x18\x01 \x01(\tR\btemplate\x12\x1d\n" +
|
"\btemplate\x18\x01 \x01(\tR\btemplate\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"match_path\x18\x02 \x01(\tR\tmatchPath\"\xa2\x03\n" +
|
"match_path\x18\x02 \x01(\tR\tmatchPath\x12]\n" +
|
||||||
|
"\rmatch_headers\x18\x03 \x03(\v28.olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntryR\fmatchHeaders\x12W\n" +
|
||||||
|
"\vmatch_query\x18\x04 \x03(\v26.olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntryR\n" +
|
||||||
|
"matchQuery\x1a?\n" +
|
||||||
|
"\x11MatchHeadersEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a=\n" +
|
||||||
|
"\x0fMatchQueryEntry\x12\x10\n" +
|
||||||
|
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||||
|
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa2\x03\n" +
|
||||||
"\x0eActionArgument\x12\x12\n" +
|
"\x0eActionArgument\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
|
||||||
"\x05title\x18\x02 \x01(\tR\x05title\x12\x12\n" +
|
"\x05title\x18\x02 \x01(\tR\x05title\x12\x12\n" +
|
||||||
|
|
@ -4330,7 +4355,7 @@ func file_olivetin_api_v1_olivetin_proto_rawDescGZIP() []byte {
|
||||||
return file_olivetin_api_v1_olivetin_proto_rawDescData
|
return file_olivetin_api_v1_olivetin_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 73)
|
var file_olivetin_api_v1_olivetin_proto_msgTypes = make([]protoimpl.MessageInfo, 75)
|
||||||
var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
||||||
(*Action)(nil), // 0: olivetin.api.v1.Action
|
(*Action)(nil), // 0: olivetin.api.v1.Action
|
||||||
(*ActionWebhookExecHint)(nil), // 1: olivetin.api.v1.ActionWebhookExecHint
|
(*ActionWebhookExecHint)(nil), // 1: olivetin.api.v1.ActionWebhookExecHint
|
||||||
|
|
@ -4401,99 +4426,103 @@ var file_olivetin_api_v1_olivetin_proto_goTypes = []any{
|
||||||
(*EntityDefinition)(nil), // 66: olivetin.api.v1.EntityDefinition
|
(*EntityDefinition)(nil), // 66: olivetin.api.v1.EntityDefinition
|
||||||
(*GetEntityRequest)(nil), // 67: olivetin.api.v1.GetEntityRequest
|
(*GetEntityRequest)(nil), // 67: olivetin.api.v1.GetEntityRequest
|
||||||
(*RestartActionRequest)(nil), // 68: olivetin.api.v1.RestartActionRequest
|
(*RestartActionRequest)(nil), // 68: olivetin.api.v1.RestartActionRequest
|
||||||
nil, // 69: olivetin.api.v1.ActionArgument.SuggestionsEntry
|
nil, // 69: olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||||
nil, // 70: olivetin.api.v1.Entity.FieldsEntry
|
nil, // 70: olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||||
nil, // 71: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
nil, // 71: olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||||
nil, // 72: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
nil, // 72: olivetin.api.v1.Entity.FieldsEntry
|
||||||
|
nil, // 73: olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||||
|
nil, // 74: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||||
}
|
}
|
||||||
var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{
|
var file_olivetin_api_v1_olivetin_proto_depIdxs = []int32{
|
||||||
2, // 0: olivetin.api.v1.Action.arguments:type_name -> olivetin.api.v1.ActionArgument
|
2, // 0: olivetin.api.v1.Action.arguments:type_name -> olivetin.api.v1.ActionArgument
|
||||||
1, // 1: olivetin.api.v1.Action.exec_on_webhooks:type_name -> olivetin.api.v1.ActionWebhookExecHint
|
1, // 1: olivetin.api.v1.Action.exec_on_webhooks:type_name -> olivetin.api.v1.ActionWebhookExecHint
|
||||||
3, // 2: olivetin.api.v1.ActionArgument.choices:type_name -> olivetin.api.v1.ActionArgumentChoice
|
69, // 2: olivetin.api.v1.ActionWebhookExecHint.match_headers:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchHeadersEntry
|
||||||
69, // 3: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry
|
70, // 3: olivetin.api.v1.ActionWebhookExecHint.match_query:type_name -> olivetin.api.v1.ActionWebhookExecHint.MatchQueryEntry
|
||||||
70, // 4: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry
|
3, // 4: olivetin.api.v1.ActionArgument.choices:type_name -> olivetin.api.v1.ActionArgumentChoice
|
||||||
8, // 5: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard
|
71, // 5: olivetin.api.v1.ActionArgument.suggestions:type_name -> olivetin.api.v1.ActionArgument.SuggestionsEntry
|
||||||
9, // 6: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent
|
72, // 6: olivetin.api.v1.Entity.fields:type_name -> olivetin.api.v1.Entity.FieldsEntry
|
||||||
9, // 7: olivetin.api.v1.DashboardComponent.contents:type_name -> olivetin.api.v1.DashboardComponent
|
8, // 7: olivetin.api.v1.GetDashboardResponse.dashboard:type_name -> olivetin.api.v1.Dashboard
|
||||||
0, // 8: olivetin.api.v1.DashboardComponent.action:type_name -> olivetin.api.v1.Action
|
9, // 8: olivetin.api.v1.Dashboard.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||||
11, // 9: olivetin.api.v1.StartActionRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
9, // 9: olivetin.api.v1.DashboardComponent.contents:type_name -> olivetin.api.v1.DashboardComponent
|
||||||
11, // 10: olivetin.api.v1.StartActionAndWaitRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
0, // 10: olivetin.api.v1.DashboardComponent.action:type_name -> olivetin.api.v1.Action
|
||||||
20, // 11: olivetin.api.v1.StartActionAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
11, // 11: olivetin.api.v1.StartActionRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||||
20, // 12: olivetin.api.v1.StartActionByGetAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
11, // 12: olivetin.api.v1.StartActionAndWaitRequest.arguments:type_name -> olivetin.api.v1.StartActionArgument
|
||||||
20, // 13: olivetin.api.v1.GetLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
20, // 13: olivetin.api.v1.StartActionAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||||
20, // 14: olivetin.api.v1.GetActionLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
20, // 14: olivetin.api.v1.StartActionByGetAndWaitResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||||
20, // 15: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
20, // 15: olivetin.api.v1.GetLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||||
71, // 16: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
20, // 16: olivetin.api.v1.GetActionLogsResponse.logs:type_name -> olivetin.api.v1.LogEntry
|
||||||
72, // 17: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
20, // 17: olivetin.api.v1.ExecutionStatusResponse.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||||
44, // 18: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged
|
73, // 18: olivetin.api.v1.DumpVarsResponse.contents:type_name -> olivetin.api.v1.DumpVarsResponse.ContentsEntry
|
||||||
45, // 19: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged
|
74, // 19: olivetin.api.v1.DumpPublicIdActionMapResponse.contents:type_name -> olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry
|
||||||
46, // 20: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
44, // 20: olivetin.api.v1.EventStreamResponse.entity_changed:type_name -> olivetin.api.v1.EventEntityChanged
|
||||||
47, // 21: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
45, // 21: olivetin.api.v1.EventStreamResponse.config_changed:type_name -> olivetin.api.v1.EventConfigChanged
|
||||||
43, // 22: olivetin.api.v1.EventStreamResponse.output_chunk:type_name -> olivetin.api.v1.EventOutputChunk
|
46, // 22: olivetin.api.v1.EventStreamResponse.execution_finished:type_name -> olivetin.api.v1.EventExecutionFinished
|
||||||
20, // 23: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
47, // 23: olivetin.api.v1.EventStreamResponse.execution_started:type_name -> olivetin.api.v1.EventExecutionStarted
|
||||||
20, // 24: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
43, // 24: olivetin.api.v1.EventStreamResponse.output_chunk:type_name -> olivetin.api.v1.EventOutputChunk
|
||||||
61, // 25: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
20, // 25: olivetin.api.v1.EventExecutionFinished.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||||
60, // 26: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
20, // 26: olivetin.api.v1.EventExecutionStarted.log_entry:type_name -> olivetin.api.v1.LogEntry
|
||||||
6, // 27: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
61, // 27: olivetin.api.v1.InitResponse.oAuth2Providers:type_name -> olivetin.api.v1.OAuth2Provider
|
||||||
0, // 28: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
60, // 28: olivetin.api.v1.InitResponse.additionalLinks:type_name -> olivetin.api.v1.AdditionalLink
|
||||||
66, // 29: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
6, // 29: olivetin.api.v1.InitResponse.effective_policy:type_name -> olivetin.api.v1.EffectivePolicy
|
||||||
4, // 30: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
0, // 30: olivetin.api.v1.GetActionBindingResponse.action:type_name -> olivetin.api.v1.Action
|
||||||
36, // 31: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding
|
66, // 31: olivetin.api.v1.GetEntitiesResponse.entity_definitions:type_name -> olivetin.api.v1.EntityDefinition
|
||||||
7, // 32: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
4, // 32: olivetin.api.v1.EntityDefinition.instances:type_name -> olivetin.api.v1.Entity
|
||||||
10, // 33: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
36, // 33: olivetin.api.v1.DumpPublicIdActionMapResponse.ContentsEntry.value:type_name -> olivetin.api.v1.DebugBinding
|
||||||
13, // 34: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
7, // 34: olivetin.api.v1.OliveTinApiService.GetDashboard:input_type -> olivetin.api.v1.GetDashboardRequest
|
||||||
15, // 35: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
10, // 35: olivetin.api.v1.OliveTinApiService.StartAction:input_type -> olivetin.api.v1.StartActionRequest
|
||||||
17, // 36: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
13, // 36: olivetin.api.v1.OliveTinApiService.StartActionAndWait:input_type -> olivetin.api.v1.StartActionAndWaitRequest
|
||||||
68, // 37: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
15, // 37: olivetin.api.v1.OliveTinApiService.StartActionByGet:input_type -> olivetin.api.v1.StartActionByGetRequest
|
||||||
48, // 38: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
17, // 38: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:input_type -> olivetin.api.v1.StartActionByGetAndWaitRequest
|
||||||
28, // 39: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
68, // 39: olivetin.api.v1.OliveTinApiService.RestartAction:input_type -> olivetin.api.v1.RestartActionRequest
|
||||||
19, // 40: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
48, // 40: olivetin.api.v1.OliveTinApiService.KillAction:input_type -> olivetin.api.v1.KillActionRequest
|
||||||
22, // 41: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
28, // 41: olivetin.api.v1.OliveTinApiService.ExecutionStatus:input_type -> olivetin.api.v1.ExecutionStatusRequest
|
||||||
24, // 42: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
19, // 42: olivetin.api.v1.OliveTinApiService.GetLogs:input_type -> olivetin.api.v1.GetLogsRequest
|
||||||
30, // 43: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
22, // 43: olivetin.api.v1.OliveTinApiService.GetActionLogs:input_type -> olivetin.api.v1.GetActionLogsRequest
|
||||||
32, // 44: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
24, // 44: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:input_type -> olivetin.api.v1.ValidateArgumentTypeRequest
|
||||||
34, // 45: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
30, // 45: olivetin.api.v1.OliveTinApiService.WhoAmI:input_type -> olivetin.api.v1.WhoAmIRequest
|
||||||
37, // 46: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
32, // 46: olivetin.api.v1.OliveTinApiService.SosReport:input_type -> olivetin.api.v1.SosReportRequest
|
||||||
39, // 47: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
34, // 47: olivetin.api.v1.OliveTinApiService.DumpVars:input_type -> olivetin.api.v1.DumpVarsRequest
|
||||||
50, // 48: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
37, // 48: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:input_type -> olivetin.api.v1.DumpPublicIdActionMapRequest
|
||||||
52, // 49: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
39, // 49: olivetin.api.v1.OliveTinApiService.GetReadyz:input_type -> olivetin.api.v1.GetReadyzRequest
|
||||||
54, // 50: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
50, // 50: olivetin.api.v1.OliveTinApiService.LocalUserLogin:input_type -> olivetin.api.v1.LocalUserLoginRequest
|
||||||
41, // 51: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
52, // 51: olivetin.api.v1.OliveTinApiService.PasswordHash:input_type -> olivetin.api.v1.PasswordHashRequest
|
||||||
56, // 52: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
54, // 52: olivetin.api.v1.OliveTinApiService.Logout:input_type -> olivetin.api.v1.LogoutRequest
|
||||||
58, // 53: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
41, // 53: olivetin.api.v1.OliveTinApiService.EventStream:input_type -> olivetin.api.v1.EventStreamRequest
|
||||||
62, // 54: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
56, // 54: olivetin.api.v1.OliveTinApiService.GetDiagnostics:input_type -> olivetin.api.v1.GetDiagnosticsRequest
|
||||||
64, // 55: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
58, // 55: olivetin.api.v1.OliveTinApiService.Init:input_type -> olivetin.api.v1.InitRequest
|
||||||
67, // 56: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
62, // 56: olivetin.api.v1.OliveTinApiService.GetActionBinding:input_type -> olivetin.api.v1.GetActionBindingRequest
|
||||||
5, // 57: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
64, // 57: olivetin.api.v1.OliveTinApiService.GetEntities:input_type -> olivetin.api.v1.GetEntitiesRequest
|
||||||
12, // 58: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
67, // 58: olivetin.api.v1.OliveTinApiService.GetEntity:input_type -> olivetin.api.v1.GetEntityRequest
|
||||||
14, // 59: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
5, // 59: olivetin.api.v1.OliveTinApiService.GetDashboard:output_type -> olivetin.api.v1.GetDashboardResponse
|
||||||
16, // 60: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
12, // 60: olivetin.api.v1.OliveTinApiService.StartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||||
18, // 61: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
14, // 61: olivetin.api.v1.OliveTinApiService.StartActionAndWait:output_type -> olivetin.api.v1.StartActionAndWaitResponse
|
||||||
12, // 62: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
16, // 62: olivetin.api.v1.OliveTinApiService.StartActionByGet:output_type -> olivetin.api.v1.StartActionByGetResponse
|
||||||
49, // 63: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
18, // 63: olivetin.api.v1.OliveTinApiService.StartActionByGetAndWait:output_type -> olivetin.api.v1.StartActionByGetAndWaitResponse
|
||||||
29, // 64: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
12, // 64: olivetin.api.v1.OliveTinApiService.RestartAction:output_type -> olivetin.api.v1.StartActionResponse
|
||||||
21, // 65: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
49, // 65: olivetin.api.v1.OliveTinApiService.KillAction:output_type -> olivetin.api.v1.KillActionResponse
|
||||||
23, // 66: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
29, // 66: olivetin.api.v1.OliveTinApiService.ExecutionStatus:output_type -> olivetin.api.v1.ExecutionStatusResponse
|
||||||
25, // 67: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
21, // 67: olivetin.api.v1.OliveTinApiService.GetLogs:output_type -> olivetin.api.v1.GetLogsResponse
|
||||||
31, // 68: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
23, // 68: olivetin.api.v1.OliveTinApiService.GetActionLogs:output_type -> olivetin.api.v1.GetActionLogsResponse
|
||||||
33, // 69: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
25, // 69: olivetin.api.v1.OliveTinApiService.ValidateArgumentType:output_type -> olivetin.api.v1.ValidateArgumentTypeResponse
|
||||||
35, // 70: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
31, // 70: olivetin.api.v1.OliveTinApiService.WhoAmI:output_type -> olivetin.api.v1.WhoAmIResponse
|
||||||
38, // 71: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
33, // 71: olivetin.api.v1.OliveTinApiService.SosReport:output_type -> olivetin.api.v1.SosReportResponse
|
||||||
40, // 72: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
35, // 72: olivetin.api.v1.OliveTinApiService.DumpVars:output_type -> olivetin.api.v1.DumpVarsResponse
|
||||||
51, // 73: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
38, // 73: olivetin.api.v1.OliveTinApiService.DumpPublicIdActionMap:output_type -> olivetin.api.v1.DumpPublicIdActionMapResponse
|
||||||
53, // 74: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
40, // 74: olivetin.api.v1.OliveTinApiService.GetReadyz:output_type -> olivetin.api.v1.GetReadyzResponse
|
||||||
55, // 75: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
51, // 75: olivetin.api.v1.OliveTinApiService.LocalUserLogin:output_type -> olivetin.api.v1.LocalUserLoginResponse
|
||||||
42, // 76: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
53, // 76: olivetin.api.v1.OliveTinApiService.PasswordHash:output_type -> olivetin.api.v1.PasswordHashResponse
|
||||||
57, // 77: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
55, // 77: olivetin.api.v1.OliveTinApiService.Logout:output_type -> olivetin.api.v1.LogoutResponse
|
||||||
59, // 78: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
42, // 78: olivetin.api.v1.OliveTinApiService.EventStream:output_type -> olivetin.api.v1.EventStreamResponse
|
||||||
63, // 79: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
57, // 79: olivetin.api.v1.OliveTinApiService.GetDiagnostics:output_type -> olivetin.api.v1.GetDiagnosticsResponse
|
||||||
65, // 80: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
59, // 80: olivetin.api.v1.OliveTinApiService.Init:output_type -> olivetin.api.v1.InitResponse
|
||||||
4, // 81: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
63, // 81: olivetin.api.v1.OliveTinApiService.GetActionBinding:output_type -> olivetin.api.v1.GetActionBindingResponse
|
||||||
57, // [57:82] is the sub-list for method output_type
|
65, // 82: olivetin.api.v1.OliveTinApiService.GetEntities:output_type -> olivetin.api.v1.GetEntitiesResponse
|
||||||
32, // [32:57] is the sub-list for method input_type
|
4, // 83: olivetin.api.v1.OliveTinApiService.GetEntity:output_type -> olivetin.api.v1.Entity
|
||||||
32, // [32:32] is the sub-list for extension type_name
|
59, // [59:84] is the sub-list for method output_type
|
||||||
32, // [32:32] is the sub-list for extension extendee
|
34, // [34:59] is the sub-list for method input_type
|
||||||
0, // [0:32] is the sub-list for field type_name
|
34, // [34:34] is the sub-list for extension type_name
|
||||||
|
34, // [34:34] is the sub-list for extension extendee
|
||||||
|
0, // [0:34] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_olivetin_api_v1_olivetin_proto_init() }
|
func init() { file_olivetin_api_v1_olivetin_proto_init() }
|
||||||
|
|
@ -4514,7 +4543,7 @@ func file_olivetin_api_v1_olivetin_proto_init() {
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_olivetin_api_v1_olivetin_proto_rawDesc), len(file_olivetin_api_v1_olivetin_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_olivetin_api_v1_olivetin_proto_rawDesc), len(file_olivetin_api_v1_olivetin_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 73,
|
NumMessages: 75,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ func applyActionExecTriggers(pb *apiv1.Action, cfg *config.Action) {
|
||||||
pb.ExecOnWebhooks = append(pb.ExecOnWebhooks, &apiv1.ActionWebhookExecHint{
|
pb.ExecOnWebhooks = append(pb.ExecOnWebhooks, &apiv1.ActionWebhookExecHint{
|
||||||
Template: wh.Template,
|
Template: wh.Template,
|
||||||
MatchPath: wh.MatchPath,
|
MatchPath: wh.MatchPath,
|
||||||
|
MatchHeaders: wh.MatchHeaders,
|
||||||
|
MatchQuery: wh.MatchQuery,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,24 @@ func getNewTestServerAndClient(injectedConfig *config.Config) (*httptest.Server,
|
||||||
return ts, client
|
return ts, client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyActionExecTriggersIncludesWebhookHeaderAndQueryMatches(t *testing.T) {
|
||||||
|
cfg := &config.Action{
|
||||||
|
ExecOnWebhook: []config.WebhookConfig{
|
||||||
|
{
|
||||||
|
MatchHeaders: map[string]string{"X-GitHub-Event": "push"},
|
||||||
|
MatchQuery: map[string]string{"source": "github"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pb := &apiv1.Action{}
|
||||||
|
|
||||||
|
applyActionExecTriggers(pb, cfg)
|
||||||
|
|
||||||
|
require.Len(t, pb.ExecOnWebhooks, 1)
|
||||||
|
assert.Equal(t, cfg.ExecOnWebhook[0].MatchHeaders, pb.ExecOnWebhooks[0].MatchHeaders)
|
||||||
|
assert.Equal(t, cfg.ExecOnWebhook[0].MatchQuery, pb.ExecOnWebhooks[0].MatchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetActionsAndStart(t *testing.T) {
|
func TestGetActionsAndStart(t *testing.T) {
|
||||||
cfg := config.DefaultConfig()
|
cfg := config.DefaultConfig()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ReservedArgumentNamePrefix is reserved for OliveTin-injected system arguments.
|
||||||
|
const ReservedArgumentNamePrefix = "ot_"
|
||||||
|
|
||||||
// Action represents the core functionality of OliveTin - commands that show up
|
// Action represents the core functionality of OliveTin - commands that show up
|
||||||
// as buttons in the UI.
|
// as buttons in the UI.
|
||||||
type Action struct {
|
type Action struct {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,34 @@ func (cfg *Config) Sanitize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.sanitizeDashboardsForInlineActions()
|
cfg.sanitizeDashboardsForInlineActions()
|
||||||
|
|
||||||
|
if err := cfg.validateReservedActionArgumentNames(); err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) validateReservedActionArgumentNames() error {
|
||||||
|
for _, action := range cfg.Actions {
|
||||||
|
if err := action.validateReservedArgumentNames(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *Action) validateReservedArgumentNames() error {
|
||||||
|
if action == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range action.Arguments {
|
||||||
|
if strings.HasPrefix(arg.Name, ReservedArgumentNamePrefix) {
|
||||||
|
return fmt.Errorf("action %q argument %q uses reserved prefix %q", action.Title, arg.Name, ReservedArgumentNamePrefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Config) sanitizeDashboardsForInlineActions() {
|
func (cfg *Config) sanitizeDashboardsForInlineActions() {
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,59 @@ func TestSanitizeConfigInlineDashboardActions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateReservedActionArgumentNames(t *testing.T) {
|
||||||
|
c := DefaultConfig()
|
||||||
|
c.Actions = append(c.Actions, &Action{
|
||||||
|
Title: "Reserved arg",
|
||||||
|
Arguments: []ActionArgument{
|
||||||
|
{Name: "ot_custom", Type: "ascii"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err := c.validateReservedActionArgumentNames()
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), `action "Reserved arg" argument "ot_custom" uses reserved prefix "ot_"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateReservedActionArgumentNamesAllowsNonReserved(t *testing.T) {
|
||||||
|
c := DefaultConfig()
|
||||||
|
c.Actions = append(c.Actions, &Action{
|
||||||
|
Title: "Allowed arg",
|
||||||
|
Arguments: []ActionArgument{
|
||||||
|
{Name: "target", Type: "ascii"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, c.validateReservedActionArgumentNames())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateReservedActionArgumentNamesChecksInlineActions(t *testing.T) {
|
||||||
|
c := DefaultConfig()
|
||||||
|
c.Dashboards = []*DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "Dashboard",
|
||||||
|
Contents: []*DashboardComponent{
|
||||||
|
{
|
||||||
|
Title: "Inline reserved arg",
|
||||||
|
InlineAction: &Action{
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []ActionArgument{
|
||||||
|
{Name: "ot_custom", Type: "ascii"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.sanitizeDashboardsForInlineActions()
|
||||||
|
err := c.validateReservedActionArgumentNames()
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), `action "Inline reserved arg" argument "ot_custom" uses reserved prefix "ot_"`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateUniqueLocalUserAPIKeys(t *testing.T) {
|
func TestValidateUniqueLocalUserAPIKeys(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ var (
|
||||||
"unicode_identifier": `^[\w\-\.\_\d]+$`,
|
"unicode_identifier": `^[\w\-\.\_\d]+$`,
|
||||||
"ascii": `^[a-zA-Z0-9]+$`,
|
"ascii": `^[a-zA-Z0-9]+$`,
|
||||||
"ascii_identifier": `^[a-zA-Z0-9\-\._]+$`,
|
"ascii_identifier": `^[a-zA-Z0-9\-\._]+$`,
|
||||||
|
"shell_safe_identifier": `^[a-zA-Z0-9@\.\_\+\-]+$`,
|
||||||
"ascii_sentence": `^[a-zA-Z0-9\-\._, ]+$`,
|
"ascii_sentence": `^[a-zA-Z0-9\-\._, ]+$`,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -576,6 +576,38 @@ func TestTypeSafetyCheckAsciiIdentifier(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTypeSafetyCheckShellSafeIdentifier(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{"Simple username", "alice123", false},
|
||||||
|
{"Email username", "alice@example.com", false},
|
||||||
|
{"Plus addressing", "alice+test@example.com", false},
|
||||||
|
{"Hyphen underscore dot", "alice-test_user.example", false},
|
||||||
|
{"Invalid space", "alice example", true},
|
||||||
|
{"Invalid shell substitution", "$(whoami)", true},
|
||||||
|
{"Invalid backtick", "`whoami`", true},
|
||||||
|
{"Invalid semicolon", "alice;id", true},
|
||||||
|
{"Invalid ampersand", "alice&id", true},
|
||||||
|
{"Invalid pipe", "alice|id", true},
|
||||||
|
{"Invalid quote", "alice'example", true},
|
||||||
|
{"Invalid slash", "alice/example", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := TypeSafetyCheck("username", tt.value, "shell_safe_identifier")
|
||||||
|
if tt.hasError {
|
||||||
|
assert.NotNil(t, err, "Expected error for value '%s'", tt.value)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err, "Expected no error for value '%s', but got: %v", tt.value, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTypeSafetyCheckAsciiSentence(t *testing.T) {
|
func TestTypeSafetyCheckAsciiSentence(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
|
|
@ -666,13 +666,15 @@ func stepACLCheck(req *ExecutionRequest) bool {
|
||||||
|
|
||||||
func stepParseArgs(req *ExecutionRequest) bool {
|
func stepParseArgs(req *ExecutionRequest) bool {
|
||||||
ensureArgumentMap(req)
|
ensureArgumentMap(req)
|
||||||
injectSystemArgs(req)
|
|
||||||
|
|
||||||
if !hasBindingAndAction(req) {
|
if !hasBindingAndAction(req) {
|
||||||
return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil"))
|
return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil"))
|
||||||
}
|
}
|
||||||
|
|
||||||
filterToDefinedArgumentsOnly(req)
|
filterToDefinedArgumentsOnly(req)
|
||||||
|
if err := injectSystemArgs(req); err != nil {
|
||||||
|
return fail(req, err)
|
||||||
|
}
|
||||||
mangleInvalidArgumentValues(req)
|
mangleInvalidArgumentValues(req)
|
||||||
|
|
||||||
if hasExec(req) {
|
if hasExec(req) {
|
||||||
|
|
@ -735,7 +737,7 @@ func filterToDefinedArgumentsOnly(req *ExecutionRequest) {
|
||||||
|
|
||||||
func keepArgument(name string, definedNames map[string]struct{}) bool {
|
func keepArgument(name string, definedNames map[string]struct{}) bool {
|
||||||
_, ok := definedNames[name]
|
_, ok := definedNames[name]
|
||||||
return ok || strings.HasPrefix(name, "ot_")
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasWebhookTag(req *ExecutionRequest) bool {
|
func hasWebhookTag(req *ExecutionRequest) bool {
|
||||||
|
|
@ -747,9 +749,38 @@ func hasWebhookTag(req *ExecutionRequest) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func injectSystemArgs(req *ExecutionRequest) {
|
var systemArgumentDefinitions = []config.ActionArgument{
|
||||||
req.Arguments["ot_executionTrackingId"] = req.TrackingID
|
{Name: "ot_executionTrackingId", Type: "ascii_identifier", RejectNull: true},
|
||||||
req.Arguments["ot_username"] = req.AuthenticatedUser.Username
|
{Name: "ot_username", Type: "shell_safe_identifier", RejectNull: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func injectSystemArgs(req *ExecutionRequest) error {
|
||||||
|
args, err := validatedSystemArgs(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, value := range args {
|
||||||
|
req.Arguments[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validatedSystemArgs(req *ExecutionRequest) (map[string]string, error) {
|
||||||
|
values := map[string]string{
|
||||||
|
"ot_executionTrackingId": req.TrackingID,
|
||||||
|
"ot_username": req.AuthenticatedUser.Username,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range systemArgumentDefinitions {
|
||||||
|
arg := &systemArgumentDefinitions[i]
|
||||||
|
if err := ValidateArgument(arg, values[arg.Name], req.Binding.Action); err != nil {
|
||||||
|
return nil, fmt.Errorf("system argument %q failed validation: %w", arg.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasBindingAndAction(req *ExecutionRequest) bool {
|
func hasBindingAndAction(req *ExecutionRequest) bool {
|
||||||
|
|
@ -939,36 +970,20 @@ func prepareCommand(cmd *exec.Cmd, streamer *OutputStreamer, req *ExecutionReque
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepExecAfter(req *ExecutionRequest) bool {
|
func stepExecAfter(req *ExecutionRequest) bool {
|
||||||
if req.Binding.Action.ShellAfterCompleted == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := newTimeoutContext(context.Background(), time.Duration(req.Binding.Action.Timeout)*time.Second, req.executor)
|
ctx, cancel := newTimeoutContext(context.Background(), time.Duration(req.Binding.Action.Timeout)*time.Second, req.executor)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
args := map[string]string{
|
cmd, args, err := buildShellAfterCommand(ctx, req, &stdout, &stderr)
|
||||||
"output": req.logEntry.Output,
|
|
||||||
"exitCode": fmt.Sprintf("%v", req.logEntry.ExitCode),
|
|
||||||
"ot_executionTrackingId": req.TrackingID,
|
|
||||||
"ot_username": req.AuthenticatedUser.Username,
|
|
||||||
}
|
|
||||||
|
|
||||||
finalParsedCommand, err := tpl.ParseTemplateWithActionContext(req.Binding.Action.ShellAfterCompleted, req.Binding.Entity, args)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "Could not prepare shellAfterCompleted command: " + err.Error() + "\n"
|
return fail(req, err)
|
||||||
req.logEntry.Output += msg
|
}
|
||||||
log.Warn(msg)
|
if cmd == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := wrapCommandInShell(ctx, finalParsedCommand)
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
cmd.Env = buildEnv(args)
|
cmd.Env = buildEnv(args)
|
||||||
|
|
||||||
runerr := cmd.Start()
|
runerr := cmd.Start()
|
||||||
|
|
@ -998,6 +1013,43 @@ func stepExecAfter(req *ExecutionRequest) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildShellAfterCommand(ctx context.Context, req *ExecutionRequest, stdout, stderr *bytes.Buffer) (*exec.Cmd, map[string]string, error) {
|
||||||
|
if req.Binding.Action.ShellAfterCompleted == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := buildShellAfterArgs(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalParsedCommand, err := tpl.ParseTemplateWithActionContext(req.Binding.Action.ShellAfterCompleted, req.Binding.Entity, args)
|
||||||
|
if err != nil {
|
||||||
|
msg := "Could not prepare shellAfterCompleted command: " + err.Error() + "\n"
|
||||||
|
req.logEntry.Output += msg
|
||||||
|
log.Warn(msg)
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := wrapCommandInShell(ctx, finalParsedCommand)
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
|
||||||
|
return cmd, args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildShellAfterArgs(req *ExecutionRequest) (map[string]string, error) {
|
||||||
|
args, err := validatedSystemArgs(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args["output"] = req.logEntry.Output
|
||||||
|
args["exitCode"] = fmt.Sprintf("%v", req.logEntry.ExitCode)
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
//gocyclo:ignore
|
//gocyclo:ignore
|
||||||
func stepTrigger(req *ExecutionRequest) bool {
|
func stepTrigger(req *ExecutionRequest) bool {
|
||||||
if req.Binding.Action.Triggers == nil {
|
if req.Binding.Action.Triggers == nil {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package executor
|
package executor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -379,7 +380,7 @@ func TestFilterToDefinedArgumentsOnly(t *testing.T) {
|
||||||
assert.Empty(t, req.Arguments["extra_undefined"])
|
assert.Empty(t, req.Arguments["extra_undefined"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterToDefinedArgumentsPreservesSystemArgs(t *testing.T) {
|
func TestFilterToDefinedArgumentsDropsReservedPrefixArgs(t *testing.T) {
|
||||||
req := newExecRequest()
|
req := newExecRequest()
|
||||||
req.Binding.Action = &config.Action{
|
req.Binding.Action = &config.Action{
|
||||||
Title: "Filter test",
|
Title: "Filter test",
|
||||||
|
|
@ -393,8 +394,209 @@ func TestFilterToDefinedArgumentsPreservesSystemArgs(t *testing.T) {
|
||||||
|
|
||||||
filterToDefinedArgumentsOnly(req)
|
filterToDefinedArgumentsOnly(req)
|
||||||
|
|
||||||
assert.Equal(t, "track-123", req.Arguments["ot_executionTrackingId"])
|
assert.Empty(t, req.Arguments["ot_executionTrackingId"])
|
||||||
assert.Equal(t, "webhook", req.Arguments["ot_username"])
|
assert.Empty(t, req.Arguments["ot_username"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepParseArgsInjectsSystemArgsAfterFiltering(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Filter then inject",
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []config.ActionArgument{
|
||||||
|
{Name: "name", Type: "ascii"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req.Arguments = map[string]string{
|
||||||
|
"name": "Alice",
|
||||||
|
"ot_executionTrackingId": "attacker-track",
|
||||||
|
"ot_username": "mallory",
|
||||||
|
"ot_custom": "polluted",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, stepParseArgs(req))
|
||||||
|
assert.Equal(t, "Alice", req.Arguments["name"])
|
||||||
|
assert.Equal(t, "server-track-456", req.Arguments["ot_executionTrackingId"])
|
||||||
|
assert.Equal(t, "alice", req.Arguments["ot_username"])
|
||||||
|
assert.Empty(t, req.Arguments["ot_custom"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepParseArgsDropsReservedPrefixArgsFromEnvironment(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice@example.com"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "No reserved prefix pollution",
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []config.ActionArgument{},
|
||||||
|
}
|
||||||
|
req.Arguments = map[string]string{
|
||||||
|
"ot_custom": "polluted",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, stepParseArgs(req))
|
||||||
|
env := buildEnv(req.Arguments)
|
||||||
|
|
||||||
|
assert.False(t, containsEnvPrefix(env, "OT_CUSTOM="))
|
||||||
|
assert.True(t, containsEnvPrefix(env, "OT_USERNAME=alice@example.com"))
|
||||||
|
assert.True(t, containsEnvPrefix(env, "OT_EXECUTIONTRACKINGID=server-track-456"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemArgumentDefinitionsAreReservedAndShellSafe(t *testing.T) {
|
||||||
|
unsafeTypes := map[string]struct{}{
|
||||||
|
"email": {},
|
||||||
|
"password": {},
|
||||||
|
"raw_string_multiline": {},
|
||||||
|
"url": {},
|
||||||
|
"very_dangerous_raw_string": {},
|
||||||
|
}
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
|
||||||
|
for _, arg := range systemArgumentDefinitions {
|
||||||
|
assert.True(t, strings.HasPrefix(arg.Name, config.ReservedArgumentNamePrefix))
|
||||||
|
assert.NotEmpty(t, arg.Type)
|
||||||
|
assert.True(t, arg.RejectNull)
|
||||||
|
|
||||||
|
_, duplicate := seen[arg.Name]
|
||||||
|
assert.False(t, duplicate, "duplicate system argument definition %q", arg.Name)
|
||||||
|
seen[arg.Name] = struct{}{}
|
||||||
|
|
||||||
|
_, unsafe := unsafeTypes[arg.Type]
|
||||||
|
assert.False(t, unsafe, "system argument %q uses unsafe type %q", arg.Name, arg.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatedSystemArgsMatchesSystemArgumentDefinitions(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice@example.com"}
|
||||||
|
|
||||||
|
args, err := validatedSystemArgs(req)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, args, len(systemArgumentDefinitions))
|
||||||
|
for _, arg := range systemArgumentDefinitions {
|
||||||
|
assert.Contains(t, args, arg.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildShellAfterArgsOnlyAddsExpectedNonSystemArgs(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{
|
||||||
|
Output: "hello",
|
||||||
|
ExitCode: 7,
|
||||||
|
}
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice@example.com"}
|
||||||
|
req.Binding.Action = &config.Action{ShellAfterCompleted: "echo test"}
|
||||||
|
|
||||||
|
args, err := buildShellAfterArgs(req)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, args, len(systemArgumentDefinitions)+2)
|
||||||
|
assert.Contains(t, args, "output")
|
||||||
|
assert.Contains(t, args, "exitCode")
|
||||||
|
for _, arg := range systemArgumentDefinitions {
|
||||||
|
assert.Contains(t, args, arg.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepParseArgsAllowsEmailUsernameSystemArg(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{}
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice@example.com"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Email username",
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []config.ActionArgument{},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, stepParseArgs(req))
|
||||||
|
assert.Equal(t, "alice@example.com", req.Arguments["ot_username"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepParseArgsFailsWhenUsernameSystemArgIsInvalid(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{}
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice;id"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Invalid system arg",
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []config.ActionArgument{},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, stepParseArgs(req))
|
||||||
|
assert.Contains(t, req.logEntry.Output, `system argument "ot_username" failed validation`)
|
||||||
|
assert.Empty(t, req.Arguments["ot_username"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStepParseArgsFailsWhenTrackingIDSystemArgIsInvalid(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{}
|
||||||
|
req.TrackingID = "track/../../bad"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Invalid tracking ID",
|
||||||
|
Shell: "echo test",
|
||||||
|
Arguments: []config.ActionArgument{},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, stepParseArgs(req))
|
||||||
|
assert.Contains(t, req.logEntry.Output, `system argument "ot_executionTrackingId" failed validation`)
|
||||||
|
assert.Empty(t, req.Arguments["ot_executionTrackingId"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildShellAfterArgsUsesValidatedSystemArgs(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{
|
||||||
|
Output: "hello",
|
||||||
|
ExitCode: 7,
|
||||||
|
}
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice@example.com"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Shell after",
|
||||||
|
ShellAfterCompleted: "echo test",
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := buildShellAfterArgs(req)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "alice@example.com", args["ot_username"])
|
||||||
|
assert.Equal(t, "server-track-456", args["ot_executionTrackingId"])
|
||||||
|
assert.Equal(t, "hello", args["output"])
|
||||||
|
assert.Equal(t, "7", args["exitCode"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildShellAfterArgsFailsWhenSystemArgIsInvalid(t *testing.T) {
|
||||||
|
req := newExecRequest()
|
||||||
|
req.logEntry = &InternalLogEntry{}
|
||||||
|
req.TrackingID = "server-track-456"
|
||||||
|
req.AuthenticatedUser = &authpublic.AuthenticatedUser{Username: "alice;id"}
|
||||||
|
req.Binding.Action = &config.Action{
|
||||||
|
Title: "Shell after invalid username",
|
||||||
|
ShellAfterCompleted: "echo test",
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := buildShellAfterArgs(req)
|
||||||
|
|
||||||
|
assert.Nil(t, args)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), `system argument "ot_username" failed validation`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsEnvPrefix(env []string, prefix string) bool {
|
||||||
|
for _, item := range env {
|
||||||
|
if strings.HasPrefix(item, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTriggerExecutesTriggeredAction(t *testing.T) {
|
func TestTriggerExecutesTriggeredAction(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue