mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	OSS: Adding UI handlers and configurable headers (#390)
* adding UI handlers and UI header configuration * forcing specific static headers * properly getting UI config value from config/environment * fixing formatting in stub UI text * use http.Header * case-insensitive X-Vault header check * fixing var name * wrap both stubbed and real UI in header handler * adding test for >1 keys
This commit is contained in:
		 Chris Hoffman
					Chris Hoffman
				
			
				
					committed by
					
						 Matthew Irish
						Matthew Irish
					
				
			
			
				
	
			
			
			 Matthew Irish
						Matthew Irish
					
				
			
						parent
						
							2c2f0d853f
						
					
				
				
					commit
					af33ece136
				
			
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -65,6 +65,7 @@ tags | |||||||
| ui/dist | ui/dist | ||||||
| ui/tmp | ui/tmp | ||||||
| ui/root | ui/root | ||||||
|  | http/bindata_assetfs.go | ||||||
|  |  | ||||||
| # dependencies | # dependencies | ||||||
| ui/node_modules | ui/node_modules | ||||||
|   | |||||||
| @@ -452,6 +452,7 @@ func (c *ServerCommand) Run(args []string) int { | |||||||
| 		ClusterName:        config.ClusterName, | 		ClusterName:        config.ClusterName, | ||||||
| 		CacheSize:          config.CacheSize, | 		CacheSize:          config.CacheSize, | ||||||
| 		PluginDirectory:    config.PluginDirectory, | 		PluginDirectory:    config.PluginDirectory, | ||||||
|  | 		EnableUI:           config.EnableUI, | ||||||
| 		EnableRaw:          config.EnableRawEndpoint, | 		EnableRaw:          config.EnableRawEndpoint, | ||||||
| 	} | 	} | ||||||
| 	if c.flagDev { | 	if c.flagDev { | ||||||
| @@ -607,6 +608,16 @@ CLUSTER_SYNTHESIS_COMPLETE: | |||||||
| 		coreConfig.ClusterAddr = u.String() | 		coreConfig.ClusterAddr = u.String() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Override the UI enabling config by the environment variable | ||||||
|  | 	if enableUI := os.Getenv("VAULT_UI"); enableUI != "" { | ||||||
|  | 		var err error | ||||||
|  | 		coreConfig.EnableUI, err = strconv.ParseBool(enableUI) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.UI.Output("Error parsing the environment variable VAULT_UI") | ||||||
|  | 			return 1 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Initialize the core | 	// Initialize the core | ||||||
| 	core, newCoreError := vault.NewCore(coreConfig) | 	core, newCoreError := vault.NewCore(coreConfig) | ||||||
| 	if newCoreError != nil { | 	if newCoreError != nil { | ||||||
|   | |||||||
| @@ -6,9 +6,11 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/elazarl/go-bindata-assetfs" | ||||||
| 	"github.com/hashicorp/errwrap" | 	"github.com/hashicorp/errwrap" | ||||||
| 	cleanhttp "github.com/hashicorp/go-cleanhttp" | 	cleanhttp "github.com/hashicorp/go-cleanhttp" | ||||||
| 	"github.com/hashicorp/vault/helper/consts" | 	"github.com/hashicorp/vault/helper/consts" | ||||||
| @@ -54,6 +56,9 @@ const ( | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	ReplicationStaleReadTimeout = 2 * time.Second | 	ReplicationStaleReadTimeout = 2 * time.Second | ||||||
|  |  | ||||||
|  | 	// Set to false by stub_asset if the ui build tag isn't enabled | ||||||
|  | 	uiBuiltIn = true | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Handler returns an http.Handler for the API. This can be used on | // Handler returns an http.Handler for the API. This can be used on | ||||||
| @@ -82,6 +87,14 @@ func Handler(core *vault.Core) http.Handler { | |||||||
| 	} | 	} | ||||||
| 	mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, false, nil))) | 	mux.Handle("/v1/sys/", handleRequestForwarding(core, handleLogical(core, false, nil))) | ||||||
| 	mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil))) | 	mux.Handle("/v1/", handleRequestForwarding(core, handleLogical(core, false, nil))) | ||||||
|  | 	if core.UIEnabled() == true { | ||||||
|  | 		if uiBuiltIn { | ||||||
|  | 			mux.Handle("/ui/", http.StripPrefix("/ui/", handleUIHeaders(core, handleUI(http.FileServer(&UIAssetWrapper{FileSystem: assetFS()}))))) | ||||||
|  | 		} else { | ||||||
|  | 			mux.Handle("/ui/", handleUIHeaders(core, handleUIStub())) | ||||||
|  | 		} | ||||||
|  | 		mux.Handle("/", handleRootRedirect()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Wrap the handler in another handler to trigger all help paths. | 	// Wrap the handler in another handler to trigger all help paths. | ||||||
| 	helpWrappedHandler := wrapHelpHandler(mux, core) | 	helpWrappedHandler := wrapHelpHandler(mux, core) | ||||||
| @@ -145,6 +158,72 @@ func stripPrefix(prefix, path string) (string, bool) { | |||||||
| 	return path, true | 	return path, true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func handleUIHeaders(core *vault.Core, h http.Handler) http.Handler { | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 		header := w.Header() | ||||||
|  |  | ||||||
|  | 		userHeaders, err := core.UIHeaders() | ||||||
|  | 		if err != nil { | ||||||
|  | 			respondError(w, http.StatusInternalServerError, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if userHeaders != nil { | ||||||
|  | 			for k := range userHeaders { | ||||||
|  | 				v := userHeaders.Get(k) | ||||||
|  | 				header.Set(k, v) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		h.ServeHTTP(w, req) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleUI(h http.Handler) http.Handler { | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 		h.ServeHTTP(w, req) | ||||||
|  | 		return | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleUIStub() http.Handler { | ||||||
|  | 	stubHTML := ` | ||||||
|  | 	<!DOCTYPE html> | ||||||
|  | 	<html> | ||||||
|  | 	<p>Vault UI is not available in this binary. To get Vault UI do one of the following:</p> | ||||||
|  | 	<ul> | ||||||
|  | 	<li><a href="https://www.vaultproject.io/downloads.html">Download an official release</a></li> | ||||||
|  | 	<li>Run <code>make release</code> to create your own release binaries. | ||||||
|  | 	<li>Run <code>make dev-ui</code> to create a development binary with the UI. | ||||||
|  | 	</ul> | ||||||
|  | 	</html> | ||||||
|  | 	` | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 		w.Write([]byte(stubHTML)) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleRootRedirect() http.Handler { | ||||||
|  | 	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 		http.Redirect(w, req, "/ui/", 307) | ||||||
|  | 		return | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type UIAssetWrapper struct { | ||||||
|  | 	FileSystem *assetfs.AssetFS | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fs *UIAssetWrapper) Open(name string) (http.File, error) { | ||||||
|  | 	file, err := fs.FileSystem.Open(name) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return file, nil | ||||||
|  | 	} | ||||||
|  | 	// serve index.html instead of 404ing | ||||||
|  | 	if err == os.ErrNotExist { | ||||||
|  | 		return fs.FileSystem.Open("index.html") | ||||||
|  | 	} | ||||||
|  | 	return nil, err | ||||||
|  | } | ||||||
|  |  | ||||||
| func parseRequest(r *http.Request, w http.ResponseWriter, out interface{}) error { | func parseRequest(r *http.Request, w http.ResponseWriter, out interface{}) error { | ||||||
| 	// Limit the maximum number of bytes to MaxRequestSize to protect | 	// Limit the maximum number of bytes to MaxRequestSize to protect | ||||||
| 	// against an indefinite amount of data being read. | 	// against an indefinite amount of data being read. | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								http/stub_assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								http/stub_assets.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | // +build !ui | ||||||
|  |  | ||||||
|  | package http | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	assetfs "github.com/elazarl/go-bindata-assetfs" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	uiBuiltIn = false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // assetFS is a stub for building Vault without a UI. | ||||||
|  | func assetFS() *assetfs.AssetFS { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -363,8 +363,8 @@ type Core struct { | |||||||
| 	replicationState           *uint32 | 	replicationState           *uint32 | ||||||
| 	activeNodeReplicationState *uint32 | 	activeNodeReplicationState *uint32 | ||||||
|  |  | ||||||
| 	// uiEnabled indicates whether Vault Web UI is enabled or not | 	// uiConfig contains UI configuration | ||||||
| 	uiEnabled bool | 	uiConfig *UIConfig | ||||||
|  |  | ||||||
| 	// rawEnabled indicates whether the Raw endpoint is enabled | 	// rawEnabled indicates whether the Raw endpoint is enabled | ||||||
| 	rawEnabled bool | 	rawEnabled bool | ||||||
| @@ -620,6 +620,9 @@ func NewCore(conf *CoreConfig) (*Core, error) { | |||||||
| 	} | 	} | ||||||
| 	c.auditBackends = auditBackends | 	c.auditBackends = auditBackends | ||||||
|  |  | ||||||
|  | 	uiStoragePrefix := systemBarrierPrefix + "ui" | ||||||
|  | 	c.uiConfig = NewUIConfig(conf.EnableUI, physical.NewView(c.physical, uiStoragePrefix), NewBarrierView(c.barrier, uiStoragePrefix)) | ||||||
|  |  | ||||||
| 	return c, nil | 	return c, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1510,6 +1513,16 @@ func (c *Core) StepDown(req *logical.Request) (retErr error) { | |||||||
| 	return retErr | 	return retErr | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // UIEnabled returns if the UI is enabled | ||||||
|  | func (c *Core) UIEnabled() bool { | ||||||
|  | 	return c.uiConfig.Enabled() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UIHeaders returns configured UI headers | ||||||
|  | func (c *Core) UIHeaders() (http.Header, error) { | ||||||
|  | 	return c.uiConfig.Headers(context.Background()) | ||||||
|  | } | ||||||
|  |  | ||||||
| // sealInternal is an internal method used to seal the vault.  It does not do | // sealInternal is an internal method used to seal the vault.  It does not do | ||||||
| // any authorization checking. The stateLock must be held prior to calling. | // any authorization checking. The stateLock must be held prior to calling. | ||||||
| func (c *Core) sealInternal(keepLock bool) error { | func (c *Core) sealInternal(keepLock bool) error { | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"hash" | 	"hash" | ||||||
|  | 	"net/http" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -77,6 +78,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend { | |||||||
| 				"rotate", | 				"rotate", | ||||||
| 				"config/cors", | 				"config/cors", | ||||||
| 				"config/auditing/*", | 				"config/auditing/*", | ||||||
|  | 				"config/ui/headers/*", | ||||||
| 				"plugins/catalog/*", | 				"plugins/catalog/*", | ||||||
| 				"revoke-prefix/*", | 				"revoke-prefix/*", | ||||||
| 				"revoke-force/*", | 				"revoke-force/*", | ||||||
| @@ -148,6 +150,41 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend { | |||||||
| 				HelpSynopsis:    strings.TrimSpace(sysHelp["config/cors"][1]), | 				HelpSynopsis:    strings.TrimSpace(sysHelp["config/cors"][1]), | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
|  | 			&framework.Path{ | ||||||
|  | 				Pattern: "config/ui/headers/" + framework.GenericNameRegex("header"), | ||||||
|  |  | ||||||
|  | 				Fields: map[string]*framework.FieldSchema{ | ||||||
|  | 					"header": &framework.FieldSchema{ | ||||||
|  | 						Type:        framework.TypeString, | ||||||
|  | 						Description: "The name of the header.", | ||||||
|  | 					}, | ||||||
|  | 					"values": &framework.FieldSchema{ | ||||||
|  | 						Type:        framework.TypeStringSlice, | ||||||
|  | 						Description: "The values to set the header.", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  |  | ||||||
|  | 				Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||||
|  | 					logical.ReadOperation:   b.handleConfigUIHeadersRead, | ||||||
|  | 					logical.UpdateOperation: b.handleConfigUIHeadersUpdate, | ||||||
|  | 					logical.DeleteOperation: b.handleConfigUIHeadersDelete, | ||||||
|  | 				}, | ||||||
|  |  | ||||||
|  | 				HelpDescription: strings.TrimSpace(sysHelp["config/ui/headers"][0]), | ||||||
|  | 				HelpSynopsis:    strings.TrimSpace(sysHelp["config/ui/headers"][1]), | ||||||
|  | 			}, | ||||||
|  |  | ||||||
|  | 			&framework.Path{ | ||||||
|  | 				Pattern: "config/ui/headers/$", | ||||||
|  |  | ||||||
|  | 				Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||||
|  | 					logical.ListOperation: b.handleConfigUIHeadersList, | ||||||
|  | 				}, | ||||||
|  |  | ||||||
|  | 				HelpDescription: strings.TrimSpace(sysHelp["config/ui/headers"][0]), | ||||||
|  | 				HelpSynopsis:    strings.TrimSpace(sysHelp["config/ui/headers"][1]), | ||||||
|  | 			}, | ||||||
|  |  | ||||||
| 			&framework.Path{ | 			&framework.Path{ | ||||||
| 				Pattern: "capabilities$", | 				Pattern: "capabilities$", | ||||||
|  |  | ||||||
| @@ -2699,6 +2736,68 @@ func (b *SystemBackend) handleDisableAudit(ctx context.Context, req *logical.Req | |||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *SystemBackend) handleConfigUIHeadersRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	header := data.Get("header").(string) | ||||||
|  |  | ||||||
|  | 	value, err := b.Core.uiConfig.GetHeader(ctx, header) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if value == "" { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &logical.Response{ | ||||||
|  | 		Data: map[string]interface{}{ | ||||||
|  | 			"value": value, | ||||||
|  | 		}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SystemBackend) handleConfigUIHeadersList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	headers, err := b.Core.uiConfig.HeaderKeys(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if len(headers) == 0 { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return logical.ListResponse(headers), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SystemBackend) handleConfigUIHeadersUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	header := data.Get("header").(string) | ||||||
|  | 	values := data.Get("values").([]string) | ||||||
|  | 	if header == "" || len(values) == 0 { | ||||||
|  | 		return logical.ErrorResponse("header and values must be specified"), logical.ErrInvalidRequest | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if strings.HasPrefix(strings.ToLower(header), "x-vault-") { | ||||||
|  | 		return logical.ErrorResponse("X-Vault headers cannot be set"), logical.ErrInvalidRequest | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Translate the list of values to the valid header string | ||||||
|  | 	value := http.Header{ | ||||||
|  | 		header: values, | ||||||
|  | 	} | ||||||
|  | 	err := b.Core.uiConfig.SetHeader(ctx, header, value.Get(header)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SystemBackend) handleConfigUIHeadersDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
|  | 	header := data.Get("header").(string) | ||||||
|  | 	err := b.Core.uiConfig.DeleteHeader(ctx, header) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // handleRawRead is used to read directly from the barrier | // handleRawRead is used to read directly from the barrier | ||||||
| func (b *SystemBackend) handleRawRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | func (b *SystemBackend) handleRawRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||||||
| 	path := data.Get("path").(string) | 	path := data.Get("path").(string) | ||||||
| @@ -3332,6 +3431,21 @@ This path responds to the following HTTP methods. | |||||||
|         Clears the CORS configuration and disables acceptance of CORS requests. |         Clears the CORS configuration and disables acceptance of CORS requests. | ||||||
| 		`, | 		`, | ||||||
| 	}, | 	}, | ||||||
|  | 	"config/ui/headers": { | ||||||
|  | 		"Configures response headers that should be returned from the UI.", | ||||||
|  | 		` | ||||||
|  | This path responds to the following HTTP methods. | ||||||
|  |     GET /<header> | ||||||
|  |         Returns the header value. | ||||||
|  |     POST /<header> | ||||||
|  |         Sets the header value for the UI. | ||||||
|  |     DELETE /<header> | ||||||
|  |         Clears the header value for UI. | ||||||
|  |          | ||||||
|  |     LIST / | ||||||
|  |         List the headers configured for the UI. | ||||||
|  |         `, | ||||||
|  | 	}, | ||||||
| 	"init": { | 	"init": { | ||||||
| 		"Initializes or returns the initialization status of the Vault.", | 		"Initializes or returns the initialization status of the Vault.", | ||||||
| 		` | 		` | ||||||
|   | |||||||
							
								
								
									
										226
									
								
								vault/ui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								vault/ui.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,226 @@ | |||||||
|  | package vault | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/logical" | ||||||
|  | 	"github.com/hashicorp/vault/physical" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	uiConfigKey          = "config" | ||||||
|  | 	uiConfigPlaintextKey = "config_plaintext" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	staticHeaders = http.Header{ | ||||||
|  | 		"Content-Security-Policy": { | ||||||
|  | 			"default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // UIConfig contains UI configuration. This takes both a physical view and a barrier view | ||||||
|  | // because it is stored in both plaintext and encrypted to allow for getting the header | ||||||
|  | // values before the barrier is unsealed | ||||||
|  | type UIConfig struct { | ||||||
|  | 	l               sync.RWMutex | ||||||
|  | 	physicalStorage physical.Backend | ||||||
|  | 	barrierStorage  logical.Storage | ||||||
|  |  | ||||||
|  | 	enabled bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewUIConfig creates a new UI config | ||||||
|  | func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig { | ||||||
|  | 	return &UIConfig{ | ||||||
|  | 		physicalStorage: physicalStorage, | ||||||
|  | 		barrierStorage:  barrierStorage, | ||||||
|  | 		enabled:         enabled, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Enabled returns if the UI is enabled | ||||||
|  | func (c *UIConfig) Enabled() bool { | ||||||
|  | 	c.l.RLock() | ||||||
|  | 	defer c.l.RUnlock() | ||||||
|  | 	return c.enabled | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Headers returns the response headers that should be returned in the UI | ||||||
|  | func (c *UIConfig) Headers(ctx context.Context) (http.Header, error) { | ||||||
|  | 	c.l.RLock() | ||||||
|  | 	defer c.l.RUnlock() | ||||||
|  |  | ||||||
|  | 	config, err := c.get(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	headers := make(http.Header) | ||||||
|  | 	if config != nil { | ||||||
|  | 		headers = config.Headers | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for k := range staticHeaders { | ||||||
|  | 		v := staticHeaders.Get(k) | ||||||
|  | 		headers.Set(k, v) | ||||||
|  | 	} | ||||||
|  | 	return headers, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HeaderKeys returns the list of the configured headers | ||||||
|  | func (c *UIConfig) HeaderKeys(ctx context.Context) ([]string, error) { | ||||||
|  | 	c.l.RLock() | ||||||
|  | 	defer c.l.RUnlock() | ||||||
|  |  | ||||||
|  | 	config, err := c.get(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if config == nil { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	var keys []string | ||||||
|  | 	for k := range config.Headers { | ||||||
|  | 		keys = append(keys, k) | ||||||
|  | 	} | ||||||
|  | 	return keys, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetHeader retrieves the configured value for the given header | ||||||
|  | func (c *UIConfig) GetHeader(ctx context.Context, header string) (string, error) { | ||||||
|  | 	c.l.RLock() | ||||||
|  | 	defer c.l.RUnlock() | ||||||
|  |  | ||||||
|  | 	config, err := c.get(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if config == nil { | ||||||
|  | 		return "", nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	value := config.Headers.Get(header) | ||||||
|  | 	return value, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetHeader sets the value for the given header | ||||||
|  | func (c *UIConfig) SetHeader(ctx context.Context, header, value string) error { | ||||||
|  | 	if val := staticHeaders.Get(header); val != "" { | ||||||
|  | 		return fmt.Errorf("the header %s is not settable", header) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.l.Lock() | ||||||
|  | 	defer c.l.Unlock() | ||||||
|  |  | ||||||
|  | 	config, err := c.get(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if config == nil { | ||||||
|  | 		config = &uiConfigEntry{ | ||||||
|  | 			Headers: http.Header{ | ||||||
|  | 				header: {value}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		config.Headers.Set(header, value) | ||||||
|  | 	} | ||||||
|  | 	return c.save(ctx, config) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteHeader deletes the header configuration for the given header | ||||||
|  | func (c *UIConfig) DeleteHeader(ctx context.Context, header string) error { | ||||||
|  | 	c.l.Lock() | ||||||
|  | 	defer c.l.Unlock() | ||||||
|  |  | ||||||
|  | 	config, err := c.get(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if config == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config.Headers.Del(header) | ||||||
|  | 	return c.save(ctx, config) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *UIConfig) get(ctx context.Context) (*uiConfigEntry, error) { | ||||||
|  | 	// Read plaintext always to ensure in sync with barrier value | ||||||
|  | 	plaintextConfigRaw, err := c.physicalStorage.Get(ctx, uiConfigPlaintextKey) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	configRaw, err := c.barrierStorage.Get(ctx, uiConfigKey) | ||||||
|  | 	if err == nil { | ||||||
|  | 		if configRaw == nil { | ||||||
|  | 			return nil, nil | ||||||
|  | 		} | ||||||
|  | 		config := new(uiConfigEntry) | ||||||
|  | 		if err := json.Unmarshal(configRaw.Value, config); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		// Check that plaintext value matches barrier value, if not sync values | ||||||
|  | 		if plaintextConfigRaw == nil || bytes.Compare(plaintextConfigRaw.Value, configRaw.Value) != 0 { | ||||||
|  | 			if err := c.save(ctx, config); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return config, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Respond with error if not sealed | ||||||
|  | 	if !strings.Contains(err.Error(), ErrBarrierSealed.Error()) { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Respond with plaintext value | ||||||
|  | 	if configRaw == nil { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	config := new(uiConfigEntry) | ||||||
|  | 	if err := json.Unmarshal(plaintextConfigRaw.Value, config); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return config, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *UIConfig) save(ctx context.Context, config *uiConfigEntry) error { | ||||||
|  | 	if len(config.Headers) == 0 { | ||||||
|  | 		if err := c.physicalStorage.Delete(ctx, uiConfigPlaintextKey); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		return c.barrierStorage.Delete(ctx, uiConfigKey) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	configRaw, err := json.Marshal(config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	entry := &physical.Entry{ | ||||||
|  | 		Key:   uiConfigPlaintextKey, | ||||||
|  | 		Value: configRaw, | ||||||
|  | 	} | ||||||
|  | 	if err := c.physicalStorage.Put(ctx, entry); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	barrEntry := &logical.StorageEntry{ | ||||||
|  | 		Key:   uiConfigKey, | ||||||
|  | 		Value: configRaw, | ||||||
|  | 	} | ||||||
|  | 	return c.barrierStorage.Put(ctx, barrEntry) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type uiConfigEntry struct { | ||||||
|  | 	Headers http.Header `json:"headers"` | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								vault/ui_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								vault/ui_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | package vault | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/logical" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/helper/logformat" | ||||||
|  | 	"github.com/hashicorp/vault/physical/inmem" | ||||||
|  | 	log "github.com/mgutz/logxi/v1" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestConfig_Enabled(t *testing.T) { | ||||||
|  | 	logger := logformat.NewVaultLogger(log.LevelTrace) | ||||||
|  | 	phys, err := inmem.NewTransactionalInmem(nil, logger) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	logl := &logical.InmemStorage{} | ||||||
|  |  | ||||||
|  | 	config := NewUIConfig(true, phys, logl) | ||||||
|  | 	if !config.Enabled() { | ||||||
|  | 		t.Fatal("ui should be enabled") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config = NewUIConfig(false, phys, logl) | ||||||
|  | 	if config.Enabled() { | ||||||
|  | 		t.Fatal("ui should not be enabled") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestConfig_Headers(t *testing.T) { | ||||||
|  | 	logger := logformat.NewVaultLogger(log.LevelTrace) | ||||||
|  | 	phys, err := inmem.NewTransactionalInmem(nil, logger) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	logl := &logical.InmemStorage{} | ||||||
|  |  | ||||||
|  | 	config := NewUIConfig(true, phys, logl) | ||||||
|  | 	headers, err := config.Headers(context.Background()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if len(headers) != len(staticHeaders) { | ||||||
|  | 		t.Fatalf("expected %d headers, got %d", len(staticHeaders), len(headers)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	head, err := config.GetHeader(context.Background(), "Test-Header") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if head != "" { | ||||||
|  | 		t.Fatal("header returned found, should not be found") | ||||||
|  | 	} | ||||||
|  | 	err = config.SetHeader(context.Background(), "Test-Header", "123") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	head, err = config.GetHeader(context.Background(), "Test-Header") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if head == "" { | ||||||
|  | 		t.Fatal("header not found when it should be") | ||||||
|  | 	} | ||||||
|  | 	if head != "123" { | ||||||
|  | 		t.Fatalf("expected: %s, got: %s", "123", head) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	head, err = config.GetHeader(context.Background(), "tEST-hEADER") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if head == "" { | ||||||
|  | 		t.Fatal("header not found when it should be") | ||||||
|  | 	} | ||||||
|  | 	if head != "123" { | ||||||
|  | 		t.Fatalf("expected: %s, got: %s", "123", head) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	keys, err := config.HeaderKeys(context.Background()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if len(keys) != 1 { | ||||||
|  | 		t.Fatalf("expected 1 key, got %d", len(keys)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = config.SetHeader(context.Background(), "Test-Header-2", "321") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	keys, err = config.HeaderKeys(context.Background()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if len(keys) != 2 { | ||||||
|  | 		t.Fatalf("expected 1 key, got %d", len(keys)) | ||||||
|  | 	} | ||||||
|  | 	err = config.DeleteHeader(context.Background(), "Test-Header-2") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = config.DeleteHeader(context.Background(), "Test-Header") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	head, err = config.GetHeader(context.Background(), "Test-Header") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if head != "" { | ||||||
|  | 		t.Fatal("header returned found, should not be found") | ||||||
|  | 	} | ||||||
|  | 	keys, err = config.HeaderKeys(context.Background()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("err: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if len(keys) != 0 { | ||||||
|  | 		t.Fatalf("expected 0 key, got %d", len(keys)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										23
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | Copyright (c) 2014, Elazar Leibovich | ||||||
|  | All rights reserved. | ||||||
|  |  | ||||||
|  | Redistribution and use in source and binary forms, with or without | ||||||
|  | modification, are permitted provided that the following conditions are met: | ||||||
|  |  | ||||||
|  | * Redistributions of source code must retain the above copyright notice, this | ||||||
|  |   list of conditions and the following disclaimer. | ||||||
|  |  | ||||||
|  | * Redistributions in binary form must reproduce the above copyright notice, | ||||||
|  |   this list of conditions and the following disclaimer in the documentation | ||||||
|  |   and/or other materials provided with the distribution. | ||||||
|  |  | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||||
|  | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||||
|  | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||||
|  | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||||
|  | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||||
|  | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||||
|  | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||||
|  | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										46
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | # go-bindata-assetfs | ||||||
|  |  | ||||||
|  | Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`. | ||||||
|  |  | ||||||
|  | [GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs) | ||||||
|  |  | ||||||
|  | ### Installation | ||||||
|  |  | ||||||
|  | Install with | ||||||
|  |  | ||||||
|  |     $ go get github.com/jteeuwen/go-bindata/... | ||||||
|  |     $ go get github.com/elazarl/go-bindata-assetfs/... | ||||||
|  |  | ||||||
|  | ### Creating embedded data | ||||||
|  |  | ||||||
|  | Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage, | ||||||
|  | instead of running `go-bindata` run `go-bindata-assetfs`. | ||||||
|  |  | ||||||
|  | The tool will create a `bindata_assetfs.go` file, which contains the embedded data. | ||||||
|  |  | ||||||
|  | A typical use case is | ||||||
|  |  | ||||||
|  |     $ go-bindata-assetfs data/... | ||||||
|  |  | ||||||
|  | ### Using assetFS in your code | ||||||
|  |  | ||||||
|  | The generated file provides an `assetFS()` function that returns a `http.Filesystem` | ||||||
|  | wrapping the embedded files. What you usually want to do is: | ||||||
|  |  | ||||||
|  |     http.Handle("/", http.FileServer(assetFS())) | ||||||
|  |  | ||||||
|  | This would run an HTTP server serving the embedded files. | ||||||
|  |  | ||||||
|  | ## Without running binary tool | ||||||
|  |  | ||||||
|  | You can always just run the `go-bindata` tool, and then | ||||||
|  |  | ||||||
|  | use | ||||||
|  |  | ||||||
|  |      import "github.com/elazarl/go-bindata-assetfs" | ||||||
|  |      ... | ||||||
|  |      http.Handle("/", | ||||||
|  |         http.FileServer( | ||||||
|  |         &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data"})) | ||||||
|  |  | ||||||
|  | to serve files embedded from the `data` directory. | ||||||
							
								
								
									
										167
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | package assetfs | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	defaultFileTimestamp = time.Now() | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FakeFile implements os.FileInfo interface for a given path and size | ||||||
|  | type FakeFile struct { | ||||||
|  | 	// Path is the path of this file | ||||||
|  | 	Path string | ||||||
|  | 	// Dir marks of the path is a directory | ||||||
|  | 	Dir bool | ||||||
|  | 	// Len is the length of the fake file, zero if it is a directory | ||||||
|  | 	Len int64 | ||||||
|  | 	// Timestamp is the ModTime of this file | ||||||
|  | 	Timestamp time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) Name() string { | ||||||
|  | 	_, name := filepath.Split(f.Path) | ||||||
|  | 	return name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) Mode() os.FileMode { | ||||||
|  | 	mode := os.FileMode(0644) | ||||||
|  | 	if f.Dir { | ||||||
|  | 		return mode | os.ModeDir | ||||||
|  | 	} | ||||||
|  | 	return mode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) ModTime() time.Time { | ||||||
|  | 	return f.Timestamp | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) Size() int64 { | ||||||
|  | 	return f.Len | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) IsDir() bool { | ||||||
|  | 	return f.Mode().IsDir() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *FakeFile) Sys() interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AssetFile implements http.File interface for a no-directory file with content | ||||||
|  | type AssetFile struct { | ||||||
|  | 	*bytes.Reader | ||||||
|  | 	io.Closer | ||||||
|  | 	FakeFile | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile { | ||||||
|  | 	if timestamp.IsZero() { | ||||||
|  | 		timestamp = defaultFileTimestamp | ||||||
|  | 	} | ||||||
|  | 	return &AssetFile{ | ||||||
|  | 		bytes.NewReader(content), | ||||||
|  | 		ioutil.NopCloser(nil), | ||||||
|  | 		FakeFile{name, false, int64(len(content)), timestamp}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) { | ||||||
|  | 	return nil, errors.New("not a directory") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *AssetFile) Size() int64 { | ||||||
|  | 	return f.FakeFile.Size() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *AssetFile) Stat() (os.FileInfo, error) { | ||||||
|  | 	return f, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AssetDirectory implements http.File interface for a directory | ||||||
|  | type AssetDirectory struct { | ||||||
|  | 	AssetFile | ||||||
|  | 	ChildrenRead int | ||||||
|  | 	Children     []os.FileInfo | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory { | ||||||
|  | 	fileinfos := make([]os.FileInfo, 0, len(children)) | ||||||
|  | 	for _, child := range children { | ||||||
|  | 		_, err := fs.AssetDir(filepath.Join(name, child)) | ||||||
|  | 		fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}}) | ||||||
|  | 	} | ||||||
|  | 	return &AssetDirectory{ | ||||||
|  | 		AssetFile{ | ||||||
|  | 			bytes.NewReader(nil), | ||||||
|  | 			ioutil.NopCloser(nil), | ||||||
|  | 			FakeFile{name, true, 0, time.Time{}}, | ||||||
|  | 		}, | ||||||
|  | 		0, | ||||||
|  | 		fileinfos} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) { | ||||||
|  | 	if count <= 0 { | ||||||
|  | 		return f.Children, nil | ||||||
|  | 	} | ||||||
|  | 	if f.ChildrenRead+count > len(f.Children) { | ||||||
|  | 		count = len(f.Children) - f.ChildrenRead | ||||||
|  | 	} | ||||||
|  | 	rv := f.Children[f.ChildrenRead : f.ChildrenRead+count] | ||||||
|  | 	f.ChildrenRead += count | ||||||
|  | 	return rv, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *AssetDirectory) Stat() (os.FileInfo, error) { | ||||||
|  | 	return f, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AssetFS implements http.FileSystem, allowing | ||||||
|  | // embedded files to be served from net/http package. | ||||||
|  | type AssetFS struct { | ||||||
|  | 	// Asset should return content of file in path if exists | ||||||
|  | 	Asset func(path string) ([]byte, error) | ||||||
|  | 	// AssetDir should return list of files in the path | ||||||
|  | 	AssetDir func(path string) ([]string, error) | ||||||
|  | 	// AssetInfo should return the info of file in path if exists | ||||||
|  | 	AssetInfo func(path string) (os.FileInfo, error) | ||||||
|  | 	// Prefix would be prepended to http requests | ||||||
|  | 	Prefix string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (fs *AssetFS) Open(name string) (http.File, error) { | ||||||
|  | 	name = path.Join(fs.Prefix, name) | ||||||
|  | 	if len(name) > 0 && name[0] == '/' { | ||||||
|  | 		name = name[1:] | ||||||
|  | 	} | ||||||
|  | 	if b, err := fs.Asset(name); err == nil { | ||||||
|  | 		timestamp := defaultFileTimestamp | ||||||
|  | 		if fs.AssetInfo != nil { | ||||||
|  | 			if info, err := fs.AssetInfo(name); err == nil { | ||||||
|  | 				timestamp = info.ModTime() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return NewAssetFile(name, b, timestamp), nil | ||||||
|  | 	} | ||||||
|  | 	if children, err := fs.AssetDir(name); err == nil { | ||||||
|  | 		return NewAssetDirectory(name, children, fs), nil | ||||||
|  | 	} else { | ||||||
|  | 		// If the error is not found, return an error that will | ||||||
|  | 		// result in a 404 error. Otherwise the server returns | ||||||
|  | 		// a 500 error for files not found. | ||||||
|  | 		if strings.Contains(err.Error(), "not found") { | ||||||
|  | 			return nil, os.ErrNotExist | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/elazarl/go-bindata-assetfs/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | // assetfs allows packages to serve static content embedded | ||||||
|  | // with the go-bindata tool with the standard net/http package. | ||||||
|  | // | ||||||
|  | // See https://github.com/jteeuwen/go-bindata for more information | ||||||
|  | // about embedding binary data with go-bindata. | ||||||
|  | // | ||||||
|  | // Usage example, after running | ||||||
|  | //    $ go-bindata data/... | ||||||
|  | // use: | ||||||
|  | //     http.Handle("/", | ||||||
|  | //        http.FileServer( | ||||||
|  | //        &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"})) | ||||||
|  | package assetfs | ||||||
							
								
								
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/vendor.json
									
									
									
									
										vendored
									
									
								
							| @@ -864,6 +864,12 @@ | |||||||
| 			"revision": "bb3d318650d48840a39aa21a027c6630e198e626", | 			"revision": "bb3d318650d48840a39aa21a027c6630e198e626", | ||||||
| 			"revisionTime": "2017-11-10T20:55:13Z" | 			"revisionTime": "2017-11-10T20:55:13Z" | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"checksumSHA1": "7DxViusFRJ7UPH0jZqYatwDrOkY=", | ||||||
|  | 			"path": "github.com/elazarl/go-bindata-assetfs", | ||||||
|  | 			"revision": "38087fe4dafb822e541b3f7955075cc1c30bd294", | ||||||
|  | 			"revisionTime": "2018-02-23T16:03:09Z" | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"checksumSHA1": "5BP5xofo0GoFi6FtgqFFbmHyUKI=", | 			"checksumSHA1": "5BP5xofo0GoFi6FtgqFFbmHyUKI=", | ||||||
| 			"path": "github.com/fatih/structs", | 			"path": "github.com/fatih/structs", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user