package Apache2::Controller::Directives; =head1 NAME Apache2::Controller::Directives - server config directives for A2C =head1 VERSION Version 1.000.100 =cut use version; our $VERSION = version->new('1.000.100'); =head1 SYNOPSIS # apache2 config file PerlLoadModule Apache2::Controller::Directives # for Apache2::Controller::Render::Template settings: A2C_Render_Template_Path /var/myapp/templates # etc. All values are detainted using C<< m{ \A (.*) \z }mxs >>, since they are assumed to be trusted because they come from the server config file. As long as you don't give your users the ability to set directives, it should be okay. =cut use strict; use warnings FATAL => 'all'; use English '-no_match_vars'; use Carp qw( croak ); use Log::Log4perl qw(:easy); use YAML::Syck; use Readonly; use Apache2::Module (); use Apache2::Const -compile => qw( OR_ALL NO_ARGS TAKE1 ITERATE ITERATE2 RAW_ARGS ); use Apache2::Controller::X; use Apache2::Controller::Const qw( @RANDCHARS ); my @directives = ( # dispatch { name => 'A2C_Dispatch_Map', func => __PACKAGE__.'::A2C_Dispatch_Map', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE, errmsg => 'A2C_Dispatch_Map /path/to/yaml/syck/dispatch/map/file', }, # template rendering { name => 'A2C_Render_Template_Path', func => __PACKAGE__.'::A2C_Render_Template_Path', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE, errmsg => 'A2C_Render_Template_Path /primary/path [/second ... [/n]]', }, { name => 'A2C_Render_Template_Opts', func => __PACKAGE__.'::A2C_Render_Template_Opts', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE2, errmsg => q{ # specify Template Toolkit options: A2C_Render_Template_Opts INTERPOLATE 1 A2C_Render_Template_Opts PRE_PROCESS header scripts style A2C_Render_Template_Opts POST_CHOMP 1 }, }, # session stuff { name => 'A2C_Session_Class', func => __PACKAGE__.'::A2C_Session_Class', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Session_Class Apache::Session::File' }, { name => 'A2C_Session_Opts', func => __PACKAGE__.'::A2C_Session_Opts', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE2, errmsg => q{ # specify options for chosen Apache::Session subclass. # example: A2C_Session_Opts Directory /tmp/sessions A2C_Session_Opts LockDirectory /var/lock/sessions }, }, { name => 'A2C_Session_Secret', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::RAW_ARGS, errmsg => q{ # specify a constant secret for continuity across server restarts A2C_Session_Secret foobar # if no parameters, server startup will generate a secret, # but this won't work for cluster farms etc. A2C_Session_Secret }, }, { name => 'A2C_Session_Always_Save', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::NO_ARGS, errmsg => 'example: A2C_Session_Always_Save', }, { name => 'A2C_Session_Cookie_Opts', func => __PACKAGE__.'::A2C_Session_Cookie_Opts', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE2, errmsg => q{ # specify Apache2::Cookie options for session cookie. # example: A2C_Session_Cookie_Opts name myapp_sessionid A2C_Session_Cookie_Opts expires +3M }, }, # A2C:DBI::Connector { name => 'A2C_DBI_DSN', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_DSN DBI:mysql:database=foo', }, { name => 'A2C_DBI_User', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_User database_username', }, { name => 'A2C_DBI_Password', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_Password database_password', }, { name => 'A2C_DBI_Options', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE2, errmsg => q{ # specify DBI connect() options: A2C_DBI_Options RaiseError 1 A2C_DBI_Options AutoCommit 0 }, }, { name => 'A2C_DBI_Cleanup', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_Cleanup 1', }, { name => 'A2C_DBI_Class', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_Class MyApp::DBI', }, { name => 'A2C_DBI_Pnotes_Name', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_DBI_Pnotes_Name reader', }, # A2C:Auth::OpenID { name => 'A2C_Auth_OpenID_Login', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Login /myapp/login', }, { name => 'A2C_Auth_OpenID_Logout', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Logout /myapp/logout', }, { name => 'A2C_Auth_OpenID_Register', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Register /myapp/register', }, { name => 'A2C_Auth_OpenID_Timeout', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Timeout +1h', }, { name => 'A2C_Auth_OpenID_Table', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Table openid', }, { name => 'A2C_Auth_OpenID_User_Field', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_User_Field uname', }, { name => 'A2C_Auth_OpenID_URL_Field', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_URL_Field openid_url', }, { name => 'A2C_Auth_OpenID_DBI_Name', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_DBI_Name dbh', }, { name => 'A2C_Auth_OpenID_Trust_Root', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_Trust_Root http://blah.tld/blah', }, { name => 'A2C_Auth_OpenID_LWP_Class', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::TAKE1, errmsg => 'example: A2C_Auth_OpenID_LWP_Class LWPx::ParanoidAgent', }, { name => 'A2C_Auth_OpenID_LWP_Opts', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::ITERATE2, errmsg => q{ # specify options to the LWP class. example: A2C_Auth_OpenID_LWP_Opts timeout 10 A2C_Auth_OpenID_LWP_Opts agent A2C-openid A2C_Auth_OpenID_LWP_Opts whitelisted_hosts 127.0.0.1 foo.bar.tld # (don't whitelist stuff for ParanoidAgent unless you know # what you're doing... we do this for the test suite) }, }, { name => 'A2C_Auth_OpenID_Allow_Login', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::NO_ARGS, errmsg => 'example: A2C_Auth_OpenID_Allow_Login', }, { name => 'A2C_Auth_OpenID_Consumer_Secret', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::RAW_ARGS, errmsg => q{ # specify a constant secret for continuity across server restarts A2C_Auth_OpenID_Consumer_Secret foobar # if no parameters, server startup will generate a secret, # but this won't work for cluster farms etc. A2C_Auth_OpenID_Consumer_Secret }, }, { name => 'A2C_Auth_OpenID_NoPreserveParams', req_override => Apache2::Const::OR_ALL, args_how => Apache2::Const::NO_ARGS, errmsg => 'example: A2C_Auth_OpenID_NoPreserveParams', }, ); Apache2::Module::add(__PACKAGE__, \@directives); =head1 Apache2::Controller::Dispatch See L =head2 A2C_Dispatch_Map This is the path to a file compatible with L. If you do not provide a C<< dispatch_map() >> subroutine, the hash will be loaded with this file. Different subclasses of L have different data structures. YMMV. Or, if you just specify a package name, it will generate a dispatch map with one 'default' entry with that package. =cut sub A2C_Dispatch_Map { my ($self, $parms, $value) = @_; ($value) = $value =~ m{ \A (.*) \z }mxs; if ($value =~ m{ :: }mxs) { $self->{A2C_Dispatch_Map} = { default => $value }; return; } my $file = $value; # DEBUG("using file '$file' as A2C_Dispatch_Map"); croak "A2C_Dispatch_Map $file does not exist or is not readable." if !(-e $file && -f _ && -r _); # why not go ahead and load the file! # slurp it in so it can be detainted. my $file_contents; { local $/; open my $loadfile_fh, '<', $file || croak "Cannot read A2C_Dispatch_Map $file: $OS_ERROR"; $file_contents = <$loadfile_fh>; close $loadfile_fh; } eval { $self->{A2C_Dispatch_Map} = Load($file_contents) }; croak "Could not load A2C_Dispatch_Map $file: $EVAL_ERROR" if $EVAL_ERROR; # DEBUG("success!"); return; } =head1 Apache2::Controller::Render::Template See L. =head2 A2C_Render_Template_Path This is the base path for templates used by Apache2::Controller::Render::Template. The directive takes only one parameter and verifies that the directory exists and is readable. (At startup time Apache2 is root... this should verify readability by www user? Hrmm how is it going to figure out what user that is? It will have to access the server config via $parms. Except that this does not appear to work? It returns an empty hash.) =cut sub A2C_Render_Template_Path { my ($self, $parms, @directories_untainted) = @_; my @directories = map { my ($val) = $_ =~ m{ \A (.*) \z }mxs; $val; } @directories_untainted; # uhh... this doesn't work? # my $srv_cfg = Apache2::Module::get_config($self, $parms->server); # DEBUG(sub{"SERVER CONFIG:\n".Dump({ # map {("$_" => $srv_cfg->{$_})} keys %{$srv_cfg} # }) }); # DEBUG("server is ".$parms->server); # I need to figure out how to merge these or something croak("A2C_Render_Template_Path '$_' does not exist or is not readable.") for grep !( -d $_ && -r _ ), @directories; my $current = $self->{A2C_Render_Template_Path} || [ ]; # DEBUG("pushing (@directories) to (@{$current})"); push @{ $self->{A2C_Render_Template_Path} }, @directories; } =head2 A2C_Render_Template_Opts A2C_Render_Template_Opts INTERPOLATE 1 A2C_Render_Template_Opts PRE_PROCESS header meta style scripts A2C_Render_Template_Opts POST_CHOMP 1 Options for Template Toolkit. See L