feat: Add UserValue; improvements; improve utils

This commit is contained in:
Myzel394 2024-07-28 15:54:46 +02:00
parent 184cf63411
commit f89dc3ae81
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
7 changed files with 155 additions and 23 deletions

View File

@ -2,6 +2,7 @@ package common
import (
"fmt"
"strings"
)
type ParserError interface {}
@ -33,3 +34,10 @@ func (e LineNotFoundError) Error() string {
return "Line not found"
}
type ValueNotInEnumError struct {
availableValues []string
}
func (e ValueNotInEnumError) Error() string {
return fmt.Sprint("'%s' is not valid. Select one from: %v", strings.Join(e.availableValues, ","))
}

83
common/extra-values.go Normal file
View File

@ -0,0 +1,83 @@
package common
import (
"os"
"strings"
)
type passwdInfo struct {
Name string
UID string
GID string
HomePath string
}
var _cachedPasswdInfo []passwdInfo
func fetchPasswdInfo() ([]passwdInfo, error) {
if len(_cachedPasswdInfo) > 0 {
return _cachedPasswdInfo, nil
}
readBytes, err := os.ReadFile("/etc/passwd")
if err != nil {
return []passwdInfo{}, err
}
lines := strings.Split(string(readBytes), "\n")
infos := make([]passwdInfo, 0)
for _, line := range lines {
splitted := strings.Split(line, ":")
if len(splitted) < 6 {
continue
}
info := passwdInfo{
Name: splitted[0],
UID: splitted[2],
GID: splitted[3],
HomePath: splitted[5],
}
infos = append(infos, info)
}
_cachedPasswdInfo = infos
return infos, nil
}
// UserValue returns a Value that fetches user names from /etc/passwd
// if `separatorForMultiple` is not empty, it will return an ArrayValue
func UserValue(separatorForMultiple string) Value {
return CustomValue{
FetchValue: func() Value {
infos, err := fetchPasswdInfo()
if err != nil {
return StringValue{}
}
enumValues := EnumValue{
Values: Map(infos, func(info passwdInfo) string {
return info.Name
}),
}
if separatorForMultiple == "" {
return enumValues
} else {
return ArrayValue{
AllowDuplicates: false,
SubValue: enumValues,
Separator: separatorForMultiple,
}
}
},
}
}

View File

@ -111,9 +111,9 @@ func (p *SimpleConfigParser) GetOption(option string) (SimpleConfigLine, error)
}
}
func (p *SimpleConfigParser) ParseFromFile(content string) []error {
func (p *SimpleConfigParser) ParseFromFile(content string) []ParserError {
lines := strings.Split(content, "\n")
errors := make([]error, 0)
errors := make([]ParserError, 0)
for index, line := range lines {
if p.Options.IgnorePattern.MatchString(line) {

View File

@ -18,3 +18,14 @@ func GetLine(path string, line int) (string, error) {
return lines[line], nil
}
func Map[T any, O any](values []T, f func(T) O) []O {
result := make([]O, len(values))
for index, value := range values {
result[index] = f(value)
}
return result
}

View File

@ -19,7 +19,7 @@ func ClearDiagnostics(context *glsp.Context, uri protocol.DocumentUri) {
)
}
func SendDiagnosticsFromParserErrors(context *glsp.Context, uri protocol.DocumentUri, parserErrors []error) {
func SendDiagnosticsFromParserErrors(context *glsp.Context, uri protocol.DocumentUri, parserErrors []common.ParserError) {
diagnosticErrors := make([]protocol.Diagnostic, 0)
for _, parserError := range parserErrors {

View File

@ -52,15 +52,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
"AllowUsers": common.NewOption(
`This keyword can be followed by a list of user name patterns, separated by spaces. If specified, login is allowed only for user names that match one of the patterns. Only user names are valid; a numerical user ID is not recognized. By default, login is allowed for all users. If the pattern takes the form USER@HOST then USER and HOST are separately checked, restricting logins to particular users from particular hosts. HOST criteria may additionally contain addresses to match in CIDR address/masklen format. The allow/deny users directives are processed in the following order: DenyUsers, AllowUsers.
See PATTERNS in ssh_config(5) for more information on patterns. This keyword may appear multiple times in sshd_config with each instance appending to the list.`,
common.CustomValue{
FetchValue: func() common.Value {
return common.ArrayValue{
AllowDuplicates: false,
SubValue: common.StringValue{},
Separator: " ",
}
},
},
common.UserValue(" "),
),
"AuthenticationMethods": common.NewOption(
`Specifies the authentication methods that must be successfully completed for a user to be granted access. This option must be followed by one or more lists of comma-separated authentication method names, or by the single string any to indicate the default behaviour of accepting any single authentication method. If the default is overridden, then successful authentication requires completion of every method in at least one of these lists.
@ -107,10 +99,23 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
common.StringValue{},
),
// "AuthorizedKeysCommandUser": `Specifies the user under whose account the AuthorizedKeysCommand is run. It is recommended to use a dedicated user that has no other role on the host than running authorized keys commands. If AuthorizedKeysCommand is specified but AuthorizedKeysCommandUser is not, then sshd(8) will refuse to start.`,
// "AuthorizedKeysFile": `Specifies the file that contains the public keys used for user authentication. The format is described in the AUTHORIZED_KEYS FILE FORMAT section of sshd(8). Arguments to AuthorizedKeysFile accept the tokens described in the “TOKENS” section. After expansion, AuthorizedKeysFile is taken to be an absolute path or one relative to the user's home directory. Multiple files may be listed, separated by whitespace. Alternately this option may be set to none to skip checking for user keys in files. The default is ".ssh/authorized_keys .ssh/authorized_keys2".`,
// "AuthorizedPrincipalsCommand": `Specifies a program to be used to generate the list of allowed certificate principals as per AuthorizedPrincipalsFile. The program must be owned by root, not writable by group or others and specified by an absolute path. Arguments to AuthorizedPrincipalsCommand accept the tokens described in the “TOKENS” section. If no arguments are specified then the username of the target user is used.
// The program should produce on standard output zero or more lines of AuthorizedPrincipalsFile output. If either AuthorizedPrincipalsCommand or AuthorizedPrincipalsFile is specified, then certificates offered by the client for authentication must contain a principal that is listed. By default, no AuthorizedPrincipalsCommand is run.`,
"AuthorizedKeysCommandUser": common.NewOption(
`Specifies the user under whose account the AuthorizedKeysCommand is run. It is recommended to use a dedicated user that has no other role on the host than running authorized keys commands. If AuthorizedKeysCommand is specified but AuthorizedKeysCommandUser is not, then sshd(8) will refuse to start.`,
common.UserValue(""),
),
"AuthorizedKeysFile": common.NewOption(
`Specifies the file that contains the public keys used for user authentication. The format is described in the AUTHORIZED_KEYS FILE FORMAT section of sshd(8). Arguments to AuthorizedKeysFile accept the tokens described in the “TOKENS” section. After expansion, AuthorizedKeysFile is taken to be an absolute path or one relative to the user's home directory. Multiple files may be listed, separated by whitespace. Alternately this option may be set to none to skip checking for user keys in files. The default is ".ssh/authorized_keys .ssh/authorized_keys2".`,
common.ArrayValue{
SubValue: common.StringValue{},
Separator: " ",
AllowDuplicates: false,
},
),
"AuthorizedPrincipalsCommand": common.NewOption(
`Specifies a program to be used to generate the list of allowed certificate principals as per AuthorizedPrincipalsFile. The program must be owned by root, not writable by group or others and specified by an absolute path. Arguments to AuthorizedPrincipalsCommand accept the tokens described in the TOKENS section. If no arguments are specified then the username of the target user is used.
The program should produce on standard output zero or more lines of AuthorizedPrincipalsFile output. If either AuthorizedPrincipalsCommand or AuthorizedPrincipalsFile is specified, then certificates offered by the client for authentication must contain a principal that is listed. By default, no AuthorizedPrincipalsCommand is run.`,
common.StringValue{},
),
// "AuthorizedPrincipalsCommandUser": `Specifies the user under whose account the AuthorizedPrincipalsCommand is run. It is recommended to use a dedicated user that has no other role on the host than running authorized principals commands. If AuthorizedPrincipalsCommand is specified but AuthorizedPrincipalsCommandUser is not, then sshd(8) will refuse to start.`,
// "AuthorizedPrincipalsFile": `Specifies a file that lists principal names that are accepted for certificate authentication. When using certificates signed by a key listed in TrustedUserCAKeys, this file lists names, one of which must appear in the certificate for it to be accepted for authentication. Names are listed one per line preceded by key options (as described in “AUTHORIZED_KEYS FILE FORMAT” in sshd(8)). Empty lines and comments starting with # are ignored.
// Arguments to AuthorizedPrincipalsFile accept the tokens described in the “TOKENS” section. After expansion, AuthorizedPrincipalsFile is taken to be an absolute path or one relative to the user's home directory. The default is none, i.e. not to use a principals file in this case, the username of the user must appear in a certificate's principals list for it to be accepted.

View File

@ -49,15 +49,13 @@ func getRootCompletions() []protocol.CompletionItem {
return completions
}
func getOptionCompletions(optionName string) []protocol.CompletionItem {
option := Options[optionName]
switch option.Value.(type) {
func getCompletionsFromValue(value common.Value) []protocol.CompletionItem {
switch value.(type) {
case common.EnumValue:
enumOption := option.Value.(common.EnumValue)
completions := make([]protocol.CompletionItem, len(option.Value.(common.EnumValue).Values))
enumValue := value.(common.EnumValue)
completions := make([]protocol.CompletionItem, len(value.(common.EnumValue).Values))
for index, value := range enumOption.Values {
for index, value := range enumValue.Values {
textFormat := protocol.InsertTextFormatPlainText
completions[index] = protocol.CompletionItem{
@ -66,9 +64,36 @@ func getOptionCompletions(optionName string) []protocol.CompletionItem {
}
}
return completions
case common.CustomValue:
customValue := value.(common.CustomValue)
val := customValue.FetchValue()
return getCompletionsFromValue(val)
case common.ArrayValue:
arrayValue := value.(common.ArrayValue)
return getCompletionsFromValue(arrayValue.SubValue)
case common.OrValue:
orValue := value.(common.OrValue)
completions := make([]protocol.CompletionItem, 0)
for _, subValue := range orValue.Values {
completions = append(completions, getCompletionsFromValue(subValue)...)
}
return completions
}
return []protocol.CompletionItem{}
}
func getOptionCompletions(optionName string) []protocol.CompletionItem {
option := Options[optionName]
completions := getCompletionsFromValue(option.Value)
return completions
}