github.com/decred/politeia@v1.4.0/politeiawww/plugins.go (about)

     1  // Copyright (c) 2022 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/json"
     9  	"strings"
    10  
    11  	plugin "github.com/decred/politeia/politeiawww/plugin/v1"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // setupPlugins initializes the plugins that have been specified in the
    16  // politeiawww config. The config plugin settings are parsed during this
    17  // process and passed to the appropriate plugin on initialization.
    18  func (p *politeiawww) setupPlugins() error {
    19  	// Parse the plugin settings
    20  	settings := make(map[string][]plugin.Setting)
    21  	for _, rawSetting := range p.cfg.PluginSettings {
    22  		pluginID, s, err := parsePluginSetting(rawSetting)
    23  		if err != nil {
    24  			return errors.Errorf("failed to parse %v", rawSetting)
    25  		}
    26  		ss, ok := settings[pluginID]
    27  		if !ok {
    28  			ss = make([]plugin.Setting, 0, 16)
    29  		}
    30  		ss = append(ss, *s)
    31  		settings[pluginID] = ss
    32  	}
    33  
    34  	// Initialize the plugins
    35  	plugins := make(map[string]plugin.Plugin, len(p.cfg.Plugins))
    36  	for _, pluginID := range p.cfg.Plugins {
    37  		s, ok := settings[pluginID]
    38  		if !ok {
    39  			s = []plugin.Setting{}
    40  		}
    41  		args := plugin.InitArgs{
    42  			Settings: s,
    43  		}
    44  		pp, err := plugin.NewPlugin(pluginID, args)
    45  		if err != nil {
    46  			return errors.Errorf("failed to initialize %v", pluginID)
    47  		}
    48  		plugins[pluginID] = pp
    49  	}
    50  
    51  	// Initialize the user plugin interfaces
    52  	var (
    53  		um  plugin.UserManager
    54  		am  plugin.AuthManager
    55  		err error
    56  	)
    57  	if !p.cfg.DisableUsers {
    58  		if p.cfg.UserPlugin == "" {
    59  			return errors.Errorf("user plugin not provided; a user " +
    60  				"plugin must be provided when the user layer is enabled")
    61  		}
    62  		if p.cfg.AuthPlugin == "" {
    63  			return errors.Errorf("auth plugin not provided; an auth " +
    64  				"plugin must be provided when the user layer is enabled")
    65  		}
    66  
    67  		// Initialize the user manager
    68  		s, ok := settings[p.cfg.UserPlugin]
    69  		if !ok {
    70  			s = []plugin.Setting{}
    71  		}
    72  		args := plugin.InitArgs{
    73  			Settings: s,
    74  		}
    75  		um, err = plugin.NewUserManager(p.cfg.UserPlugin, args)
    76  		if err != nil {
    77  			return errors.Errorf("failed to initialize the user manager plugin %v",
    78  				p.cfg.UserPlugin)
    79  		}
    80  
    81  		// Initialize the authorizer
    82  		s, ok = settings[p.cfg.AuthPlugin]
    83  		if !ok {
    84  			s = []plugin.Setting{}
    85  		}
    86  		args = plugin.InitArgs{
    87  			Settings: s,
    88  		}
    89  		am, err = plugin.NewAuthManager(p.cfg.AuthPlugin, args)
    90  		if err != nil {
    91  			return errors.Errorf("failed to initialize the auth manager plugin %v",
    92  				p.cfg.AuthPlugin)
    93  		}
    94  	}
    95  
    96  	// Set the user plugin fields
    97  	p.pluginIDs = p.cfg.Plugins
    98  	p.plugins = plugins
    99  	p.userManager = um
   100  	p.authManager = am
   101  
   102  	return nil
   103  }
   104  
   105  // parsePluginSetting parses a plugin setting. Plugin settings will be in
   106  // following format. The value may be a single value or an array of values.
   107  //
   108  // pluginID,key,value
   109  // pluginID,key,["value1","value2","value3"...]
   110  //
   111  // When multiple values are provided, the values must be formatted as a JSON
   112  // encoded []string. Both of the following JSON formats are acceptable.
   113  //
   114  // pluginID,key,["value1","value2","value3"]
   115  // pluginsetting="pluginID,key,[\"value1\",\"value2\",\"value3\"]"
   116  func parsePluginSetting(setting string) (string, *plugin.Setting, error) {
   117  	formatMsg := `expected plugin setting format is ` +
   118  		`pluginID,key,value OR pluginID,key,["value1","value2","value3"]`
   119  
   120  	// Parse the plugin setting
   121  	var (
   122  		parsed = strings.Split(setting, ",")
   123  
   124  		// isMulti indicates whether the plugin setting contains
   125  		// multiple values. If the setting only contains a single
   126  		// value then isMulti will be false.
   127  		isMulti = regexpPluginSettingMulti.MatchString(setting)
   128  	)
   129  	switch {
   130  	case len(parsed) < 3:
   131  		return "", nil, errors.Errorf("missing csv entry '%v'; %v",
   132  			setting, formatMsg)
   133  	case len(parsed) == 3:
   134  		// This is expected; continue
   135  	case len(parsed) > 3 && isMulti:
   136  		// This is expected; continue
   137  	default:
   138  		return "", nil, errors.Errorf("invalid format '%v'; %v",
   139  			setting, formatMsg)
   140  	}
   141  
   142  	var (
   143  		pluginID     = parsed[0]
   144  		settingKey   = parsed[1]
   145  		settingValue = parsed[2]
   146  	)
   147  
   148  	// Clean the strings. The setting value is allowed to be case
   149  	// sensitive.
   150  	pluginID = strings.ToLower(strings.TrimSpace(pluginID))
   151  	settingKey = strings.ToLower(strings.TrimSpace(settingKey))
   152  	settingValue = strings.TrimSpace(settingValue)
   153  
   154  	// Handle multiple values
   155  	if isMulti {
   156  		// Parse values
   157  		values := regexpPluginSettingMulti.FindString(setting)
   158  
   159  		// Verify the values are formatted as valid JSON
   160  		var s []string
   161  		err := json.Unmarshal([]byte(values), &s)
   162  		if err != nil {
   163  			return "", nil, err
   164  		}
   165  
   166  		// Re-encode the JSON. This will remove any funny
   167  		// formatting like whitespaces.
   168  		b, err := json.Marshal(s)
   169  		if err != nil {
   170  			return "", nil, err
   171  		}
   172  
   173  		// Save the value
   174  		settingValue = string(b)
   175  	}
   176  
   177  	return pluginID, &plugin.Setting{
   178  		Key:   settingKey,
   179  		Value: settingValue,
   180  	}, nil
   181  }