jmx4perl/lib/JMX/Jmx4Perl/Agent/Jolokia/WebXmlHandler.pm
2017-10-31 14:38:28 +01:00

453 lines
12 KiB
Perl

#!/usr/bin/perl
package JMX::Jmx4Perl::Agent::Jolokia::WebXmlHandler;
=head1 NAME
JMX::Jmx4Perl::Agent::Jolokia::WebXmlHandler - Handler for web.xml
transformation
=head1 DESCRIPTION
This module is repsonsible for various manipulations on a F<web.xml> descriptor
as found in JEE WAR archives. It uses L<XML::LibXML> for the dirty work, and
L<XML::Tidy> to clean up after the manipulation. The later module is optional,
but recommended.
=head1 METHODS
=over 4
=cut
use Data::Dumper;
use vars qw($HAS_LIBXML $HAS_XML_TWIG);
use strict;
# Trigger for <login-config>
my $REALM = "Jolokia";
# Class used as proxy dispatcher
my $JSR_160_PROXY_CLASS = "org.jolokia.jsr160.Jsr160RequestDispatcher";
BEGIN {
$HAS_LIBXML = eval "require XML::LibXML; use XML::LibXML::XPathContext; 1";
$HAS_XML_TWIG = eval "require XML::Twig; 1";
}
=item $handler = JMX::Jmx4Perl::Agent::Jolokia::WebXmlHandler->new(%args)
Creates a new handler. The following arguments can be used:
"logger" Logger to use
=cut
sub new {
my $class = shift;
my %args = @_;
my $self = {logger => $args{logger}};
bless $self,(ref($class) || $class);
$self->_fatal("No XML::LibXML found. Please install it to allow changes and queries on web.xml") unless $HAS_LIBXML;
return $self;
}
=item $handler->add_security($webxml,{ role => $role })
Add a security constraint to the given web.xml. This triggers on the realm
"Jolokia" on the loging-config and the URL-Pattern "/*" for the security
mapping. Any previous sections are removed and replaced.
C<$role> is the role to insert.
This method returns the updated web.xml as a string.
=cut
sub add_security {
my $self = shift;
my $webxml = shift;
my $args = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
my $parent = $doc->getDocumentElement;
$self->_remove_security_elements($doc);
$self->_create_login_config($doc,$parent);
$self->_create_security_constraint($doc,$parent,$args->{role});
$self->_create_security_role($doc,$parent,$args->{role});
$self->_info("Added security mapping for role ","[em]",$args->{role},"[/em]");
return $self->_cleanup_doc($doc);
}
=item $handler->remove_security($webxml)
Remove login-config with Realm "Jolokia" and security constraint to
"/*" along with the associated role definit. Return the updated web.xml
as string.
=cut
sub remove_security {
my $self = shift;
my $webxml = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
$self->_remove_security_elements($doc);
$self->_info("Removed security mapping");
return $self->_cleanup_doc($doc);
}
=item $handler->add_jsr160_proxy($webxml)
Adds a JSR-160 proxy declaration which is contained as init-param of the
servlet definition ("dispatcherClasses"). If the init-param is missing, a new
is created otherwise an existing is updated. Does nothing, if the init-param
"dispatcherClasses" already contains the JSR 160 dispacher.
Returns the updated web.xml as string.
=cut
sub add_jsr160_proxy {
my $self = shift;
my $webxml = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
my @init_params = $self->_init_params($doc,"dispatcherClasses");
if (!@init_params) {
$self->_add_jsr160_proxy($doc);
$self->_info("Added JSR-160 proxy");
} elsif (@init_params == 1) {
my $param = $init_params[0];
my ($value,$classes) = $self->_extract_dispatcher_classes($init_params[0]);
unless (grep { $_ eq $JSR_160_PROXY_CLASS } @$classes) {
$self->_update_text($value,join(",",@$classes,$JSR_160_PROXY_CLASS));
$self->_info("Added JSR-160 proxy");
} else {
$self->_info("JSR-160 proxy already active");
return undef;
}
} else {
# Error
$self->_fatal("More than one init-param 'dispatcherClasses' found");
}
return $self->_cleanup_doc($doc);
}
=item $handler->remove_jsr160_proxy($webxml)
Removes a JSR-160 proxy declaration which is contained as init-param of the
servlet definition ("dispatcherClasses"). Does nothing, if the init-param
"dispatcherClasses" already doese not contain the JSR 160 dispacher.
Returns the updated web.xml as string.
=cut
sub remove_jsr160_proxy {
my $self = shift;
my $webxml = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
my @init_params = $self->_init_params($doc,"dispatcherClasses");
if (!@init_params) {
$self->info("No JSR-160 proxy active");
return undef;
} elsif (@init_params == 1) {
my ($value,$classes) = $self->_extract_dispatcher_classes($init_params[0]);
if (grep { $_ eq $JSR_160_PROXY_CLASS } @$classes) {
$self->_update_text($value,join(",",grep { $_ ne $JSR_160_PROXY_CLASS } @$classes));
$self->_info("Removed JSR-160 proxy");
} else {
$self->_info("No JSR-160 proxy active");
return undef;
}
} else {
$self->_fatal("More than one init-param 'dispatcherClasses' found");
}
return $self->_cleanup_doc($doc);
}
=item $handler->find($webxml,$xquery)
Find a single element with a given XQuery query. Croaks if more than one
element is found. Returns either C<undef> (nothing found) or the matched
node's text content.
=cut
sub find {
my $self = shift;
my $webxml = shift;
my $query = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
my @nodes = $self->_find_nodes($doc,$query);
$self->_fatal("More than one element found matching $query") if @nodes > 1;
return @nodes == 0 ? undef : $nodes[0]->textContent;
}
=item $handler->has_authentication($webxml)
Checks, whether authentication is switched on.
=cut
sub has_authentication {
my $self = shift;
my $webxml = shift;
$self->find
($webxml,
"//j2ee:security-constraint[j2ee:web-resource-collection/j2ee:url-pattern='/*']/j2ee:auth-constraint/j2ee:role-name");
}
=item $handler->has_jsr160_proxy($webxml)
Checks, whether a JSR-160 proxy is configured.
=cut
sub has_jsr160_proxy {
my $self = shift;
my $webxml = shift;
my $doc = XML::LibXML->load_xml(string => $webxml);
my @init_params = $self->_init_params($doc,"dispatcherClasses");
if (@init_params > 1) {
$self->_fatal("More than one dispatcherClasses init-param found");
} elsif (@init_params == 1) {
my $param = $init_params[0];
my ($value,$classes) = $self->_extract_dispatcher_classes($init_params[0]);
return grep { $_ eq $JSR_160_PROXY_CLASS } @$classes;
} else {
return 0;
}
}
# ===============================================================================
sub _remove_security_elements {
my $self = shift;
my $doc = shift;
my $role = shift;
$self->_remove_login_config($doc);
my $role = $self->_remove_security_constraint($doc);
$self->_remove_security_role($doc,$role);
}
sub _create_login_config {
my $self = shift;
my $doc = shift;
my $parent = shift;
my $l = _e($doc,$parent,"login-config");
_e($doc,$l,"auth-method","BASIC");
_e($doc,$l,"realm-name",$REALM);
}
sub _create_security_constraint {
my $self = shift;
my $doc = shift;
my $parent = shift;
my $role = shift;
my $s = _e($doc,$parent,"security-constraint");
my $w = _e($doc,$s,"web-resource-collection");
_e($doc,$w,"web-resource-name","Jolokia-Agent Access");
_e($doc,$w,"url-pattern","/*");
my $a = _e($doc,$s,"auth-constraint");
_e($doc,$a,"role-name",$role);
}
sub _create_security_role {
my $self = shift;
my $doc = shift;
my $parent = shift;
my $role = shift;
my $s = _e($doc,$parent,"security-role");
_e($doc,$s,"role-name",$role);
}
sub _remove_security_constraint {
my $self = shift;
my $doc = shift;
my @s = $doc->getElementsByTagName("security-constraint");
for my $s (@s) {
my @r = $s->getElementsByTagName("role-name");
my $role;
for my $r (@r) {
$role = $r->textContent;
}
my @u = $s->getElementsByTagName("url-pattern");
for my $u (@u) {
if ($u->textContent eq "/*") {
$s->parentNode->removeChild($s);
return $role;
}
}
}
}
sub _remove_login_config {
my $self = shift;
my $doc = shift;
my @l = $doc->getElementsByTagName("realm-name");
for my $l (@l) {
if ($l->textContent eq $REALM) {
my $toRemove = $l->parentNode;
$toRemove->parentNode->removeChild($toRemove);
return;
}
}
}
sub _remove_security_role {
my $self = shift;
my $doc = shift;
my $role = shift;
my @s = $doc->getElementsByTagName("security-role");
for my $s (@s) {
my @r = $s->getElementsByTagName("role-name");
for my $r (@r) {
if ($r->textContent eq $role) {
$s->parentNode->removeChild($s);
return;
}
}
}
}
sub _init_params {
my $self = shift;
my $doc = shift;
my $param_name = shift;
return $self->_find_nodes
($doc,
"/j2ee:web-app/j2ee:servlet[j2ee:servlet-name='jolokia-agent']/j2ee:init-param[j2ee:param-name='$param_name']");
}
sub _extract_dispatcher_classes {
my $self = shift;
my $param = shift;
my @values = $self->_find_nodes($param,"j2ee:param-value");
$self->_fatal("No or more than one param-value found") if (!@values || @values > 1);
my $value = $values[0];
my $content = $value->textContent();
my @classes = split /\s*,\s*/,$content;
return ($value,\@classes);
}
sub _update_text {
my $self = shift;
my $el = shift;
my $value = shift;
my $parent = $el->parentNode;
$parent->removeChild($el);
$parent->appendTextChild($el->nodeName,$value);
}
sub _add_jsr160_proxy {
my $self = shift;
my $doc = shift;
my @init_params = $self->_find_nodes
($doc,
"/j2ee:web-app/j2ee:servlet[j2ee:servlet-name='jolokia-agent']/j2ee:init-param");
my $first = $init_params[0] || $self->_fatal("No init-params found");
my $new_init = $doc->createElement("init-param");
_e($doc,$new_init,"param-name","dispatcherClasses");
_e($doc,$new_init,"param-value",$JSR_160_PROXY_CLASS);
$first->parentNode->insertBefore($new_init,$first);
}
sub _find_nodes {
my $self = shift;
my $doc = shift;
my $query = shift;
my $xpc = XML::LibXML::XPathContext->new;
$xpc->registerNs('j2ee', 'http://java.sun.com/xml/ns/j2ee');
return $xpc->findnodes($query,$doc);
}
sub _e {
my $doc = shift;
my $parent = shift;
my $e = $doc->createElement(shift);
my $c = shift;
if ($c) {
$e->appendChild($doc->createTextNode($c));
}
$parent->appendChild($e);
return $e;
}
sub _cleanup_doc {
my $self = shift;
my $doc = shift;
if ($HAS_XML_TWIG) {
my $ret = XML::Twig->nparse_pp($doc->toString)->toString(1);
#print $ret;
return $ret;
} else {
return $doc->toString(1);
}
}
sub _fatal {
my $self = shift;
$self->{logger}->error(@_);
die "\n";
}
sub _info {
my $self = shift;
$self->{logger}->info(@_);
}
=back
=head1 LICENSE
This file is part of jmx4perl.
Jmx4perl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
The Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
jmx4perl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with jmx4perl. If not, see <http://www.gnu.org/licenses/>.
A commercial license is available as well. Please contact roland@cpan.org for
further details.
=head1 AUTHOR
roland@cpan.org
=cut
1;