/* Copyright (c) Cloud Software Group, Inc. * * 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. */ using System; using System.Windows.Forms; using System.Collections.Generic; using System.Xml; using System.IO; using System.Resources; using XenAdmin.XenSearch; using System.Reflection; using XenAPI; using XenAdmin.Core; using System.Collections.ObjectModel; namespace XenAdmin.Plugins { internal enum PluginMenu { file = 0, view = 1, pool = 2, server = 3, vm = 4, storage = 5, templates = 6, tools = 7, window = 8, help = 9, none = 10 } internal enum PluginSerializationLevel { none, global, obj } internal enum PluginContextMenu { none = 0, pool, server, vm, storage, template, folder } public class PluginDescriptor : IDisposable { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private List _features = new List(); private string _error; private Dictionary _searches = new Dictionary(); private Dictionary _methodLists = new Dictionary(); private ResourceManager _resourceManager; private bool _enabled; private string _organization; // the name of the company folder inside the "Plugins" folder private string _name; // the name of the plugin folder and .xcplugin.xml file private string _label; // optional - $plugin.label in resources file, 'label' in the XML, replaces _name for GUI display purposes private Version _version; // required - 'version' in the XML, currently only 1 and 2 are accepted private string _description; // optional - $plugin.description in resources file, 'description' in the XML private string _copyright; // optional - $plugin.copyright in resources file, 'copyright' in the XML private string _link; // optional - $plugin.link in resources file, 'link' in the XML public const string ATT_DESCRIPTION = "description"; public const string ATT_COPYRIGHT = "copyright"; public const string ATT_LINK = "link"; public const string ATT_LABEL = "label"; public const string ATT_VERSION = "version"; public const string METHOD_LIST_ELEMENT = "MethodList"; public const string METHOD_LIST_ATT_NAME = "name"; public const string SEARCH_ELEMENT = "Search"; public PluginDescriptor(XmlNode node, string name, string organization) { _name = name; _organization = organization; LoadAll(node); // this will initialize the features Enabled = Settings.IsPluginEnabled(Name, Organization) && Properties.Settings.Default.LoadPlugins; ValidateAll(); } private void ValidateAll() { if (_error != null) { Enabled = false; log.Error(string.Format("Error loading plugin '{0}::{1}'. Displaying as disabled. Detail: '{2}'", Organization, Name, _error)); return; } foreach (Feature f in _features) { _error = f.CheckForError(); if (_error != null) { Enabled = false; log.Error(string.Format("Error loading plugin '{0}::{1}'. Displaying as disabled. Detail: '{2}'", Organization, Name, _error)); return; } } } private void LoadAll(XmlNode node) { string v = Helpers.GetStringXmlAttribute(node, ATT_VERSION, ""); if (v == "2") { _version = new Version(2, 0); } else if (v == "1" || v == "1.0.0.0") { _version = new Version(1, 0); } else { _error = string.Format(Messages.UNRECOGNISED_PLUGIN_VERSION, v); return; } _description = Helpers.GetStringXmlAttribute(node, ATT_DESCRIPTION, ""); _copyright = Helpers.GetStringXmlAttribute(node, ATT_COPYRIGHT, ""); _link = Helpers.GetStringXmlAttribute(node, ATT_LINK, ""); _resourceManager = LoadResources(); // localize if (_resourceManager != null) { string label = _resourceManager.GetString(string.Format("{0}.{1}", Name, ATT_LABEL)); _label = string.IsNullOrEmpty(label) ? _label : label; string description = _resourceManager.GetString(string.Format("{0}.{1}", Name, ATT_DESCRIPTION)); _description = string.IsNullOrEmpty(description) ? _description : description; string copyright = _resourceManager.GetString(string.Format("{0}.{1}", Name, ATT_COPYRIGHT)); _copyright = string.IsNullOrEmpty(copyright) ? _copyright : copyright; string link = _resourceManager.GetString(string.Format("{0}.{1}", Name, ATT_LINK)); _link = string.IsNullOrEmpty(link) ? _link : link; } LoadMethodLists(node); // features depend on the list of methods LoadSearches(node); // features depend on the list of searches LoadFeatures(node); } public Search GetSearch(string uuid) { if (!string.IsNullOrEmpty(uuid) && _searches.ContainsKey(uuid)) { return _searches[uuid]; } return null; } public RbacMethodList GetMethodList(string name) { if (!string.IsNullOrEmpty(name) && _methodLists.ContainsKey(name)) { return _methodLists[name]; } return null; } public static string PowerShellExecutable { get { string text = System.Environment.GetFolderPath(Environment.SpecialFolder.System); return Path.Combine(text, @"WindowsPowerShell\v1.0\powershell.exe"); } } public ReadOnlyCollection Features { get { return new ReadOnlyCollection(_features); } } private ResourceManager LoadResources() { string resources = string.Format("{0}\\Plugins\\{1}\\{2}\\{2}.resources.dll", Application.StartupPath, Organization, Name); string cultured_resources = string.Format("{0}\\Plugins\\{1}\\{2}\\{3}\\{2}.resources.dll", Application.StartupPath, Organization, Name, Program.CurrentLanguage); if (File.Exists(cultured_resources)) { resources = cultured_resources; } try { if (File.Exists(resources)) { // We load this "unsafely" because of CA-144950: the plugin is almost certainly // downloaded from the web and won't install without this. I considered adding // a confirmation step, but as all we do with the resources is to extract some // strings, there is no security implication. (This doesn't affect security // confirmations on programs called by the plugin). return new ResourceManager(Name, Assembly.UnsafeLoadFrom(resources)); } } catch (Exception e) { _error = e.Message; } return null; } private void LoadMethodLists(XmlNode node) { foreach (XmlNode child in node.ChildNodes) { if (child.NodeType == XmlNodeType.Element && child.Name == METHOD_LIST_ELEMENT) { string name = Helpers.GetStringXmlAttribute(child, METHOD_LIST_ATT_NAME); if (name == null) { _error = string.Format(Messages.CANNOT_PARSE_NODE_PARAM, node.Name, METHOD_LIST_ATT_NAME); } string[] list = child.InnerText.Trim().Split(','); _methodLists.Add(name, new RbacMethodList(list)); } } } private void LoadSearches(XmlNode node) { foreach (XmlNode child in node.ChildNodes) { if (child.NodeType == XmlNodeType.Element && child.Name == SEARCH_ELEMENT) { try { Search s = SearchMarshalling.LoadSearch(child); _searches.Add(s.UUID, s); } catch (Exception e) { _error = e.Message; return; } } } } private void LoadFeatures(XmlNode node) { foreach (XmlNode child in node.ChildNodes) { if (child.NodeType != XmlNodeType.Element) continue; switch (child.Name) { case MenuItemFeature.ELEMENT_NAME: _features.Add(new MenuItemFeature(_resourceManager, child, this)); break; case TabPageFeature.ELEMENT_NAME: _features.Add(new TabPageFeature(_resourceManager, child, this)); break; case ParentMenuItemFeature.ELEMENT_NAME: _features.Add(new ParentMenuItemFeature(_resourceManager, child, this)); break; case SEARCH_ELEMENT: break; case METHOD_LIST_ELEMENT: break; default: _error = string.Format("XML node '{0}' not recognised", child.Name); return; } } } public void DisposeURLs(IXenObject xmo) { foreach (Feature f in Features) { TabPageFeature tabPageFeature = f as TabPageFeature; if (tabPageFeature != null) { tabPageFeature.DisposeURL(xmo); } } } public bool Enabled { get { return _enabled; } set { if (value == _enabled) return; _enabled = value; foreach (Feature f in Features) { if (!value) { f.Dispose(); } else if (value) { f.Initialize(); } } } } public override string ToString() { if (!string.IsNullOrEmpty(Label)) { return Label; } return Name; } public string Label { get { return _label; } } public Version Version { get { return _version; } } public string Description { get { return _description; } } public string Copyright { get { return _copyright; } } public string Link { get { return _link; } } public string Error { get { return _error; } } public string Organization { get { return _organization; } } public string Name { get { return _name; } } protected virtual void Dispose(bool disposing) { if (disposing) { foreach (Feature feature in Features) { feature.Dispose(); } } } #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } }