From f6d2ee97ffe8c3bab1c28aff8eb7b0eb5ce54ad9 Mon Sep 17 00:00:00 2001
From: Becca Petrin <beccapetrin@posteo.net>
Date: Tue, 6 Nov 2018 16:47:18 -0800
Subject: [PATCH] support listing at parent plugin catalog

---
 api/sys_plugins.go            | 17 +++++++++++++++++
 vault/logical_system.go       | 22 +++++++++++++++++++++-
 vault/logical_system_paths.go | 20 ++++++++++++++++++++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/api/sys_plugins.go b/api/sys_plugins.go
index fda919449997..b00b314ee5eb 100644
--- a/api/sys_plugins.go
+++ b/api/sys_plugins.go
@@ -20,6 +20,9 @@ type ListPluginsInput struct {
 type ListPluginsResponse struct {
 	// PluginsByType is the list of plugins by type.
 	PluginsByType map[consts.PluginType][]string `json:"types"`
+
+	// NamesDeprecated is the list of names of the plugins.
+	NamesDeprecated []string `json:"names"`
 }
 
 // ListPlugins lists all plugins in the catalog and returns their names as a
@@ -53,6 +56,20 @@ func (c *Sys) ListPlugins(i *ListPluginsInput) (*ListPluginsResponse, error) {
 		return nil, errors.New("data from server response is empty")
 	}
 
+	if resp.StatusCode == 405 {
+		// We received an Unsupported Operation response from Vault, indicating
+		// Vault of an older version that doesn't support the READ method yet.
+		var result struct {
+			Data struct {
+				Keys []string `json:"keys"`
+			} `json:"data"`
+		}
+		if err := resp.DecodeJSON(&result); err != nil {
+			return nil, err
+		}
+		return &ListPluginsResponse{NamesDeprecated: result.Data.Keys}, nil
+	}
+
 	result := &ListPluginsResponse{
 		PluginsByType: make(map[consts.PluginType][]string),
 	}
diff --git a/vault/logical_system.go b/vault/logical_system.go
index a0cf9d15cd12..f042abe43daa 100644
--- a/vault/logical_system.go
+++ b/vault/logical_system.go
@@ -256,7 +256,8 @@ func (b *SystemBackend) handleTidyLeases(ctx context.Context, req *logical.Reque
 }
 
 func (b *SystemBackend) handlePluginCatalogTypedList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
-	pluginType, err := consts.ParsePluginType(d.Get("type").(string))
+	pluginTypeStr := d.Get("type").(string)
+	pluginType, err := consts.ParsePluginType(pluginTypeStr)
 	if err != nil {
 		return nil, err
 	}
@@ -268,6 +269,25 @@ func (b *SystemBackend) handlePluginCatalogTypedList(ctx context.Context, req *l
 	return logical.ListResponse(plugins), nil
 }
 
+func (b *SystemBackend) handlePluginsListDeprecated(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
+	var plugins []string
+	pluginsAdded := make(map[string]bool)
+	for _, pluginType := range consts.PluginTypes {
+		names, err := b.Core.pluginCatalog.List(ctx, pluginType)
+		if err != nil {
+			return nil, err
+		}
+		for _, name := range names {
+			if _, found := pluginsAdded[name]; !found {
+				plugins = append(plugins, name)
+				pluginsAdded[name] = true
+			}
+		}
+	}
+	sort.Strings(plugins)
+	return logical.ListResponse(plugins), nil
+}
+
 func (b *SystemBackend) handlePluginCatalogUntypedList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
 	pluginsByType := make(map[string]interface{})
 	for _, pluginType := range consts.PluginTypes {
diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go
index ab97aa04ebbe..4d7b58547d1b 100644
--- a/vault/logical_system_paths.go
+++ b/vault/logical_system_paths.go
@@ -560,6 +560,26 @@ func (b *SystemBackend) sealPaths() []*framework.Path {
 
 func (b *SystemBackend) pluginsCatalogPaths() []*framework.Path {
 	return []*framework.Path{
+		{
+			Pattern: "plugins/catalog/?$",
+
+			Fields: map[string]*framework.FieldSchema{
+				"type": &framework.FieldSchema{
+					Type:        framework.TypeString,
+					Description: strings.TrimSpace(sysHelp["plugin-catalog_type"][0]),
+				},
+			},
+
+			Operations: map[logical.Operation]framework.OperationHandler{
+				logical.ListOperation: &framework.PathOperation{
+					Callback: b.handlePluginsListDeprecated,
+					Summary:  "List the plugins in the catalog.",
+				},
+			},
+
+			HelpSynopsis:    strings.TrimSpace(sysHelp["plugin-catalog"][0]),
+			HelpDescription: strings.TrimSpace(sysHelp["plugin-catalog"][1]),
+		},
 		{
 			Pattern: "plugins/catalog/(?P<type>auth|database|secret)/?$",