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 }