-
Notifications
You must be signed in to change notification settings - Fork 157
/
credential_type.go
210 lines (173 loc) · 5.59 KB
/
credential_type.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package schema
import (
"fmt"
"net/url"
"github.com/1Password/shell-plugins/sdk"
)
// CredentialType provides the schema of a credential type that the plugin provides.
type CredentialType struct {
// What the credential is called within the platform, e.g. "API Key" or "Personal Access Token".
Name sdk.CredentialName
// The field(s) on this credential type.
Fields []CredentialField
// (Optional) A URL to the documentation about this credential type.
DocsURL *url.URL
// (Optional) A URL to the dashboard, console, settings, etc. where this credential type can be created and revoked.
ManagementURL *url.URL
// (Optional) A function to scan the system for occurences of this credential type.
Importer sdk.Importer
// The default provisioner to use for this credential if the executable doesn't override it.
DefaultProvisioner sdk.Provisioner
}
// CredentialField provides the schema of a single field on a credential type.
type CredentialField struct {
// The name of the field, e.g. "Token", "Password", or "Username".
Name sdk.FieldName
// Alternative names for this field. Can be used to deprecate field names without breaking existing setups.
// If there are values present for multiple entries, the first match will be chosen.
AlternativeNames []string
// A description of the field.
MarkdownDescription string
// Whether this field is secret and should be concealed where possible.
Secret bool
// Whether this field is optional.
Optional bool
// (Optional) Describes how values of this field look like, such as the length, charset, etc.
Composition *ValueComposition
}
func (c CredentialType) Field(name string) *CredentialField {
for _, field := range c.Fields {
if field.Name.String() == name {
return &field
}
}
return nil
}
// ValueComposition describes what a value for a certain field looks like. This gets used for various purposes,
// including but not limited to the Save in 1Password functionality and secrets scanning functionality.
type ValueComposition struct {
// The length of the value, if it's guaranteed to be of a fixed length.
Length int
// Which characters the value can consist of.
Charset Charset
// (Optional) A certain prefix that's always present on the value, as popularized by GitHub.
Prefix string
}
type Charset struct {
Uppercase bool
Lowercase bool
Digits bool
Symbols bool
Specific []rune
}
func (c CredentialType) Validate() (bool, ValidationReport) {
report := ValidationReport{
Heading: fmt.Sprintf("Credential: %s", c.Name),
Checks: []ValidationCheck{},
}
report.AddCheck(ValidationCheck{
Description: "Has name set",
Assertion: c.Name != "",
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Name is using title case",
Assertion: IsTitleCaseString(c.Name.String()),
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Has documentation URL set",
Assertion: c.DocsURL != nil,
Severity: ValidationSeverityWarning,
})
report.AddCheck(ValidationCheck{
Description: "Has management URL set",
Assertion: c.ManagementURL != nil,
Severity: ValidationSeverityWarning,
})
report.AddCheck(ValidationCheck{
Description: "Has at least 1 field",
Assertion: len(c.Fields) > 0,
Severity: ValidationSeverityError,
})
allFieldsHaveNameSet := true
allFieldsHaveDescriptionSet := true
allFieldsInTitleCase := true
allCompositionsValid := true
hasSecretField := false
for _, f := range c.Fields {
if f.Name == "" {
allFieldsHaveNameSet = false
}
if f.MarkdownDescription == "" {
allFieldsHaveDescriptionSet = false
}
if !IsTitleCaseString(f.Name.String()) {
allFieldsInTitleCase = false
}
comp := f.Composition
if comp != nil {
cs := comp.Charset
if !cs.Lowercase && !cs.Uppercase && !cs.Digits && !cs.Symbols && len(cs.Specific) == 0 {
allCompositionsValid = false
}
}
if f.Secret {
hasSecretField = true
}
}
report.AddCheck(ValidationCheck{
Description: "All fields have name set",
Assertion: allFieldsHaveNameSet,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "All field names are using title case",
Assertion: allFieldsInTitleCase,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "All fields have a description set",
Assertion: allFieldsHaveDescriptionSet,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "All specified value compositions are valid",
Assertion: allCompositionsValid,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Has at least 1 field that is secret",
Assertion: hasSecretField,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Has no duplicate field names",
Assertion: c.hasNoDuplicateFieldNames(),
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Has a provisioner set",
Assertion: c.DefaultProvisioner != nil,
Severity: ValidationSeverityError,
})
report.AddCheck(ValidationCheck{
Description: "Has an importer set",
Assertion: c.Importer != nil,
Severity: ValidationSeverityWarning,
})
return report.IsValid(), report
}
func (c CredentialType) hasNoDuplicateFieldNames() bool {
allFieldNames := make(map[string]struct{})
for _, f := range c.Fields {
for _, name := range append(f.AlternativeNames, f.Name.String()) {
if _, found := allFieldNames[name]; !found {
allFieldNames[name] = struct{}{}
} else {
return false
}
}
}
return true
}