Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions plugins/localxpose/access_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package localxpose

import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"

"github.com/99designs/keyring"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/importer"
"github.com/1Password/shell-plugins/sdk/provision"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

const (
serviceName = "https://loclx.io"
keyringKeyName = "token"
tokenFilePath = "~/.localxpose/.access"
)

func AccessToken() schema.CredentialType {
return schema.CredentialType{
Name: credname.AccessToken,
DocsURL: sdk.URL("https://localxpose.io/docs"),
ManagementURL: sdk.URL("https://localxpose.io/dashboard/access"),
Fields: []schema.CredentialField{
{
Name: fieldname.AccessToken,
MarkdownDescription: "Token used to authenticate to LocalXpose.",
Secret: true,
},
},
DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping),
Importer: importer.TryAll(
importer.TryEnvVarPair(defaultEnvVarMapping),
TryOSKeyring(),
TryAccessTokenFile(),
)}
}

var defaultEnvVarMapping = map[string]sdk.FieldName{
Comment thread
JillRegan marked this conversation as resolved.
"LX_ACCESS_TOKEN": fieldname.AccessToken,
}

func TryAccessTokenFile() sdk.Importer {
return importer.TryFile(tokenFilePath, func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) {
if contents.ToString() == "" {
return
}
out.AddCandidate(sdk.ImportCandidate{
Fields: map[sdk.FieldName]string{
fieldname.AccessToken: contents.ToString(),
},
})
})
}

func TryOSKeyring() sdk.Importer {
return func(ctx context.Context, in sdk.ImportInput, out *sdk.ImportOutput) {
availableBackends := keyring.AvailableBackends()
if len(availableBackends) == 0 {
return
}
for _, backendType := range availableBackends {
attempt := out.NewAttempt(importer.SourceOther(string(backendType), ""))
openKeyring, err := keyring.Open(keyring.Config{
Comment thread
LocalXpose marked this conversation as resolved.
AllowedBackends: []keyring.BackendType{backendType},
KeychainTrustApplication: true,
Comment thread
JillRegan marked this conversation as resolved.
ServiceName: serviceName,
})
if err != nil {
attempt.AddError(err)
return
}
key, err := openKeyring.Get(keyringKeyName)
if err != nil {
attempt.AddError(err)
continue
}

data, err := decode(key.Data)
if err != nil {
attempt.AddError(err)
data = key.Data
}

attempt.AddCandidate(sdk.ImportCandidate{
Fields: map[sdk.FieldName]string{
fieldname.AccessToken: string(data),
},
})
}
}
}

// decode handles keys stored with previous versions of github.com/zalando/go-keyring
Comment thread
JillRegan marked this conversation as resolved.
func decode(keyData []byte) ([]byte, error) {
const (
prefixBase64Encoded = "go-keyring-base64:"
prefixHexEncoded = "go-keyring-encoded:"
)
switch {
case bytes.HasPrefix(keyData, []byte(prefixBase64Encoded)):
return base64.StdEncoding.DecodeString(string(keyData[len(prefixBase64Encoded):]))
case bytes.HasPrefix(keyData, []byte(prefixHexEncoded)):
return hex.DecodeString(string(keyData[len(prefixHexEncoded):]))
default:
return keyData, nil
}
}
53 changes: 53 additions & 0 deletions plugins/localxpose/access_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package localxpose

import (
"testing"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/plugintest"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

func TestAccessTokenProvisioner(t *testing.T) {
plugintest.TestProvisioner(t, AccessToken().DefaultProvisioner, map[string]plugintest.ProvisionCase{
"default": {
ItemFields: map[sdk.FieldName]string{
fieldname.AccessToken: "PROVISIONERqLtcqQ8a3oRVfK5tiHzDOhEXAMPLE",
},
ExpectedOutput: sdk.ProvisionOutput{
Environment: map[string]string{
"LX_ACCESS_TOKEN": "PROVISIONERqLtcqQ8a3oRVfK5tiHzDOhEXAMPLE",
},
},
},
})
}

func TestAccessTokenImporter(t *testing.T) {
expectedFields := map[sdk.FieldName]string{
fieldname.AccessToken: "31QJpgl8FB9qLtcqQ8a3oRVfK5tiHzDOhEXAMPLE",
}

plugintest.TestImporter(t, AccessToken().Importer, map[string]plugintest.ImportCase{
"environment": {
Environment: map[string]string{
"LX_ACCESS_TOKEN": "31QJpgl8FB9qLtcqQ8a3oRVfK5tiHzDOhEXAMPLE",
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: expectedFields,
},
},
},
"file": {
Files: map[string]string{
"~/.localxpose/.access": plugintest.LoadFixture(t, "localxpose.access"),
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: expectedFields,
},
},
},
})
}
36 changes: 36 additions & 0 deletions plugins/localxpose/localxpose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package localxpose

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/needsauth"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
)

func LocalXposeCLI() schema.Executable {
return schema.Executable{
Name: "LocalXpose CLI",
Runs: []string{"loclx"},
DocsURL: sdk.URL("https://localxpose.io/docs/cli"),
NeedsAuth: needsauth.IfAll(
Comment thread
LocalXpose marked this conversation as resolved.
needsauth.NotForHelpOrVersion(),
needsauth.NotForCommand("account", "login"), // skip 1Password authentication for "loclx account login" and its subcommands
needsauth.NotForCommand("a", "login"), // skip 1Password authentication for "loclx account login" and its subcommands
needsauth.NotForCommand("account", "logout"), // skip 1Password authentication for "loclx account logout" and its subcommands
needsauth.NotForCommand("a", "logout"), // skip 1Password authentication for "loclx account logout" and its subcommands
needsauth.NotForCommand("service", "restart"), // skip 1Password authentication for "loclx service restart" and its subcommands
needsauth.NotForCommand("service", "start"), // skip 1Password authentication for "loclx service start" and its subcommands
needsauth.NotForCommand("service", "status"), // skip 1Password authentication for "loclx service status" and its subcommands
needsauth.NotForCommand("service", "stop"), // skip 1Password authentication for "loclx service stop" and its subcommands
needsauth.NotForCommand("service", "uninstall"), // skip 1Password authentication for "loclx service uninstall" and its subcommands
needsauth.NotForCommand("setting"), // skip 1Password authentication for "loclx setting" and its subcommands
needsauth.NotForCommand("update"), // skip 1Password authentication for "loclx update" and its subcommands
needsauth.NotWithoutArgs(),
),
Uses: []schema.CredentialUsage{
{
Name: credname.AccessToken,
},
},
}
}
22 changes: 22 additions & 0 deletions plugins/localxpose/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package localxpose

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/schema"
)

func New() schema.Plugin {
return schema.Plugin{
Name: "localxpose",
Platform: schema.PlatformInfo{
Name: "LocalXpose",
Homepage: sdk.URL("https://localxpose.io"),
},
Credentials: []schema.CredentialType{
AccessToken(),
},
Executables: []schema.Executable{
LocalXposeCLI(),
},
}
}
1 change: 1 addition & 0 deletions plugins/localxpose/test-fixtures/localxpose.access
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
31QJpgl8FB9qLtcqQ8a3oRVfK5tiHzDOhEXAMPLE
Loading