------=_Part_12944838_269192103.1345724057851
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Hi Chris,
thanks for taking the time to make this patch, these are some excellent ideas! (CC'ing
engine-devel so that we can discuss this with other guys as well)
First of all, I really like the way you designed plugin source page URLs (going through
PluginSourcePageServlet ), e.g.
"/webadmin/webadmin/plugin/<pluginName>/<pluginSourcePage>.html",
plus the concept of "path" JSON attribute.
WebadminDynamicHostingServlet loads and caches all plugin definitions ( *.json files), and
directly embeds them into WebAdmin host page as pluginDefinitions JavaScript object.
I'm assuming that pluginDefinitions object will now look like this:
var pluginDefinitions = {
"test": {
"name": "test",
"version": "1.0",
"url": "/webadmin/webadmin/plugin/test/foo.html",
"path": "/tmp",
"config": {"a":1, "b":2, "c":3}
}
}
Originally, the pluginDefinitions object looked like this:
var pluginDefinitions = {
"test": "/webadmin/webadmin/plugin/test/foo.html" // Simple pluginName
-> pluginSourcePageUrl mappings
}
This is because PluginManager (WebAdmin) only needs pluginName ("name") and
pluginSourcePageUrl ("url") during startup, when creating plugin iframe. But
this can be changed :)
Plugin "version" makes sense, plus the plugin configuration object
("config") can be useful directly on the client. Let me explain:
Originally, plugin configuration was supposed to be passed to actual plugin code (through
immediately-invoked-function-expression, or IIFE), just like this:
(function (pluginApi, pluginConfig) { // JavaScript IIFE
// ... actual plugin code ...
})(
parent.pluginApi, /* reference to global pluginApi object */
{"a":1, "b":2, "c":3} /* embedded plugin configuration as
JavaScript object */
);
The whole purpose of PluginSourcePageServlet was to "wrap" actual plugin code
into HTML, so that users don't need to write HTML pages for their plugins manually.
PluginSourcePageServlet would handle any plugin dependencies (placed into HTML head), with
actual plugin code being wrapped into IIFE, as shown above. Plugin configuration was meant
to be stored in a separate file, e.g. <pluginName>-config.json , so that users could
change the default plugin configuration to suit their needs.
Inspired by your patch, rather than reading/embedding plugin configuration when serving
plugin HTML page ( PluginSourcePageServlet ), it's even better to have the plugin
configuration embedded directly into WebAdmin host page, along with introducing new
pluginApi function to retrieve the plugin configuration object.
Based on this, I suggest following modifications to the original concept:
- modify original pluginDefinitions structure, from pluginName -> pluginSourcePageUrl ,
to pluginName -> pluginDefObject
- pluginDefObject is basically a subset of physical plugin definition ( test.json , see
below), suitable for use on the client
- add following attributes to pluginDefObject : version , url , config
* note #1: name is not needed, since it's already the key of pluginName ->
pluginDefObject mapping
* note #2: path is not needed on the client (more on this below)
- introduce pluginApi.config(pluginName) function for plugins to retrieve their
configuration object, and remove pluginConfig parameter from main IIFE (as shown above)
[a] Physical plugin definition file (JSON) might be located at oVirt "DataDir",
e.g. /usr/share/ovirt-engine/ui-plugins/test.json , for example:
{
"name": "test",
"version": "1.0",
"url": "/webadmin/webadmin/plugin/test/start.html",
"path": "/tmp",
"config": "test-config.json"
}
[b] Plugin configuration file (JSON) might be located at oVirt "ConfigDir", e.g.
/etc/ovirt-engine/ui-plugins/test-config.json , for example:
{
"a":1, "b":2, "c":3
}
[c] Finally, plugin static resources (plugin source page, actual plugin code, plugin
dependencies, CSS/images, etc.) would be located at /tmp (as shown in [a]), for example:
/tmp/start.html -> plugin source page, used to load actual plugin code
/tmp/test.js -> actual plugin code
/tmp/deps/jquery-min.js -> simulate 3rd party plugin dependency
For example:
"/webadmin/webadmin/plugin/test/start.html" will be mapped to /tmp/start.html
"/webadmin/webadmin/plugin/test/deps/jquery-min.js" will be mapped to
/tmp/deps/jquery-min.js
This approach has some pros and cons:
(+) plugin static resources can be served through PluginSourcePageServlet (pretty much
like oVirt documentation resources, served through oVirt Engine root war's FileServlet
)
(+) plugin author has complete control over plugin source page
(-) plugin author actually needs to write plugin source page
Overall, I think this approach is better than the previous one (where
PluginSourcePageServlet took care of rendering plugin source page, but sacrificed some
flexibility).
By the way, here's what would happen behind the scenes:
1. user requests WebAdmin host page, WebadminDynamicHostingServlet loads and caches
all plugin definitions [a] + plugin configurations [b] and constructs/embeds appropriate
pluginDefinitions JavaScript object
2. during WebAdmin startup, PluginManager registers the plugin
(name/version/url/config), and creates/attaches the iframe to fetch plugin source page
ansynchronously
3. PluginSourcePageServlet handles plugin source page request, resolves the correct
path [c] and just streams the file content back to client
1. The plugin configuration files should probably have an
"enabled" field and an "apiVersion" field that should be examined to
determine whether or not to use the plugin.
Sounds good, we can implement these later on :)
2. I suspect the way I've modified PluginSourcePage makes it
vulnerable to directory climbing attacks.
Yes, but we can defend against these, restricting access only to plugin's
"path" and its sub-directories.
3. Is /usr/share/ovirt-engine the right place for the plugin config
files?
I suppose you mean plugin definition files [a], cannot tell for sure, but we can change
this anytime :)
Chris, please let me know what you think, and again - many thanks for sending the patch!
Regards,
Vojtech
----- Original Message -----
From: "Chris Frantz" <Chris.Frantz(a)hp.com>
To: vszocs(a)redhat.com
Sent: Wednesday, August 22, 2012 7:56:45 PM
Subject: UI Plugins configuration
Vojtech,
I decided to work on making the plugin patch a bit more configurable, following some of
the ideas expressed by Itamar and others in the meeting yesterday. The attached patch is a
simple first-attempt.
Plugin configurations are stored in /usr/share/ovirt-engine/ui-plugins/*.json.
Example:
{
"name": "test",
"version": "1.0",
"url": "/webadmin/webadmin/plugin/test/foo.html",
"path": "/tmp",
"config": {"a":1, "b":2, "c": 3}
}
The engine reads all of the *.json files in that directory to build the list of known
plugins and gives that list to the webadmin.
When webadmin loads a plugin, it requests the URL given in the plugin config file. The
"plugin" URL is mapped to PluginSourcePage, which will translate the first part
of the path ("test") into whatever path is stored in pluginConfig
("/tmp") in this case, and then serve the static file (e.g.
"/tmp/foo.html").
I didn't use the renderPluginSourcePage() method in favor of just serving a static
file, but I have no strong opinion on the matter. However, a plugin may want to store
static resources at "path" and have the engine serve those resources. By just
serving files through PluginSourcePage, we don't need any other servlets to provide
those resources.
There is still a bit of work to do:
1. The plugin configuration files should probably have an "enabled" field and an
"apiVersion" field that should be examined to determine whether or not to use
the plugin.
2. I suspect the way I've modified PluginSourcePage makes it vulnerable to directory
climbing attacks.
3. Is /usr/share/ovirt-engine the right place for the plugin config files?
Let me know what you think,
--Chris
------=_Part_12944838_269192103.1345724057851
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable
<html><head><style type=3D'text/css'>p { margin: 0;
}</style></head><body><=
div style=3D'font-family: times new roman,new york,times,serif; font-size: =
12pt; color: #000000'>Hi Chris,<br><br>thanks for taking the time to
make t=
his patch, these are some excellent ideas! (CC'ing engine-devel so that we =
can discuss this with other guys as well)<br><br>First of all, I really lik=
e the way you designed plugin source page URLs (going through <em>PluginSou=
rcePageServlet</em>), e.g.
"/webadmin/webadmin/plugin/<pluginName>/&l=
t;pluginSourcePage>.html", plus the concept of "path" JSON
attribute.<br=
<br><span style=3D"font-style:
italic;">WebadminDynamicHostingServlet</spa=
n> loads and caches all
plugin definitions (<span style=3D"font-style: ital=
ic;">*.json</span> files), and directly embeds them into WebAdmin host
page=
as <span style=3D"font-style: italic;">pluginDefinitions</span>
JavaScript=
object. I'm assuming that <span style=3D"font-style:
italic;">pluginDefini=
tions</span> object will now look like this:<br><br><span
style=3D"font-fam=
ily: courier new,courier,monaco,monospace,sans-serif;">var pluginDefinition=
s =3D {</span><br style=3D"font-family: courier
new,courier,monaco,monospac=
e,sans-serif;"><span style=3D"font-family: courier
new,courier,monaco,monos=
pace,sans-serif;"> "test": {</span><br
style=3D"font-family: cou=
rier new,courier,monaco,monospace,sans-serif;"><span style=3D"font-family:
=
courier new,courier,monaco,monospace,sans-serif;">
"name"=
: "test",</span><br style=3D"font-family: courier
new,courier,monaco,monosp=
ace,sans-serif;"><span style=3D"font-family: courier
new,courier,monaco,mon=
ospace,sans-serif;"> "version":
"1.0",</span><br style=3D=
"font-family: courier new,courier,monaco,monospace,sans-serif;"><span
style=
=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;"> &n=
bsp; "url":
"/webadmin/webadmin/plugin/test/foo.html",</span><br styl=
e=3D"font-family: courier new,courier,monaco,monospace,sans-serif;"><span
s=
tyle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;">&nbs=
p; "path": "/tmp",</span><br
style=3D"font-family: courier new,=
courier,monaco,monospace,sans-serif;"><span style=3D"font-family: courier
n=
ew,courier,monaco,monospace,sans-serif;">
"config": {"a":=
1, "b":2, "c":3}</span><br style=3D"font-family: courier
new,courier,monaco=
,monospace,sans-serif;"><span style=3D"font-family: courier
new,courier,mon=
aco,monospace,sans-serif;"> }</span><br
style=3D"font-family: co=
urier new,courier,monaco,monospace,sans-serif;"><span
style=3D"font-family:=
courier
new,courier,monaco,monospace,sans-serif;">}</span><br><br>Original=
ly, the <span style=3D"font-style: italic;">pluginDefinitions</span>
object=
looked like this:<br><br style=3D"font-family: courier
new,courier,monaco,=
monospace,sans-serif;"><span style=3D"font-family: courier
new,courier,mona=
co,monospace,sans-serif;">var pluginDefinitions =3D {</span><br
style=3D"fo=
nt-family: courier new,courier,monaco,monospace,sans-serif;"><span style=3D=
"font-family: courier
new,courier,monaco,monospace,sans-serif;">  =
;"test": "/webadmin/webadmin/plugin/test/foo.html" // Simple
pluginName -&g=
t; pluginSourcePageUrl mappings</span><br style=3D"font-family: courier
new=
,courier,monaco,monospace,sans-serif;"><span style=3D"font-family: courier
=
new,courier,monaco,monospace,sans-serif;">}</span><br><br>This
is because P=
luginManager (WebAdmin) only needs <span style=3D"font-style:
italic;">plug=
inName</span> ("name") and <span style=3D"font-style:
italic;">pluginSource=
PageUrl</span> ("url") during startup, when creating plugin iframe. But
thi=
s can be changed :)<br><br>Plugin "version" makes sense, plus the
plugin co=
nfiguration object ("config") can be useful directly on the client. Let me =
explain:<br><br>Originally, plugin configuration was supposed to be passed =
to actual plugin code (through immediately-invoked-function-expression, or =
IIFE), just like this:<br><br style=3D"font-family: courier
new,courier,mon=
aco,monospace,sans-serif;"><span style=3D"font-family: courier
new,courier,=
monaco,monospace,sans-serif;">(function (pluginApi, pluginConfig) { // Java=
Script IIFE</span><br style=3D"font-family: courier
new,courier,monaco,mono=
space,sans-serif;"><span style=3D"font-family: courier
new,courier,monaco,m=
onospace,sans-serif;"> // ... actual plugin code
...</span><br s=
tyle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;"><spa=
n style=3D"font-family: courier new,courier,monaco,monospace,sans-serif;">}=
)(</span><br style=3D"font-family: courier
new,courier,monaco,monospace,san=
s-serif;"><span style=3D"font-family: courier
new,courier,monaco,monospace,=
sans-serif;"> parent.pluginApi, /* reference to global
pluginApi=
object */</span><br style=3D"font-family: courier
new,courier,monaco,monos=
pace,sans-serif;"><span style=3D"font-family: courier
new,courier,monaco,mo=
nospace,sans-serif;"> {"a":1, "b":2,
"c":3} /* embedded plugin c=
onfiguration as JavaScript object */</span><br style=3D"font-family:
courie=
r new,courier,monaco,monospace,sans-serif;"><span style=3D"font-family:
cou=
rier
new,courier,monaco,monospace,sans-serif;">);</span><br><br>The
whole p=
urpose of <span style=3D"font-style:
italic;">PluginSourcePageServlet</span=
was to "wrap" actual plugin code into HTML, so that users
don't need to w=
rite HTML pages for their plugins manually. <span
style=3D"font-style: ital=
ic;">PluginSourcePageServlet</span> would handle any plugin dependencies
(p=
laced into HTML head), with actual plugin code being wrapped into IIFE, as =
shown above. Plugin configuration was meant to be stored in a separate file=
, e.g. <span style=3D"font-style:
italic;"><pluginName>-config.json</=
span>, so that users could change the default plugin configuration to suit =
their needs.<br><br>Inspired by your patch, rather than reading/embedding p=
lugin configuration when serving plugin HTML page (<span style=3D"font-styl=
e: italic;">PluginSourcePageServlet</span>), it's even better to have
the p=
lugin configuration embedded directly into WebAdmin host page, along with i=
ntroducing new <span style=3D"font-style: italic;">pluginApi</span>
functio=
n to retrieve the plugin configuration object.<br><br>Based on this, I sugg=
est following modifications to the original concept:<br><br>- modify origin=
al <span style=3D"font-style: italic;">pluginDefinitions</span>
structure, =
from <span style=3D"font-style: italic;">pluginName ->
pluginSourcePageU=
rl</span>, to <span style=3D"font-style: italic;">pluginName
-> pluginDe=
fObject</span><br>- <span style=3D"font-style:
italic;">pluginDefObject</sp=
an> is basically a subset of physical plugin definition (<span style=3D"fon=
t-style: italic;">test.json</span>, see below), suitable for use on the
cli=
ent<br>- add following attributes to <span style=3D"font-style:
italic;">pl=
uginDefObject</span>: <span style=3D"font-style:
italic;">version</span>, <=
span style=3D"font-style: italic;">url</span>, <span
style=3D"font-style: i=
talic;">config</span><br> * note #1: <span
style=3D"font-style: =
italic;">name</span> is not needed, since it's already the key of
<span sty=
le=3D"font-style: italic;">pluginName -> pluginDefObject</span>
mapping<=
br> * note #2: <span style=3D"font-style:
italic;">path</span> i=
s not needed on the client (more on this below)<br>- introduce <span style=
=3D"font-style: italic;">pluginApi.config(pluginName)</span> function
for p=
lugins to retrieve their configuration object, and remove <span style=3D"fo=
nt-style: italic;">pluginConfig</span> parameter from main IIFE (as shown
a=
bove)<br><br>[a] Physical plugin definition file (JSON) might be located at=
oVirt "DataDir", e.g. <span style=3D"font-style:
italic;">/usr/share/ovirt=
-engine/ui-plugins/test.json</span>, for example:<br><br
style=3D"font-fami=
ly: courier new,courier,monaco,monospace,sans-serif;"><span
style=3D"font-f=
amily: courier new,courier,monaco,monospace,sans-serif;">{</span><br
style=
=3D"font-family: courier new,courier,monaco,monospace,sans-serif;"><span
st=
yle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;"> =
; "name": "test",</span><br
style=3D"font-family: courier new,courier,=
monaco,monospace,sans-serif;"><span style=3D"font-family: courier
new,couri=
er,monaco,monospace,sans-serif;"> "version":
"1.0",</span><br st=
yle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;"><span=
style=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;">&n=
bsp; "url":
"/webadmin/webadmin/plugin/test/start.html",</span><br sty=
le=3D"font-family: courier new,courier,monaco,monospace,sans-serif;"><span
=
style=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;">&nb=
sp; "path": "/tmp",</span><br
style=3D"font-family: courier new,courie=
r,monaco,monospace,sans-serif;"><span style=3D"font-family: courier
new,cou=
rier,monaco,monospace,sans-serif;"> "config":
"test-config.json"=
</span><br style=3D"font-family: courier
new,courier,monaco,monospace,sans-=
serif;"><span style=3D"font-family: courier
new,courier,monaco,monospace,sa=
ns-serif;">}</span><br><br>[b] Plugin configuration file (JSON)
might be lo=
cated at oVirt "ConfigDir", e.g. <span style=3D"font-style:
italic;">/etc/o=
virt-engine/ui-plugins/test-config.json</span>, for example:<br><br style=
=3D"font-family: courier new,courier,monaco,monospace,sans-serif;"><span
st=
yle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;">{</sp=
an><br style=3D"font-family: courier new,courier,monaco,monospace,sans-seri=
f;"><span style=3D"font-family: courier
new,courier,monaco,monospace,sans-s=
erif;"> "a":1, "b":2,
"c":3</span><br style=3D"font-family: cour=
ier new,courier,monaco,monospace,sans-serif;"><span style=3D"font-family:
c=
ourier
new,courier,monaco,monospace,sans-serif;">}</span><br><br>[c]
Finall=
y, plugin static resources (plugin source page, actual plugin code, plugin =
dependencies, CSS/images, etc.) would be located at <span style=3D"font-sty=
le: italic;">/tmp</span> (as shown in [a]), for example:<br><br
style=3D"fo=
nt-family: courier new,courier,monaco,monospace,sans-serif;"><span style=3D=
"font-family: courier new,courier,monaco,monospace,sans-serif;">/tmp/start.=
html -> plugin source page, used to load actual plugin code</span><br st=
yle=3D"font-family: courier
new,courier,monaco,monospace,sans-serif;"><span=
style=3D"font-family: courier new,courier,monaco,monospace,sans-serif;">/t=
mp/test.js -> actual plugin code</span><br style=3D"font-family:
courier=
new,courier,monaco,monospace,sans-serif;"><span style=3D"font-family:
cour=
ier new,courier,monaco,monospace,sans-serif;">/tmp/deps/jquery-min.js ->=
simulate 3rd party plugin dependency</span><br><br>For
example:<br>"/webad=
min/webadmin/plugin/test/start.html" will be mapped to <span style=3D"font-=
style:
italic;">/tmp/start.html</span><br>"/webadmin/webadmin/plugin/test/d=
eps/jquery-min.js" will be mapped to <span style=3D"font-style:
italic;">/t=
mp/deps/jquery-min.js</span><br><br>This approach has some pros and
cons:<b=
r>(+) plugin static resources can be served through <span style=3D"font-sty=
le: italic;">PluginSourcePageServlet</span> (pretty much like oVirt
documen=
tation resources, served through oVirt Engine root war's <span style=3D"fon=
t-style: italic;">FileServlet</span>)<br>(+) plugin author has
complete con=
trol over plugin source page<br>(-) plugin author actually needs to write p=
lugin source page<br><br>Overall, I think this approach is better than the =
previous one (where <span style=3D"font-style:
italic;">PluginSourcePageSer=
vlet</span> took care of rendering plugin source page, but sacrificed some =
flexibility).<br><br>By the way, here's what would happen behind the
scenes=
:<br><ol><li>user requests WebAdmin host page, <span
style=3D"font-style: i=
talic;">WebadminDynamicHostingServlet</span> loads and caches all plugin
de=
finitions [a] + plugin configurations [b] and constructs/embeds appropriate=
<span style=3D"font-style: italic;">pluginDefinitions</span>
JavaScript ob=
ject<br><br></li><li>during WebAdmin startup, <span
style=3D"font-style: it=
alic;">PluginManager</span> registers the plugin
(name/version/url/config),=
and creates/attaches the iframe to fetch plugin source page ansynchronousl=
y<br><br></li><li><span style=3D"font-style:
italic;">PluginSourcePageServl=
et</span> handles plugin source page request, resolves the correct path [c]=
and just streams the file content back to
client<br><br></li></ol>> 1. =
The plugin configuration files should probably have an "enabled"=20
field and an "apiVersion" field that should be examined to determine=20
whether or not to use the plugin.<br><br>Sounds good, we can implement thes=
e later on :)<br><br>> 2. I suspect the way I've modified
PluginSo=
urcePage makes it vulnerable to directory climbing attacks.<br><br>Yes, but=
we can defend against these, restricting access only to plugin's "path"
an=
d its sub-directories.<br><br>> 3. Is /usr/share/ovirt-engine
the =
right place for the plugin config files?<br><br>I suppose you mean plugin d=
efinition files [a], cannot tell for sure, but we can change this anytime :=
)<br><br><br>Chris, please let me know what you think, and again - many
tha=
nks for sending the
patch!<br><br><br>Regards,<br>Vojtech<br><br><br><hr
id=
=3D"zwchr"><br>From: "Chris Frantz"
&lt;Chris.Frantz(a)hp.com&gt;<br>To: vszo=
cs(a)redhat.com<br>Sent: Wednesday, August 22, 2012 7:56:45 PM<br>Subject: UI=
Plugins configuration<br><br>Vojtech,<br><br>I decided to work on
making t=
he plugin patch a bit more configurable, following some of the ideas expres=
sed by Itamar and others in the meeting yesterday. The attached patch=
is a simple first-attempt.<br><br>Plugin configurations are stored in /usr=
/share/ovirt-engine/ui-plugins/*.json.<br><br>Example:<br>{<br> =
"name":
"test",<br> &n=
bsp; "version":
"1.0",<br> &n=
bsp; "url":
"/webadmin/webadmin/plugin/test/foo.html",<br>=
"path":
"/tmp",<br> &n=
bsp; "config":
{"a":1, "b":2, "c": 3}<br=
}<br><br>The engine reads all of the *.json files in that
directory to bui=
ld the list of known plugins and gives that list to the
webadmin.<br><br>Wh=
en webadmin loads a plugin, it requests the URL given in the plugin config =
file. The "plugin" URL is mapped to PluginSourcePage, which will
tran=
slate the first part of the path ("test") into whatever path is stored in p=
luginConfig ("/tmp") in this case, and then serve the static file (e.g.
"/t=
mp/foo.html").<br><br>I didn't use the renderPluginSourcePage()
method in f=
avor of just serving a static file, but I have no strong opinion on the mat=
ter. However, a plugin may want to store static resources at "path"
a=
nd have the engine serve those resources. By just serving files throu=
gh PluginSourcePage, we don't need any other servlets to provide those reso=
urces.<br><br>There is still a bit of work to do:<br><br>1.
The plugi=
n configuration files should probably have an "enabled" field and an
"apiVe=
rsion" field that should be examined to determine whether or not to use the=
plugin.<br><br>2. I suspect the way I've modified
PluginSourcePage m=
akes it vulnerable to directory climbing attacks.<br><br>3. Is
/usr/s=
hare/ovirt-engine the right place for the plugin config files?<br><br>Let m=
e know what you
think,<br>--Chris<br><br></div></body></html>
------=_Part_12944838_269192103.1345724057851--